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

Base64 digest PFX(PKCS12)->ETSI。凯德斯。分离签名->PAdES LTV

马朝斑
2023-03-14

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

共有1个答案

刁浩言
2023-03-14

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

如何使用BouncyCastle创建CAdES签名容器,该容器可用于创建PAdES基线B或T PDF签名?

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

BouncyCastle确实包含用于生成CMS签名容器的CMSSignedDataGenerator。

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

因此,您可以使用定制的CMSSignedDataGenerator创建PAdES基线签名所需的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方法签名)

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助手类CMSTypeDataInputStream

然后,为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助手类PadesUnsignedAttribute生成器)

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

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

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

(比较SignPadesBc中的测试

同时,您在评论中表示,您正在考虑使用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签名接口的实现)

你可以这样用

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 testtestSignPadesBaselineT

 类似资料:
  • 你能详细解释一下吗?最后给我举个例子(一个是附加的,一个是分离的),说明iText分离签名的确切含义? 我发现了这个精彩的留档: iText数字签名pdf关于iText数字签名,但我仍然不确定我是否理解iText分离签名的概念。 阅读文档(参见链接),我发现了以下定义: 在PDF中,我们有时指的是分离的签名。根据维基百科的说法,分离签名是一种“与签名数据分离”的数字签名,而不是“捆绑在一起形成一个

  • 我想实现PDF的“并行”签名过程,这样用户就可以不是“一个接一个”地进行数字签名,而是同时进行数字签名。为了实现这一点,我决定为所有用户创建初始文档的单独副本,并在它们上获得签名。最终,所有签名都应该连接到单个PDF中。 让我们假设,PDF在签名过程中没有变化,除了签名字段创建(所有acroForms、signaturecontainer、可视签名等都是在之前创建的,并且对所有这些都是相似的)。

  • 我想实现PDF的“并行”签名过程,这样用户就可以对文档进行数字签名,而不是“一个一个”,而是同时进行。为了实现这一点,我决定为所有用户创建初始文档的单独副本,并在其上获得签名。最终,所有签名都应该连接到单个PDF中。 让我们假设,除了签名字段创建(所有的acroForms、signatureContainers、可视签名等都是在之前创建的,并且都是类似的),在签名过程中PDF没有改变。 在进一步的

  • 我想使用BouncyCastle解析和验证OpenPGP分离签名。签名如下所示: 下面是我如何尝试在Kotlin中创建CMSSignedData: 我应该如何使用BouncyCastle验证这种签名?

  • > 文件,将ZIP文件的内容描述为XML(文件); 包含传输文档内容的文件(例如,文件); 具有分离数字签名内容的文件(文件-分离数字签名); > 方法(请参阅下面的此方法)当前未使用文件的分离签名。我确实试过了,但没有成功。如何正确验证文件对应文件的分离签名? 在方法(请参阅下面的方法)中,如何验证从文件中提取的证书与从Base64格式的输入字符串中提取的证书的一致性? 代码行-->Certif

  • 我的java applet能够使用Detached类型的Bouncy Castle 1.4.9生成CMSSignedData。然后将字节数组sigData.getEncoded()存储在服务器上的一个表中(该表可以访问未包含的内容数据)。现在我想在服务器中创建封装的CMSSignedData,以便用户下载一个.p7m文件。 我需要开发的函数具有分离签名的字节数组和内容数据的字节数组,并且必须返回一