PDFium Component for Delphi memeriksa setiap signature PDF ECDSA dan EdDSA terhadap profil algoritma ISO/TS 32002 dan melaporkan vonisnya di TPadesSignatureValidation.AlgorithmPolicyStatus. Hanya P-256, P-384, P-521, tiga curve Brainpool r1, Ed25519, dan Ed448 yang bisa lolos, masing-masing dengan digest yang cocok, dan ketidakcocokan memunculkan ppeiSignatureAlgorithmMismatch. Policy ini lebih penting daripada kelihatannya. Signature di atas brainpoolP160r1, atau key P-256 yang menandatangani digest SHA-512, bisa terverifikasi dengan sempurna di level matematika, jadi Windows CryptoAPI melaporkan nilai signature sebagai baik sementara validator PDF 2.0 yang ketat menolak berkasnya. Pemeriksaan policy menutup celah itu, dan sengaja dipisahkan dari pertanyaan apakah byte signature benar secara kriptografis
Apa yang sebenarnya diizinkan ISO/TS 32002 untuk signature elliptic curve?
ISO/TS 32002 mengizinkan tepat enam curve ECDSA dan dua skema EdDSA di signature PDF, dan mengaitkan tiap curve ke ukuran digest yang boleh dibawanya. PDFium Component menenkode tabel itu di PadesCurveDigestAllowed, berkunci OID curve dari sertifikat signer. Curve NIST ketat: digest harus selebar bit yang sama dengan curve-nya, SHA-2 atau SHA-3. Curve Brainpool lebih longgar dan menerima lebarnya sendiri atau apa pun yang lebih lebar:
- P-256 (
1.2.840.10045.3.1.7): hanya SHA-256 atau SHA3-256 - P-384 (
1.3.132.0.34): hanya SHA-384 atau SHA3-384 - P-521 (
1.3.132.0.35): hanya SHA-512 atau SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): digest SHA-2 atau SHA-3 apa pun dari 256 sampai 512 bit - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 384 atau 512 bit - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): hanya SHA-512 atau SHA3-512
EdDSA tidak punya pilihan curve dan tidak punya pilihan digest, dan persis itu sebabnya aturannya menyangkut encoding alih-alih kekuatan. Per RFC 8419, SignerInfo Ed25519 harus mendeklarasikan SHA-512 sebagai digestAlgorithm-nya tanpa parameter, dan SignerInfo Ed448, di jalur signed-attributes yang selalu dipakai PAdES, harus mendeklarasikan id-shake256-len (2.16.840.1.101.3.4.2.18) dengan parameter INTEGER tepat 512. Untuk kedua skema, AlgorithmIdentifier signature dan AlgorithmIdentifier public key sertifikat tidak boleh membawa parameter apa pun. Produsen yang menulis NULL di sana — kebiasaan yang encoder RSA tanamkan ke banyak library ASN.1 — menghasilkan signature yang tak patuh meski key dan nilai signature-nya baik-baik saja
Cara PDFium Component mengekstrak triple algoritma dari CMS
PDFium sendiri tidak bisa menjawab pertanyaan ini, karena API signature publiknya membaca dictionary signature tapi tidak memverifikasi CMS maupun mengekspos curve sertifikat signer. Karena itu lapisan inspeksi PAdES yang dibangun di atas PDFium meng-parse CMS SignedData (RFC 5652) sendiri. InspectPadesSignatureAlgorithm membaca digestAlgorithm dan signatureAlgorithm milik SignerInfo pertama, lalu menemukan sertifikat signer dan membaca SubjectPublicKeyInfo-nya untuk mendapatkan algoritma key dan curve-nya. Pencarian sertifikat sengaja dibatasi: paling banyak 64 sertifikat di set certificates CMS diperiksa, pencocokannya perbandingan byte persis atas issuer dan serial number dari issuerAndSerialNumber, dan kode jatuh ke "satu-satunya sertifikat yang ada" hanya saat set itu memuat tepat satu sertifikat yang bisa di-parse. Mengambil sertifikat EC pertama dari set tak berurut itu mudah, dan itu akan membiarkan sertifikat CA memutuskan curve yang dikira dipakai signer
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 menerapkan policy sebagai bagian dari pass kepatuhannya, mulai dari sertifikat yang ia temukan di dalam CMS, dan TPdf.ValidatePadesTrust menjalankannya ulang terhadap sertifikat signer yang benar-benar dipakai Windows CryptoAPI untuk verifikasi, jadi sertifikat yang dilaporkan CryptoAPI yang punya kata final. Setiap input mentah mendarat di TPadesSignatureAlgorithmInfo, termasuk DigestParametersPresent, DigestParameterBits, SignatureParametersPresent, dan PublicKeyParametersAreNamedCurve, jadi penolakan selalu bisa dijelaskan dari record, bukan dari satu baris log
Mengapa signature P-256 dengan SHA3-256 gagal memenuhi policy?
Signature P-256 gagal memenuhi policy PDFium Component kapan pun digestAlgorithm milik CMS dan digest yang tersirat di signatureAlgorithm ECDSA tidak sepakat, bahkan kalau keduanya masing-masing bisa diterima untuk curve itu. EvaluatePadesSignatureAlgorithm pertama memetakan ecdsa-with-SHA256, ecdsa-with-SHA3-256, dan saudara-saudaranya ke sebuah digest, membandingkannya dengan digestAlgorithm yang dideklarasikan, dan mengembalikan pcsInvalid atas perbedaan apa pun sebelum tabel curve dikonsultasikan. Kasusnya nyata: sebuah tool signing mengganti hash-nya ke SHA3-256 tapi mempertahankan identifier ecdsa-with-SHA256 yang hard-coded, dan hasilnya berkas yang tak bisa ditafsirkan konsisten oleh verifier mana pun yang patuh. Fungsinya publik, jadi matriksnya bisa disematkan di unit test tanpa membangun 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 tidak cocok
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, kini kongruen
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // curve tidak ada di profil
end;
Encoding curve-nya mendapat ketelitian yang sama. RFC 5480 §2.1.1 membiarkan ECParameters berupa OID named curve, implicit curve (NULL), atau set parameter eksplisit lengkap, dan profil PKIX mensyaratkan bentuk named. PDFium Component mengembalikan pcsInvalid saat sertifikat id-ecPublicKey membawa tanpa parameter, parameter implisit, atau parameter eksplisit, karena parameter eksplisit membiarkan penyerang mendeskripsikan curve yang sekadar menyerupai yang standar. Named curve yang benar tapi sekadar tak ada di daftar ISO/TS 32002, seperti brainpoolP160r1 di atas atau secp256k1, mendapat pcsUnsupported
Invalid, unsupported, atau indeterminate: membaca statusnya dengan jujur
Ketiga status non-valid milik AlgorithmPolicyStatus bermakna berbeda, dan melumaskannya jadi satu keranjang "gagal" membuang informasi yang dibutuhkan auditor. pcsInvalid berarti kombinasi algoritma yang dikenali itu malformed atau tidak cocok; ia menambahkan ppeiSignatureAlgorithmMismatch ke TPadesValidationResult.Issues dan menyeret IntegrityStatus agregat ke pcsInvalid, jadi IsCryptographicallyValid mengembalikan False bahkan saat nilai signature CMS lolos pemeriksaan. pcsUnsupported berarti curve atau digestnya di luar yang disebut profil, itu hasil kapabilitas, bukan bukti perusakan. pcsIndeterminate berarti sertifikat signer tidak bisa disematkan, biasanya CertificateSet dengan beberapa kandidat tanpa kecocokan issuerAndSerialNumber yang persis, jadi kode menolak menebak curve-nya; sejak v3.124.0 ia juga menandai signature RSA di atas SHA-1 atau digest 112 bit seperti SHA-224, yang tak lagi disepakati untuk validasi masa kini. Pemisahan yang sama berlaku untuk EdDSA di mesin yang CryptoAPI-nya tak bisa memverifikasi Ed25519 atau Ed448: CmsSignatureStatus tetap pcsUnsupported sementara AlgorithmPolicyStatus masih bisa pcsValid, karena encoding-nya benar dan yang hilang hanya verifier-nya. Kalau Anda sedang mengejar penolakan dari Adobe atau validator berbasis DSS, panduan kenapa validator menolak signature PAdES membahas penyebab umum yang lain
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, tanpa 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;
Apa yang tidak dijamin pcsValid?
AlgorithmPolicyStatus = pcsValid hanya mensertifikasi bahwa signature ECDSA atau EdDSA memakai curve yang disetujui dengan digest yang kongruen dan terenkode benar, dan bahwa signature RSA memakai digest dari suite masa kini; ia tak mengatakan apa pun tentang benar-tidaknya nilai signature itu. Sebelum v3.124.0 cabang RSA milik EvaluatePadesSignatureAlgorithm sengaja dibuat longgar: signatureAlgorithm apa pun di bawah arc PKCS #1 1.2.840.113549.1.1.* mengembalikan pcsValid, termasuk sha1WithRSAEncryption yang legacy. Sejak PDFiumPas v3.124.0 cabang RSA menerapkan suite signature ETSI TS 119 312. Digest MD2, MD4, dan MD5 adalah pcsInvalid. SHA-1 dan digest 112 bit seperti SHA-224 adalah pcsIndeterminate, jadi signature SHA-1 mempertahankan hasil integritas menyeluruhnya dan ditandai untuk review alih-alih ditolak. digestAlgorithm yang berbeda dari digest yang ditetapkan algoritma signature, seperti sha256WithRSAEncryption di atas digest SHA-1, atau algoritma signature RSA di key signer non-RSA, adalah pcsInvalid, dilaporkan sebagai ppeiSignatureAlgorithmMismatch dan menggagalkan integritas. Sertifikat signer yang tak ditemukan memberi pcsIndeterminate, seperti yang sudah terjadi untuk ECDSA, dan digest yang tak dikenali atau OID RSA non-signature memberi pcsUnsupported. Panjang modulus tetap tidak diperiksa, parameter PSS tidak divalidasi di sini (artikel RSASSA-PSS-params RFC 4055 membahas cara mereka terenkode di sisi signing), dan digest SHA-1 atau MD5 tambahan memunculkan issue terpisah ppeiBadDigestAlgorithm. Demikian pula, matematika signature, rantai sertifikat, dan revocation tetap tugas CmsSignatureStatus, CertificateTrustStatus, dan RevocationStatus, yang datang dari Windows CryptoAPI. Perlakukan pcsValid sebagai "profil algoritmanya berlaku", jangan pernah sebagai "key ini cukup kuat"
Untuk aplikasi Delphi yang menerima invoice, kontrak, atau paket arsip PDF yang ditandatangani, susunan praktisnya pendek: jalankan ValidatePadesTrust, tolak saat ada ppeiSignatureAlgorithmMismatch, arahkan pcsUnsupported dan pcsIndeterminate ke manusia, dan tegakkan floor ukuran key RSA Anda sendiri karena policy tidak akan melakukannya. PDFium Component untuk Delphi dan Lazarus mengirim validator PAdES, pembangun laporan bukti, dan pipeline signing, jadi library yang sama bisa memproduksi signature-signature ini dan memeriksanya ujung ke ujung