Tehnični članak

Preverjanje politike ECDSA/EdDSA po ISO/TS 32002 v PDFium

PDFium Component za Delphi preveri vsak podpis PDF ECDSA in EdDSA proti profilu algoritmov ISO/TS 32002 in sodbo poroča v TPadesSignatureValidation.AlgorithmPolicyStatus. Prestati lahko le P-256, P-384, P-521, tri krivulje Brainpool r1, Ed25519 in Ed448, vsaka z ujemajočim digestom, neskladje pa sproži ppeiSignatureAlgorithmMismatch. Ta politika je pomembnejša, kot se sliši. Podpis na brainpoolP160r1 ali ključ P-256, ki podpisuje digest SHA-512, se lahko na matematični ravni povsem lepo preveri, zato Windows CryptoAPI prijavi vrednost podpisa kot dobro, strog validirnik PDF 2.0 pa datoteko zavrne. Preverjanje politike zapre to vrzel in je namerno ločeno od vprašanja, ali so bajti podpisa kriptografsko pravilni

Kaj ISO/TS 32002 dejansko dovoljuje za podpise na eliptičnih krivuljah?

ISO/TS 32002 dovoljuje točno šest krivulj ECDSA in dve shemi EdDSA v podpisih PDF in vsako krivuljo veže na velikosti digestov, ki jih sme nositi. PDFium Component to tabelo kodira v PadesCurveDigestAllowed, ključano po OID krivulje iz certifikata podpisnika. Krivulje NIST so stroge: digest mora imeti isto bitno širino kot krivulja, bodisi SHA-2 bodisi SHA-3. Krivulje Brainpool so popustljivejše in sprejmejo svojo širino ali karkoli širšega:

  • P-256 (1.2.840.10045.3.1.7): le SHA-256 ali SHA3-256
  • P-384 (1.3.132.0.34): le SHA-384 ali SHA3-384
  • P-521 (1.3.132.0.35): le SHA-512 ali SHA3-512
  • brainpoolP256r1 (1.3.36.3.3.2.8.1.1.7): kateri koli digest SHA-2 ali SHA-3 od 256 do 512 bitov
  • brainpoolP384r1 (1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 s 384 ali 512 biti
  • brainpoolP512r1 (1.3.36.3.3.2.8.1.1.13): le SHA-512 ali SHA3-512
Matrika profila algoritmov ISO TS 32002 v PDFium Component: P-256, P-384 in P-521 sprejmejo v PadesCurveDigestAllowed le ujemajoče bitne širine digestov, brainpoolP256r1 sprejme 256 do 512 bitov, brainpoolP384r1 384 in 512, Ed25519 in Ed448 deklarirata SHA-512 in SHAKE256 z dolžino 512, neskladja pa sprožijo ppeiSignatureAlgorithmMismatch, krivulje izven profila pa vrnejo pcsUnsupported
Skozi gredo lahko šest krivulj ECDSA in dve shemi EdDSA, vsaka krivulja pa je vezana na bitne širine digestov, ki jih sme nositi; vse drugo je neveljavno ali nepodprto, nikoli tiho sprejeto

EdDSA nima izbire krivulje niti izbire digesta, in točno zato so njegova pravila o kodiranju, ne o moči. Po RFC 8419 mora SignerInfo Ed25519 deklarirati SHA-512 kot svoj digestAlgorithm brez parametrov, SignerInfo Ed448 pa, na poti podpisanih atributov, ki jo PAdES vedno uporablja, deklarirati id-shake256-len (2.16.840.1.101.3.4.2.18) s parametrom INTEGER, natanko 512. Za obe shemi morata AlgorithmIdentifier podpisa in AlgorithmIdentifier javnega ključa certifikata nositi sploh nič parametrov. Izdelovalec, ki tam zapiše NULL, navada, ki so jo kodirniki RSA vzgojili v marsikateri knjižnici ASN.1, izdela neskladen podpis, čeprav sta ključ in vrednost podpisa v redu

Kako PDFium Component izlušči trojko algoritmov iz CMS

PDFium samo ne more odgovoriti na to vprašanje, ker njegov javni API podpisov bere slovar podpisov, CMS pa ne preveri in ne razkrije krivulje certifikata podpisnika. Sloj za pregledovanje PAdES, zgrajen na PDFium, zato razčleni CMS SignedData (RFC 5652) sam. InspectPadesSignatureAlgorithm prebere digestAlgorithm in signatureAlgorithm prvega SignerInfo, nato poišče certifikat podpisnika in prebere njegov SubjectPublicKeyInfo, da dobi algoritem ključa in krivuljo. Iskanje certifikata je namerno omejeno: pregledanih je največ 64 certifikatov v množici certificates CMS, ujemanje je točna primerjava bajtov izdajatelja in serijske številke iz issuerAndSerialNumber, koda pa se vrne na »edini certifikat, ki ga je tam« šele, kadar množica drži točno en razčlenljiv certifikat. Izbrati prvi certifikat EC iz neurejene množice bi bilo lahko in bi pustilo, da odloči certifikat CA, katero krivuljo je podpisnik domnevno uporabil

Kako PDFium Component pregleda trojko algoritmov podpisa PDF: InspectPadesSignatureAlgorithm prebere digestAlgorithm in signatureAlgorithm prvega SignerInfo v CMS, pripne certifikat podpisnika prek točnega ujemanja issuerAndSerialNumber med največ 64 kandidati, prebere SubjectPublicKeyInfo za krivuljo, EvaluatePadesSignatureAlgorithm pa vrne AlgorithmPolicyStatus
PDFium sam ne preveri CMS in ne razkrije krivulje podpisnika, zato sloj PAdES razčleni SignedData in v zapisu obdrži vsak surovi OID za razložljivo zavrnitev
uses
  PDFium, FPdfPades;

const
  StatusNames: array[TPadesCryptoStatus] of string =
    ('not checked', 'valid', 'invalid', 'unsupported', 'indeterminate');

var
  Pdf: TPdf;
  R: TPadesValidationResult;
  A: TPadesSignatureAlgorithmInfo;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice-ecdsa-signed.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePades;
    for I := 0 to Length(R.Signatures) - 1 do
    begin
      A := R.Signatures[I].AlgorithmInfo;
      Writeln('Signature ', I);
      Writeln('  digestAlgorithm    : ', string(A.DigestAlgorithmOid));
      Writeln('  signatureAlgorithm : ', string(A.SignatureAlgorithmOid));
      Writeln('  public key / curve : ', string(A.PublicKeyAlgorithmOid),
        ' / ', string(A.CurveOid));
      Writeln('  policy             : ',
        StatusNames[R.Signatures[I].AlgorithmPolicyStatus]);
    end;
    if ppeiSignatureAlgorithmMismatch in R.Issues then
      Writeln('At least one signature violates the ISO/TS 32002 profile');
  finally
    Pdf.Free;
  end;
end;

TPdf.ValidatePades uporabi politiko kot del svojega prehoda skladnosti, začenši od certifikata, ki ga najde znotraj CMS, TPdf.ValidatePadesTrust pa jo ponovno požene proti certifikatu podpisnika, ki ga je Windows CryptoAPI dejansko uporabil za preverjanje, tako da ima certifikat, prijavljen s strani CryptoAPI, zadnjo besedo. Vsak surovi vhod pristane v TPadesSignatureAlgorithmInfo, vključno s DigestParametersPresent, DigestParameterBits, SignatureParametersPresent in PublicKeyParametersAreNamedCurve, tako da je zavrnitev vedno razložljiva iz zapisa, ne iz vrstice dnevnika

Zakaj podpis P-256 s SHA3-256 podre politiko?

Podpis P-256 podere politiko PDFium Component vsakič, kadar se CMS digestAlgorithm in digest, ki ga nakazuje ECDSA signatureAlgorithm, razhajata, tudi če sta oba posamezno sprejemljiva za krivuljo. EvaluatePadesSignatureAlgorithm najprej preslika ecdsa-with-SHA256, ecdsa-with-SHA3-256 in njune sorodnike na digest, primerja to z deklariranim digestAlgorithm in vrne pcsInvalid ob vsaki razliki, preden se posvetuje tabela krivulj. Primer je resničen: podpisovalno orodje preklopi svoj hash na SHA3-256, obdrži pa trdo zakodiran identifikator ecdsa-with-SHA256, rezultat pa je datoteka, ki je noben skladen preverjevalnik ne more dosledno razlagati. Funkcija je javna, tako da lahko matriko pripnete v enotski preizkus brez gradnje PDF:

var
  Info: TPadesSignatureAlgorithmInfo;
begin
  Info := Default(TPadesSignatureAlgorithmInfo);
  Info.Family := psafEcdsa;
  Info.PublicKeyAlgorithmOid := '1.2.840.10045.2.1';      // id-ecPublicKey
  Info.PublicKeyParametersPresent := True;
  Info.PublicKeyParametersAreNamedCurve := True;
  Info.CurveOid := '1.2.840.10045.3.1.7';                 // P-256
  Info.DigestAlgorithmOid := '2.16.840.1.101.3.4.2.8';    // SHA3-256
  Info.SignatureAlgorithmOid := '2.16.840.1.101.3.4.3.10'; // ecdsa-with-SHA3-256
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsValid);

  Info.SignatureAlgorithmOid := '1.2.840.10045.4.3.2';    // ecdsa-with-SHA256
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsInvalid); // neskladje digestov

  Info.CurveOid := '1.3.36.3.3.2.8.1.1.1';                // brainpoolP160r1
  Info.DigestAlgorithmOid := '2.16.840.1.101.3.4.2.1';    // SHA-256, zdaj skladen
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // krivulja ni v profilu
end;

