PDFium Component pre Delphi kontroluje každý ECDSA a EdDSA podpis PDF proti algoritmickému profilu ISO/TS 32002 a verdikt hlási v TPadesSignatureValidation.AlgorithmPolicyStatus. Prejsť môžu len P-256, P-384, P-521, tri krivky Brainpool r1, Ed25519 a Ed448, každá s párujúcim digestom, a nesúlad vyhodí ppeiSignatureAlgorithmMismatch. Tá politika je dôležitejšia, než znie. Podpis na brainpoolP160r1 alebo kľúč P-256 podpisujúci digest SHA-512 sa na matematickej úrovni overí úplne v poriadku, takže Windows CryptoAPI hlási hodnotu podpisu ako dobrú, kým prísny validátor PDF 2.0 súbor odmietne. Politická kontrola tú medzeru zatvára a zámerne je oddelená od otázky, či sú bajty podpisu kryptograficky správne
Čo ISO/TS 32002 naozaj pripúšťa pre podpisy na eliptických krivkách?
ISO/TS 32002 pripúšťa presne šesť ECDSA kriviek a dve schémy EdDSA v PDF podpisoch a každú krivku viaže na šírky digestov, ktoré môže niesť. PDFium Component kóduje túto tabuľku v PadesCurveDigestAllowed, kľúčovanej OID krivky z certifikátu podpisovateľa. NIST krivky sú prísne: digest musí mať rovnakú bitovú šírku ako krivka, buď SHA-2 alebo SHA-3. Krivky Brainpool sú voľnejšie a pripúšťajú vlastnú šírku alebo čokoľvek širšie:
- P-256 (
1.2.840.10045.3.1.7): len SHA-256 alebo SHA3-256 - P-384 (
1.3.132.0.34): len SHA-384 alebo SHA3-384 - P-521 (
1.3.132.0.35): len SHA-512 alebo SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): ľubovoľný digest SHA-2 alebo SHA-3 od 256 do 512 bitov - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 s 384 alebo 512 bitmi - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): len SHA-512 alebo SHA3-512
EdDSA nemá žiadnu voľbu krivky ani voľbu digestu, presne preto sa jeho pravidlá týkajú kódovania, nie sily. Podľa RFC 8419 musí SignerInfo Ed25519 deklarovať SHA-512 ako svoj digestAlgorithm bez parametrov a SignerInfo Ed448, na ceste signed-attributes, ktorú PAdES vždy používa, musí deklarovať id-shake256-len (2.16.840.1.101.3.4.2.18) s INTEGER parametrom presne 512. Pre obe schémy musí AlgorithmIdentifier podpisu a AlgorithmIdentifier verejného kľúča certifikátu nesať vôbec žiadne parametre. Producent, ktorý tam zapíše NULL, zvyk, do ktorého RSA enkodéry vycvičili mnohé ASN.1 knižnice, vyrobí nekonformný podpis, aj keď kľúč aj hodnota podpisu sú v poriadku
Ako PDFium Component vyzvedá trojicu algoritmov z CMS
PDFium sám na túto otázku neodpovie, lebo jeho verejné signature API číta signature slovník, ale ani neoveruje CMS, ani neexponuje krivku certifikátu podpisovateľa. Vrstva na prehliadanie PAdES postavená na PDFium preto parsuje CMS SignedData (RFC 5652) sama. InspectPadesSignatureAlgorithm číta digestAlgorithm a signatureAlgorithm prvého SignerInfo, potom nájde certifikát podpisovateľa a prečíta jeho SubjectPublicKeyInfo, aby dostal algoritmus kľúča a krivku. Hľadanie certifikátu je zámerne ohraničené: skúma sa najviac 64 certifikátov v množine certificates v CMS, zhoda je presné bajtové porovnanie issuer a serial number z issuerAndSerialNumber a kód prepadne na "jediný certifikát, ktorý tam je", len keď množina drží presne jeden vyparsovateľný certifikát. Zobrať prvý EC certifikát z neutriedených množiny by bolo ľahké a nechalo by CA certifikát rozhodnúť, ktorú krivku podpisovateľ vraj použil
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 aplikuje politiku ako súčasť svojho prechodu zhodou, štartujúc od certifikátu, ktorý nájde vnútri CMS, a TPdf.ValidatePadesTrust ju znova spustí proti certifikátu podpisovateľa, ktorý Windows CryptoAPI naozaj použil na overenie, takže certifikát hlásený CryptoAPI má posledné slovo. Každý surový vstup pristane v TPadesSignatureAlgorithmInfo, vrátane DigestParametersPresent, DigestParameterBits, SignatureParametersPresent a PublicKeyParametersAreNamedCurve, takže zamietnutie je vždy vysvetliteľné z recordu, nie z log riadku
Prečo padne podpis P-256 s SHA3-256 na politike?
Podpis P-256 padne na politike PDFium Component vždy, keď sa digestAlgorithm v CMS a digest implikovaný ECDSA signatureAlgorithm rozchádzajú, aj keď sú oba jednotlivo pre krivku prijateľné. EvaluatePadesSignatureAlgorithm najprv mapuje ecdsa-with-SHA256, ecdsa-with-SHA3-256 a ich súrodencov na digest, porovná to s deklarovaným digestAlgorithm a pri akomkoľvek rozdiele vráti pcsInvalid, skôr než sa vôbec pozrie do tabuľky kriviek. Prípad je reálny: podpisovací nástroj prepne hash na SHA3-256, ale nechá natvrdo zakódovaný identifikátor ecdsa-with-SHA256 a výsledkom je súbor, ktorý žiaden konformný verifikátor nedokáže interpretovať konzistentne. Funkcia je verejná, takže matricu si možno pripichnúť v unit teste bez stavby 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 nesedí
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, teraz kongruentný
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // krivka nie je v profile
end;
Kódovanie krivky dostáva rovnakú prísnosť. RFC 5480 §2.1.1 pripúšťa, aby ECParameters boli OID menovanej krivky, implicitná krivka (NULL) alebo plná explicitná množina parametrov, a profily PKIX vyžadujú menovanú formu. PDFium Component vracia pcsInvalid, keď certifikát id-ecPublicKey nesie žiadne parametre, implicitné parametre ani explicitné parametre, lebo explicitné parametre dovoľujú útočníkovi opísať krivku, ktorá štandardnú len pripomína. Správne menovaná krivka, ktorá v zozname ISO/TS 32002 jednoducho chýba, ako brainpoolP160r1 vyššie alebo secp256k1, dostane namiesto toho pcsUnsupported
Invalid, unsupported alebo indeterminate: poctivé čítanie statusu
Tri neplatné statusy AlgorithmPolicyStatus znamenajú rôzne veci a ich zrútenie do jedného koša "zlé" zahodí informácie, ktoré audítori potrebujú. pcsInvalid znamená, že rozpoznaná kombinácia algoritmov je deformovaná alebo nesúladná; pridáva ppeiSignatureAlgorithmMismatch do TPadesValidationResult.Issues a stiahne agregovaný IntegrityStatus na pcsInvalid, takže IsCryptographicallyValid vráti False, aj keď hodnota CMS podpisu obstojí. pcsUnsupported znamená, že krivka alebo digest je mimo toho, čo profil menuje, čo je výsledok schopností, nie dôkaz manipulácie. pcsIndeterminate znamená, že certifikát podpisovateľa sa nepodarilo pripichnúť, obvykle CertificateSet s viacerými kandidátmi a bez presnej zhody issuerAndSerialNumber, takže kód odmietne krivku hádať; od v3.124.0 tiež označí RSA podpis nad SHA-1 alebo digestom 112 bitov, ako SHA-224, ktorý sa už pre aktuálnu validáciu nezhoduje. To isté rozdelenie platí pre EdDSA na stroji, ktorého CryptoAPI nedokáže overiť Ed25519 ani Ed448: CmsSignatureStatus zostáva pcsUnsupported, kým AlgorithmPolicyStatus môže byť stále pcsValid, lebo kódovanie bolo správne a chýbal len verifikátor. Ak prenasledujete zamietnutie od Adobe alebo validátora založeného na DSS, príručka o tom, prečo validátory odmietajú PAdES podpisy pokrýva ďalšie bežné príčiny
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, bez 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;
Čo pcsValid negarantuje?
AlgorithmPolicyStatus = pcsValid certifikuje len to, že ECDSA alebo EdDSA podpis používa schválenú krivku s kongruentným, správne zakódovaným digestom a že RSA podpis používa digest z aktuálnej sady; o správnosti hodnoty podpisu nepovie nič. Pred v3.124.0 bola RSA vetva EvaluatePadesSignatureAlgorithm zámerne široká: akýkoľvek signatureAlgorithm pod oblúkom PKCS #1 1.2.840.113549.1.1.* vracal pcsValid, legacy sha1WithRSAEncryption nevynímajúc. Od PDFiumPas v3.124.0 aplikuje RSA vetva signature sady ETSI TS 119 312. Digesty MD2, MD4 a MD5 sú pcsInvalid. SHA-1 a 112-bitové digesty, ako SHA-224, sú pcsIndeterminate, takže podpis SHA-1 si drží svoj celkový integrity výsledok a označí sa na revíziu namiesto zamietnutia. digestAlgorithm, ktorý sa líši od digestu fixovaného signature algoritmom, ako sha256WithRSAEncryption nad digestom SHA-1, alebo RSA signature algoritmus na kľúči podpisovateľa, ktorý RSA nie je, je pcsInvalid, hlásený ako ppeiSignatureAlgorithmMismatch a padá na integrity. Certifikát podpisovateľa, ktorý sa nenájde, dáva pcsIndeterminate, ako už dával pri ECDSA, a nerozpoznané digesty alebo RSA OID, ktoré nie sú signature, dávajú pcsUnsupported. Dĺžka modulu sa stále nekontroluje, parametre PSS sa tu nevalidujú (článok o RSASSA-PSS-params podľa RFC 4055 popisuje, ako sa kódujú na strane podpisovania) a digesty SHA-1 alebo MD5 navyše vyhodia samostatný issue ppeiBadDigestAlgorithm. Podpisová matematika, reťaz certifikátov a revokácia podobne ostávajú prácou CmsSignatureStatus, CertificateTrustStatus a RevocationStatus, ktoré prichádzajú z Windows CryptoAPI. Berte pcsValid ako "algoritmický profil drží", nikdy ako "tento kľúč je dosť silný"
Pre Delphi aplikáciu, ktorá prijíma podpísané PDF faktúry, zmluvy alebo archívne balíky, je praktické nastavenie krátke: spustite ValidatePadesTrust, zamietajte pri ppeiSignatureAlgorithmMismatch, smerujte pcsUnsupported a pcsIndeterminate na človeka a vynúťte si vlastný strop pod dĺžkou RSA kľúča, lebo politika to neurobí. PDFium Component pre Delphi a Lazarus dodáva PAdES validátor, builder evidence reportu aj podpisovú pipeline, takže tá istá knižnica dokáže tieto podpisy vyrábať aj kontrolovať end to end