如何解决使用我的代码制作PAdES LT签名时,Adobe显示PKCS7解析错误
我正在研究制作PAdES签名的Web应用程序。我已经成功实现了PAdES Baseline-B。但是,当我尝试通过添加here中所述的所有必要信息(包括OCSP响应,CRL和证书)来创建PAdES Baseline-LT时,似乎文件已损坏并且Adobe显示以下错误:签名验证期间出现错误:PKCS7解析错误
如果您想看一下,这里是签名的PDF:https://easyupload.io/fxkzvs
我在对PDF签名后附加了DSS,所以我附加以获取LT子类型的那些附加对象不会影响签名本身,因此我不确定为什么会出现PKCS7错误,如果我做出的签名相同(在创建Baseline-B时)看起来不错。
这是创建和插入这些附加数据的代码部分:
public appendVri(pdfRaw,pdfToSign,vri: VRI) {
if (pdfRaw instanceof ArrayBuffer) {
pdfRaw = new Uint8Array(pdfRaw);
}
const pdf = this.loadPdf(pdfRaw);
const root = this.findRootEntry(pdf.xref);
const rootSuccessor = this.findSuccessorEntry(pdf.xref.entries,root);
const certsEntry = [];
const xObjects = [];
let offsetDss;
let offsetVri;
// let offsetCerts[];
// let offsetOcsp[];
// let offsetCrls[];
const dummy = this.findFreeXrefNr(pdf.xref.entries,xObjects);
xObjects.push(dummy);
const dssEntry = this.findFreeXrefNr(pdf.xref.entries,xObjects);
xObjects.push(dssEntry);
const vriEntry = this.findFreeXrefNr(pdf.xref.entries,xObjects);
xObjects.push(vriEntry);
for (let i = 0; i < vri.certs.length; i++) {
certsEntry[i] = this.findFreeXrefNr(pdf.xref.entries,xObjects);
xObjects.push(certsEntry[i]);
}
const ocspEntry = [];
for (let i = 0; i < vri.OCSPs.length; i++) {
ocspEntry[i] = this.findFreeXrefNr(pdf.xref.entries,xObjects);
xObjects.push(ocspEntry[i]);
}
const crlsEntry = [];
for (let i = 0; i < vri.CRLs.length; i++) {
crlsEntry[i] = this.findFreeXrefNr(pdf.xref.entries,xObjects);
xObjects.push(crlsEntry[i]);
}
let certsReference = '/Certs [';
for (let i = 0; i < vri.certs.length; i++) {
certsReference = certsReference + certsEntry[i] + ' 0 R';
if (i === vri.certs.length - 1) {
certsReference = certsReference + '] \n';
} else {
certsReference = certsReference + ' ';
}
}
let ocspReference = '/OCSPs [';
for (let i = 0; i < vri.OCSPs.length; i++) {
ocspReference = ocspReference + ocspEntry[i] + ' 0 R';
if (i === vri.OCSPs.length - 1) {
ocspReference = ocspReference + '] \n';
} else {
ocspReference = ocspReference + ' ';
}
}
let crlsReference = '/CRLs [';
for (let i = 0; i < vri.CRLs.length; i++) {
crlsReference = crlsReference + crlsEntry[i] + ' 0 R';
if (i === vri.CRLs.length - 1) {
crlsReference = crlsReference + '] \n';
} else {
crlsReference = crlsReference + ' ';
}
}
const offsets = [];
const appendDss = '\n' + pdfToSign.dssEntry + ' 0 obj\n' +
'<< \n' +
'/VRI ' + vriEntry + ' 0 R \n' +
certsReference +
ocspReference +
crlsReference +
'>>';
offsetDss = (pdf.stream.bytes.length);
offsets.push(offsetDss);
let array = this.insertIntoArray(pdf.stream.bytes,offsetDss,appendDss);
const sigHash = this.strHex(this.digest(forge.util.decode64(pdfToSign.sig),'SHA1')).toUpperCase();
const appendVri = '\n' + vriEntry + ' 0 obj\n' +
'<< \n' +
'/' + sigHash + ' << \n' +
certsReference +
ocspReference +
crlsReference +
'>> \n' +
'>>\n';
offsetVri = offsetDss + appendDss.length;
offsets.push(offsetVri);
array = this.insertIntoArray(array,offsetVri,appendVri);
let lastOffset = offsetVri + appendVri.length;
const appendCerts = [];
const appendOcsp = [];
const appendCrls = [];
let offsetDelta = 0;
appendCerts[-1] = '';
for (let i = 0; i < vri.certs.length; i++) {
appendCerts[i] = certsEntry[i] + ' 0 obj\n' +
'<< \n' +
'/Length ' + vri.certs[i].length + ' \n' +
'>>\n' +
'stream\n' +
atob(vri.certs[i]) + '\n' +
'endstream\n' +
'endobj\n';
const currentOffset = lastOffset + appendCerts[i - 1].length;
offsets.push(currentOffset);
array = this.insertIntoArray(array,currentOffset,appendCerts[i]);
lastOffset = currentOffset;
offsetDelta += currentOffset;
}
lastOffset = lastOffset + appendCerts[appendCerts.length - 1].length;
appendOcsp[-1] = '';
for (let i = 0; i < vri.OCSPs.length; i++) {
appendOcsp[i] = ocspEntry[i] + ' 0 obj\n' +
'<< \n' +
'/Length ' + vri.OCSPs[i].length + ' \n' +
'>>\n' +
'stream\n' +
atob(vri.OCSPs[i]) + '\n' +
'endstream\n' +
'endobj\n';
const currentOffset = lastOffset + appendOcsp[i - 1].length;
offsets.push(currentOffset);
array = this.insertIntoArray(array,appendOcsp[i]);
lastOffset = currentOffset;
offsetDelta += currentOffset;
}
lastOffset = lastOffset + appendOcsp[appendOcsp.length - 1].length;
appendCrls[-1] = '';
for (let i = 0; i < vri.CRLs.length; i++) {
appendCrls[i] = crlsEntry[i] + ' 0 obj\n' +
'<< \n' +
'/Length ' + vri.CRLs[i].length + ' \n' +
'>>\n' +
'stream\n' +
atob(vri.CRLs[i]) + '\n' +
'endstream\n' +
'endobj\n';
const currentOffset = lastOffset + appendCrls[i - 1].length;
offsets.push(currentOffset);
array = this.insertIntoArray(array,appendCrls[i]);
lastOffset = currentOffset;
offsetDelta += currentOffset;
}
offsetDelta += appendDss.length + appendVri.length;
let middle = '';
offsets.forEach(offset => {
offset = offset + '';
middle += offset.padStart(10,'0') + ' ' + '00000' + ' ' + ' n' + '\n';
});
let xref = '\nxref\n' +
pdfToSign.dssEntry + ' ' + (2 + vri.certs.length + vri.CRLs.length + vri.OCSPs.length) + '\n' +
middle;
const sha256Hex = sha256(array,false);
xref += this.createTrailer(pdf.xref.topDict,array.length,sha256Hex,pdf.xref.entries.length);
array = this.insertIntoArray(array,xref);
return array;
}
编辑:我已按照@mkl的建议修复了所有内容。 Adobe不再抛出此错误,但是我的签名仍然被视为Baseline-T而不是Baseline-LTA,可以在此处进行检查:https://ec.europa.eu/cefdigital/DSS/webapp-demo/validation
这是单版pdf的新版本:https://easyupload.io/i5bs9k
解决方法
在PDF中存在多个问题,无论是原始签名的PDF还是您的添加内容。
原始签名的PDF
您暗示在使用appendVri
扩展签名的PDF之前可以很好地进行验证。我无法重现。
我通过截断为67127字节从共享的PDF中提取了原始签名的PDF,对于该文件,我已经在签名验证期间收到 Error。 PKCS7解析错误:版本错误。因此,扩展前的PDF中已经存在此问题。
在错误消息:版本不正确中,实际问题也变得很清楚。让我们看一下嵌入式CMS容器的ASN.1转储的开始:
0 15733: SEQUENCE {
4 9: . OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
: . . (PKCS #7)
15 15718: . [0] {
19 15714: . . SEQUENCE {
23 1: . . . INTEGER 5
...
INTEGER 5
是这里的CMSVersion
,这就是问题所在。
您创建的签名具有 ETSI.CAdES.detached 的 SubFilter 值,即(非旧版)PAdES签名。
根据当前的PAdES标准(ETSI EN 319 142-1 v1.1.1),这意味着嵌入式签名容器是CAdES中指定的 DER编码的SignedData对象(ETSI EN 319142 -1第4.1节)。反过来,CAdES需要将CMSVersion
设置为1或3 (ETSI EN 319 122-1 V1.1.1第4.4节)。
因此,签名中的INTEGER 5
和CMSVersion
不正确,值必须为1
或3
(取决于签名的其他详细信息)。 / p>
当我在原始PDF中修补签名容器以声明3
而不是5
的版本时,Adobe Acrobat就对 PKCS7解析立即感到安抚。
有趣的是,根据普通CMS标准(RFC 5652),值5
是正确的,因为该签名容器中的crls
集合具有类型为 other (OCSP响应)。仅在CAdES(以及相应地,PAdES)的情况下,该值必须减小。
在PAdES的上下文中,这实际上是可以理解的,这里根本不使用crls
集合。另一方面,普通的CAdES需要在其中添加OCSP响应,因此我不确定将版本限制为1
和3
的理由。当crls
中的CRL和/或OCSP响应被更新/组织/...
由appendVri
后处理的PDF
appendVri
引入了以下额外错误:
-
您的交叉引用表条目不正确,看起来像这样:
0000067127 00000 n\n
但是他们需要像这样:
0000067127 00000 n \n
即
00000
世代号和n
文字之间必须恰好有一个空格。由于条目必须长20个字节,并且您使用单字节eol标记,因此多余的空间必须在n
文字的之后。 -
在您的预告片中,您仅复制了原始尺寸条目:
/Size 18
但是您添加了对象编号不超过28的对象,因此大小条目必须为
/Size 29
-
在您的预告片中,您没有链接到先前的原始交叉引用表。但是对于增量更新,您必须这样做。因此,您需要添加一个
/Prev 66604
到您的预告片。
有了这些更改,Adobe Reader不再抱怨结构错误。
杂项
在准备要签名的PDF时,您似乎在目录中添加了 DSS 条目,指向尚未在准备的PDF中定义的对象:
1 0 obj
<</AcroForm<</Fields[11 0 R] /SigFlags 3>>
/DSS 19 0 R
/Type /Catalog
/Outlines 2 0 R
/Pages 3 0 R
>>
...
trailer <<
/Size 18
/Root 1 0 R
/Info 10 0 R
/ID [<1f7703d1f61b41d20c76b866132baa8b><6a44acaeb3052d4c807f6782f2eed88c>]
>>
然后在您的方法appendVri
中创建了一个带有 DSS 的对象19,该对象最初被指向任何地方的引用所引用。
虽然可能不是无效的,但这有点可疑。特别是在PDF-Insecurity Shadow Attacks出版之后,将这种悬挂的参考文献留作签名准备工作的一部分可能被认为是可疑的。
如果其他一些PDF处理器最终正在处理已签名(但未扩展)的PDF,则它可能将对象19用于其他用途,结果是具有无效数字安全存储的PDF。
插入的OCSP响应
在评论中您说
即使我解决了您发现的所有问题,我的签名仍被验证为Baseline-T而不是LT
实际上,在前面的部分中,我仅检查了CMS容器和PDF的结构完整性,没有检查与验证有关的确切信息。
因此,在您更新后的,更正的PDF示例中,我看了您添加到文档安全性存储中的吊销数据,这里的确出现了一个问题:当OCSP响应时,您仅嵌入了BasicOCSPResponse
对象,而不是包装基本OCSP响应对象的完整OCSPResponse
对象。
但是,PAdES规范要求您嵌入完整的OCSP响应对象
OCSP 数组(可选)分别间接指向流的数组 包含DER编码的在线证书状态协议(OCSP)响应(应按照IETF RFC 6960 [5]中的定义)。
(ETSI EN 319 142-1 V1.1.1第5.4.2.2节)
因此,请使用完整的OCSP响应,而不仅仅是内部的基本OCSP响应。如果您再也无法访问它们,则可以根据规范包装它们,从基本响应中重新构建它们:
OCSPResponse ::= SEQUENCE { responseStatus OCSPResponseStatus,responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } OCSPResponseStatus ::= ENUMERATED { successful (0),-- Response has valid confirmations malformedRequest (1),-- Illegal confirmation request internalError (2),-- Internal error in issuer tryLater (3),-- Try again later -- (4) is not used sigRequired (5),-- Must sign the request unauthorized (6) -- Request unauthorized } ResponseBytes ::= SEQUENCE { responseType OBJECT IDENTIFIER,response OCTET STRING }
对于基本的OCSP响应者,responseType将为id-pkix-ocsp-basic。
id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp } id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
只需采用successful
状态。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。