Kodiranje krivulje dobi isto strogost. RFC 5480 §2.1.1 pusti ECParameters biti OID poimenovane krivulje, implicitna krivulja (NULL) ali poln eksplicitni nabor parametrov, profili PKIX pa zahtevajo poimenovano obliko. PDFium Component vrne pcsInvalid, kadar certifikat id-ecPublicKey ne nosi parametrov, nosi implicitne ali eksplicitne parametre, ker eksplicitni parametri pustijo napadalcu opisati krivuljo, ki le zgleda podobna standardni. Pravilno poimenovana krivulja, ki je na seznamu ISO/TS 32002 preprosto ni, kot je brainpoolP160r1 zgoraj ali secp256k1, dobi namesto tega pcsUnsupported

Neveljavno, nepodprto ali nedoločeno: iskreno branje statusa

Trije neveljavni statusi AlgorithmPolicyStatus pomenijo različne stvari, njihovo spraskanje v en vedro »spodletelo« pa vrže stran informacijo, ki jo revizorji potrebujejo. pcsInvalid pomeni, da je prepoznana kombinacija algoritmov deformirana ali neskladna; doda ppeiSignatureAlgorithmMismatch v TPadesValidationResult.Issues in pripelje agregatni IntegrityStatus do pcsInvalid, tako da IsCryptographicallyValid vrne False, tudi kadar se vrednost podpisa CMS izkaže za dobro. pcsUnsupported pomeni, da sta krivulja ali digest izven tistega, kar poimenuje profil, kar je rezultat zmožnosti, ne dokaz prikrivanja. pcsIndeterminate pomeni, da se certifikat podpisnika ni dal pripeti, običajno CertificateSet z več kandidati in brez točnega ujemanja issuerAndSerialNumber, zato koda odkloni ugibanje krivulje; od v3.124.0 označi tudi podpis RSA nad SHA-1 ali 112-bitnim digestom, kot je SHA-224, ki za trenutno validacijo ni več dogovorjen. Isti razkol velja za EdDSA na stroju, katerega CryptoAPI ne zmore preveriti Ed25519 ali Ed448: CmsSignatureStatus ostane pcsUnsupported, AlgorithmPolicyStatus pa je lahko še vedno pcsValid, ker je bilo kodiranje pravilno in manjkal je le preverjevalnik. Če zasledujete zavrnitev od Adobe ali validirnika na osnovi DSS, vodnik o tem, zakaj validirniki zavrnejo podpise PAdES, pokriva druge pogoste vzroke

