Base64 摘要 + PFX(PKCS12)-> ETSI.CAdES.分离签名 ->帕德斯生命周期价值

发布于 2025-01-09 04:54:36 字数 417 浏览 2 评论 0 原文

我有一个 API 可以创建 PDF 文档的 Base64 摘要。 现在我想创建另一个 API,它采用此摘要和 PFX 并创建 ETSI.CAdES.detached 签名并采用 LTV 信息(证书链、OCSP 响应、CRL),我想将其嵌入到 PDF 中以获得 PAdES-LTV 签名使用第三个 API(我的第三个 API 将采用从此 API 获得的 CAdES 签名和 LTV 信息,并将它们嵌入到我的 PDF 中)。我不知道如何创建这个ETSI.CAdES.detached 签名使用该摘要以及带有 Java 和 Bouncy Castle 的 PFX。我尝试遵循此 github 教程。

I have an API that creates Base64 digest of a PDF Document.
Now I want to create another API that takes this digest and PFX and creates an ETSI.CAdES.detached signature and takes LTV informations(Certs chain,OCSP response,CRL) that I want to embed in my PDF to obtain a PAdES-LTV signature using 3rd API(My 3rd API will take CAdES signature and LTV informations obtained from this API and will embed them in my PDF).I dont know how to create this ETSI.CAdES.detached signature using that digest and a PFX with Java and Bouncy Castle.I try to follow this github tutorial.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

少女的英雄梦 2025-01-16 04:54:36

正如您所声明的,您有自己的代码来准备 PDF 进行签名并将签名容器注入其中。因此,你的问题基本上可以归结为

如何使用 BouncyCastle 创建可用于创建 PAdES BASELINE B 或 T PDF 签名的 CAdES 签名容器?

iText 7 签名框架中的实现

由于我没有您现有的代码,因此我必须使用不同的框架进行测试。我为此使用了 iText 7 签名框架。

BouncyCastle 确实包含一个 CMSSignedDataGenerator 来生成 CMS 签名容器。

不幸的是,其中 SignerInfo 生成的默认实现与 CAdES/PAdES 不兼容,因为它不创建签名的 ESSCertID[v2] 属性。不过幸运的是,该实现的设计允许插入自定义属性集。

因此,您可以使用自定义的 CMSSignedDataGenerator 创建 PAdES BASELINE 签名所需的 CAdES 容器。

因此,当您准备好要签名的 PDF 后,您可以像这样继续:

InputStream data = [InputStream containing the PDF byte ranges to sign];
ContentSigner contentSigner = [BouncyCastle ContentSigner for your private key];
X509CertificateHolder x509CertificateHolder = [BouncyCastle X509CertificateHolder for your X.509 signer certificate];

DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();


CMSTypedData msg = new CMSTypedDataInputStream(data);

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

gen.addSignerInfoGenerator(
        new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
                .setSignedAttributeGenerator(new PadesSignedAttributeGenerator())
                .setUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator())
                .build(contentSigner, x509CertificateHolder));

gen.addCertificates(new JcaCertStore(Collections.singleton(x509CertificateHolder)));

CMSSignedData sigData = gen.generate(msg, false);
byte[] cmsBytes = sigData.getEncoded();

(PadesSignatureContainerBc 方法sign)

byte[] cmsBytes 包含要注入到准备好的 PDF 签名占位符中的字节。

需要以下帮助器类:

首先,InputStream 的包装器包含要由 BouncyCastle 签名处理的 PDF 范围。

class CMSTypedDataInputStream implements CMSTypedData {
    InputStream in;

    public CMSTypedDataInputStream(InputStream is) {
        in = is;
    }

    @Override
    public ASN1ObjectIdentifier getContentType() {
        return PKCSObjectIdentifiers.data;
    }

    @Override
    public Object getContent() {
        return in;
    }

