0

我正在研究一种允许在远程服务器上使用 p12 证书进行签名的解决方案。

首先,我有在服务器上计算的文档摘要,然后我将其发送到另一台服务器上以供签名。

这是 PDF 文件,您会找到两个 PDF 版本。“ CURRENT_SIGNATURE.pdf”文件是我使用下面的代码得到的结果。而“TARGER_SIGNATUREPDF.pdf”是我想要的目标。如您所见,TARGET 文件显示“嵌入在签名中的证书吊销列表”的区别。另一方面,CURRENT表示“文件中包含的证书撤销列表”。另外,TARGET文件只有一个签名,没有添加修订: https ://www.grosfichiers.com/i4fmqCz43is

结果验证:

在此处输入图像描述

我现在的目标是添加 LTV 验证,知道我正在使用以下服务器部分进行签名:PadesCMSSignedDataBuilder

************************ 在服务器 A 上 ************************

    public class ServerA {
    private static PAdESSignatureParameters signatureParameters;
    private static DSSDocument documentToSign;
    public static ExternalCMSPAdESService service;
    private static final String TSA_URL = "http://dss.nowina.lu/pki-factory/tsa/good-tsa";


    public static void main(String[] args) throws Exception {
        documentToSign = new FileDocument(new File("Doc 2.pdf"));

        signatureParameters = new PAdESSignatureParameters();
        signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
        signatureParameters.setLocation("Luxembourg");
        signatureParameters.setReason("DSS testing");
        signatureParameters.setContactInfo("Jira");
        signatureParameters.setGenerateTBSWithoutCertificate(true);
        CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();

        commonCertificateVerifier.setCrlSource(new OnlineCRLSource());
        commonCertificateVerifier.setOcspSource(new OnlineOCSPSource());
        commonCertificateVerifier.setCheckRevocationForUntrustedChains(true);
        service = new ExternalCMSPAdESService(commonCertificateVerifier);
        byte[] documentDigest = computeDocumentDigest(documentToSign, signatureParameters);

        // Embedded CAdES is generated by a third party
        byte[] cmsSignedData = ServerB.getSignedCMSignedData(documentDigest);

        service.setCmsSignedData(cmsSignedData);
        DSSDocument finalDoc = service.signDocument(documentToSign, signatureParameters, null);


        PAdESService service = new PAdESService(commonCertificateVerifier);
        TimestampDataLoader timestampDataLoader = new TimestampDataLoader();// uses the specific content-type
        OnlineTSPSource tsa1 = new OnlineTSPSource("http://dss.nowina.lu/pki-factory/tsa/ee-good-tsa");
        tsa1.setDataLoader(timestampDataLoader);
        service.setTspSource(tsa1);
        PAdESSignatureParameters extensionParameters = new PAdESSignatureParameters();
        extensionParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LT);

        DSSDocument extendedDocument = service.extendDocument(finalDoc, extensionParameters);


        save(finalDoc);
        save2(extendedDocument);
    }

    private static void save(DSSDocument signedDocument) {
        try (FileOutputStream fos = new FileOutputStream("DSS.pdf")) {
            Utils.copy(signedDocument.openStream(), fos);
        } catch (Exception e) {
            Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to save file : " + e.getMessage(), ButtonType.CLOSE);
            alert.showAndWait();
            return;
        }
    }
    private static void save2(DSSDocument signedDocument) {
        try (FileOutputStream fos = new FileOutputStream("DSS-2.pdf")) {
            Utils.copy(signedDocument.openStream(), fos);
        } catch (Exception e) {
            Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to save file : " + e.getMessage(), ButtonType.CLOSE);
            alert.showAndWait();
            return;
        }
    }

    public static CertificateVerifier getOfflineCertificateVerifier() {
        CertificateVerifier cv = new CommonCertificateVerifier();
        cv.setDataLoader(new IgnoreDataLoader());
        return cv;
    }

    protected static byte[] computeDocumentDigest(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters) {
        IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
        final PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
        return pdfSignatureService.digest(toSignDocument, parameters);
    }

    private static class ExternalCMSPAdESService extends PAdESService {

        private static final long serialVersionUID = -2003453716888412577L;

        private byte[] cmsSignedData;

        public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
            super(certificateVerifier);
        }

        @Override
        protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
                                               final SignatureValue signatureValue) {
            if (this.cmsSignedData == null) {
                throw new NullPointerException("A CMS signed data must be provided");
            }
            return this.cmsSignedData;
        }

        public void setCmsSignedData(final byte[] cmsSignedData) {
            this.cmsSignedData = cmsSignedData;
        }

    }
}

