PDFium Component cho Delphi kiểm tra mọi chữ ký PDF ECDSA và EdDSA đối chiếu profile thuật toán ISO/TS 32002 và báo phán quyết trong TPadesSignatureValidation.AlgorithmPolicyStatus. Chỉ P-256, P-384, P-521, ba curve Brainpool r1, Ed25519 và Ed448 có thể pass, mỗi cái với một digest khớp, và một sự lệch nhau sẽ raise ppeiSignatureAlgorithmMismatch. Chính sách này quan trọng hơn vẻ ngoài của nó. Một chữ ký trên brainpoolP160r1, hay một key P-256 ký một digest SHA-512, hoàn toàn verify ngon ở cấp toán học, nên Windows CryptoAPI báo giá trị chữ ký tốt trong khi một validator PDF 2.0 nghiêm ngặt từ chối file. Phép kiểm chính sách lấp khoảng hổng đó, và nó được cố ý tách khỏi câu hỏi các byte chữ ký có đúng về mặt mật mã hay không
ISO/TS 32002 thực sự cho phép gì cho chữ ký đường cong elip?
ISO/TS 32002 cho phép đúng sáu curve ECDSA và hai scheme EdDSA trong chữ ký PDF, và nó buộc mỗi curve với các kích thước digest mà nó được mang. PDFium Component mã hóa bảng đó trong PadesCurveDigestAllowed, khóa theo curve OID từ certificate của người ký. Các curve NIST nghiêm ngặt: digest phải có cùng độ rộng bit với curve, hoặc SHA-2 hoặc SHA-3. Các curve Brainpool khoan dung hơn và nhận độ rộng của chính nó hoặc bất cứ cái gì rộng hơn:
- P-256 (
1.2.840.10045.3.1.7): chỉ SHA-256 hay SHA3-256 - P-384 (
1.3.132.0.34): chỉ SHA-384 hay SHA3-384 - P-521 (
1.3.132.0.35): chỉ SHA-512 hay SHA3-512 - brainpoolP256r1 (
1.3.36.3.3.2.8.1.1.7): mọi digest SHA-2 hay SHA-3 từ 256 tới 512 bit - brainpoolP384r1 (
1.3.36.3.3.2.8.1.1.11): SHA-2 / SHA-3 384 hay 512 bit - brainpoolP512r1 (
1.3.36.3.3.2.8.1.1.13): chỉ SHA-512 hay SHA3-512
EdDSA chẳng có lựa chọn curve nào cũng như lựa chọn digest nào, và chính vì thế mà các luật của nó xoay quanh encoding chứ không phải độ mạnh. Theo RFC 8419, một SignerInfo Ed25519 phải khai báo SHA-512 làm digestAlgorithm không kèm tham số, và một SignerInfo Ed448, trên đường signed-attributes mà PAdES luôn dùng, phải khai báo id-shake256-len (2.16.840.1.101.3.4.2.18) với tham số INTEGER đúng bằng 512. Với cả hai scheme, chữ ký AlgorithmIdentifier và public key AlgorithmIdentifier của certificate không được mang bất kỳ tham số nào. Một producer viết một NULL ở đó — thói quen mà các RSA encoder đã huấn luyện vào nhiều thư viện ASN.1 — sẽ sinh ra một chữ ký không tuân thủ dù key và giá trị chữ ký đều ổn
PDFium Component trích bộ ba thuật toán từ CMS thế nào
PDFium bản thân nó không thể trả lời câu hỏi này, vì API chữ ký công khai của nó đọc dictionary chữ ký nhưng chẳng verify CMS cũng như chẳng phơi curve của certificate người ký. Vì thế lớp inspection PAdES dựng trên PDFium tự parse CMS SignedData (RFC 5652). InspectPadesSignatureAlgorithm đọc digestAlgorithm và signatureAlgorithm của SignerInfo đầu tiên, rồi tìm certificate người ký và đọc SubjectPublicKeyInfo của nó để lấy thuật toán key và curve. Việc tra certificate được cố ý giới hạn: tối đa 64 certificate trong tập certificates của CMS được xem xét, phép khớp là so byte chính xác issuer và số serial từ issuerAndSerialNumber, và code chỉ rơi về “certificate duy nhất có ở đó” khi tập chứa đúng một certificate parse được. Chọn certificate EC đầu tiên từ một tập không thứ tự thì dễ, và nó sẽ để một certificate CA quyết định curve mà người ký đáng lẽ đã dùng
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 áp chính sách như một phần của lượt kiểm tuân thủ, bắt đầu từ certificate nó định vị bên trong CMS, và TPdf.ValidatePadesTrust chạy lại nó đối với certificate người ký mà Windows CryptoAPI thực sự dùng để verify, nên certificate mà CryptoAPI báo cáo mới là người có tiếng nói cuối. Mọi input thô đều nằm trong TPadesSignatureAlgorithmInfo, bao gồm DigestParametersPresent, DigestParameterBits, SignatureParametersPresent và PublicKeyParametersAreNamedCurve, nên một lần từ chối luôn lý giải được từ record thay vì từ một dòng log
Vì sao một chữ ký P-256 với SHA3-256 gãy chính sách?
Một chữ ký P-256 gãy chính sách của PDFium Component mỗi khi digestAlgorithm của CMS và digest mà signatureAlgorithm ECDSA hàm ý bất đồng, kể cả khi cả hai riêng lẻ đều chấp nhận được cho curve. EvaluatePadesSignatureAlgorithm trước tiên map ecdsa-with-SHA256, ecdsa-with-SHA3-256 và các anh em của chúng về một digest, so sánh với digestAlgorithm đã khai báo, và trả pcsInvalid ngay mọi khác biệt trước khi bảng curve được tra. Ca này có thật: một công cụ ký đổi hash sang SHA3-256 nhưng giữ nguyên identifier ecdsa-with-SHA256 hard-code, và kết quả là một file mà chẳng verifier nào tuân thủ có thể diễn giải một cách nhất quán. Hàm là public, nên ma trận có thể được ghim trong một unit test mà chẳng cần dựng 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 lệch nhau
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, giờ đã tương hợp
Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // curve không nằm trong profile
end;
Encoding của curve nhận cùng độ nghiêm ngặt. RFC 5480 §2.1.1 cho ECParameters là một named curve OID, một implicit curve (NULL) hay một bộ tham số tường minh đầy đủ, và các profile PKIX đòi hình thức named. PDFium Component trả pcsInvalid khi một certificate id-ecPublicKey mang không tham số, tham số implicit hay tham số explicit, vì tham số explicit cho phép kẻ tấn công mô tả một curve chỉ giống một curve chuẩn. Một curve có tên đúng đắn nhưng đơn giản vắng mặt trong danh sách ISO/TS 32002, như brainpoolP160r1 ở trên hay secp256k1, nhận pcsUnsupported thay thế
Invalid, unsupported hay indeterminate: đọc trạng thái một cách trung thực
Ba trạng thái không-hợp-lệ của AlgorithmPolicyStatus mang những nghĩa khác nhau, và dồn chúng vào chung một xô “thất bại” là vứt bỏ đúng thứ thông tin các auditor cần. pcsInvalid nghĩa là một tổ hợp thuật toán được nhận diện bị dị dạng hay lệch nhau; nó thêm ppeiSignatureAlgorithmMismatch vào TPadesValidationResult.Issues và kéo IntegrityStatus tổng thể về pcsInvalid, nên IsCryptographicallyValid trả False kể cả khi giá trị chữ ký CMS kiểm ra đúng. pcsUnsupported nghĩa là curve hay digest nằm ngoài những gì profile nêu tên, một kết quả về khả năng chứ không phải bằng chứng can thiệp. pcsIndeterminate nghĩa là certificate người ký không thể được ghim xuống, thường là một CertificateSet với vài ứng viên và chẳng khớp chính xác issuerAndSerialNumber nào, nên code từ chối đoán curve; từ v3.124.0 nó còn đánh dấu một chữ ký RSA trên SHA-1 hay một digest 112 bit như SHA-224, thứ không còn được công nhận cho validation hiện hành. Phân tách tương tự áp dụng cho EdDSA trên một máy mà CryptoAPI của nó không verify được Ed25519 hay Ed448: CmsSignatureStatus giữ pcsUnsupported trong khi AlgorithmPolicyStatus vẫn có thể là pcsValid, vì encoding đúng và chỉ thiếu verifier. Nếu bạn đang truy một lần từ chối từ Adobe hay một validator dựa trên DSS, hướng dẫn vì sao validator từ chối chữ ký PAdES trình bày các nguyên nhân phổ biến khác
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, không kiểm thu hồi
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;
pcsValid không đảm bảo điều gì?
AlgorithmPolicyStatus = pcsValid chỉ chứng nhận một chữ ký ECDSA hay EdDSA dùng một curve được duyệt với một digest tương hợp và được mã hóa đúng, và một chữ ký RSA dùng digest từ một bộ hiện hành; nó chẳng nói gì về việc giá trị chữ ký có đúng hay không. Trước v3.124.0, nhánh RSA của EvaluatePadesSignatureAlgorithm được cố ý để rộng: bất kỳ signatureAlgorithm nào dưới arc PKCS #1 1.2.840.113549.1.1.* đều trả pcsValid, kể cả sha1WithRSAEncryption cũ kỹ. Từ PDFiumPas v3.124.0, nhánh RSA áp các bộ chữ ký ETSI TS 119 312. Các digest MD2, MD4 và MD5 là pcsInvalid. SHA-1 và các digest 112 bit như SHA-224 là pcsIndeterminate, nên một chữ ký SHA-1 giữ nguyên kết quả integrity tổng thể và được cờ để rà soát thay vì bị từ chối. Một digestAlgorithm khác với digest mà thuật toán chữ ký cố định, như sha256WithRSAEncryption trên một digest SHA-1, hay một thuật toán chữ ký RSA trên một key người ký không phải RSA, là pcsInvalid, báo cáo như ppeiSignatureAlgorithmMismatch và làm gãy integrity. Một certificate người ký không tìm được cho pcsIndeterminate, như nó vốn đã làm với ECDSA, còn các digest không nhận diện được hay các OID RSA không phải chữ ký cho pcsUnsupported. Độ dài modulus vẫn không được kiểm, các tham số PSS không được validate ở đây (bài về RSASSA-PSS-params RFC 4055 trình bày cách chúng được mã hóa phía ký), và các digest SHA-1 hay MD5 còn raise thêm issue riêng ppeiBadDigestAlgorithm. Tương tự, phép toán chữ ký, chuỗi certificate và thu hồi vẫn là việc của CmsSignatureStatus, CertificateTrustStatus và RevocationStatus, các thứ đến từ Windows CryptoAPI. Hãy coi pcsValid là “profile thuật toán đứng vững”, đừng bao giờ coi là “key này đủ mạnh”
Với một ứng dụng Delphi nhận các hóa đơn PDF, hợp đồng hay gói lưu trữ có chữ ký, thiết lập thực dụng khá ngắn: chạy ValidatePadesTrust, từ chối khi thấy ppeiSignatureAlgorithmMismatch, chuyển pcsUnsupported và pcsIndeterminate cho người xử lý, và tự đặt sàn kích thước key RSA của riêng bạn vì chính sách sẽ không làm điều đó. PDFium Component for Delphi and Lazarus giao kèm PAdES validator, bộ dựng báo cáo bằng chứng và pipeline ký, nên cùng một thư viện có thể sinh các chữ ký này và kiểm chúng trọn vẹn từ đầu tới cuối