    @Override
    public void write(OutputStream out) throws IOException,
            CMSException {
        byte[] buffer = new byte[8 * 1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();
    }
}

(PadesSignatureContainerBc 辅助类 CMSTypedDataInputStream)

然后是自定义的签名属性生成器对于 PAdES:

class PadesSignedAttributeGenerator implements CMSAttributeTableGenerator {
    @Override
    public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
        String currentAttribute = null;
        try {
            ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
            currentAttribute = "SigningCertificateAttribute";
            AlgorithmIdentifier digAlgId = (AlgorithmIdentifier) params.get(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER);
            signedAttributes.add(createSigningCertificateAttribute(digAlgId));
            currentAttribute = "ContentTypeAttribute";
            ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance(params.get(CMSAttributeTableGenerator.CONTENT_TYPE));
            signedAttributes.add(new Attribute(CMSAttributes.contentType, new DERSet(contentType)));
            currentAttribute = "MessageDigestAttribute";
            byte[] messageDigest = (byte[])params.get(CMSAttributeTableGenerator.DIGEST);
            signedAttributes.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest))));

            return new AttributeTable(signedAttributes);
        } catch (Exception e) {
            throw new CMSAttributeTableGenerationException(currentAttribute, e);
        }
    }

    Attribute createSigningCertificateAttribute(AlgorithmIdentifier digAlg) throws IOException, OperatorCreationException {
        final IssuerSerial issuerSerial = getIssuerSerial();
        DigestCalculator digestCalculator = digestCalculatorProvider.get(digAlg);
        digestCalculator.getOutputStream().write(x509CertificateHolder.getEncoded());
        final byte[] certHash = digestCalculator.getDigest();

        if (OIWObjectIdentifiers.idSHA1.equals(digAlg.getAlgorithm())) {
            final ESSCertID essCertID = new ESSCertID(certHash, issuerSerial);
            SigningCertificate signingCertificate = new SigningCertificate(essCertID);
            return new Attribute(id_aa_signingCertificate, new DERSet(signingCertificate));
        } else {
            ESSCertIDv2 essCertIdv2;
            if (NISTObjectIdentifiers.id_sha256.equals(digAlg.getAlgorithm())) {
                // SHA-256 is default
                essCertIdv2 = new ESSCertIDv2(null, certHash, issuerSerial);
            } else {
                essCertIdv2 = new ESSCertIDv2(digAlg, certHash, issuerSerial);
            }
            SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(essCertIdv2);
            return new Attribute(id_aa_signingCertificateV2, new DERSet(signingCertificateV2));
        }
    }

    IssuerSerial getIssuerSerial() {
        final X500Name issuerX500Name = x509CertificateHolder.getIssuer();
        final GeneralName generalName = new GeneralName(issuerX500Name);
        final GeneralNames generalNames = new GeneralNames(generalName);
        final BigInteger serialNumber = x509CertificateHolder.getSerialNumber();
        return new IssuerSerial(generalNames, serialNumber);
    }
}

(PadesSignatureContainerBc 辅助类 PadesSignedAttributeGenerator )

最后是用于签名时间戳的自定义无符号属性生成器:

class PadesUnsignedAttributeGenerator implements CMSAttributeTableGenerator {
    @Override
    public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
        if (tsaClient == null)
            return null;
        try {
            ASN1EncodableVector unsignedAttributes = new ASN1EncodableVector();
            byte[] signature = (byte[])params.get(CMSAttributeTableGenerator.SIGNATURE);
            byte[] timestamp = tsaClient.getTimeStampToken(tsaClient.getMessageDigest().digest(signature));
            unsignedAttributes.add(new Attribute(id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestamp))));
            return new AttributeTable(unsignedAttributes);
        } catch (Exception e) {
            throw new CMSAttributeTableGenerationException("", e);
        }
    }
}

(PadesSignatureContainerBc 辅助类PadesUnsignedAttributeGenerator)

这里我假设一个 ITSAClient tsaClient,一个 iText 7 时间戳请求客户端。您当然可以使用您选择的任意 RFC 3161 时间戳请求客户端。

如果您已将私钥读入 JCA/JCE PrivateKey pk,则只需使用 BouncyCastle JcaContentSignerBuilder 创建所需的 ContentSigner contentSigner,例如像这样:

ContentSigner contentSigner = new JcaContentSignerBuilder("SHA512withRSA").build(pk);