并且能够签署计算的哈希:

************************ 在服务器 B 上 ************************

public class ServerB {

private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;

/**
 * Computes a CAdES with specific things for PAdES
 */
public static byte[] getSignedCMSignedData(byte[] documentDigest) throws Exception {
    signatureParameters = new PAdESSignatureParameters();
    signatureParameters.setSigningCertificate(getSigningCert());
    signatureParameters.setCertificateChain(getCertificateChain());
    signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
    signatureParameters.setLocation("Luxembourg");
    signatureParameters.setReason("DSS testing");
    signatureParameters.setContactInfo("Jira");

    CMSProcessableByteArray content = new CMSProcessableByteArray(documentDigest);

    PadesCMSSignedDataBuilder padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(getOfflineCertificateVerifier());
    SignatureAlgorithm signatureAlgorithm = signatureParameters.getSignatureAlgorithm();

    CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());
    SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(signatureParameters, documentDigest);

    CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner,
            signerInfoGeneratorBuilder, null);

    CMSUtils.generateDetachedCMSSignedData(generator, content);

    SignatureTokenConnection signingToken = new Pkcs12SignatureToken("certificate.p12",
            new KeyStore.PasswordProtection("123456".toCharArray()));
    DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");

    SignatureValue signatureValue = signingToken.sign(new ToBeSigned(customContentSigner.getOutputStream().toByteArray()),
            signatureParameters.getDigestAlgorithm(), privateKey);

    customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
    generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(signatureParameters, customContentSigner, signerInfoGeneratorBuilder, null);

    CMSSignedData cmsSignedData = CMSUtils.generateDetachedCMSSignedData(generator, content);
    return DSSASN1Utils.getDEREncoded(cmsSignedData);
}

public static CertificateVerifier getOfflineCertificateVerifier() {
    CertificateVerifier cv = new CommonCertificateVerifier();
    cv.setDataLoader(new IgnoreDataLoader());
    return cv;
}

public static List<CertificateToken> getCertificateChain() throws Exception {
    List<CertificateToken> list = new ArrayList<>();
    CertificateToken[] l = getKey("certificate.p12","123456").getCertificateChain();
    for (int i = 0; i < l.length; i++) {
        list.add(l[i]);
    }
    return list;
}

public static CertificateToken getSigningCert() throws Exception {
    return getKey("certificate.p12","123456").getCertificate();
}

public static DSSPrivateKeyEntry getKey(String certificate, String pin) throws Exception {
    try (Pkcs12SignatureToken signatureToken = new Pkcs12SignatureToken("certificate.p12",
            new KeyStore.PasswordProtection("123456".toCharArray()))) {
        List<DSSPrivateKeyEntry> keys = signatureToken.getKeys();
        KSPrivateKeyEntry dssPrivateKeyEntry = (KSPrivateKeyEntry) keys.get(0);
        DSSPrivateKeyEntry entry = signatureToken.getKey(dssPrivateKeyEntry.getAlias(),
                new KeyStore.PasswordProtection("123456".toCharArray()));
        return entry;
    }
}
private static class ExternalCMSPAdESService extends PAdESService {

    private static final long serialVersionUID = -2003453716888412577L;

    private byte[] cmsSignedData;

    public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) {
        super(certificateVerifier);
    }

    @Override
    protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters,
                                           final SignatureValue signatureValue) {
        if (this.cmsSignedData == null) {
            throw new NullPointerException("A CMS signed data must be provided");
        }
        return this.cmsSignedData;
    }

    public void setCmsSignedData(final byte[] cmsSignedData) {
        this.cmsSignedData = cmsSignedData;
    }

}
}
4