Odločitvena pot EvaluatePadesSignatureAlgorithm v PDFium Component: neskladje digestov nastavi pcsInvalid in ppeiSignatureAlgorithmMismatch, poimenovana krivulja izven profila ISO TS 32002, kot je brainpoolP160r1, nastavi pcsUnsupported, certifikat podpisnika, ki se ga ne da pripeti, nastavi pcsIndeterminate, veja RSA pa od v3.124.0 uporablja nabora digestov ETSI TS 119 312, velikost ključa pa še vedno prepusti aplikaciji
Trije neveljavni statusi pomenijo različne stvari: neveljavno je dokaz pokvarjene kombinacije, nepodprto je rezultat zmožnosti, nedoločeno pa pomeni, da je koda odklonila ugibati
var
  Pdf: TPdf;
  Options: TPadesTrustValidationOptions;
  R: TPadesValidationResult;
  S: TPadesSignatureValidation;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice-ecdsa-signed.pdf';
    Pdf.Active := True;
    Options := TPadesTrustValidationOptions.Default; // brez povezave, brez preklica
    R := Pdf.ValidatePadesTrust(Options);
    for I := 0 to Length(R.Signatures) - 1 do
    begin
      S := R.Signatures[I];
      if S.IsDocumentTimeStamp then
        Continue;
      case S.AlgorithmPolicyStatus of
        pcsInvalid:       Writeln(I, ': reject, algorithm combination is invalid');
        pcsUnsupported:   Writeln(I, ': manual review, curve or digest outside the profile');
        pcsIndeterminate: Writeln(I, ': manual review, signer not identified or digest no longer agreed');
        pcsValid:
          if S.AlgorithmInfo.Family = psafRsa then
            Writeln(I, ': RSA digest suite accepted, check key size yourself')
          else
            Writeln(I, ': EC/EdDSA profile satisfied');
      end;
    end;
    Writeln('Cryptographically valid: ', R.IsCryptographicallyValid);
  finally
    Pdf.Free;
  end;
