Teknisk artikkel

ISO/TS 32002 ECDSA- og EdDSA-policykontroller i Delphi

PDFium Component for Delphi sjekker hver ECDSA- og EdDSA-PDF-signatur mot algoritmeprofilen i ISO/TS 32002 og rapporterer dommen i TPadesSignatureValidation.AlgorithmPolicyStatus. Bare P-256, P-384, P-521, de tre Brainpool r1-kurvene, Ed25519 og Ed448 kan passere, hver med en matchende digest, og en mismatch reiser ppeiSignatureAlgorithmMismatch. Den policyen betyr mer enn den høres ut som. En signatur på brainpoolP160r1, eller en P-256-nøkkel som signerer en SHA-512-digest, kan verifisere helt fint på matematikknivå, så Windows CryptoAPI rapporterer signaturverdien som god mens en streng PDF 2.0-validator avviser filen. Policykontrollen lukker det gapet, og den er bevisst holdt adskilt fra spørsmålet om signaturbytene er kryptografisk korrekte

Hva tillater egentlig ISO/TS 32002 for elliptisk kurve-signaturer?

ISO/TS 32002 tillater nøyaktig seks ECDSA-kurver og to EdDSA-ordninger i PDF-signaturer, og den knytter hver kurve til digest-størrelsene den kan bære. PDFium Component innkoder den tabellen i PadesCurveDigestAllowed, nøklet på kurve-OID-en fra signerens sertifikat. NIST-kurvene er strenge: digesten må ha samme bitbredde som kurven, enten SHA-2 eller SHA-3. Brainpool-kurvene er slankere og godtar egen bredde eller hva som helst bredere:

  • P-256 (1.2.840.10045.3.1.7): bare SHA-256 eller SHA3-256
  • P-384 (1.3.132.0.34): bare SHA-384 eller SHA3-384
  • P-521 (1.3.132.0.35): bare SHA-512 eller SHA3-512
  • brainpoolP256r1 (1.3.36.3.3.2.8.1.1.7): enhver SHA-2- eller SHA-3-digest fra 256 til 512 biter
  • brainpoolP384r1 (1.3.36.3.3.2.8.1.1.11): 384- eller 512-bits SHA-2 / SHA-3
  • brainpoolP512r1 (1.3.36.3.3.2.8.1.1.13): bare SHA-512 eller SHA3-512
Matrise over ISO TS 32002-algoritmeprofilen i PDFium Component: P-256, P-384 og P-521 godtar bare matchende digest-bredder i PadesCurveDigestAllowed, brainpoolP256r1 godtar 256 til 512 biter, brainpoolP384r1 godtar 384 og 512, Ed25519 og Ed448 deklarerer SHA-512 og SHAKE256 med lengde 512, og mismatches reiser ppeiSignatureAlgorithmMismatch mens kurver utenfor profilen gir pcsUnsupported
Seks ECDSA-kurver og to EdDSA-ordninger kan passere, og hver kurve er knyttet til digest-breddene den kan bære; alt annet er ugyldig eller ustøttet, aldri stille godkjent

EdDSA har ikke noe kurvevalg og ikke noe digest-valg, noe som er nøyaktig grunnen til at reglene handler om koding i stedet for styrke. Etter RFC 8419 må en Ed25519 SignerInfo deklarere SHA-512 som sin digestAlgorithm uten parametre, og en Ed448 SignerInfo, på signed-attributes-veien PAdES alltid bruker, må deklarere id-shake256-len (2.16.840.1.101.3.4.2.18) med en INTEGER-parameter på nøyaktig 512. For begge ordninger må signaturens AlgorithmIdentifier og sertifikatets public key AlgorithmIdentifier ikke bære noen parametre i det hele tatt. En produsent som skriver en NULL der, vanen RSA-enkodere har trent inn i mange ASN.1-biblioteker, produserer en ikke-konform signatur selv om nøkkelen og signaturverdien er fine

Hvordan PDFium Component trekker ut algoritmetrikkelen fra CMS

