PDFBox - 添加签名属性后文档损坏

pdfbox - document getting corrupted after adding signed attributes

提问人:Tanmay Sharma 提问时间:2/20/2023 最后编辑:Tanmay Sharma 更新时间:2/22/2023 访问量:285

问:

我正在尝试使用pdfbox对文档进行数字签名。我使用过滤器作为FILTER_ADOBE_PPKLITE,子过滤器作为SUBFILTER_ETSI_CADES_DETACHED。对于ETSI_CADES_Detached,需要添加签名属性。我正在从 CSC 获取签名哈希和证书> 但是在添加签名属性后,它会使文档损坏。共享屏幕截图以供参考

错误图像

1

似乎哈希越来越烦恼了。共享代码以供参考。

PDDocument document = PDDocument.load(inputStream);

            outFile = File.createTempFile("signedFIle", ".pdf");

            Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);//Retrieve certificates from CSC.


            setCertificateChain(certificateChain);

            // sign
            FileOutputStream output = new FileOutputStream(outFile);
            IOUtils.copy(inputStream, output);

            // create signature dictionary
            PDSignature signature = new PDSignature();
//            signature.setType(COSName.SIG);


//        PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);

            int accessPermissions = SigUtils.getMDPPermission(document);
            if (accessPermissions == 1)
            {
                throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
            }

            

            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
            signature.setName("Test Name");
//            signature.setLocation("Bucharest, RO");
//            signature.setReason("PDFBox Signing");
            signature.setSignDate(Calendar.getInstance());
            Rectangle2D humanRect = new Rectangle2D.Float(location.getLeft(), location.getBottom(), location.getRight(), location.getTop());

            PDRectangle rect = createSignatureRectangle(document, humanRect);

            SignatureOptions signatureOptions = new SignatureOptions();
            signatureOptions.setVisualSignature(createVisualSignatureTemplate(document, 0, rect, signature));
            signatureOptions.setPage(0);
            document.addSignature(signature, signatureOptions);


            ExternalSigningSupport externalSigning =
                    document.saveIncrementalForExternalSigning(output);

            InputStream content = externalSigning.getContent();



            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            X509Certificate cert = (X509Certificate) certificateChain[0];
            gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));

            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            // Use a buffer to read the input stream in chunks
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = content.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }

            byte[] hashBytes = digest.digest();

            ESSCertIDv2 certid = new ESSCertIDv2(
                    new AlgorithmIdentifier(new ASN1ObjectIdentifier("*****")),
                    MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
            );

            SigningCertificateV2 sigcert = new SigningCertificateV2(certid);

            final DERSet attrValues = new DERSet(sigcert);
            Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, attrValues);
            ASN1EncodableVector v = new ASN1EncodableVector();
            v.add(attr);
            v.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(hashBytes))));

            AttributeTable atttributeTable = new AttributeTable(v);
            //Create a standard attribute table from the passed in parameters - certhash
            CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);


            final byte[] signedHash = signHash(requestId, providerId, accessToken, hashBytes); //Retrieve signed hash from CSC.

            ContentSigner contentSigner = new ContentSigner() {

                @Override
                public byte[] getSignature() {
                    return signedHash;
                }

                @Override
                public OutputStream getOutputStream() {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    byteArrayOutputStream.writeBytes(hashBytes);
                    return byteArrayOutputStream;
                }

                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
                }
            };



            org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
            JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());

//            RevocationInfoResponse revocationInfoResponse = sealingService.getRevocationInfo(requestId, accessToken, revocationInfoRequest);



            sigb.setSignedAttributeGenerator(attrGen);


//            sigb.setDirectSignature( true );
            gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));
            CMSTypedData msg = new CMSProcessableInputStream( inputStream);

            CMSSignedData signedData = gen.generate((CMSTypedData)msg, false);
            byte[] cmsSignature = signedData.getEncoded();

            inputStream.close();

            externalSigning.setSignature(cmsSignature);
            IOUtils.closeQuietly(signatureOptions);

            return new FileInputStream(outFile);

如果我使用子过滤器作为SUBFILTER_ADBE_PKCS7_DETACHED并且不添加addtibutesTable,那么它就可以正常工作。但对于SUBFILTER_ETSI_CADES_DETACHED,需要添加属性。

Java PDFBOX 数字签名 X509证书 Pades

评论

0赞 mkl 2/21/2023
在代码中,您完全忽略了已签名的属性应为已签名的属性您的代码只需对纯文档哈希进行签名。要解决此问题,您首先需要确保 创建一个值为 of 的消息摘要属性,并将 your 替换为 a 实际对它检索到的要签名的字节进行签名。阅读 RFC 5652 可以帮助您了解需要做什么......attrGenhashBytesContentSigner nonSignerContentSigner
0赞 Tanmay Sharma 2/21/2023
@mkl,我更新了代码,添加了带有 hashBytes 值的 message-digest 属性,并且在初始化 contentSigner 时也进行了一些更改。但正如描述中所述,我们从一些外部提供 (CSC) 获取证书和 signedHash。我们没有 privateKey。因此,在从 CSC 获取 signedHash 后,我们直接覆盖 ContentSignature 的 getSignature 方法并返回该 signedHash。ContentSigner 实际上无法对字节进行签名。你能提供我如何解决这个问题的方法吗?
0赞 Himanshu Jindal 2/27/2023
@mkl PDFBox 是否有任何源代码/文档可以帮助更好地了解该库?
0赞 mkl 2/27/2023
@HimanshuJindal “PDFBox 是否有任何源代码/文档可以帮助更好地深入理解该库?” - 好吧,在我看来,查看源代码可以给出最好的理解,并且 PDFBox 是开源的。所以只要看看那里。此外,PDFBox API 通常希望您在内部了解 PDF 格式。因此,请查看 ISO 32000。第 1 部分等效内容可在以下网址下载 Adobe.com/go/pdfreference