(比较 testSignPadesBaselineT href="https://github.com/mkl-public/testarea-itext7/blob/master/src/test/java/mkl/testarea/itext7/signature/SignPadesBc.java#L65" rel="nofollow noreferrer"> SignPadesBc)

PDFBox 3 签名框架中的实现

同时,您在评论中指出您正在考虑使用 PDFBox 进行签名。幸运的是,上面提供的代码几乎不需要任何更改就可以与 PDFBox 一起使用。

要将上面的代码与 PDFBox 一起使用,只需将其包装到 PDFBox SignatureInterface 框架中即可:

public class PadesSignatureContainerBc implements SignatureInterface {

    public PadesSignatureContainerBc(X509CertificateHolder x509CertificateHolder, ContentSigner contentSigner, TSAClient tsaClient) throws OperatorCreationException {
        this.contentSigner = contentSigner;
        this.tsaClient = tsaClient;
        this.x509CertificateHolder = x509CertificateHolder;

        digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
    }

    @Override
    public byte[] sign(InputStream content) throws IOException {
        try {
            CMSTypedData msg = new CMSTypedDataInputStream(content);

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            gen.addSignerInfoGenerator(
                    new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
                            .setSignedAttributeGenerator(new PadesSignedAttributeGenerator())
                            .setUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator())
                            .build(contentSigner, x509CertificateHolder));

            gen.addCertificates(new JcaCertStore(Collections.singleton(x509CertificateHolder)));

            CMSSignedData sigData = gen.generate(msg, false);
            return sigData.getEncoded();
        } catch (OperatorCreationException | GeneralSecurityException | CMSException e) {
            throw new IOException(e);
        }
    }

    final ContentSigner contentSigner;
    final X509CertificateHolder x509CertificateHolder;
    final TSAClient tsaClient;

    final DigestCalculatorProvider digestCalculatorProvider;

    class CMSTypedDataInputStream implements CMSTypedData {
        InputStream in;

        public CMSTypedDataInputStream(InputStream is) {
            in = is;
        }

        @Override
        public ASN1ObjectIdentifier getContentType() {
            return PKCSObjectIdentifiers.data;
        }

        @Override
        public Object getContent() {
            return in;
        }

        @Override
        public void write(OutputStream out) throws IOException,
                CMSException {
            byte[] buffer = new byte[8 * 1024];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            in.close();
        }
    }

    class PadesSignedAttributeGenerator implements CMSAttributeTableGenerator {
        @Override
        public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
            String currentAttribute = null;
            try {
                ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
                currentAttribute = "SigningCertificateAttribute";
                AlgorithmIdentifier digAlgId = (AlgorithmIdentifier) params.get(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER);
                signedAttributes.add(createSigningCertificateAttribute(digAlgId));
                currentAttribute = "ContentType";
                ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance(params.get(CMSAttributeTableGenerator.CONTENT_TYPE));
                signedAttributes.add(new Attribute(CMSAttributes.contentType, new DERSet(contentType)));
                currentAttribute = "MessageDigest";
                byte[] messageDigest = (byte[])params.get(CMSAttributeTableGenerator.DIGEST);
                signedAttributes.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest))));

                return new AttributeTable(signedAttributes);
            } catch (Exception e) {
                throw new CMSAttributeTableGenerationException(currentAttribute, e);
            }
        }

        Attribute createSigningCertificateAttribute(AlgorithmIdentifier digAlg) throws IOException, OperatorCreationException {
            final IssuerSerial issuerSerial = getIssuerSerial();
            DigestCalculator digestCalculator = digestCalculatorProvider.get(digAlg);
            digestCalculator.getOutputStream().write(x509CertificateHolder.getEncoded());
            final byte[] certHash = digestCalculator.getDigest();

            if (OIWObjectIdentifiers.idSHA1.equals(digAlg.getAlgorithm())) {
                final ESSCertID essCertID = new ESSCertID(certHash, issuerSerial);
                SigningCertificate signingCertificate = new SigningCertificate(essCertID);
                return new Attribute(id_aa_signingCertificate, new DERSet(signingCertificate));
            } else {
                ESSCertIDv2 essCertIdv2;
                if (NISTObjectIdentifiers.id_sha256.equals(digAlg.getAlgorithm())) {
                    // SHA-256 is default
                    essCertIdv2 = new ESSCertIDv2(null, certHash, issuerSerial);
                } else {
                    essCertIdv2 = new ESSCertIDv2(digAlg, certHash, issuerSerial);
                }
                SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(essCertIdv2);
                return new Attribute(id_aa_signingCertificateV2, new DERSet(signingCertificateV2));
            }
        }

        public IssuerSerial getIssuerSerial() {
            final X500Name issuerX500Name = x509CertificateHolder.getIssuer();
            final GeneralName generalName = new GeneralName(issuerX500Name);
            final GeneralNames generalNames = new GeneralNames(generalName);
            final BigInteger serialNumber = x509CertificateHolder.getSerialNumber();
            return new IssuerSerial(generalNames, serialNumber);
        }
    }

    class PadesUnsignedAttributeGenerator implements CMSAttributeTableGenerator {
        @Override
        public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
            if (tsaClient == null)
                return null;
            try {
                ASN1EncodableVector unsignedAttributes = new ASN1EncodableVector();
                byte[] signature = (byte[])params.get(CMSAttributeTableGenerator.SIGNATURE);
                byte[] timestamp = tsaClient.getTimeStampToken(new ByteArrayInputStream(signature)).getEncoded();
                unsignedAttributes.add(new Attribute(id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestamp))));
                return new AttributeTable(unsignedAttributes);
            } catch (Exception e) {
                throw new CMSAttributeTableGenerationException("", e);
            }
        }
    }
}

