De PDFium Component for Delphi toetst elke ECDSA- en EdDSA-PDF-handtekening aan het ISO/TS 32002-algoritmeprofiel en meldt het oordeel in TPadesSignatureValidation.AlgorithmPolicyStatus. Alleen P-256, P-384, P-521, de drie Brainpool r1-curven, Ed25519 en Ed448 kunnen slagen, elk met een bijpassende digest, en een mismatch werpt ppeiSignatureAlgorithmMismatch op. Dat beleid is belangrijker dan het klinkt. Een handtekening op brainpoolP160r1, of een P-256-sleutel die een SHA-512-digest ondertekent, verifieert op rekenkundig niveau prima, dus Windows CryptoAPI meldt de handtekeningwaarde als goed terwijl een strenge PDF 2.0-validator het bestand verwerpt. De beleidscontrole dicht dat gat, en hij is met opzet gescheiden van de vraag of de handtekeningbytes cryptografisch correct zijn
Wat staat ISO/TS 32002 werkelijk toe voor elliptic curve-handtekeningen?
ISO/TS 32002 staat in PDF-handtekeningen precies zes ECDSA-curven en twee EdDSA-schema's toe, en hij koppelt elke curve aan de digestgroottes die ze mag dragen. De PDFium Component codeert die tabel in PadesCurveDigestAllowed, geïndexeerd op de curve-OID uit het ondertekenaarcertificaat. De NIST-curven zijn streng: de digest moet dezelfde bitbreedte hebben als de curve, SHA-2 of SHA-3. De Brainpool-curven zijn coulanter en accepteren hun eigen breedte of alles wat breder is:
- P-256 (
1.2.840.10045.3.1.7): alleen SHA-256 of SHA3-256 - P-384 (
1.3.132.0.34): alleen SHA-384 of SHA3-384 - P-521 (
1.3.132.0.35): alleen SHA-512 of SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): elke SHA-2- of SHA-3-digest van 256 tot 512 bits - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 van 384 of 512 bits - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): alleen SHA-512 of SHA3-512
EdDSA heeft geen curvekeuze en geen digestkeuze, en precies daarom gaan zijn regels over encoding in plaats van sterkte. Volgens RFC 8419 moet een Ed25519-SignerInfo SHA-512 als zijn digestAlgorithm declareren zonder parameters, en een Ed448-SignerInfo, op het signed-attributes-pad dat PAdES altijd gebruikt, moet id-shake256-len (2.16.840.1.101.3.4.2.18) declareren met een INTEGER-parameter van precies 512. Voor beide schema's moeten de AlgorithmIdentifier van de handtekening en de AlgorithmIdentifier van de openbare certificaatsleutel helemaal geen parameters dragen. Een producent die daar een NULL wegschrijft, de gewoonte die RSA-encoders in veel ASN.1-bibliotheken hebben ingetraind, produceert een niet-conforme handtekening ook al zijn de sleutel en de handtekeningwaarde in orde
Hoe de PDFium Component het algoritme-drietal uit de CMS haalt
PDFium zelf kan deze vraag niet beantwoorden, want zijn publieke handtekening-API leest de handtekeningdictionary maar verifieert de CMS niet en legt de curve van het ondertekenaarcertificaat niet bloot. De op PDFium gebouwde PAdES-inspectielaag parseert de CMS-SignedData (RFC 5652) daarom zelf. InspectPadesSignatureAlgorithm leest de digestAlgorithm en signatureAlgorithm van de eerste SignerInfo, zoekt dan het ondertekenaarcertificaat en leest zijn SubjectPublicKeyInfo om de sleutelalgoritme en de curve te bemachtigen. Het certificaatzoeken is bewust begrensd: hooguit 64 certificaten in de certificates-set van de CMS worden bekeken, de match is een exacte bytevergelijking van issuer en serienummer uit issuerAndSerialNumber, en de code valt alleen terug op "het enige certificaat dat er is" wanneer de set precies één parseerbaar certificaat bevat. De eerste EC-certificaat uit een ongeordende set pakken zou makkelijk zijn, en het zou een CA-certificaat laten beslissen welke curve de ondertekenaar zogenaamd gebruikte
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 past het beleid toe als deel van zijn conformiteitspas, vertrekkend vanuit het certificaat dat hij in de CMS vindt, en TPdf.ValidatePadesTrust voert het opnieuw uit tegen het ondertekenaarcertificaat dat Windows CryptoAPI werkelijk voor de verificatie gebruikte, dus het door CryptoAPI gemelde certificaat heeft het laatste woord. Elke ruwe invoer belandt in TPadesSignatureAlgorithmInfo, inclusief DigestParametersPresent, DigestParameterBits, SignatureParametersPresent en PublicKeyParametersAreNamedCurve, dus een afwijzing is altijd verklaarbaar vanuit het record in plaats van uit een logregel
Waarom zakt een P-256-handtekening met SHA3-256 door het beleid?
Een P-256-handtekening zakt door het beleid van de PDFium Component telkens wanneer de digestAlgorithm in de CMS en de digest die de ECDSA-signatureAlgorithm impliceert uit elkaar liggen, ook al zijn beide op zich acceptabel voor de curve. EvaluatePadesSignatureAlgorithm mapt eerst ecdsa-with-SHA256, ecdsa-with-SHA3-256 en hun verwanten naar een digest, vergelijkt die met de opgegeven digestAlgorithm, en geeft pcsInvalid terug bij elk verschil voordat de curvetabel wordt geraadpleegd. Het geval is echt: een ondertekeninstrument schakelt zijn hash om naar SHA3-256 maar houdt een hardgecodeerde ecdsa-with-SHA256-identifier aan, en het resultaat is een bestand dat geen conforme verificateur consistent kan interpreteren. De functie is publiek, dus de matrix kan in een unittest worden vastgepind zonder een PDF te bouwen:
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 loopt uit de pas
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 congruent
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // curve zit niet in het profiel
end;
De curve-encoding krijgt dezelfde strengheid. RFC 5480 §2.1.1 laat ECParameters een named-curve-OID, een impliciete curve (NULL) of een volledige expliciete parameterset zijn, en PKIX-profielen eisen de benoemde vorm. De PDFium Component geeft pcsInvalid terug wanneer een id-ecPublicKey-certificaat geen parameters, impliciete parameters of expliciete parameters draagt, want expliciete parameters laten een aanvaller een curve beschrijven die slechts op een standaardcurve lijkt. Een correct benoemde curve die simpelweg in de ISO/TS 32002-lijst ontbreekt, zoals brainpoolP160r1 hierboven of secp256k1, krijgt pcsUnsupported in plaats daarvan
Ongeldig, niet ondersteund of onbepaald: de status eerlijk lezen
De drie niet-geldige statussen van AlgorithmPolicyStatus betekenen verschillende dingen, en ze samenvouwen in één "gefaald"-bak gooit de informatie weg die auditors nodig hebben. pcsInvalid betekent dat een herkende algoritme-combinatie misvormd is of uit de pas loopt; hij voegt ppeiSignatureAlgorithmMismatch toe aan TPadesValidationResult.Issues en duwt de geaggregeerde IntegrityStatus naar pcsInvalid, dus IsCryptographicallyValid geeft False terug zelfs wanneer de CMS-handtekeningwaarde klopt. pcsUnsupported betekent dat de curve of digest buiten ligt wat het profiel noemt, wat een capaciteitsuitspraak is, geen bewijs van manipulatie. pcsIndeterminate betekent dat het ondertekenaarcertificaat niet kon worden vastgepind, meestal een CertificateSet met meerdere kandidaten en geen exacte issuerAndSerialNumber-match, dus de code weigert de curve te gokken; sinds v3.124.0 markeert hij ook een RSA-handtekening over SHA-1 of een 112-bits digest zoals SHA-224, die niet langer is overeengekomen voor huidige validatie. Dezelfde splitsing geldt voor EdDSA op een machine waarvan CryptoAPI Ed25519 of Ed448 niet kan verifiëren: CmsSignatureStatus blijft pcsUnsupported terwijl AlgorithmPolicyStatus nog steeds pcsValid kan zijn, want de encoding was correct en alleen de verificateur ontbrak. Zit u een afwijzing van Adobe of een op DSS gebaseerde validator achterna, dan behandelt de gids waarom validators PAdES-handtekeningen afwijzen de andere veelvoorkomende oorzaken
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;
Wat garandeert pcsValid niet?
AlgorithmPolicyStatus = pcsValid certificeert alleen dat een ECDSA- of EdDSA-handtekening een goedgekeurde curve gebruikt met een congruente, correct gecodeerde digest, en dat een RSA-handtekening een digest uit een actuele suite gebruikt; over de vraag of de handtekeningwaarde klopt zegt hij niets. Vóór v3.124.0 was de RSA-tak van EvaluatePadesSignatureAlgorithm opzettelijk breed: elke signatureAlgorithm onder de PKCS #1-boog 1.2.840.113549.1.1.* gaf pcsValid terug, legacy-sha1WithRSAEncryption incluis. Sinds PDFiumPas v3.124.0 past de RSA-tak de ETSI TS 119 312-handtekeningsuites toe. MD2-, MD4- en MD5-digests zijn pcsInvalid. SHA-1 en 112-bits digests zoals SHA-224 zijn pcsIndeterminate, dus een SHA-1-handtekening houdt zijn algehele integriteitsuitslag en wordt gemarkeerd voor review in plaats van afgewezen. Een digestAlgorithm die afwijkt van de digest die het handtekeningalgoritme vastlegt, zoals sha256WithRSAEncryption over een SHA-1-digest, of een RSA-handtekeningalgoritme op een niet-RSA-ondertekenaarsleutel, is pcsInvalid, gemeld als ppeiSignatureAlgorithmMismatch, en laat de integriteit zakken. Een ondertekenaarcertificaat dat niet wordt gevonden geeft pcsIndeterminate, zoals het al voor ECDSA deed, en onbekende digests of niet-handtekening-RSA-OIDs geven pcsUnsupported. Moduluslengte wordt nog steeds niet gecontroleerd, PSS-parameters worden hier niet gevalideerd (het RFC 4055-artikel over RSASSA-PSS-params behandelt hoe ze aan de ondertekenkant zijn gecodeerd), en SHA-1- of MD5-digests werpen daarnaast de aparte ppeiBadDigestAlgorithm-kwestie op. De handtekeningrekenkunde, de certificaatketen en de revocatie blijven zoals het hoort de taak van CmsSignatureStatus, CertificateTrustStatus en RevocationStatus, die uit Windows CryptoAPI komen. Beschouw pcsValid als "het algoritmeprofiel houdt stand", nooit als "deze sleutel is sterk genoeg"
Voor een Delphi-applicatie die ondertekende PDF-facturen, contracten of archiefpakketten accepteert, is de praktische opzet kort: draai ValidatePadesTrust, wijs af op ppeiSignatureAlgorithmMismatch, routeer pcsUnsupported en pcsIndeterminate naar een mens, en handhaaf uw eigen RSA-sleutelgrootte-ondergrens want het beleid doet dat niet. De PDFium Component for Delphi and Lazarus verscheept de PAdES-validator, de bouwer van bewijsrapporten en de ondertekenpipeline, dus dezelfde library kan deze handtekeningen produceren en ze end-to-end controleren