使用DSS(CMS容器)确保LTV验证

2022-05-13 00:00:00 digital-signature java dss

我正在开发一个解决方案,该解决方案允许在远程服务器上使用p12证书进行签名。

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

这里是PDF文件,您将找到两个PDF版本。";CURRENT_SIGNATURE.pdf";文件是我使用下面的代码获得的结果。而";TARGER_SIGNATUREPDF.pdf";正是我想要的目标。正如您所看到的,目标文件显示的证书吊销列表嵌入到签名中。另一方面,当前表示文档中包含的证书吊销列表。此外,目标文件只有一个签名,没有添加修订版本 : 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;
    }

}
}

解决方案

在an answer to another question的备注中,开始了一场讨论,您在该讨论中指出了此问题并请求帮助。在那次讨论中,很明显,你还不完全知道你想要达到什么目的。因此,让我们稍微澄清一下。

LTV验证

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

LTV是LOngTV的缩写。它所代表的目标是确保签名在若干年后仍可被验证。

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

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

到目前为止,一切顺利。

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

然而,从那时起,这种机制被证明过于简单和静态。根据使用的验证模型,在签名之前收集的信息不够好:要检查给定的证书在签名时是否有效,严格地说,需要在签名时间之后生成的信息。若要处理算法变弱的问题,可能需要反复为整个文档加时间戳。

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

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

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

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

Adobe已经建立了他们自己的启用LTV的配置文件,该配置文件假定对验证数据的要求不那么严格(不需要时间戳),并且不关心算法会变得薄弱。他们基本上收集了他们在文档中找到的所有与验证相关的信息,并按原样使用它们。(更确切地说,这是Adobe 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);

在哪里

  • certificateVerifier是为在线资源初始化的CommonCertificateVerifier
  • tspSource是为您选择的时间戳服务初始化的OnlineTSPSource
  • extensionLevel为所需级别,如SignatureLevel.PAdES_BASELINE_LT

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

相关文章