( PDFBox PadesSignatureContainerBc SignatureInterface 的实现)

你可以像这样使用它

try (   PDDocument pdDocument = Loader.loadPDF(SOURCE_PDF)   )
{
    SignatureInterface signatureInterface = new PadesSignatureContainerBc(new X509CertificateHolder(chain[0].getEncoded()),
            new JcaContentSignerBuilder("SHA512withRSA").build(pk),
            new TSAClient(new URL("http://timestamp.server/rfc3161endpoint"), null, null, MessageDigest.getInstance("SHA-256")));

    PDSignature signature = new PDSignature();
    signature.setFilter(COSName.getPDFName("MKLx_PAdES_SIGNER"));
    signature.setSubFilter(COSName.getPDFName("ETSI.CAdES.detached"));
    signature.setName("Example User");
    signature.setLocation("Los Angeles, CA");
    signature.setReason("Testing");
    signature.setSignDate(Calendar.getInstance());
    pdDocument.addSignature(signature);

    ExternalSigningSupport externalSigning = pdDocument.saveIncrementalForExternalSigning(RESULT_OUTPUT);
    // invoke external signature service
    byte[] cmsSignature = signatureInterface.sign(externalSigning.getContent());
    // set signature bytes received from the service
    externalSigning.setSignature(cmsSignature);
}

(PDFBox SignPadesBc 测试 testSignPadesBaselineT)

As you have declared, you have your own code for preparing a PDF for signing and for injecting the signature container into it. Thus, your question essentially burns down to

How to create a CAdES signature container with BouncyCastle that can be used to create a PAdES BASELINE B or T PDF signature?

Implementation in the iText 7 Signing Framework

As I do not have your existing code, I had to use a different framework for my tests. I used the iText 7 signing framework for that.

BouncyCastle does contain a CMSSignedDataGenerator to generate CMS signature containers.

The default implementation of the SignerInfo generation therein unfortunately is not CAdES/PAdES compatible as it does not create signed ESSCertID[v2] attributes. Fortunately, though, the implementation is designed to allow plugging in custom attributes sets.

Thus, you can create the CAdES containers required for PAdES BASELINE signatures with a customized CMSSignedDataGenerator.

So when you have prepared the PDF for signing, you can proceed like this:

InputStream data = [InputStream containing the PDF byte ranges to sign];
ContentSigner contentSigner = [BouncyCastle ContentSigner for your private key];
X509CertificateHolder x509CertificateHolder = [BouncyCastle X509CertificateHolder for your X.509 signer certificate];

DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();


CMSTypedData msg = new CMSTypedDataInputStream(data);

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