PDFium selv kan ikke svare på dette spørsmålet, for det offentlige signatur-API-et leser signaturordboken men verifiserer verken CMS-en eller eksponerer kurven i signerens sertifikat. PAdES-inspeksjonslaget bygget på PDFium parser derfor CMS SignedData (RFC 5652) selv. InspectPadesSignatureAlgorithm leser digestAlgorithm og signatureAlgorithm i den første SignerInfo-en, finner så signerens sertifikat og leser SubjectPublicKeyInfo for å få nøkkelalgoritmen og kurven. Sertifikatoppslaget er bevisst avgrenset: høyst 64 sertifikater i CMS certificates-settet undersøkes, treffet er en eksakt byte-sammenligning av utsteder og serienummer fra issuerAndSerialNumber, og koden faller tilbake på «det eneste sertifikatet som finnes» bare når settet holder nøyaktig ett parserbart sertifikat. Å plukke det første EC-sertifikatet fra et uordnet sett ville vært enkelt, og det ville la et CA-sertifikat avgjøre hvilken kurve signataren angivelig brukte

Hvordan PDFium Component inspiserer en PDF-signaturalgoritme-trippel: InspectPadesSignatureAlgorithm leser digestAlgorithm og signatureAlgorithm i den første SignerInfo-en i CMS-en, fester signerens sertifikat gjennom et eksakt issuerAndSerialNumber-treff blant høyst 64 kandidater, leser SubjectPublicKeyInfo for kurven, og EvaluatePadesSignatureAlgorithm gir AlgorithmPolicyStatus
PDFium selv verifiserer verken CMS-en eller eksponerer signatarens kurve, så PAdES-laget parser SignedData og beholder hver rå OID i recorden for en forklarbar avvisning
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 anvender policyen som del av sin compliance-pass, med utgangspunkt i sertifikatet den lokaliserte inne i CMS-en, og TPdf.ValidatePadesTrust kjører den på nytt mot signerersertifikatet Windows CryptoAPI faktisk brukte til verifisering, så sertifikatet CryptoAPI rapporterer har det siste ordet. Hvert råinput lander i TPadesSignatureAlgorithmInfo, inkludert DigestParametersPresent, DigestParameterBits, SignatureParametersPresent og PublicKeyParametersAreNamedCurve, så en avvisning er alltid forklarbar fra recorden i stedet for fra en logglinje

Hvorfor feiler en P-256-signatur med SHA3-256 policyen?

En P-256-signatur feiler PDFium Components policy hver gang CMS digestAlgorithm og digesten antydet av ECDSA signatureAlgorithm er uenige, selv om begge er hver for seg akseptable for kurven. EvaluatePadesSignatureAlgorithm mapper først ecdsa-with-SHA256, ecdsa-with-SHA3-256 og søsknene deres til en digest, sammenligner den med den deklarerte digestAlgorithm og gir pcsInvalid ved enhver forskjell før kurvetabellen konsulteres. Tilfellet er reelt: et signeringsverktøy bytter hashen sin til SHA3-256 men beholder en hardkodet ecdsa-with-SHA256-identifikator, og resultatet er en fil ingen konform verifikator kan tolke konsistent. Funksjonen er offentlig, så matrisen kan festes i en unit-test uten å bygge en 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); // digest-mismatch

  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, nå kongruent
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // kurven er ikke i profilen
end;

Kurvekodingen får samme strenghet. RFC 5480 §2.1.1 lar ECParameters være en navngitt kurve-OID, en implisitt kurve (NULL) eller et fullstendig eksplisitt parametersett, og PKIX-profiler krever den navngitte formen. PDFium Component gir pcsInvalid når et id-ecPublicKey-sertifikat bærer ingen parametre, implisitte parametre eller eksplisitte parametre, for eksplisitte parametre lar en angriper beskrive en kurve som bare ligner en standard én. En korrekt navngitt kurve som bare mangler i ISO/TS 32002-listen, som brainpoolP160r1 over eller secp256k1, får pcsUnsupported i stedet

Ugyldig, ustøttet eller ubestemt: å lese statusen ærlig

