我正在开发一个项目,需要使用 Node.js 对 PDF 进行数字签名。我的目标是使用 .pfx 证书签署 PDF 并包含时间戳 (TSA)。然而,尽管遵循了预期的流程,Adobe Acrobat 仍报告签名无效。证书本身有效,并且签名的文件通过了 ITI 验证程序(巴西官方数字签名验证程序)的验证,但未在 Acrobat 中通过验证。
我做了什么:
1. Used the @signpdf/signpdf library for signing.
2. Added a ByteRange placeholder with @signpdf/placeholder-plain.
3. Configured the TSA using the URL: https://freetsa.org/tsr.
4. Ensured the .pfx certificate matches the private key.
const fs = require('fs');
const { plainAddPlaceholder } = require('@signpdf/placeholder-plain');
const { SignPdf } = require('@signpdf/signpdf');
const { P12Signer } = require('@signpdf/signer-p12');
// Paths
const pdfPath = '../resources/pdf-entrada.pdf'; // Input PDF
const pfxPath = '../resources/certificado.pfx'; // PFX certificate
const signedPath = '../resources/output-signed.pdf'; // Final signed PDF
const pfxPassword = 'your_password_here'; // Certificate password
const tsaUrl = 'https://freetsa.org/tsr'; // TSA URL
async function signPdfWithTSA() {
try {
console.log('--- Start of Signing Process ---');
console.log('[1] Reading the original PDF...');
const pdfBuffer = fs.readFileSync(pdfPath);
console.log(`[1] Original PDF size: ${pdfBuffer.length} bytes`);
console.log('[2] Adding ByteRange (Placeholder for signature)...');
const pdfWithPlaceholder = plainAddPlaceholder({
pdfBuffer,
reason: 'Digital Signature',
contactInfo: '[email protected]',
name: 'Your Name',
location: 'City / Country',
});
console.log(`[2] PDF with Placeholder size: ${pdfWithPlaceholder.length} bytes`);
console.log('[3] Reading the PFX certificate...');
const pfxBuffer = fs.readFileSync(pfxPath);
console.log(`[3] PFX certificate size: ${pfxBuffer.length} bytes`);
console.log('[4] Configuring signer with TSA...');
const signer = new P12Signer(pfxBuffer, { passphrase: pfxPassword, tsaUrl });
const signPdf = new SignPdf();
console.log('[5] Signing the PDF...');
const signedPdfBuffer = await signPdf.sign(pdfWithPlaceholder, signer);
console.log(`[5] Signed PDF size: ${signedPdfBuffer.length} bytes`);
console.log('[6] Saving the signed PDF...');
fs.writeFileSync(signedPath, signedPdfBuffer);
console.log(`[6] Signed PDF saved at: ${signedPath}`);
console.log('--- End of Signing Process ---');
} catch (error) {
console.error('Error while signing the PDF:', error.message || error);
}
}
signPdfWithTSA();
我的控制台输出:
--- Start of Signing Process ---
[1] Reading the original PDF...
[1] Original PDF size: 22368 bytes
[2] Adding ByteRange (Placeholder for signature)...
[2] PDF with Placeholder size: 39989 bytes
[3] Reading the PFX certificate...
[3] PFX certificate size: 9200 bytes
[4] Configuring signer with TSA...
[5] Signing the PDF...
[5] Signed PDF size: 39989 bytes
[6] Saving the signed PDF...
[6] Signed PDF saved at: ../resources/output-signed.pdf
--- End of Signing Process ---
问题:
简而言之, 您的签名者证书具有 Acrobat 明确不接受的证书策略 OID。您需要不同类型的证书。
详细来说,我们来看看Adobe到底是怎么说的:
因此,从加密角度来说,签名是可以的(“尚未修改”),问题是 Acrobat 不接受您的证书(“签名者的证书无效”)。因此,签名有效性问题与您的代码无关。
为什么 Acrobat 认为您的证书无效?我们来看看 Acrobat 证书查看器中的证书:
正如您在底部看到的,Acrobat 在您的证书中发现了无效的策略限制。这是指我在右侧“详细信息”选项卡上选择的“证书策略”值:您的证书已使用 OID 2.16.76.1.2.1.133 的证书策略创建。
但是请查看根“Autoridade Certificadora Raiz Brasileira v5”证书条目的“策略”选项卡:
这里可以看到签名者证书有Policy值限制。不幸的是,我们无法滚动允许的证书策略字段,但查看 Acrobat 的数据文件可以找到可接受的 OID 范围:
如您所见,当您的证书具有来自 2.16.76.1.2.1.* 分支的策略时,仅接受来自 2.16.76.1.2.3.* 和 2.16.76.1.2.4.* OID 分支的值。
换句话说,Acrobat 明确将像您这样的证书排除在信任之外。如果您希望其他用户的 Acrobat 实例接受您的签名,则必须切换到 Acrobat 信任的证书。
分析嵌入的签名容器,确实没有嵌入时间戳令牌。因此,由于某种原因,您的代码没有嵌入它。由于我不知道所使用的签名 API,因此我无法建议进一步的措施。
不同的验证器可能对签名应用不同的验证例程。特别是可接受的签名者证书的范围(由信任锚和附加限制定义)可能会有所不同。
如上所述,Acrobat 仅接受由“Autoridade Certificadora Raiz Brasileira v5”颁发的签名者证书(如果它们包含某些证书策略 OID 条目)。 ITI 也可能允许其他 OID 范围,或者可能完全忽略策略 OID。