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

如何解决使用我的代码制作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 5CMSVersion不正确,值必须为13(取决于签名的其他详细信息)。 / p>

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

>

有趣的是,根据普通CMS标准(RFC 5652),值5是正确的,因为该签名容器中的crls集合具有类型为 other (OCSP响应)。仅在CAdES(以及相应地,PAdES)的情况下,该值必须减小。

在PAdES的上下文中,这实际上是可以理解的,这里根本不使用crls集合。另一方面,普通的CAdES需要在其中添加OCSP响应,因此我不确定将版本限制为13的理由。当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 }

RFC 6960 section 4.2.1

只需采用successful状态。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-