PDFium Component för Delphi kontrollerar varje ECDSA- och EdDSA-PDF-signatur mot algoritmsprofilen ISO/TS 32002 och rapporterar domen i TPadesSignatureValidation.AlgorithmPolicyStatus. Bara P-256, P-384, P-521, de tre Brainpool r1-kurvorna, Ed25519 och Ed448 kan klara, var och en med en matchande digest, och en mismatch utlöser ppeiSignatureAlgorithmMismatch. Den policyn spelar större roll än den låter. En signatur på brainpoolP160r1, eller en P-256-nyckel som signerar en SHA-512-digest, kan verifiera utmärkt på matematiknivå, så att Windows CryptoAPI rapporterar signaturvärdet som gott medan en strikt PDF 2.0-validator avvisar filen. Policykontrollen stänger den luckan, och den är medvetet åtskild från frågan om signaturbytena är kryptografiskt korrekta
Vad tillåter ISO/TS 32002 egentligen för signaturer med elliptiska kurvor?
ISO/TS 32002 tillåter exakt sex ECDSA-kurvor och två EdDSA-scheman i PDF-signaturer, och knyter varje kurva till de digeststorlekar den får bära. PDFium Component kodar den tabellen i PadesCurveDigestAllowed, nycklad med kurv-OID:n från signerarens certifikat. NIST-kurvorna är strikta: digesten måste ha samma bitbredd som kurvan, antingen SHA-2 eller SHA-3. Brainpool-kurvorna är lösare och accepterar sin egen bredd eller vad som helst bredare:
- P-256 (
1.2.840.10045.3.1.7): bara SHA-256 eller SHA3-256 - P-384 (
1.3.132.0.34): bara SHA-384 eller SHA3-384 - P-521 (
1.3.132.0.35): bara SHA-512 eller SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): valfri SHA-2- eller SHA-3-digest från 256 till 512 bitar - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): 384- eller 512-bitars SHA-2 / SHA-3 - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): bara SHA-512 eller SHA3-512
EdDSA har inget kurvval och inget digestval, vilket är exakt varför dess regler handlar om kodning snarare än styrka. Enligt RFC 8419 måste en Ed25519-SignerInfo deklarera SHA-512 som sin digestAlgorithm utan parametrar, och en Ed448-SignerInfo, på den signerade attributväg som PAdES alltid använder, måste deklarera id-shake256-len (2.16.840.1.101.3.4.2.18) med en INTEGER-parameter på exakt 512. För båda schemana måste signaturens AlgorithmIdentifier och certifikatets publika nyckels AlgorithmIdentifier bära inga parametrar alls. En producent som skriver en NULL där — vanan som RSA-inkodare har tränat in i många ASN.1-bibliotek — producerar en icke-konform signatur även om nyckeln och signaturvärdet är fina
Hur PDFium Component extraherar algoritmtrippeln ur CMS
PDFium själv kan inte svara på den frågan, eftersom dess publika signatur-API läser signaturordlistan men varken verifierar CMS:en eller exponerar signerarcertifikatets kurva. Därför tolkar PAdES-inspektionslagret byggd på PDFium själv CMS SignedData (RFC 5652). InspectPadesSignatureAlgorithm läser digestAlgorithm och signatureAlgorithm för den första SignerInfo:n, hittar sedan signerarcertifikatet och läser dess SubjectPublicKeyInfo för att få nyckelalgoritmen och kurvan. Certifikatsökningen är medvetet avgränsad: högst 64 certifikat i CMS:ens certificates-mängd granskas, matchningen är en exakt bytejämförelse av utfärdare och serienummer från issuerAndSerialNumber, och koden faller tillbaka på "det enda certifikat som finns" bara när mängden håller exakt ett tolkbart certifikat. Att plocka första EC-certifikatet ur en oordnad mängd vore lätt, och det skulle låta ett CA-certifikat avgöra vilken kurva signeraren påstås ha använt
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 tillämpar policyn som en del av sitt efterlevnadsvarv, med utgångspunkt i certifikatet den lokaliserar inne i CMS:en, och TPdf.ValidatePadesTrust kör om den mot signerarcertifikatet som Windows CryptoAPI faktiskt använde för verifiering, så att det CryptoAPI-rapporterade certifikatet har sista ordet. Varje rå ingång hamnar i TPadesSignatureAlgorithmInfo, inklusive DigestParametersPresent, DigestParameterBits, SignatureParametersPresent och PublicKeyParametersAreNamedCurve, så att en avvisning alltid kan förklaras från posten i stället för från en loggrad
Varför faller en P-256-signatur med SHA3-256 på policyn?
En P-256-signatur faller på PDFium Components policy närhelst CMS:ens digestAlgorithm och digesten som ECDSA-signatureAlgorithm implicerar tvistar, även om båda var för sig är acceptabla för kurvan. EvaluatePadesSignatureAlgorithm mappar först ecdsa-with-SHA256, ecdsa-with-SHA3-256 och deras syskon till en digest, jämför den med den deklarerade digestAlgorithm och returnerar pcsInvalid vid varje skillnad innan kurvtabellen konsulteras. Fallet är verkligt: ett signeringsverktyg byter sin hash till SHA3-256 men behåller en hårdkodad ecdsa-with-SHA256-identifierare, och resultatet är en fil som ingen konform verifierare kan tolka konsekvent. Funktionen är publik, så matrisen kan låsas i ett unittest utan att bygga 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 stämmer inte
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); // kurvan finns inte i profilen
end;
Kurvkodningen får samma strikthet. RFC 5480 §2.1.1 låter ECParameters vara en namngiven kurv-OID, en implicit kurva (NULL) eller en fullständig explicit parametermängd, och PKIX-profiler kräver den namngivna formen. PDFium Component returnerar pcsInvalid när ett id-ecPublicKey-certifikat bär inga parametrar, implicita parametrar eller explicita parametrar, eftersom explicita parametrar låter en angripare beskriva en kurva som bara liknar en standardkurva. En korrekt namngiven kurva som bara saknas i ISO/TS 32002-listan, som brainpoolP160r1 ovan eller secp256k1, får pcsUnsupported i stället
Ogiltigt, stöds ej eller obestämt: att läsa statusen ärligt
De tre icke-giltiga statusarna på AlgorithmPolicyStatus betyder olika saker, och att trycka ihop dem i en enda "underkänd"-hink kastar bort informationen revisorer behöver. pcsInvalid betyder att en igenkänd algoritmkombination är felformad eller mismatchad; den lägger till ppeiSignatureAlgorithmMismatch i TPadesValidationResult.Issues och driver den samlade IntegrityStatus till pcsInvalid, så att IsCryptographicallyValid returnerar False även när CMS-signaturvärdet stämmer. pcsUnsupported betyder att kurvan eller digesten ligger utanför vad profilen namnger, vilket är ett kapacitetsresultat, inte bevis för manipulering. pcsIndeterminate betyder att signerarcertifikatet inte kunde fastställas, vanligen en CertificateSet med flera kandidater och ingen exakt issuerAndSerialNumber-match, så att koden vägrar gissa kurvan; sedan v3.124.0 märker den också en RSA-signatur över SHA-1 eller en 112-bitars digest som SHA-224, som inte längre överenskommits för aktuell validering. Samma tredelning gäller EdDSA på en maskin vars CryptoAPI inte kan verifiera Ed25519 eller Ed448: CmsSignatureStatus stannar på pcsUnsupported medan AlgorithmPolicyStatus fortfarande kan vara pcsValid, eftersom kodningen var korrekt och bara verifieraren saknades. Om du jagar en avvisning från Adobe eller en DSS-baserad validator täcker guiden till varför validatorer avvisar PAdES-signaturer de andra vanliga orsakerna
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 spärrning
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;
Vad garanterar pcsValid inte?
AlgorithmPolicyStatus = pcsValid intygar bara att en ECDSA- eller EdDSA-signatur använder en godkänd kurva med en kongruent, korrekt kodad digest, och att en RSA-signatur använder en digest från en aktuell svit; den säger ingenting om signaturvärdet är korrekt. Före v3.124.0 var RSA-grenen i EvaluatePadesSignatureAlgorithm medvetet vid: en signatureAlgorithm under PKCS #1-bågen 1.2.840.113549.1.1.* returnerade pcsValid, äldre sha1WithRSAEncryption inbegripen. Sedan PDFiumPas v3.124.0 tillämpar RSA-grenen signatursviterna i ETSI TS 119 312. MD2-, MD4- och MD5-digests är pcsInvalid. SHA-1 och 112-bitars digests som SHA-224 är pcsIndeterminate, så att en SHA-1-signatur behåller sitt samlade integritetsresultat och märks för granskning i stället för att avvisas. En digestAlgorithm som skiljer sig från digesten som signaturalgoritmen fixerat, som sha256WithRSAEncryption över en SHA-1-digest, eller en RSA-signaturalgoritm på en icke-RSA-signerarnyckel, är pcsInvalid, rapporteras som ppeiSignatureAlgorithmMismatch och faller på integriteten. Ett signerarcertifikat som inte kan hittas ger pcsIndeterminate, som det redan gjorde för ECDSA, och okända digests eller icke-signatur-RSA-OID:er ger pcsUnsupported. Moduluslängden kontrolleras fortfarande inte, PSS-parametrar valideras inte här (artikeln om RFC 4055 RSASSA-PSS-parametrar täcker hur de kodas på signeringssidan), och SHA-1- eller MD5-digests utlöser dessutom det separata problemet ppeiBadDigestAlgorithm. På samma sätt är signaturmatematiken, certifikatkedjan och spärrningen fortfarande uppgiften för CmsSignatureStatus, CertificateTrustStatus och RevocationStatus, som kommer från Windows CryptoAPI. Behandla pcsValid som "algoritmsprofilen håller", aldrig som "den här nyckeln är stark nog"
För en Delphi-applikation som tar emot signerade PDF-fakturor, avtal eller arkivpaket är den praktiska uppställningen kort: kör ValidatePadesTrust, avvisa vid ppeiSignatureAlgorithmMismatch, dirigera pcsUnsupported och pcsIndeterminate till en människa, och kräv din egen undre gräns för RSA-nyckelstorlek, för policyn gör det inte. PDFium Component för Delphi och Lazarus skeppar PAdES-validatoren, byggaren av bevisrapporter och signeringspipelinen, så att samma bibliotek kan producera dessa signaturer och kontrollera dem hela vägen