end;

Kaj pcsValid ne jamči?

AlgorithmPolicyStatus = pcsValid le priča, da podpis ECDSA ali EdDSA uporablja odobreno krivuljo s skladnim, pravilno kodiranim digestom, podpis RSA pa digest iz trenutnega nabora; o tem, ali je vrednost podpisa pravilna, ne pove nič. Pred v3.124.0 je bila veja RSA EvaluatePadesSignatureAlgorithm namerno široka: vsak signatureAlgorithm pod loku PKCS #1 1.2.840.113549.1.1.* je vrnil pcsValid, vključno s starejšim sha1WithRSAEncryption. Od PDFiumPas v3.124.0 veja RSA uporablja nabora podpisov ETSI TS 119 312. Digesti MD2, MD4 in MD5 so pcsInvalid. SHA-1 in 112-bitni digesti, kot je SHA-224, so pcsIndeterminate, tako da podpis SHA-1 obdrži svoj skupni rezultat celovitosti in je označen za pregled, namesto da bi bil zavrnjen. digestAlgorithm, ki se razlikuje od digesta, ki ga je določil algoritem podpisa, kot je sha256WithRSAEncryption nad digestom SHA-1, ali algoritem podpisa RSA na ključu podpisnika, ki ni RSA, je pcsInvalid, prijavljen kot ppeiSignatureAlgorithmMismatch, in podre celovitost. Certifikat podpisnika, ki ga ni mogoče najti, da pcsIndeterminate, kot je že dajal za ECDSA, neprepoznani digesti ali OIDI RSA, ki niso podpisi, pa pcsUnsupported. Dolžina modula se še vedno ne preverja, parametri PSS se tukaj ne validirajo (članek o parametrih RSASSA-PSS RFC 4055 pokriva, kako so kodirani na strani podpisovanja), digesti SHA-1 ali MD5 pa dodatno sprožijo ločeno težavo ppeiBadDigestAlgorithm. Enako ostajajo matemacija podpisa, veriga certifikatov in preklic naloga CmsSignatureStatus, CertificateTrustStatus in RevocationStatus, ki prihajajo iz Windows CryptoAPI. pcsValid obravnavajte kot »profil algoritmov drži«, nikoli kot »ta ključ je dovolj močan«

Za aplikacijo Delphi, ki sprejema podpisane račune, pogodbe ali arhivske pakete PDF, je praktična nastavitev kratka: poženite ValidatePadesTrust, zavrnite ob ppeiSignatureAlgorithmMismatch, pcsUnsupported in pcsIndeterminate usmerite človeku in vsilite lastno spodnjo mejo velikosti ključev RSA, ker je politika te ne bo vsilila. PDFium Component za Delphi in Lazarus prinaša validirnik PAdES, graditelj poročil o dokazih in cevovod podpisovanja, tako da lahko ista knjižnica te podpise izdela in preveri od začetka do konca