De tre ikke-gyldige statusene til AlgorithmPolicyStatus betyr forskjellige ting, og å kollapse dem i én «feilet»-bøtte kaster bort informasjonen revisorer trenger. pcsInvalid betyr at en gjenkjent algoritmekombinasjon er misdannet eller mismatchet; den legger ppeiSignatureAlgorithmMismatch til TPadesValidationResult.Issues og driver den samlede IntegrityStatus til pcsInvalid, så IsCryptographicallyValid gir False selv når CMS signaturverdi sjekker ut. pcsUnsupported betyr at kurven eller digesten er utenfor det profilen navngir, noe som er et kapasitetsresultat, ikke bevis på manipulering. pcsIndeterminate betyr at signerens sertifikat ikke kunne festes, vanligvis et CertificateSet med flere kandidater og intet eksakt issuerAndSerialNumber-treff, så koden nekter å gjette kurven; siden v3.124.0 markerer den også en RSA-signatur over SHA-1 eller en 112-bits digest som SHA-224, som ikke lenger er avtalt for aktuell validering. Samme deling gjelder EdDSA på en maskin der CryptoAPI ikke kan verifisere Ed25519 eller Ed448: CmsSignatureStatus forblir pcsUnsupported mens AlgorithmPolicyStatus fortsatt kan være pcsValid, for kodingen var korrekt og bare verifikatoren manglet. Jager du en avvisning fra Adobe eller en DSS-basert validator, dekker veiledningen om hvorfor validatorer avviser PAdES-signaturer de andre vanlige årsakene

Beslutningsvei for EvaluatePadesSignatureAlgorithm i PDFium Component: en digest-mismatch setter pcsInvalid og ppeiSignatureAlgorithmMismatch, en navngitt kurve utenfor ISO TS 32002-profilen som brainpoolP160r1 setter pcsUnsupported, et signerersertifikat som ikke kan festes setter pcsIndeterminate, og siden v3.124.0 anvender RSA-grenen ETSI TS 119 312 digest-pakkene, med nøkkelstørrelsen fortsatt overlatt til applikasjonen
De tre ikke-gyldige statusene betyr forskjellige ting: ugyldig er bevis på en ødelagt kombinasjon, ustøttet er et kapasitetsresultat, og ubestemt betyr at koden nektet å gjette
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; // offline, ingen tilbakekalling
    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;

Hva garanterer ikke pcsValid?

AlgorithmPolicyStatus = pcsValid sertifiserer bare at en ECDSA- eller EdDSA-signatur bruker en godkjent kurve med en kongruent, korrekt innkodet digest, og at en RSA-signatur bruker en digest fra en aktuell pakke; den sier ingenting om signaturverdien er korrekt. Før v3.124.0 var RSA-grenen av EvaluatePadesSignatureAlgorithm bevisst vid: enhver signatureAlgorithm under PKCS #1-buen 1.2.840.113549.1.1.* ga pcsValid, legacy sha1WithRSAEncryption inkludert. Siden PDFiumPas v3.124.0 anvender RSA-grenen ETSI TS 119 312 signaturpakker. MD2-, MD4- og MD5-digester er pcsInvalid. SHA-1 og 112-bits digester som SHA-224 er pcsIndeterminate, så en SHA-1-signatur beholder sitt samlede integritetsresultat og flagges til gjennomgang i stedet for å avvises. En digestAlgorithm som avviker fra digesten fastlagt av signaturalgoritmen, som sha256WithRSAEncryption over en SHA-1-digest, eller en RSA-signaturalgoritme på en ikke-RSA-signerernøkkel, er pcsInvalid, rapportert som ppeiSignatureAlgorithmMismatch og feiler integriteten. Et signerersertifikat som ikke kan finnes, gir pcsIndeterminate, som det allerede gjorde for ECDSA, og ukjente digester eller ikke-signatur-RSA-OID-er gir pcsUnsupported. Moduluslengde sjekkes fortsatt ikke, PSS-parametre valideres ikke her (artikkelen om RFC 4055 RSASSA-PSS-params dekker hvordan de enkodes på signeringssiden), og SHA-1- eller MD5-digester reiser i tillegg det separate issue-en ppeiBadDigestAlgorithm. Likeså forblir signaturmatematikken, sertifikatkjeden og tilbakekalling jobben til CmsSignatureStatus, CertificateTrustStatus og RevocationStatus, som kommer fra Windows CryptoAPI. Behandle pcsValid som «algoritmeprofilen holder», aldri som «denne nøkkelen er sterk nok»

For en Delphi-applikasjon som godtar signerte PDF-fakturaer, kontrakter eller arkivpakker, er det praktiske oppsettet kort: kjør ValidatePadesTrust, avvis på ppeiSignatureAlgorithmMismatch, rout pcsUnsupported og pcsIndeterminate til et menneske, og håndhev din egen RSA-nøkkelstørrelse-gulv, for policyen gjør det ikke. PDFium Component for Delphi og Lazarus leverer PAdES-validatoren, evidensrapport-byggeren og signeringspipelinen, så samme bibliotek kan produsere disse signaturene og sjekke dem ende til ende