当前位置: 首页 > 知识库问答 >
问题:

当我使用我的代码制作PAdES LT签名时,Adobe显示PKCS7解析错误

高溪叠
2023-03-14

我正在开发制作PAdES签名的网络应用程序。我已经成功地实现了PAdES基线-B。然而,当我试图通过添加这里描述的所有必要信息来创建PAdES基线-LT时,包括OCSP响应、CRL和证书,文件似乎被损坏了,Adobe显示了以下错误:签名验证期间的错误: PKCS7解析错误

如果你想看一下,这里是签名的PDF:https://easyupload.io/fxkzvs

我在对PDF签名后附加DSS,因此我附加到get LT subtype的那些附加对象不会影响签名本身,所以我不确定为什么会出现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, currentOffset, 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, currentOffset, 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, array.length, xref);

    return array;
}

编辑:我按照@mkl的建议修复了一切。Adobe不再抛出这个错误,但是我的签名仍然被视为基线-T,而不是基线-LTA,可以在这里检查:https://ec.europa.eu/cefdigital/DSS/webapp-demo/validation

以下是新版本的签名pdf:https://easyupload.io/i5bs9k

共有1个答案

简烨烁
2023-03-14

PDF中有多个问题,包括最初签署的PDF和您添加的内容。

您暗示在使用appendVri扩展您的签名PDF之前,可以很好地验证。我不能重现这个。

我通过截短到67127字节,从你的共享PDF中提取了最初签名的PDF,而对于该文件,我在签名验证期间已经得到了错误。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的签名。凯德斯。分离,即(非遗留)PAdES签名。

根据当前的PAdES标准(ETSI EN 319 142-1 v1.1.1),这意味着嵌入式签名容器是CAdES(ETSI EN 319 142-1第4.1节)中规定的DER编码的SignedData对象。CAdES要求将CMS版本设置为1或3(ETSI EN 319 122-1 V1.1.1第4.4节)。

因此,签名中的INTEGER 5是不正确的,值必须是13(取决于签名的其他细节)。

当我在您的原始PDF中修补签名容器以声明3而不是5的版本时,Adobe Acrobat立即对PKCS7解析感到满意。

有趣的是,根据普通CMS标准(RFC 5652),值5是正确的,因为该签名容器中的crls集合有一个带有其他类型的条目,即OCSP响应。仅仅在CAdES(以及相应的PAdES)的上下文中,该值必须减小。

在PAdES的上下文中,这实际上是可以理解的,这里毕竟没有使用crls集合。另一方面,普通的CAdES需要在那里添加OCSP响应,所以我不确定将版本限制为13的理由。可能有人只是不想在crls中的CRL和/或OCSP响应更新/组织/...

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中,您创建了一个对象19,其中的DSS将由该引用引用,该引用最初不指向任何地方。

    虽然这可能不是无效的,但这有点可疑。特别是在《不安全阴影攻击》出版后,将这种悬而未决的引用作为签署准备的一部分可能被认为是可疑的。

    此外,如果其他一些PDF处理器最终正在处理您的签名(但不是扩展)PDF,它可能会使用对象19来处理其他内容,结果是生成一个带有无效数字安全存储的PDF。

    在你的评论中

    我的签名仍然被验证为Baseline-T,而不是LT,尽管我修复了您发现的所有问题

    实际上,在前面的部分中,我只检查了CMS容器和PDF的结构完整性,没有检查您的精确验证相关信息。

    因此,在您更新、更正的PDF示例中,我查看了您添加到文档安全存储中的吊销数据,这里确实出现了一个问题:当OCSP响应时,您只嵌入了基本OCSPResponse对象,而不是完整的OCSPResponse对象,这些对象包装了基本OCSP响应对象。

    然而,PAdES规范要求您嵌入完整的OCSP响应对象

    OCSPs数组(可选)对流的间接引用数组,每个包含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应答器,响应类型将是id-pkix-ocsp-Basic。

    id-pkix-ocsp           OBJECT IDENTIFIER ::= { id-ad-ocsp }
    id-pkix-ocsp-basic     OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
    

    (RFC 6960第4.2.1节)

    只需假设成功状态。

  •  类似资料:
    • 进程 c:在函数“主要”中: prog.c:35:20:警告:格式“%d”需要类型为“int”的参数,但参数3的类型为“void * ”[-w format =]printf(" \ n % c \ t % d \ t identifier \ n ",c,p);^ prog. c: 47:24:警告:格式'%d'需要类型'int'的参数,但参数3的类型'void*'[-Wformat=]prin

    • 错误 *--unhandled-rejections=strict`(请参见https://nodejs.org/api/CLI.html#CLI_unhandled_rejections_mode)。(拒绝ID:1)(节点:1616)[DEP0018]拒绝警告:不推荐未处理得承诺拒绝.将来,未处理的承诺拒绝将以非零退出代码终止node.js进程。 app.js文件 database.js文件

    • 问题内容: 测试代码的图片 / 工作签名的图片您好,我正在将behat与与貂皮集成的selenium驱动程序一起使用,我正在尝试编写一个输入假签名的测试。我们使用鼠标在屏幕上绘制签名,因此我希望selenium能够为我做到这一点。我尝试获取该字段的ID并使用dragTo(’我页面上的另一个元素’),但它仅在签名框中单击,而没有执行任何其他操作。 我正在使用的框架是laravel for php。

    • 您将自动执行著名的歌曲“墙上的99瓶XXX”。你将打印这首歌所有99个诗句的歌词。用循环!如果你不知道歌词,用谷歌查一下。 该方案应: a.如果他们不到21岁,或者他们喜欢苏打水,那么歌词是“墙上有99瓶苏打水” B.如果他们超过21岁,那么是“99瓶啤酒” 您必须使用WHILE循环,并且counter变量必须是print语句的一部分! 所以第一节是: 99瓶苏打水挂在墙上 墙上有98瓶苏打水 最

    • 我正在用PyCharm做一个项目。项目已打开并配置了解释器,可以成功运行。远程解释器路径已正确映射。这似乎是正确的配置,但PyCharm用“未解决的引用”错误突出显示了我的有效代码,即使对于内置Python函数也是如此。为什么即使代码在运行,它们似乎也没有被检测到?有没有办法让PyCharm正确识别这些? 这个问题的具体实例是远程解释器,但是这个问题也出现在本地解释器上。

    • 我尝试在JSP页面上显示一些数据库记录。在我的项目中,我必须使用index.html和studentdeatils.jsp这样的页面。我将requestDispather设置为从索引页转到studentDetails页。 当我试图显示JSP时,我的浏览器页面中出现了以下错误。 请分享你的想法。