gen.addSignerInfoGenerator(
        new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
                .setSignedAttributeGenerator(new PadesSignedAttributeGenerator())
                .setUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator())
                .build(contentSigner, x509CertificateHolder));

gen.addCertificates(new JcaCertStore(Collections.singleton(x509CertificateHolder)));

CMSSignedData sigData = gen.generate(msg, false);
byte[] cmsBytes = sigData.getEncoded();

(PadesSignatureContainerBc method sign)

The byte[] cmsBytes contains the bytes to inject into the prepared PDF signature placeholder.

The following helper classes are needed:

First of all a wrapper for the InputStream containing the PDF ranges to sign to process by BouncyCastle.

class CMSTypedDataInputStream implements CMSTypedData {
    InputStream in;

    public CMSTypedDataInputStream(InputStream is) {
        in = is;
    }

    @Override
    public ASN1ObjectIdentifier getContentType() {
        return PKCSObjectIdentifiers.data;
    }

    @Override
    public Object getContent() {
        return in;
    }

    @Override
    public void write(OutputStream out) throws IOException,
            CMSException {
        byte[] buffer = new byte[8 * 1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();
    }
}

(PadesSignatureContainerBc helper class CMSTypedDataInputStream)

Then a customized signed attributes generator for PAdES:

class PadesSignedAttributeGenerator implements CMSAttributeTableGenerator {
    @Override
    public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
        String currentAttribute = null;
        try {
            ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
            currentAttribute = "SigningCertificateAttribute";
            AlgorithmIdentifier digAlgId = (AlgorithmIdentifier) params.get(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER);
            signedAttributes.add(createSigningCertificateAttribute(digAlgId));
            currentAttribute = "ContentTypeAttribute";
            ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance(params.get(CMSAttributeTableGenerator.CONTENT_TYPE));
            signedAttributes.add(new Attribute(CMSAttributes.contentType, new DERSet(contentType)));
            currentAttribute = "MessageDigestAttribute";
            byte[] messageDigest = (byte[])params.get(CMSAttributeTableGenerator.DIGEST);
            signedAttributes.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest))));

            return new AttributeTable(signedAttributes);
        } catch (Exception e) {
            throw new CMSAttributeTableGenerationException(currentAttribute, e);
        }
    }

    Attribute createSigningCertificateAttribute(AlgorithmIdentifier digAlg) throws IOException, OperatorCreationException {
        final IssuerSerial issuerSerial = getIssuerSerial();
        DigestCalculator digestCalculator = digestCalculatorProvider.get(digAlg);
        digestCalculator.getOutputStream().write(x509CertificateHolder.getEncoded());
        final byte[] certHash = digestCalculator.getDigest();

        if (OIWObjectIdentifiers.idSHA1.equals(digAlg.getAlgorithm())) {
            final ESSCertID essCertID = new ESSCertID(certHash, issuerSerial);
            SigningCertificate signingCertificate = new SigningCertificate(essCertID);
            return new Attribute(id_aa_signingCertificate, new DERSet(signingCertificate));
        } else {
            ESSCertIDv2 essCertIdv2;
            if (NISTObjectIdentifiers.id_sha256.equals(digAlg.getAlgorithm())) {
                // SHA-256 is default
                essCertIdv2 = new ESSCertIDv2(null, certHash, issuerSerial);
            } else {
                essCertIdv2 = new ESSCertIDv2(digAlg, certHash, issuerSerial);
            }
            SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(essCertIdv2);
            return new Attribute(id_aa_signingCertificateV2, new DERSet(signingCertificateV2));
        }
    }

    IssuerSerial getIssuerSerial() {
        final X500Name issuerX500Name = x509CertificateHolder.getIssuer();
        final GeneralName generalName = new GeneralName(issuerX500Name);
        final GeneralNames generalNames = new GeneralNames(generalName);
        final BigInteger serialNumber = x509CertificateHolder.getSerialNumber();
        return new IssuerSerial(generalNames, serialNumber);
    }
}

(PadesSignatureContainerBc helper class PadesSignedAttributeGenerator )

And finally a customized unsigned attributes generator for a signature timestamp:

