PDFium Component za Delphi preveri vsak podpis PDF ECDSA in EdDSA proti profilu algoritmov ISO/TS 32002 in sodbo poroča v TPadesSignatureValidation.AlgorithmPolicyStatus. Prestati lahko le P-256, P-384, P-521, tri krivulje Brainpool r1, Ed25519 in Ed448, vsaka z ujemajočim digestom, neskladje pa sproži ppeiSignatureAlgorithmMismatch. Ta politika je pomembnejša, kot se sliši. Podpis na brainpoolP160r1 ali ključ P-256, ki podpisuje digest SHA-512, se lahko na matematični ravni povsem lepo preveri, zato Windows CryptoAPI prijavi vrednost podpisa kot dobro, strog validirnik PDF 2.0 pa datoteko zavrne. Preverjanje politike zapre to vrzel in je namerno ločeno od vprašanja, ali so bajti podpisa kriptografsko pravilni
Kaj ISO/TS 32002 dejansko dovoljuje za podpise na eliptičnih krivuljah?
ISO/TS 32002 dovoljuje točno šest krivulj ECDSA in dve shemi EdDSA v podpisih PDF in vsako krivuljo veže na velikosti digestov, ki jih sme nositi. PDFium Component to tabelo kodira v PadesCurveDigestAllowed, ključano po OID krivulje iz certifikata podpisnika. Krivulje NIST so stroge: digest mora imeti isto bitno širino kot krivulja, bodisi SHA-2 bodisi SHA-3. Krivulje Brainpool so popustljivejše in sprejmejo svojo širino ali karkoli širšega:
- P-256 (
1.2.840.10045.3.1.7): le SHA-256 ali SHA3-256 - P-384 (
1.3.132.0.34): le SHA-384 ali SHA3-384 - P-521 (
1.3.132.0.35): le SHA-512 ali SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): kateri koli digest SHA-2 ali SHA-3 od 256 do 512 bitov - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 s 384 ali 512 biti - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): le SHA-512 ali SHA3-512
EdDSA nima izbire krivulje niti izbire digesta, in točno zato so njegova pravila o kodiranju, ne o moči. Po RFC 8419 mora SignerInfo Ed25519 deklarirati SHA-512 kot svoj digestAlgorithm brez parametrov, SignerInfo Ed448 pa, na poti podpisanih atributov, ki jo PAdES vedno uporablja, deklarirati id-shake256-len (2.16.840.1.101.3.4.2.18) s parametrom INTEGER, natanko 512. Za obe shemi morata AlgorithmIdentifier podpisa in AlgorithmIdentifier javnega ključa certifikata nositi sploh nič parametrov. Izdelovalec, ki tam zapiše NULL, navada, ki so jo kodirniki RSA vzgojili v marsikateri knjižnici ASN.1, izdela neskladen podpis, čeprav sta ključ in vrednost podpisa v redu
Kako PDFium Component izlušči trojko algoritmov iz CMS
PDFium samo ne more odgovoriti na to vprašanje, ker njegov javni API podpisov bere slovar podpisov, CMS pa ne preveri in ne razkrije krivulje certifikata podpisnika. Sloj za pregledovanje PAdES, zgrajen na PDFium, zato razčleni CMS SignedData (RFC 5652) sam. InspectPadesSignatureAlgorithm prebere digestAlgorithm in signatureAlgorithm prvega SignerInfo, nato poišče certifikat podpisnika in prebere njegov SubjectPublicKeyInfo, da dobi algoritem ključa in krivuljo. Iskanje certifikata je namerno omejeno: pregledanih je največ 64 certifikatov v množici certificates CMS, ujemanje je točna primerjava bajtov izdajatelja in serijske številke iz issuerAndSerialNumber, koda pa se vrne na »edini certifikat, ki ga je tam« šele, kadar množica drži točno en razčlenljiv certifikat. Izbrati prvi certifikat EC iz neurejene množice bi bilo lahko in bi pustilo, da odloči certifikat CA, katero krivuljo je podpisnik domnevno uporabil
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 uporabi politiko kot del svojega prehoda skladnosti, začenši od certifikata, ki ga najde znotraj CMS, TPdf.ValidatePadesTrust pa jo ponovno požene proti certifikatu podpisnika, ki ga je Windows CryptoAPI dejansko uporabil za preverjanje, tako da ima certifikat, prijavljen s strani CryptoAPI, zadnjo besedo. Vsak surovi vhod pristane v TPadesSignatureAlgorithmInfo, vključno s DigestParametersPresent, DigestParameterBits, SignatureParametersPresent in PublicKeyParametersAreNamedCurve, tako da je zavrnitev vedno razložljiva iz zapisa, ne iz vrstice dnevnika
Zakaj podpis P-256 s SHA3-256 podre politiko?
Podpis P-256 podere politiko PDFium Component vsakič, kadar se CMS digestAlgorithm in digest, ki ga nakazuje ECDSA signatureAlgorithm, razhajata, tudi če sta oba posamezno sprejemljiva za krivuljo. EvaluatePadesSignatureAlgorithm najprej preslika ecdsa-with-SHA256, ecdsa-with-SHA3-256 in njune sorodnike na digest, primerja to z deklariranim digestAlgorithm in vrne pcsInvalid ob vsaki razliki, preden se posvetuje tabela krivulj. Primer je resničen: podpisovalno orodje preklopi svoj hash na SHA3-256, obdrži pa trdo zakodiran identifikator ecdsa-with-SHA256, rezultat pa je datoteka, ki je noben skladen preverjevalnik ne more dosledno razlagati. Funkcija je javna, tako da lahko matriko pripnete v enotski preizkus brez gradnje 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); // neskladje digestov
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, zdaj skladen
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // krivulja ni v profilu
end;
Kodiranje krivulje dobi isto strogost. RFC 5480 §2.1.1 pusti ECParameters biti OID poimenovane krivulje, implicitna krivulja (NULL) ali poln eksplicitni nabor parametrov, profili PKIX pa zahtevajo poimenovano obliko. PDFium Component vrne pcsInvalid, kadar certifikat id-ecPublicKey ne nosi parametrov, nosi implicitne ali eksplicitne parametre, ker eksplicitni parametri pustijo napadalcu opisati krivuljo, ki le zgleda podobna standardni. Pravilno poimenovana krivulja, ki je na seznamu ISO/TS 32002 preprosto ni, kot je brainpoolP160r1 zgoraj ali secp256k1, dobi namesto tega pcsUnsupported
Neveljavno, nepodprto ali nedoločeno: iskreno branje statusa
Trije neveljavni statusi AlgorithmPolicyStatus pomenijo različne stvari, njihovo spraskanje v en vedro »spodletelo« pa vrže stran informacijo, ki jo revizorji potrebujejo. pcsInvalid pomeni, da je prepoznana kombinacija algoritmov deformirana ali neskladna; doda ppeiSignatureAlgorithmMismatch v TPadesValidationResult.Issues in pripelje agregatni IntegrityStatus do pcsInvalid, tako da IsCryptographicallyValid vrne False, tudi kadar se vrednost podpisa CMS izkaže za dobro. pcsUnsupported pomeni, da sta krivulja ali digest izven tistega, kar poimenuje profil, kar je rezultat zmožnosti, ne dokaz prikrivanja. pcsIndeterminate pomeni, da se certifikat podpisnika ni dal pripeti, običajno CertificateSet z več kandidati in brez točnega ujemanja issuerAndSerialNumber, zato koda odkloni ugibanje krivulje; od v3.124.0 označi tudi podpis RSA nad SHA-1 ali 112-bitnim digestom, kot je SHA-224, ki za trenutno validacijo ni več dogovorjen. Isti razkol velja za EdDSA na stroju, katerega CryptoAPI ne zmore preveriti Ed25519 ali Ed448: CmsSignatureStatus ostane pcsUnsupported, AlgorithmPolicyStatus pa je lahko še vedno pcsValid, ker je bilo kodiranje pravilno in manjkal je le preverjevalnik. Če zasledujete zavrnitev od Adobe ali validirnika na osnovi DSS, vodnik o tem, zakaj validirniki zavrnejo podpise PAdES, pokriva druge pogoste vzroke
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; // brez povezave, brez preklica
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;
Kaj pcsValid ne jamči?
AlgorithmPolicyStatus = pcsValid le priča, da podpis ECDSA ali EdDSA uporablja odobreno krivuljo s skladnim, pravilno kodiranim digestom, podpis RSA pa digest iz trenutnega nabora; o tem, ali je vrednost podpisa pravilna, ne pove nič. Pred v3.124.0 je bila veja RSA EvaluatePadesSignatureAlgorithm namerno široka: vsak signatureAlgorithm pod loku PKCS #1 1.2.840.113549.1.1.* je vrnil pcsValid, vključno s starejšim sha1WithRSAEncryption. Od PDFiumPas v3.124.0 veja RSA uporablja nabora podpisov ETSI TS 119 312. Digesti MD2, MD4 in MD5 so pcsInvalid. SHA-1 in 112-bitni digesti, kot je SHA-224, so pcsIndeterminate, tako da podpis SHA-1 obdrži svoj skupni rezultat celovitosti in je označen za pregled, namesto da bi bil zavrnjen. digestAlgorithm, ki se razlikuje od digesta, ki ga je določil algoritem podpisa, kot je sha256WithRSAEncryption nad digestom SHA-1, ali algoritem podpisa RSA na ključu podpisnika, ki ni RSA, je pcsInvalid, prijavljen kot ppeiSignatureAlgorithmMismatch, in podre celovitost. Certifikat podpisnika, ki ga ni mogoče najti, da pcsIndeterminate, kot je že dajal za ECDSA, neprepoznani digesti ali OIDI RSA, ki niso podpisi, pa pcsUnsupported. Dolžina modula se še vedno ne preverja, parametri PSS se tukaj ne validirajo (članek o parametrih RSASSA-PSS RFC 4055 pokriva, kako so kodirani na strani podpisovanja), digesti SHA-1 ali MD5 pa dodatno sprožijo ločeno težavo ppeiBadDigestAlgorithm. Enako ostajajo matemacija podpisa, veriga certifikatov in preklic naloga CmsSignatureStatus, CertificateTrustStatus in RevocationStatus, ki prihajajo iz Windows CryptoAPI. pcsValid obravnavajte kot »profil algoritmov drži«, nikoli kot »ta ključ je dovolj močan«
Za aplikacijo Delphi, ki sprejema podpisane račune, pogodbe ali arhivske pakete PDF, je praktična nastavitev kratka: poženite ValidatePadesTrust, zavrnite ob ppeiSignatureAlgorithmMismatch, pcsUnsupported in pcsIndeterminate usmerite človeku in vsilite lastno spodnjo mejo velikosti ključev RSA, ker je politika te ne bo vsilila. PDFium Component za Delphi in Lazarus prinaša validirnik PAdES, graditelj poročil o dokazih in cevovod podpisovanja, tako da lahko ista knjižnica te podpise izdela in preveri od začetka do konca