1 回答 1

1

对另一个问题的答案的评论中,您开始讨论这个问题并寻求帮助。在那次讨论中,很明显你还不完全知道你想要实现什么。因此,让我们澄清一下。

生命周期价值验证

您说您想将LTV 验证添加到您的签名中。我们先来看看这意味着什么。

LTV是Long Term V alidation的缩写。它所代表的目标是确保签名仍然可以在几年内得到验证。

这个目标试图克服的挑战是,从长远来看,验证者需要的信息将无法在线获得,并且使用的算法最终将不再被认为是安全的。

方法是检索一次所需信息并以可信任的方式将它们与签名捆绑在一起,并应用数字时间戳来证明特定的一组数据、签名和额外信息存在并在给定时间捆绑在一起(例如当使用的签名算法仍然被认为是强大的)。

到目前为止,一切都很好。

Adobe 早期(在 PDF 成为 ISO 标准之前)定义了一种实现 LTV 的机制:他们指定了一个特定的签名属性,在签名之前应该将验证所需的数据收集到该属性中,并且他们建议将时间戳应用于嵌入式签名容器.

然而,从那以后,这种机制变得过于简单和静态。根据使用的验证模型,在签名之前收集的信息还不够好:要检查给定证书在签名时是否有效,严格来说需要在签名之后生成的信息。并且为了处理变弱的算法,可能需要一次又一次地对整个文档进行时间戳。

为了处理这个 ETSI(一个欧洲规范组织),指定了将验证相关信息添加到文档的替代方法,以及添加覆盖整个文档(而不仅仅是嵌入式签名容器)的额外时间戳的方法。这些机制不会更改原始签名容器,而是将增量更新中的信息添加到原始文档中。同时,这些机制已被添加到 ISO 32000-2 中的国际 PDF 标准中。它们被归入 PAdES 一词。

ETSI 还定义了如何使用这些新机制以可互操作的方式增强签名的标准方案,PAdES BASELINE 配置文件:

  • B级仅包含一个基本签名容器,该容器被配置为特别包含一个 ESS 证书 ID 属性。
  • T级别基于B级别,但额外需要签名后时间戳。该时间戳可以作为签名时间戳应用于原始签名容器,也可以作为文档时间戳在文档的额外增量更新中应用。
  • LT级别基于T级别,并且需要在新的增量更新中添加缺少的中间证书和所需的吊销信息。
  • LTA级别基于LT级别,并且需要在另一个增量更新中添加另一个文档时间戳。

为了实现长期验证,可以重复添加LTLTA,以提供先前时间戳的验证信息,并记录所使用的算法是在它们仍然强大的时候应用的。

Adobe 已经建立了自己的“支持 LTV”的配置文件,该配置文件对验证数据的要求不那么严格(不需要时间戳),并且不关心算法变弱。他们基本上收集了他们在文档中找到的所有验证相关信息并按原样使用它们。(更确切地说,这是Adob​​e Acrobat标准设置的行为。您可以对 Acrobat 进行不同的配置以更改要求,例如使某些时间戳重要。因此,在谈论“启用 LTV”的签名时,请始终确保您拥有记住与您的讨论伙伴相同的设置...)

使用 eSig DSS 进行扩展

如果您想在服务器 A 上使用 eSig DSS 扩展 PDF 签名,只需使用finalDoc

PAdESService service = new PAdESService(certificateVerifier);
service.setTspSource(tspSource);
PAdESSignatureParameters extensionParameters = new PAdESSignatureParameters();
extensionParameters.setSignatureLevel(extensionLevel);

DSSDocument extendedDocument = service.extendDocument(finalDoc, extensionParameters);

在哪里

  • certificateVerifierCommonCertificateVerifier为在线资源初始化的,
  • tspSourceOnlineTSPSource为您选择的时间戳服务初始化的,并且
  • extensionLevel是所需的水平,例如SignatureLevel.PAdES_BASELINE_LT

结果extendedDocument应包含所需的验证相关信息。

于 2022-02-01T09:59:05.533 回答