class PadesUnsignedAttributeGenerator implements CMSAttributeTableGenerator {
    @Override
    public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
        if (tsaClient == null)
            return null;
        try {
            ASN1EncodableVector unsignedAttributes = new ASN1EncodableVector();
            byte[] signature = (byte[])params.get(CMSAttributeTableGenerator.SIGNATURE);
            byte[] timestamp = tsaClient.getTimeStampToken(tsaClient.getMessageDigest().digest(signature));
            unsignedAttributes.add(new Attribute(id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestamp))));
            return new AttributeTable(unsignedAttributes);
        } catch (Exception e) {
            throw new CMSAttributeTableGenerationException("", e);
        }
    }
}

(PadesSignatureContainerBc helper class PadesUnsignedAttributeGenerator)

Here I assume a ITSAClient tsaClient, an iText 7 time stamp request client. You can of course use an arbitrary RFC 3161 time stamp request client of your choice.

If you have read your private key into a JCA/JCE PrivateKey pk, you can simply create the needed ContentSigner contentSigner using the BouncyCastle JcaContentSignerBuilder, e.g. like this:

ContentSigner contentSigner = new JcaContentSignerBuilder("SHA512withRSA").build(pk);

(compare the test testSignPadesBaselineT in SignPadesBc)

Implementation in the PDFBox 3 Signing Framework

You meanwhile indicated in comments that you're looking into using PDFBox to sign. Fortunately the code presented above can nearly without a change be used with PDFBox.

To use the code above with PDFBox, one merely has to wrap it into a PDFBox SignatureInterface frame:

public class PadesSignatureContainerBc implements SignatureInterface {

    public PadesSignatureContainerBc(X509CertificateHolder x509CertificateHolder, ContentSigner contentSigner, TSAClient tsaClient) throws OperatorCreationException {
        this.contentSigner = contentSigner;
        this.tsaClient = tsaClient;
        this.x509CertificateHolder = x509CertificateHolder;

        digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
    }

