Il PDFium Component per Delphi verifica ogni firma PDF ECDSA e EdDSA contro il profilo algoritmico di ISO/TS 32002 e riferisce il verdetto in TPadesSignatureValidation.AlgorithmPolicyStatus. Solo P-256, P-384, P-521, le tre curve Brainpool r1, Ed25519 e Ed448 possono passare, ciascuna con un digest corrispondente, e uno scarto solleva ppeiSignatureAlgorithmMismatch. Quella policy conta più di quanto sembri. Una firma su brainpoolP160r1, o una chiave P-256 che firma un digest SHA-512, può verificare benissimo a livello matematico, quindi Windows CryptoAPI dichiara buona la firma mentre un validator severo PDF 2.0 rifiuta il file. Il controllo di policy chiude quel divario, ed è volutamente separato dalla domanda se i byte della firma siano criptograficamente corretti
Che cosa consente davvero ISO/TS 32002 per le firme a curva ellittica?
ISO/TS 32002 consente esattamente sei curve ECDSA e due schemi EdDSA nelle firme PDF, e lega ogni curva alle dimensioni di digest che può portare. Il PDFium Component codifica quella tabella in PadesCurveDigestAllowed, indicizzata per l'OID della curva dal certificato del firmatario. Le curve NIST sono rigide: il digest deve avere la stessa larghezza in bit della curva, SHA-2 oppure SHA-3. Le curve Brainpool sono più larghe e accettano la loro larghezza o qualsiasi cosa più larga:
- P-256 (
1.2.840.10045.3.1.7): solo SHA-256 o SHA3-256 - P-384 (
1.3.132.0.34): solo SHA-384 o SHA3-384 - P-521 (
1.3.132.0.35): solo SHA-512 o SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): qualsiasi digest SHA-2 o SHA-3 da 256 a 512 bit - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 da 384 o 512 bit - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): solo SHA-512 o SHA3-512
EdDSA non ha scelta di curva né scelta di digest, ed è esattamente per questo che le sue regole riguardano la codifica più che la robustezza. Per RFC 8419, un SignerInfo Ed25519 deve dichiarare SHA-512 come suo digestAlgorithm senza parametri, e un SignerInfo Ed448, sul percorso dei signed-attributes che PAdES usa sempre, deve dichiarare id-shake256-len (2.16.840.1.101.3.4.2.18) con un parametro INTEGER di esattamente 512. Per entrambi gli schemi l'AlgorithmIdentifier della firma e l'AlgorithmIdentifier della chiave pubblica del certificato non devono portare alcun parametro. Un produttore che scrive lì un NULL, l'abitudine che i codificatori RSA hanno inoculato in molte librerie ASN.1, produce una firma non conforme anche se chiave e valore della firma stanno bene
Come il PDFium Component estrae la tripla algoritmica dal CMS
PDFium da sé non può rispondere a questa domanda, perché la sua API pubblica delle firme legge il dizionario della firma ma né verifica il CMS né espone la curva del certificato del firmatario. Il livello di ispezione PAdES costruito su PDFium analizza quindi da sé il CMS SignedData (RFC 5652). InspectPadesSignatureAlgorithm legge digestAlgorithm e signatureAlgorithm del primo SignerInfo, poi trova il certificato del firmatario e legge il suo SubjectPublicKeyInfo per ottenere l'algoritmo della chiave e la curva. La ricerca del certificato è volutamente limitata: al massimo 64 certificati nell'insieme certificates del CMS vengono esaminati, la corrispondenza è un confronto esatto a byte di issuer e numero di serie da issuerAndSerialNumber, e il codice ricade su "l'unico certificato che c'è" solo quando l'insieme contiene esattamente un certificato analizzabile. Prendere il primo certificato EC da un insieme non ordinato sarebbe facile, e lascerebbe a un certificato di CA decidere quale curva il firmatario avrebbe usato
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 applica la policy come parte del suo passaggio di conformità, partendo dal certificato che individua dentro il CMS, e TPdf.ValidatePadesTrust la riesegue contro il certificato del firmatario che Windows CryptoAPI ha davvero usato per la verifica, quindi il certificato riferito da CryptoAPI ha l'ultima parola. Ogni input grezzo atterra in TPadesSignatureAlgorithmInfo, compresi DigestParametersPresent, DigestParameterBits, SignatureParametersPresent e PublicKeyParametersAreNamedCurve, così un rifiuto è sempre spiegabile dal record anziché da una riga di log
Perché una firma P-256 con SHA3-256 fallisce la policy?
Una firma P-256 fallisce la policy del PDFium Component ogni volta che il digestAlgorithm del CMS e il digest implicato dall'signatureAlgorithm ECDSA non sono d'accordo, anche se entrambi sono singolarmente accettabili per la curva. EvaluatePadesSignatureAlgorithm mappa prima ecdsa-with-SHA256, ecdsa-with-SHA3-256 e i loro fratelli a un digest, confronta quello con il digestAlgorithm dichiarato e restituisce pcsInvalid a qualsiasi differenza prima ancora di consultare la tabella delle curve. Il caso è reale: uno strumento di firma passa il suo hash a SHA3-256 ma mantiene un identificatore ecdsa-with-SHA256 hard-coded, e il risultato è un file che nessun verifier conforme può interpretare in modo coerente. La funzione è pubblica, quindi la matrice si può inchiodare in uno unit test senza costruire un 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 non corrispondente
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, ora congruente
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // curva fuori dal profilo
end;
La codifica della curva riceve la stessa severità. RFC 5480 §2.1.1 lascia che ECParameters sia un OID di curva nominata, una curva implicita (NULL) o un set completo di parametri espliciti, e i profili PKIX richiedono la forma nominata. Il PDFium Component restituisce pcsInvalid quando un certificato id-ecPublicKey non porta parametri, porta parametri impliciti o parametri espliciti, perché i parametri espliciti lasciano a un attaccante descrivere una curva che somiglia soltanto a una standard. Una curva correttamente nominata che manca semplicemente dalla lista di ISO/TS 32002, come la brainpoolP160r1 sopra o la secp256k1, riceve invece pcsUnsupported
Invalid, unsupported o indeterminate: leggere lo stato con onestà
I tre stati non validi di AlgorithmPolicyStatus significano cose diverse, e accorparli in un unico secchio "fallito" butta via l'informazione di cui gli auditor hanno bisogno. pcsInvalid significa che una combinazione di algoritmi riconosciuta è malformata o disallineata; aggiunge ppeiSignatureAlgorithmMismatch a TPadesValidationResult.Issues e spinge l'IntegrityStatus aggregato a pcsInvalid, così IsCryptographicallyValid restituisce False anche quando il valore della firma CMS torna. pcsUnsupported significa che curva o digest stanno fuori da ciò che il profilo nomina, che è un risultato di capacità, non prova di manomissione. pcsIndeterminate significa che il certificato del firmatario non è stato agganciato, di solito un CertificateSet con diversi candidati e nessuna corrispondenza esatta di issuerAndSerialNumber, quindi il codice si rifiuta di indovinare la curva; dalla v3.124.0 segnala anche una firma RSA su SHA-1 o un digest a 112 bit come SHA-224, che non è più concordato per la validazione attuale. La stessa ripartizione vale per EdDSA su una macchina il cui CryptoAPI non sa verificare Ed25519 o Ed448: CmsSignatureStatus resta pcsUnsupported mentre AlgorithmPolicyStatus può ancora essere pcsValid, perché la codifica era corretta e mancava solo il verifier. Se state inseguendo un rifiuto da Adobe o da un validator basato su DSS, la guida ai motivi per cui i validator rifiutano le firme PAdES copre le altre cause comuni
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, nessuna revoca
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;
Che cosa pcsValid non garantisce?
AlgorithmPolicyStatus = pcsValid certifica solo che una firma ECDSA o EdDSA usa una curva approvata con un digest congruente e correttamente codificato, e che una firma RSA usa un digest di una suite attuale; non dice nulla su se il valore della firma sia corretto. Prima della v3.124.0 il ramo RSA di EvaluatePadesSignatureAlgorithm era volutamente largo: qualsiasi signatureAlgorithm sotto l'arco PKCS #1 1.2.840.113549.1.1.* restituiva pcsValid, sha1WithRSAEncryption compreso. Da PDFiumPas v3.124.0 il ramo RSA applica le suite di firma ETSI TS 119 312. I digest MD2, MD4 e MD5 sono pcsInvalid. SHA-1 e i digest a 112 bit come SHA-224 sono pcsIndeterminate, quindi una firma SHA-1 conserva il suo risultato di integrità complessivo e viene segnalata per revisione anziché rifiutata. Un digestAlgorithm che differisce dal digest fissato dall'algoritmo di firma, come sha256WithRSAEncryption su un digest SHA-1, o un algoritmo di firma RSA su una chiave firmatario non RSA, è pcsInvalid, riferito come ppeiSignatureAlgorithmMismatch e fallisce l'integrità. Un certificato firmatario introvabile dà pcsIndeterminate, come già faceva per ECDSA, e digest non riconosciuti o OID RSA non di firma danno pcsUnsupported. La lunghezza del modulo non è ancora controllata, i parametri PSS non sono validati qui (l'articolo su RSASSA-PSS-params di RFC 4055 copre come vengono codificati sul lato firma), e i digest SHA-1 o MD5 sollevano in più la questione separata ppeiBadDigestAlgorithm. Allo stesso modo, la matematica della firma, la catena dei certificati e la revoca restano compito di CmsSignatureStatus, CertificateTrustStatus e RevocationStatus, che arrivano da Windows CryptoAPI. Trattate pcsValid come "il profilo algoritmico regge", mai come "questa chiave è abbastanza robusta"
Per un'applicazione Delphi che accetta fatture, contratti o pacchetti archivio PDF firmati, l'assetto pratico è breve: eseguite ValidatePadesTrust, rifiutate su ppeiSignatureAlgorithmMismatch, instradate pcsUnsupported e pcsIndeterminate a un essere umano, e imponete la vostra soglia minima di dimensione chiave RSA perché la policy non lo farà. Il PDFium Component for Delphi and Lazarus spedisce il validator PAdES, il costruttore di report di evidenza e la pipeline di firma, così la stessa libreria può produrre queste firme e verificarle end to end