A Delphi PDFium Component minden ECDSA és EdDSA PDF aláírást az ISO/TS 32002 algoritmusprofilra ellenőriz, és az ítéletet a TPadesSignatureValidation.AlgorithmPolicyStatus-ban jelenti. Csak a P-256, a P-384, a P-521, a három Brainpool r1 görbe, az Ed25519 és az Ed448 mehet át, mindegyik illeszkedő digesttel, az eltérés pedig ppeiSignatureAlgorithmMismatch-ot dob. Ez a házirend fontosabb, mint aminek hangzik. Egy brainpoolP160r1-en készült aláírás, vagy egy SHA-512 digestet aláíró P-256-os kulcs a matematika szintjén tökéletesen ellenőrizhető, így a Windows CryptoAPI az aláírásértéket jónak jelenti, miközben egy szigorú PDF 2.0 validátor elutasítja a fájlt. A házirend-ellenőrzés bezárja ezt a rést, és szándékosan külön áll attól a kérdéstől, hogy az alágírás bájtok kriptográfiailag helyesek-e
Mit enged valójában az ISO/TS 32002 az elliptikus görbés aláírásoknál?
Az ISO/TS 32002 pontosan hat ECDSA görbét és két EdDSA sémát enged a PDF aláírásoknál, és mindegyik görbét ahhoz a digestmérethez köti, amelyet hordozhat. A PDFium Component ezt a táblát a PadesCurveDigestAllowed-ban kódolja, az aláíró tanúsítványból vett görbe OID szerint kulcsolva. A NIST görbék szigorúak: a digestnek azonos bitszélességűnek kell lennie a görbével, SHA-2 vagy SHA-3. A Brainpool görbék engedékenyebbek, és elfogadják a saját szélességüket vagy bármi szélesebbet:
- P-256 (
1.2.840.10045.3.1.7): csak SHA-256 vagy SHA3-256 - P-384 (
1.3.132.0.34): csak SHA-384 vagy SHA3-384 - P-521 (
1.3.132.0.35): csak SHA-512 vagy SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): bármilyen 256–512 bites SHA-2 vagy SHA-3 digest - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): 384 vagy 512 bites SHA-2 / SHA-3 - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): csak SHA-512 vagy SHA3-512
Az EdDSA-nak nincs görbe- és digestválasztása, pontosan ezért szólnak szabályai inkább a kódolásról, mint az erőről. Az RFC 8419 szerint egy Ed25519 SignerInfo-nak SHA-512-t kell digestAlgorithm-ként deklarálnia paraméterek nélkül, egy Ed448 SignerInfo-nak pedig — a PAdES által mindig használt signed-attributes úton — id-shake256-len-t (2.16.840.1.101.3.4.2.18) kell deklarálnia pontosan 512 értékű INTEGER paraméterrel. Mindkét sémánál az alágírás AlgorithmIdentifier-ének és a tanúsítvány nyilvános kulcsának AlgorithmIdentifier-ének egyáltalán nem szabad paramétert hordoznia. Egy ott NULL-t író gyártó — ezt a szokást az RSA kódolók nevelték sok ASN.1 könyvtárba — nem megfelelő aláírást gyárt, holott a kulcs és az alágírásérték rendben van
Hogyan nyeri ki a PDFium Component az algoritmus-hármast a CMS-ből
A PDFium önmagában nem tudja megválaszolni ezt a kérdést, mert a nyilvános alágírás API-ja az alágírás szótárát olvassa, de sem a CMS-t nem ellenőrzi, sem az aláíró tanúsítvány görbéjét nem fed fel. A PDFiumra épülő PAdES vizsgálati réteg ezért maga elemzi a CMS SignedData-t (RFC 5652). Az InspectPadesSignatureAlgorithm beolvassa az első SignerInfo digestAlgorithm-ját és signatureAlgorithm-ját, majd megkeresi az aláíró tanúsítványt, és beolvassa a SubjectPublicKeyInfo-ját, hogy megkapja a kulcs algoritmusát és a görbét. A tanúsítványkeresés szándékosan határolt: a CMS certificates halmazából legfeljebb 64 tanúsítvány vizsgálódik, az egyezés a issuerAndSerialNumber-ből vett kibocsátó és sorozatszám pontos bájt-összevetése, és a kód csak akkor tér át „az egyetlen ott lévő tanúsítványra”, ha a halmaz pontosan egy elemzhető tanúsítványt tartalmaz. Az első EC tanúsítvány felvétele egy rendezetlen halmazból könnyű lenne, és az engedné, hogy egy CA tanúsítvány döntse el, melyik görbét használta állítólag az aláíró
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;
A TPdf.ValidatePades a megfelelőségi menet részeként alkalmazza a házirendet, a CMS-en belül megtalált tanúsítványból kiindulva, a TPdf.ValidatePadesTrust pedig azon az aláíró tanúsítványon futtatja újra, amelyet a Windows CryptoAPI ténylegesen használt az ellenőrzéshez, így a CryptoAPI által jelentett tanúsítványnak van végső szava. Minden nyers bemenet a TPadesSignatureAlgorithmInfo-ba kerül, a DigestParametersPresent-t, a DigestParameterBits-t, a SignatureParametersPresent-t és a PublicKeyParametersAreNamedCurve-t is beleértve, így egy elutasítás mindig a rekordból magyarázható, naplósor helyett
Miért bukik meg a házirenden egy SHA3-256-os P-256 aláírás?
Egy P-256 aláírás akkor bukik meg a PDFium Component házirendjén, ha a CMS digestAlgorithm-ja és az ECDSA signatureAlgorithm által sejtetett digest eltér, akkor is, ha mindkettő önmagában elfogadható a görbére. Az EvaluatePadesSignatureAlgorithm először az ecdsa-with-SHA256-ot, az ecdsa-with-SHA3-256-ot és testvéreiket digestre képezi le, összeveti a deklarált digestAlgorithm-mal, és bármilyen eltérésnél pcsInvalid-ot ad, mielőtt a görbetáblához érne. Az eset valós: egy aláíró eszköz SHA3-256-ra váltja a hashét, de megtartja a bedrótozott ecdsa-with-SHA256 azonosítót, és az eredmény olyan fájl, amelyet megfelelő ellenőrző következetesen nem tud értelmezni. A függvény nyilvános, így a mátrix unit tesztben szögezhető le PDF építése nélkül:
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); // a digest nem egyezik
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, immár kongruens
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // a görbe nincs a profilban
end;
A görbe kódolása ugyanolyan szigorúságot kap. Az RFC 5480 §2.1.1 engedi, hogy az ECParameters nevesített görbe OID, implicit görbe (NULL) vagy teljes explicit paraméterkészlet legyen, a PKIX profilok pedig a nevesített formát követelik meg. A PDFium Component pcsInvalid-ot ad, ha egy id-ecPublicKey tanúsítvány nem hordoz paramétert, implicit paramétert vagy explicit paramétert hordoz, mert az explicit paraméterek lehetővé teszik, hogy egy támadó olyan görbét írjon le, amely csak hasonlít egy standardhoz. A helyesen nevesített, az ISO/TS 32002 listájáról egyszerűen lemaradt görbe — a fenti brainpoolP160r1 vagy a secp256k1 — pcsUnsupported-ot kap helyette
Érvénytelen, nem támogatott vagy meghatározhatatlan: az állapot becsületes olvasása
A AlgorithmPolicyStatus három nem érvényes állapota különböző dolgokat jelent, és egyetlen „elbukott” vödörbe gyűrve elveszik az az információ, amelyre az auditoroknak szükségük van. A pcsInvalid azt jelenti, hogy egy felismert algoritmus-kombináció rosszul formált vagy eltérő; felveszi a ppeiSignatureAlgorithmMismatch-ot a TPadesValidationResult.Issues-ba, és az összesített IntegrityStatus-t pcsInvalid-ra viszi, így az IsCryptographicallyValid False-t ad akkor is, ha a CMS alágírásértéke rendben van. A pcsUnsupported azt jelenti, hogy a görbe vagy a digest a profil által nevesítetten kívül esik — ez képesség-eredmény, nem bizonyíték manipulációra. A pcsIndeterminate azt jelenti, hogy az aláíró tanúsítványt nem sikerült rögzíteni, jellemzően több jelölttel bíró CertificateSet és pontos issuerAndSerialNumber egyezés nélkül, így a kód megtagadja a görbe találgatását; a v3.124.0 óta SHA-1 feletti vagy SHA-224 jellegű, 112 bites digestet hordozó RSA aláírást is így jelöl, mert a jelenlegi ellenőrzéshez már nem egyeztetett. Ugyanez a felosztás él az EdDSA-ra olyan gépen, amelynek CryptoAPI-ja nem tudja ellenőrizni az Ed25519-et vagy az Ed448-at: a CmsSignatureStatus pcsUnsupported marad, miközben az AlgorithmPolicyStatus lehet még pcsValid, mert a kódolás helyes volt, csak az ellenőrző hiányzott. Ha Adobe-tól vagy DSS alapú validátortól üldözi az elutasítás, a validátorok PAdES aláírás-elutasításait ismertető útikalauz a többi gyakori okot sorolja fel
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, visszavonás-ellenőrzés nélkül
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;
Mit nem garantál a pcsValid?
A AlgorithmPolicyStatus = pcsValid csak azt tanúsítja, hogy egy ECDSA vagy EdDSA alágírás jóváhagyott görbét használ illeszkedő, helyesen kódolt digesttel, egy RSA alágírás pedig egy aktuális csomagból vett digestet; arról nem szól, hogy az alágírásérték helyes-e. A v3.124.0 előtt az EvaluatePadesSignatureAlgorithm RSA ága szándékosan széles volt: bármely signatureAlgorithm a PKCS #1 arc alatt, az 1.2.840.113549.1.1.* alatt, pcsValid-ot adott, a legacy sha1WithRSAEncryption-t is beleértve. A PDFiumPas v3.124.0 óta az RSA ág az ETSI TS 119 312 alágírási csomagokat alkalmazza. Az MD2, MD4 és MD5 digestek pcsInvalid-ot kapnak. A SHA-1 és az olyan 112 bites digestek, mint a SHA-224, pcsIndeterminate-et kapnak, így egy SHA-1 alágírás megtartja az összesített integritási eredményét, és felülvizsgálatra jelölődik, nem pedig elutasításra. Olyan digestAlgorithm, amely eltér az alágírás algoritmus által rögzített digesttől — például sha256WithRSAEncryption SHA-1 digest felett —, vagy RSA alágírásalgoritmus nem RSA aláírónkulcson, pcsInvalid, ppeiSignatureAlgorithmMismatch-ként jelentve megbuktatja az integritást. A nem megtalálható aláíró tanúsítvány pcsIndeterminate-et ad, ahogy az ECDSA-nál is tette, a felismerhetetlen digestek vagy nem alágírás RSA OID-ok pedig pcsUnsupported-ot. A modulus hossza továbbra sem ellenőrzött, a PSS paraméterek itt nem validálódnak (az RFC 4055 RSASSA-PSS-params cikk tárgyalja, hogyan kódolódnak az aláíró oldalon), a SHA-1 vagy MD5 digest ráadásul a külön ppeiBadDigestAlgorithm problémát is felveti. Hasonlóan: az alágírás matematikája, a tanúsítványlánc és a visszavonás továbbra is a CmsSignatureStatus, a CertificateTrustStatus és a RevocationStatus dolga, amelyek a Windows CryptoAPI-ból jönnek. A pcsValid-t úgy kezelje, mint „az algoritmusprofil áll”, sosem mint „ez a kulcs elég erős”
Egy aláírt PDF számlákat, szerződéseket vagy archív csomagokat elfogadó Delphi alkalmazásnak a gyakorlati beállítása rövid: futtassa a ValidatePadesTrust-ot, utasítsa el ppeiSignatureAlgorithmMismatch-ra, irányítsa az pcsUnsupported-ot és az pcsIndeterminate-et emberhez, és érvényesítse a saját RSA kulcsméret-alapját, mert a házirend ezt nem teszi meg. A Delphihez és Lazarushoz készült PDFium Component szállítja a PAdES validátort, a bizonyítékriport-építőt és az aláírási futószalagot, így ugyanaz a könyvtár elő tudja állítani ezeket az aláírásokat, és végről végre ellenőrizni is tudja őket