    @Override
    public byte[] sign(InputStream content) throws IOException {
        try {
            CMSTypedData msg = new CMSTypedDataInputStream(content);

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            gen.addSignerInfoGenerator(
                    new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
                            .setSignedAttributeGenerator(new PadesSignedAttributeGenerator())
                            .setUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator())
                            .build(contentSigner, x509CertificateHolder));

            gen.addCertificates(new JcaCertStore(Collections.singleton(x509CertificateHolder)));

            CMSSignedData sigData = gen.generate(msg, false);
            return sigData.getEncoded();
        } catch (OperatorCreationException | GeneralSecurityException | CMSException e) {
            throw new IOException(e);
        }
    }

    final ContentSigner contentSigner;
    final X509CertificateHolder x509CertificateHolder;
    final TSAClient tsaClient;

    final DigestCalculatorProvider digestCalculatorProvider;

    class CMSTypedDataInputStream implements CMSTypedData {
        InputStream in;

        public CMSTypedDataInputStream(InputStream is) {
            in = is;
        }

        @Override
        public ASN1ObjectIdentifier getContentType() {
            return PKCSObjectIdentifiers.data;
        }

        @Override
        public Object getContent() {
            return in;
        }

        @Override
        public void write(OutputStream out) throws IOException,
                CMSException {
            byte[] buffer = new byte[8 * 1024];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            in.close();
        }
    }

    class PadesSignedAttributeGenerator implements CMSAttributeTableGenerator {
        @Override
        public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
            String currentAttribute = null;
            try {
                ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
                currentAttribute = "SigningCertificateAttribute";
                AlgorithmIdentifier digAlgId = (AlgorithmIdentifier) params.get(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER);
                signedAttributes.add(createSigningCertificateAttribute(digAlgId));
                currentAttribute = "ContentType";
                ASN1ObjectIdentifier contentType = ASN1ObjectIdentifier.getInstance(params.get(CMSAttributeTableGenerator.CONTENT_TYPE));
                signedAttributes.add(new Attribute(CMSAttributes.contentType, new DERSet(contentType)));
                currentAttribute = "MessageDigest";
                byte[] messageDigest = (byte[])params.get(CMSAttributeTableGenerator.DIGEST);
                signedAttributes.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest))));

                return new AttributeTable(signedAttributes);
            } catch (Exception e) {
                throw new CMSAttributeTableGenerationException(currentAttribute, e);
            }
        }

        Attribute createSigningCertificateAttribute(AlgorithmIdentifier digAlg) throws IOException, OperatorCreationException {
            final IssuerSerial issuerSerial = getIssuerSerial();
            DigestCalculator digestCalculator = digestCalculatorProvider.get(digAlg);
            digestCalculator.getOutputStream().write(x509CertificateHolder.getEncoded());
            final byte[] certHash = digestCalculator.getDigest();

            if (OIWObjectIdentifiers.idSHA1.equals(digAlg.getAlgorithm())) {
                final ESSCertID essCertID = new ESSCertID(certHash, issuerSerial);
                SigningCertificate signingCertificate = new SigningCertificate(essCertID);
                return new Attribute(id_aa_signingCertificate, new DERSet(signingCertificate));
            } else {
                ESSCertIDv2 essCertIdv2;
                if (NISTObjectIdentifiers.id_sha256.equals(digAlg.getAlgorithm())) {
                    // SHA-256 is default
                    essCertIdv2 = new ESSCertIDv2(null, certHash, issuerSerial);
                } else {
                    essCertIdv2 = new ESSCertIDv2(digAlg, certHash, issuerSerial);
                }
                SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(essCertIdv2);
                return new Attribute(id_aa_signingCertificateV2, new DERSet(signingCertificateV2));
            }
        }

        public IssuerSerial getIssuerSerial() {
            final X500Name issuerX500Name = x509CertificateHolder.getIssuer();
            final GeneralName generalName = new GeneralName(issuerX500Name);
            final GeneralNames generalNames = new GeneralNames(generalName);
            final BigInteger serialNumber = x509CertificateHolder.getSerialNumber();
            return new IssuerSerial(generalNames, serialNumber);
        }
    }

    class PadesUnsignedAttributeGenerator implements CMSAttributeTableGenerator {
        @Override
        public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
            if (tsaClient == null)
                return null;
            try {
                ASN1EncodableVector unsignedAttributes = new ASN1EncodableVector();
                byte[] signature = (byte[])params.get(CMSAttributeTableGenerator.SIGNATURE);
                byte[] timestamp = tsaClient.getTimeStampToken(new ByteArrayInputStream(signature)).getEncoded();
                unsignedAttributes.add(new Attribute(id_aa_signatureTimeStampToken, new DERSet(ASN1Primitive.fromByteArray(timestamp))));
                return new AttributeTable(unsignedAttributes);
            } catch (Exception e) {
                throw new CMSAttributeTableGenerationException("", e);
            }
        }
    }
}

(PDFBox PadesSignatureContainerBc implementation of SignatureInterface)

You can use it like this

try (   PDDocument pdDocument = Loader.loadPDF(SOURCE_PDF)   )
{
    SignatureInterface signatureInterface = new PadesSignatureContainerBc(new X509CertificateHolder(chain[0].getEncoded()),
            new JcaContentSignerBuilder("SHA512withRSA").build(pk),
            new TSAClient(new URL("http://timestamp.server/rfc3161endpoint"), null, null, MessageDigest.getInstance("SHA-256")));

    PDSignature signature = new PDSignature();
    signature.setFilter(COSName.getPDFName("MKLx_PAdES_SIGNER"));
    signature.setSubFilter(COSName.getPDFName("ETSI.CAdES.detached"));
    signature.setName("Example User");
    signature.setLocation("Los Angeles, CA");
    signature.setReason("Testing");
    signature.setSignDate(Calendar.getInstance());
    pdDocument.addSignature(signature);

    ExternalSigningSupport externalSigning = pdDocument.saveIncrementalForExternalSigning(RESULT_OUTPUT);
    // invoke external signature service
    byte[] cmsSignature = signatureInterface.sign(externalSigning.getContent());
    // set signature bytes received from the service
    externalSigning.setSignature(cmsSignature);
}

(PDFBox SignPadesBc test testSignPadesBaselineT)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文