PDFium Component til Delphi tjekker hver ECDSA- og EdDSA-PDF-signatur mod ISO/TS 32002-algoritmeprofilen og rapporterer kendelsen i TPadesSignatureValidation.AlgorithmPolicyStatus. Kun P-256, P-384, P-521, de tre Brainpool r1-kurver, Ed25519 og Ed448 kan bestå, hver med en matchende digest, og en uoverensstemmelse rejser ppeiSignatureAlgorithmMismatch. Den policy betyder mere, end den lyder. En signatur på brainpoolP160r1, eller en P-256-nøgle, der signerer en SHA-512-digest, kan verificere helt fint på matteniveau, så Windows CryptoAPI rapporterer signaturværdien som god, mens en streng PDF 2.0-validator afviser filen. Policytjekket lukker det hul, og det er med vilje adskilt fra spørgsmålet, om signaturbytesene er kryptografisk korrekte
Hvad tillader ISO/TS 32002 reelt for elliptic curve-signaturer?
ISO/TS 32002 tillader præcis seks ECDSA-kurver og to EdDSA-skemaer i PDF-signaturer, og den kobler hver kurve til de digest-størrelser, den må bære. PDFium Component indkoder den tabel i PadesCurveDigestAllowed, nøglet med kurve-OID'en fra signeringscertifikatet. NIST-kurverne er strenge: digesten skal have samme bitbredde som kurven, enten SHA-2 eller SHA-3. Brainpool-kurverne er løsere og accepterer deres egen bredde eller hvad som helst bredere:
- P-256 (
1.2.840.10045.3.1.7): kun SHA-256 eller SHA3-256 - P-384 (
1.3.132.0.34): kun SHA-384 eller SHA3-384 - P-521 (
1.3.132.0.35): kun 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 bit - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): 384- eller 512-bit SHA-2 / SHA-3 - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): kun SHA-512 eller SHA3-512
EdDSA har intet kurvevalg og intet digestvalg, hvilket netop er derfor, dens regler handler om encoding frem for styrke. Per RFC 8419 skal en Ed25519 SignerInfo erklære SHA-512 som sin digestAlgorithm uden parametre, og en Ed448 SignerInfo på den signed-attributes-vej, PAdES altid bruger, skal erklære id-shake256-len (2.16.840.1.101.3.4.2.18) med en INTEGER-parameter på præcis 512. For begge skemaer skal signaturens AlgorithmIdentifier og certifikatets public key-AlgorithmIdentifier ikke bære nogen parametre overhovedet. En producent, der skriver en NULL dér — den vane, RSA-encoders har indtrænet i mange ASN.1-biblioteker — producerer en non-conforming signatur, selvom nøglen og signaturværdien er fine
Hvordan PDFium Component udtrækker algorithm-tripletten fra CMS
PDFium selv kan ikke svare på dette spørgsmål, for dens offentlige signatur-API læser signature dictionary, men verificerer hverken CMS'en eller eksponerer signeringscertifikatets kurve. PAdES-inspektionslaget bygget på PDFium parser derfor CMS SignedData (RFC 5652) selv. InspectPadesSignatureAlgorithm læser digestAlgorithm og signatureAlgorithm i den første SignerInfo, finder så signeringscertifikatet og læser dets SubjectPublicKeyInfo for at få nøglealgoritmen og kurven. Certifikatopslag er bevidst afgrænset: højst 64 certifikater i CMS'ens certificates-sæt undersøges, matchet er en eksakt byte-sammenligning af issuer og serienummer fra issuerAndSerialNumber, og koden falder tilbage til "det eneste certifikat, der er", kun når sættet holder præcis ét parseligt certifikat. At vælge det første EC-certifikat fra et usorteret sæt ville være let, og det ville lade et CA-certifikat afgøre, hvilken kurve signeren angiveligt brugte
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 af sin compliance-gennemgang, startende fra certifikatet, den lokaliserer inde i CMS'en, og TPdf.ValidatePadesTrust kører den igen mod det signeringscertifikat, Windows CryptoAPI faktisk brugte til verifikation, så det CryptoAPI-rapporterede certifikat har det sidste ord. Hvert råt input lander i TPadesSignatureAlgorithmInfo, inklusive DigestParametersPresent, DigestParameterBits, SignatureParametersPresent og PublicKeyParametersAreNamedCurve, så en afvisning altid kan forklares ud fra recorden frem for ud fra en loglinje
Hvorfor fejler en P-256-signatur med SHA3-256 policyen?
En P-256-signatur fejler PDFium Components policy, hver gang CMS'ens digestAlgorithm og digesten, som ECDSA signatureAlgorithm antyder, er uenige, selvom begge er individuelt acceptable for kurven. EvaluatePadesSignatureAlgorithm mapper først ecdsa-with-SHA256, ecdsa-with-SHA3-256 og deres søskende til en digest, sammenligner den med den deklarerede digestAlgorithm og returnerer pcsInvalid ved enhver forskel, før kurbetabellen konsulteres. Tilfældet er reelt: et signeringsværktøj skifter sin hash til SHA3-256, men beholder en hardkodet ecdsa-with-SHA256-identifikator, og resultatet er en fil, ingen conforming verifier kan fortolke konsistent. Funktionen er offentlig, så matricen kan fastlåses i en unit-test uden at 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-uoverensstemmelse
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, nu kongruent
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // kurve ikke i profilen
end;
Kurve-encodingen får samme strenghed. RFC 5480 §2.1.1 tillader, at ECParameters er en named curve-OID, en implicit kurve (NULL) eller et fuldt eksplicit parametersæt, og PKIX-profiler kræver den namede form. PDFium Component returnerer pcsInvalid, når et id-ecPublicKey-certifikat bærer ingen parametre, implicitte parametre eller eksplicitte parametre, for eksplicitte parametre lader en angriber beskrive en kurve, der blot ligner en standard. En korrekt named kurve, der simpelthen mangler på ISO/TS 32002-listen, som brainpoolP160r1 ovenfor eller secp256k1, får pcsUnsupported i stedet
Invalid, unsupported eller indeterminate: læs status ærligt
De tre ikke-gyldige statusser for AlgorithmPolicyStatus betyder forskellige ting, og at skride dem sammen i én "fejlet"-spand smider den information ud, revisorer har brug for. pcsInvalid betyder, at en genkendt algorithm-kombination er misdannet eller uenig; den tilføjer ppeiSignatureAlgorithmMismatch til TPadesValidationResult.Issues og driver den samlede IntegrityStatus til pcsInvalid, så IsCryptographicallyValid returnerer False, selv når CMS-signaturværdien tjekker ud. pcsUnsupported betyder, at kurven eller digesten er uden for, hvad profilen navngiver, hvilket er et kapacitetsresultat, ikke bevis på manipulering. pcsIndeterminate betyder, at signeringscertifikatet ikke kunne fastlåses, normalt et CertificateSet med flere kandidater og intet eksakt issuerAndSerialNumber-match, så koden nægter at gætte kurven; siden v3.124.0 markerer den også en RSA-signatur over SHA-1 eller en 112-bit digest som SHA-224, som ikke længere er aftalt for aktuel validering. Samme opdeling gælder EdDSA på en maskine, hvis CryptoAPI ikke kan verificere Ed25519 eller Ed448: CmsSignatureStatus forbliver pcsUnsupported, mens AlgorithmPolicyStatus stadig kan være pcsValid, for encodingen var korrekt, og kun verificeringen manglede. Jager du en afvisning fra Adobe eller en DSS-baseret validator, gennemgår guiden til hvorfor validatorer afviser PAdES-signaturer de andre almindelige årsager
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, no revocation
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;
Hvad garanterer pcsValid ikke?
AlgorithmPolicyStatus = pcsValid certificerer kun, at en ECDSA- eller EdDSA-signatur bruger en godkendt kurve med en kongruent, korrekt encoded digest, og at en RSA-signatur bruger en digest fra en aktuel suite; den siger ingenting om, hvorvidt signaturværdien er korrekt. Før v3.124.0 var RSA-grenen af EvaluatePadesSignatureAlgorithm bevidst bred: enhver signatureAlgorithm under PKCS #1-buen 1.2.840.113549.1.1.* returnerede pcsValid, legacy sha1WithRSAEncryption inkluderet. Siden PDFiumPas v3.124.0 anvender RSA-grenen ETSI TS 119 312 signature-suiter. MD2-, MD4- og MD5-digests er pcsInvalid. SHA-1- og 112-bit digests som SHA-224 er pcsIndeterminate, så en SHA-1-signatur beholder sit samlede integritetsresultat og flagges til review frem for at blive afvist. En digestAlgorithm, der afviger fra digesten, som signature-algoritmen fastlægger — som sha256WithRSAEncryption over en SHA-1-digest — eller en RSA-signaturalgoritme på en ikke-RSA-signernøgle, er pcsInvalid, rapporteret som ppeiSignatureAlgorithmMismatch og fejler integriteten. Et signeringscertifikat, der ikke kan findes, giver pcsIndeterminate, som det allerede gjorde for ECDSA, og ukendte digests eller ikke-signatur-RSA-OID'er giver pcsUnsupported. Moduluslængden tjekkes stadig ikke, PSS-parametre valideres ikke her (artiklen om RFC 4055 RSASSA-PSS-params gennemgår, hvordan de encodes på signer-siden), og SHA-1- eller MD5-digests rejser derudover det separate issue ppeiBadDigestAlgorithm. Ligeledes forbliver signaturmatematikken, certifikatkæden og revokering CmsSignatureStatus, CertificateTrustStatus og RevocationStatuss opgave, som kommer fra Windows CryptoAPI. Behandl pcsValid som "algorithm-profilen holder", aldrig som "denne nøgle er stærk nok"
For en Delphi-applikation, der accepterer signerede PDF-fakturaer, kontrakter eller arkivpakker, er den praktiske opsætning kort: kør ValidatePadesTrust, afvis på ppeiSignatureAlgorithmMismatch, send pcsUnsupported og pcsIndeterminate til et menneske, og håndhæv din egen RSA-nøglestørrelses-grænse, for policyen gør det ikke. PDFium Component for Delphi and Lazarus skiber PAdES-validatoren, evidence report-builderen og signeringspipelinen, så samme bibliotek kan producere disse signaturer og tjekke dem end to end