PDFium Component za Delphi proverava svaki ECDSA i EdDSA PDF potpis prema ISO/TS 32002 algoritamskom profilu i presudu prijavljuje u TPadesSignatureValidation.AlgorithmPolicyStatus. Proći mogu samo P-256, P-384, P-521, tri Brainpool r1 krive, Ed25519 i Ed448, svaka sa odgovarajućim digestom, a neslaganje diže ppeiSignatureAlgorithmMismatch. Ta politika je važnija nego što zvuči. Potpis na brainpoolP160r1, ili P-256 ključ koji potpisuje SHA-512 digest, može na nivou matematike proći besprekorno, pa Windows CryptoAPI prijavljuje vrednost potpisa kao ispravnu dok ga strogi PDF 2.0 validator odbacuje. Politika zatvara taj jaz, i namerno je odvojena od pitanja da li su bajtovi potpisa kriptografski ispravni
Šta ISO/TS 32002 zapravo dozvoljava za potpise eliptičkim krivama?
ISO/TS 32002 dozvoljava tačno šest ECDSA krivih i dve EdDSA šeme u PDF potpisima, i svaku krivu veže za veličine digesta koje može da nosi. PDFium Component tu tabelu enkoduje u PadesCurveDigestAllowed, ključanu OID-om krive iz sertifikata potpisnika. NIST krive su stroge: digest mora imati istu širinu u bitovima kao kriva, bilo SHA-2 bilo SHA-3. Brainpool krive su blaže i prihvataju sopstvenu širinu ili bilo šta šire:
- P-256 (
1.2.840.10045.3.1.7): samo SHA-256 ili SHA3-256 - P-384 (
1.3.132.0.34): samo SHA-384 ili SHA3-384 - P-521 (
1.3.132.0.35): samo SHA-512 ili SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): bilo koji SHA-2 ili SHA-3 digest od 256 do 512 bita - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 od 384 ili 512 bita - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): samo SHA-512 ili SHA3-512
EdDSA nema izbor krive ni izbor digesta, i upravo zato su njegova pravila o enkodovanju, a ne o jačini. Po RFC 8419, Ed25519 SignerInfo mora kao digestAlgorithm deklarisati SHA-512 bez parametara, a Ed448 SignerInfo, na putanji potpisanih atributa koju PAdES uvek koristi, mora deklarisati id-shake256-len (2.16.840.1.101.3.4.2.18) sa INTEGER parametrom od tačno 512. Za obe šeme AlgorithmIdentifier potpisa i AlgorithmIdentifier javnog ključa sertifikata ne smeju nositi nikakve parametre. Proizvođač koji tamo upiše NULL, navika koju su RSA enkoderi uvakbili u mnoge ASN.1 biblioteke, proizvodi potpis van standarda iako su ključ i vrednost potpisa sasvim u redu
Kako PDFium Component izvlači algoritamsku trojku iz CMS-a
PDFium sam ne ume da odgovori na ovo pitanje, jer njegov javni signature API čita rečnik potpisa ali ni CMS ne verifikuje ni krivu sertifikata potpisnika ne izlaže. Zato PAdES sloj za inspekciju nadograđen na PDFium sam parsira CMS SignedData (RFC 5652). InspectPadesSignatureAlgorithm čita digestAlgorithm i signatureAlgorithm prvog SignerInfo-a, zatim pronalazi sertifikat potpisnika i čita njegov SubjectPublicKeyInfo da bi došao do algoritma ključa i krive. Pretraga sertifikata je namerno ograničena: pregleda se najviše 64 sertifikata iz CMS certificates skupa, poklapanje je tačno bajtovsko poređenje izdavaoca i serijskog broja iz issuerAndSerialNumber, a kod se vraća na „jedini sertifikat koji tamo ima" samo kada skup drži tačno jedan parsiran sertifikat. Izabrati prvi EC sertifikat iz nesortiranog skupa bilo bi lako, i to bi sertifikatu CA dalo pravo da odluči koju je krivu potpisnik navodno koristio
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 primenjuje politiku kao deo svog prohoda usklađenosti, polazeći od sertifikata koji locira unutar CMS-a, a TPdf.ValidatePadesTrust ponovo je sprovodi nad sertifikatom potpisnika koji je Windows CryptoAPI zaista upotrebio za verifikaciju, pa sertifikat koji prijavi CryptoAPI ima poslednju reč. Svaki sirovi ulaz dospe u TPadesSignatureAlgorithmInfo, uključujući DigestParametersPresent, DigestParameterBits, SignatureParametersPresent i PublicKeyParametersAreNamedCurve, pa je odbijanje uvek objašnjivo iz zapisa, a ne iz reda u logu
Zašto P-256 potpis sa SHA3-256 pada na politici?
P-256 potpis pada na politici PDFium Component kad god se CMS digestAlgorithm i digest koji podrazumeva ECDSA signatureAlgorithm ne slažu, čak i ako su obojica pojedinačno prihvatljiva za krivu. EvaluatePadesSignatureAlgorithm prvo preslikava ecdsa-with-SHA256, ecdsa-with-SHA3-256 i njihovu braću na digest, poredi to sa deklarisanim digestAlgorithm-om i vraća pcsInvalid na bilo kakvu razliku pre nego što se konsultuje tabela krivih. Slučaj je realan: alat za potpisivanje prebaci svoj heš na SHA3-256 ali zadrži hard-kodiran ecdsa-with-SHA256 identifikator, a rezultat je fajl koji nijedan usaglašen verifikator ne ume dosledno da interpretira. Funkcija je javna, pa se matrica može prikovati u unit testu bez građenja PDF-a:
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 se ne slaže
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, sada usklađen
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // kriva nije u profilu
end;
Enkodovanje krive dobija istu strogost. RFC 5480 §2.1.1 dozvoljava da ECParameters bude OID imenovane krive, implicitna kriva (NULL) ili kompletan eksplicitni skup parametara, a PKIX profili traže imenovani oblik. PDFium Component vraća pcsInvalid kada id-ecPublicKey sertifikat ne nosi parametre, nosi implicitne parametre ili eksplicitne parametre, jer eksplicitni parametri napadaču dozvoljavaju da opiše krivu koja samo liči na standardnu. Pravilno imenovana kriva koja jednostavno nedostaje sa ISO/TS 32002 liste, poput gore navedenog brainpoolP160r1 ili secp256k1, dobija pcsUnsupported
Nevalidno, nepodržano ili neodređeno: čitanje statusa bez uljepšavanja
Tri nevalidna statusa AlgorithmPolicyStatus-a znače različite stvari, i njihovo slaganje u jednu kofu „palo je" baca u smeće podatke koje auditorima trebaju. pcsInvalid znači da je prepoznata kombinacija algoritama deformisana ili pogrešno uparena; dodaje ppeiSignatureAlgorithmMismatch u TPadesValidationResult.Issues i tera agregatni IntegrityStatus na pcsInvalid, pa IsCryptographicallyValid vraća False čak i kada se vrednost CMS potpisa proveri uredno. pcsUnsupported znači da je kriva ili digest van onoga što profil imenuje, što je rezultat mogućnosti, a ne dokaz diranja u fajl. pcsIndeterminate znači da se sertifikat potpisnika nije mogao prikovati, obično CertificateSet sa više kandidata i bez tačnog issuerAndSerialNumber poklapanja, pa kod odbija da nagađa krivu; od v3.124.0 tako se obeležava i RSA potpis preko SHA-1 ili 112-bitnog digesta poput SHA-224, za koji više nema dogovora da važi za tekuću validaciju. Ista podela važi i za EdDSA na mašini čiji CryptoAPI ne ume da verifikuje Ed25519 ili Ed448: CmsSignatureStatus ostaje pcsUnsupported dok AlgorithmPolicyStatus može biti i pcsValid, jer je enkodovanje bilo ispravno a nedostajao je samo verifikator. Ako jurite odbijanje od Adobea ili DSS validatora, vodič o tome zašto validatori odbacuju PAdES potpise pokriva ostale uobičajene razloge
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; // oflajn, bez provere opoziva
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;
Šta pcsValid ne garantuje?
AlgorithmPolicyStatus = pcsValid samo potvrđuje da ECDSA ili EdDSA potpis koristi odobrenu krivu sa usklađenim, pravilno enkodovanim digestom, i da RSA potpis koristi digest iz tekućeg skupa; ne govori ništa o tome da li je vrednost potpisa ispravna. Pre v3.124.0 RSA grana EvaluatePadesSignatureAlgorithm-a bila je namerno široka: svaki signatureAlgorithm ispod PKCS #1 grane 1.2.840.113549.1.1.* vraćao je pcsValid, uključujući zastareli sha1WithRSAEncryption. Od PDFiumPas v3.124.0 RSA grana primenjuje signature skupove iz ETSI TS 119 312. MD2, MD4 i MD5 digesti su pcsInvalid. SHA-1 i 112-bitni digesti poput SHA-224 su pcsIndeterminate, pa SHA-1 potpis zadržava svoj ukupni rezultat integriteta i obeležava se za pregled umesto da se odbaci. digestAlgorithm koji se razlikuje od digesta koji signature algoritam fiksira, poput sha256WithRSAEncryption preko SHA-1 digesta, ili RSA signature algoritam na ne-RSA ključu potpisnika, je pcsInvalid, prijavljen kao ppeiSignatureAlgorithmMismatch i pada na integritetu. Sertifikat potpisnika koji se ne može pronaći daje pcsIndeterminate, kao što je već bilo i za ECDSA, a neprepoznati digesti i ne-signature RSA OID-i daju pcsUnsupported. Dužina modulusa se i dalje ne proverava, PSS parametri se ovde ne validiraju (članak o RFC 4055 RSASSA-PSS-params pokriva kako se enkoduju na strani potpisivanja), a SHA-1 ili MD5 digesti dodatno dižu i poseban problem ppeiBadDigestAlgorithm. Isto tako, matematika potpisa, lanac sertifikata i revokacija ostaju posao CmsSignatureStatus-a, CertificateTrustStatus-a i RevocationStatus-a, koji dolaze sa Windows CryptoAPI. Tretirajte pcsValid kao „algoritamski profil važi", nikada kao „ovaj ključ je dovoljno jak"
Za Delphi aplikaciju koja prima potpisane PDF fakture, ugovore ili arhivske pakete, praktično podešavanje je kratko: pustite ValidatePadesTrust, odbacujte na ppeiSignatureAlgorithmMismatch, usmerite pcsUnsupported i pcsIndeterminate ljudima, i sami sprovedite svoj donji prag veličine RSA ključa jer to politika neće. PDFium Component za Delphi i Lazarus isporučuje PAdES validator, graditelja izveštaja o dokazima i pipeline za potpisivanje, pa ista biblioteka ume i da proizvede ove potpise i da ih proveri od početka do kraja