答:

2赞 mkl 2/22/2023 #1

在你解释的评论中

我们从一些外部提供 (CSC) 获取证书和 signedHash。我们没有 privateKey。因此,在从 CSC 获取 signedHash 后,我们直接覆盖 ContentSignature 的 getSignature 方法并返回该 signedHash。ContentSigner 实际上无法对字节进行签名。

ContentSigner 实际上可以对字节进行签名。您只需从其中调用 CSC 签名即可。

稍微简化代码,可以按如下方式执行此操作:

try (
    OutputStream output = new FileOutputStream(outFile);
    PDDocument document = PDDocument.load(resource)   
) {
    PDSignature signature = new PDSignature();

    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
    signature.setName("Test Name");
    signature.setSignDate(Calendar.getInstance());

    SignatureOptions signatureOptions = new SignatureOptions();
    signatureOptions.setPage(0);

    document.addSignature(signature, signatureOptions);
    ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);

    // retrieve signer certificate and its chain
    Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);
    X509Certificate cert = (X509Certificate) certificateChain[0];

    // build signed attribute table generator and SignerInfo generator builder
    ESSCertIDv2 certid = new ESSCertIDv2(
            new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
            MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
    );
    SigningCertificateV2 sigcert = new SigningCertificateV2(certid);
    Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(sigcert));

    ASN1EncodableVector v = new ASN1EncodableVector();
    v.add(attr);
    AttributeTable atttributeTable = new AttributeTable(v);
    CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);

    org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
    JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
    sigb.setSignedAttributeGenerator(attrGen);

    // create ContentSigner that signs by calling the CSC endpoint
    ContentSigner contentSigner = new ContentSigner() {
        private MessageDigest digest = MessageDigest.getInstance("SHA-256");
        private OutputStream stream = OutputStreamFactory.createStream(digest);

        @Override
        public byte[] getSignature() {
            try {
                byte[] hash = digest.digest();
                byte[] signedHash = signHash(requestId, providerId, accessToken, hash);
                return signedHash;
            } catch (Exception e) {
                throw new RuntimeException("Exception while signing", e);
            }
        }

        @Override
        public OutputStream getOutputStream() {
            return stream;
        }

        @Override
        public AlgorithmIdentifier getAlgorithmIdentifier() {
            return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
        }
    };

    // create the SignedData generator and execute
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
    gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));

    CMSTypedData msg = new CMSTypedDataInputStream(externalSigning.getContent());
    CMSSignedData signedData = gen.generate(msg, false);

    byte[] cmsSignature = signedData.getEncoded();
    externalSigning.setSignature(cmsSignature);
}

RemoteSigning 测试方法 testSignLikeSkdjksDfkslImproved

评论

0赞 Tanmay Sharma 11/1/2023
当我将algorithmIdentifier从1.2.840.113549.1.1.11更改为1.2.840.113549.1.1.1时,我收到错误“digest oid is null”。你能告诉我这有什么问题吗?
0赞 mkl 11/1/2023
@Tanmay '当我将 algorithmIdentifier 从 1.2.840.113549.1.1.11 更改为 1.2.840.113549.1.1.1 时,我收到错误“digest oid is null”.' - 该更改意味着说 RSA 而不是 SHA256withRSA,后者是没有显式摘要算法的签名算法。因此,显然,给出该错误的代码要求将摘要算法显式包含在签名算法中。
0赞 Tanmay Sharma 11/1/2023
用于签名的 OID 算法为 1.2.840.113549.1.1.1,用于计算哈希值的算法的 OID 应为 2.16.840.1.101.3.4.2.1。我应该在我的代码中进行哪些更新?目前,我在 acrobat 中遇到文档损坏错误。
0赞 Tanmay Sharma 11/1/2023
这是发送到外部 TSP 的请求,用于计算签名哈希 我在 acrobat 中收到此请求的文档相关错误{ "credentialID": "{{cscKeyID}}", "SAD": "{{sad}}", "hash": [ "ODEyYjk2OTA5YTdiOTgwZDYxMjMwOWJlMWVhNWMzNTcwMmZjNTJhOTg5MDg3YzcyODc3MTY3NzhhMDY3NGY2ZA==" ], "hashAlgo": "2.16.840.1.101.3.4.2.1", "signAlgo": "1.2.840.113549.1.1.1", "signAlgoParams": "SHA256withRSA" }
0赞 Tanmay Sharma 11/1/2023
上面的代码只有在发送到外部 TSP 以计算签名哈希的以下请求下才能正常工作{ "credentialID": "{{cscKeyID}}", "SAD": "{{sad}}", "hash": [ "ODEyYjk2OTA5YTdiOTgwZDYxMjMwOWJlMWVhNWMzNTcwMmZjNTJhOTg5MDg3YzcyODc3MTY3NzhhMDY3NGY2ZA==" ], "signAlgo": "1.2.840.113549.1.1.11" }