使用 DSS(数字签名服务)签署哈希

Signing a hash with DSS (Digital Signature Service)

我正在尝试使用 DSS 对 PDF 文档进行签名,我的问题是我无法在服务器 A 中计算文档的哈希值,然后在服务器 B 中对其进行签名。

知道服务器 A 包含 PDF 文档并且在服务器 B 中我们检索用于签名的证书

我的问题是如何在不需要证书的情况下计算服务器 A 中文档的哈希值。然后发送到服务器B签名?

更新:

****** 哈希的准备和计算 ********

    IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
    PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();

    PAdESSignatureParameters parameters = new PAdESSignatureParameters();

    parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
    parameters.setReason("Preuve de signature");
    parameters.setLocation("MAROC");
    parameters.setGenerateTBSWithoutCertificate(true);

    SignatureImageParameters imageParameters = new SignatureImageParameters();

    imageParameters.setPage(1);
    FileDocument imageFile = new FileDocument("logo.png");
    RemoteDocument fileImage = RemoteDocumentConverter.toRemoteDocument(imageFile);
    DSSDocument image = RemoteDocumentConverter.toDSSDocument(fileImage);
    // set an image
    imageParameters.setImage(image);

    imageParameters.setxAxis(350);
    imageParameters.setyAxis(400);
    imageParameters.setWidth(200);
    imageParameters.setHeight(100);
    parameters.setImageParameters(imageParameters);
    SignatureImageTextParameters textParameters = new SignatureImageTextParameters();
    DSSFont font = new DSSJavaFont(Font.SERIF);
    font.setSize(16); // Specifies the text size value (the default font size is 12pt)
    textParameters.setFont(font);
    textParameters.setTextColor(Color.BLUE);

    textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
    // Specifies a horizontal alignment of a text with respect to its area
    textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
    // Specifies a vertical alignment of a text block with respect to a signature field area
    textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
    imageParameters.setTextParameters(textParameters);

    FileDocument fileToSign = new FileDocument("file.pdf");
    RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
    DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);

    byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);

    DSSDocument signatureValue = SignHashDocument.signHash(hash);

    DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, DSSUtils.toByteArray(signatureValue), parameters);

    save(signedDocument);

****** 哈希签名 ********

     // Create common certificate verifier
    CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
        // Create CAdESService for signature
    CAdESService service = new CAdESService(commonCertificateVerifier);

    CAdESSignatureParameters parameters = new CAdESSignatureParameters();
    DSSPrivateKeyEntry privateKey = getKey("certificate.p12","123456");


    // We choose the level of the signature (-B, -T, -LT, -LTA).
    parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B);
    parameters.setSignaturePackaging(SignaturePackaging.ENVELOPING);

    parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
    // We set the signing certificate
    parameters.setSigningCertificate(privateKey.getCertificate());
    // We set the certificate chain
    parameters.setCertificateChain(privateKey.getCertificateChain());


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


    convertByteArrayToFile(hashToSign,"filetosign.hash");
    FileDocument fileToSign = new FileDocument("filetosign.hash");
    RemoteDocument fileSign = RemoteDocumentConverter.toRemoteDocument(fileToSign);
    DSSDocument toSignDocument = RemoteDocumentConverter.toDSSDocument(fileSign);

    //ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);

    ToBeSigned dataToSign = new ToBeSigned(hashToSign);

    DigestAlgorithm digestAlgorithm = parameters.getDigestAlgorithm();
    SignatureValue signatureValue = signingToken.sign(dataToSign, digestAlgorithm, privateKey);

    DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);

    return  signedDocument;

******** PDF 错误:*********

一般

这是针对您问题的原始修订而写的。

您的代码提到了 PAdES。因此,当您说 尝试使用 DSS 签署 PDF 文档时,我假设您指的是集成 PAdES(不是分离的 CAdES 或 XAdES)签名。

创建集成的 PDF 签名(如 PAdES)需要首先准备 PDF 以便能够携带嵌入式签名,即将签名字典添加到现有或新的签名字段。这个签名字典里面包含了很多信息,签名时间,签名原因等等,还有一个占位符,方便后面嵌入一个CMS签名容器。然后对准备好的 PDF(占位符除外)进行哈希处理。

此外,您的代码提到您选择签名级别(-B、-T、-LT、-LTA)

创建 PAdES Baseline LT 和 PAdES Baseline LTA 签名需要准备一个带有 PAdES Baseline T 签名的 PDF 并添加一组附加对象,具体取决于 T 签名的性质。

eSig DSS 可以为您做所有这些准备如果它有 PDF 可以准备

因此,如果您只想从服务器 A 向 B 发送哈希值,则必须在服务器 A 上使用 eSig DSS 来完成大部分工作,而服务器 B 仅用作哑签名服务 return正在处理一个已签名的哈希值或至多一个可用于 PAdES 的 CMS 容器。

您是否可以在服务器 A 不知道证书的情况下执行此操作,取决于您是否希望证书详细信息出现在新签名的签名小部件中。创建 widget 外观是 PDF 准备步骤的一部分,所以如果你想要这样一个带有证书信息的 widget,服务器 A 需要知道证书,通常甚至 在请求签名之前 !

服务器 A 和 B 之间 运行 使用何种协议,由您决定。您只需要相应地实施 SignatureTokenConnection 即可在 eSig DSS 中的服务器 A 上使用以与服务器 B 通信。

以防你的做法

在您展示了来自两个服务器的代码之后,可以讨论您的具体方法。

在服务器 A 上,您使用 eSig DSS 准备 PAdES 签名并使用 SignatureValue 您的 SignHashDocument.signHash 调用 returns:

嵌入 CMS 签名容器
ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);

SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);

DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);

即服务器 A 创建 CMS 签名容器,服务器 B 仅提供签名哈希。

除非您知道用于签名的证书并在 service.getDataToSign 调用之前将其设置在 parameters 中,否则这将无法工作。

原因是 CMS 容器在容器的无符号字节和(对于 PAdES)有符号字节中都包含对该证书的引用。对于无符号字节中的引用,理论上将证书与签名字节一起检索就足够了,但对于有符号字节中的引用,必须事先知道。

或者您可以尝试在服务器 B 上实现 CMS 容器的完整生成。

不过,这需要进行相当大的更改,并且您必须更深入地研究 PAdESService 来源。而不是上面在服务器 A 上引用的三行,您必须:

  • 先获取一个PDF对象库和PDF签名服务

    IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
    PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
    
  • 第一次准备 PDF 进行签名并计算文档摘要,

    byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
    
  • 将此文档摘要发送到后端(服务器 B),后者必须创建和 return 一个特殊的 CAdES 签名容器,而不仅仅是裸签名字节,

  • 并再次准备 PDF 以进行签名并注入此签名容器:

    DSSDocument signature = pdfSignatureService.sign(toSignDocument, encodedData, parameters);
    

概念验证

这里是使用 eSig DSS 5.8 的概念证明:

在您的服务器 A 上,我们基本上可以使用您现有的代码:

DSSDocument toSignDocument = PDF_DOCUMENT_TO_SIGN;
DSSDocument image = IMAGE_DOCUMENT;


PAdESSignatureParameters parameters = new PAdESSignatureParameters();
parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
parameters.setReason("Preuve de signature");
parameters.setLocation("MAROC");
parameters.setGenerateTBSWithoutCertificate(true);

SignatureImageParameters imageParameters = new SignatureImageParameters();
imageParameters.setPage(1);
imageParameters.setImage(image);
imageParameters.setxAxis(350);
imageParameters.setyAxis(400);
imageParameters.setWidth(200);
imageParameters.setHeight(100);
parameters.setImageParameters(imageParameters);

SignatureImageTextParameters textParameters = new SignatureImageTextParameters();
DSSFont font = new DSSJavaFont(Font.SERIF);
font.setSize(16);
textParameters.setFont(font);
textParameters.setTextColor(Color.BLUE);
textParameters.setSignerTextPosition(SignerTextPosition.RIGHT);
textParameters.setSignerTextHorizontalAlignment(SignerTextHorizontalAlignment.LEFT);
textParameters.setSignerTextVerticalAlignment(SignerTextVerticalAlignment.TOP);
textParameters.setText("TESTING");
imageParameters.setTextParameters(textParameters);


IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();

byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);

byte[] signatureValue = signHash(hash);

DSSDocument signedDocument = pdfSignatureService.sign(toSignDocument, signatureValue, parameters);


signedDocument.save(PATH_TO_SAVE_THE_SIGNED_DOCUMENT_TO);

(SplitPAdESSigning 测试 testSplitPAdESGenerationForMehdi)

方法signHash现在应该为给定的文档哈希独立创建一个CMS签名容器,这个容器应该符合PAdES要求。 eSig DSS 包含提供此功能的方法和 类,但它们 protected 甚至更不可见。因此,对于我们的 POC,我们只需将它们复制到我们的代码中。

为简单起见,我使用硬编码的 SHA512withRSA 作为签名算法。

因此:

byte[] signHash(byte[] hash) throws IOException {
    Pkcs12SignatureToken signingToken = new Pkcs12SignatureToken(YOUR_P12_DATA);
    DSSPrivateKeyEntry privateKey = signingToken.getKey(YOUR_ALIAS);

    CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier();
    padesCMSSignedDataBuilder = new PadesCMSSignedDataBuilder(commonCertificateVerifier);

    PAdESSignatureParameters parameters = new PAdESSignatureParameters();
    parameters.setDigestAlgorithm(DigestAlgorithm.SHA512);
    parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA);
    parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
    parameters.setSigningCertificate(privateKey.getCertificate());

    ToBeSigned dataToSign = getDataToSign(hash, parameters);
    SignatureValue signatureValue = signingToken.sign(dataToSign, DigestAlgorithm.SHA512, privateKey);
    return generateCMSSignedData(hash, parameters, signatureValue);
}

PadesCMSSignedDataBuilder padesCMSSignedDataBuilder;

(SplitPAdESSigning方法)

辅助方法 getDataToSigngenerateCMSSignedData 本质上是从 PAdESService 复制而来的;他们使用 signHash 提供的 padesCMSSignedDataBuilder (你也可以将其设为这两种方法的另一个参数,而不是成员变量):

/** @see eu.europa.esig.dss.pades.signature.PAdESService#getDataToSign(DSSDocument, PAdESSignatureParameters) */
public ToBeSigned getDataToSign(byte[] messageDigest, final PAdESSignatureParameters parameters) throws DSSException {
    final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm();
    final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId());

    SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, messageDigest);

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

    final CMSProcessableByteArray content = new CMSProcessableByteArray(messageDigest);

    CMSUtils.generateDetachedCMSSignedData(generator, content);

    final byte[] dataToSign = customContentSigner.getOutputStream().toByteArray();
    return new ToBeSigned(dataToSign);
}

/** @see eu.europa.esig.dss.pades.signature.PAdESService#generateCMSSignedData(DSSDocument, PAdESSignatureParameters, SignatureValue) */
protected byte[] generateCMSSignedData(byte[] messageDigest, final PAdESSignatureParameters parameters,
        final SignatureValue signatureValue) {
    final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm();
    final SignatureLevel signatureLevel = parameters.getSignatureLevel();
    Objects.requireNonNull(signatureAlgorithm, "SignatureAlgorithm cannot be null!");
    Objects.requireNonNull(signatureLevel, "SignatureLevel must be defined!");
    
    final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue.getValue());
    
    final SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = padesCMSSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, messageDigest);
    
    final CMSSignedDataGenerator generator = padesCMSSignedDataBuilder.createCMSSignedDataGenerator(parameters, customContentSigner,
    signerInfoGeneratorBuilder, null);
    
    final CMSProcessableByteArray content = new CMSProcessableByteArray(messageDigest);
    CMSSignedData data = CMSUtils.generateDetachedCMSSignedData(generator, content);

    return DSSASN1Utils.getDEREncoded(data);
}

(SplitPAdESSigning 方法)

PadesCMSSignedDataBuilderPAdESLevelBaselineB 由于可见性受限,复制如下:

/** @see eu.europa.esig.dss.cades.signature.CMSSignedDataBuilder */
class PadesCMSSignedDataBuilder extends CMSSignedDataBuilder {
    public PadesCMSSignedDataBuilder(CertificateVerifier certificateVerifier) {
        super(certificateVerifier);
    }

    @Override
    protected CMSSignedDataGenerator createCMSSignedDataGenerator(CAdESSignatureParameters parameters, ContentSigner contentSigner, SignerInfoGeneratorBuilder signerInfoGeneratorBuilder,
            CMSSignedData originalSignedData) throws DSSException {

        return super.createCMSSignedDataGenerator(parameters, contentSigner, signerInfoGeneratorBuilder, originalSignedData);
    }

    protected SignerInfoGeneratorBuilder getSignerInfoGeneratorBuilder(final PAdESSignatureParameters parameters, final byte[] messageDigest) {
        final CAdESLevelBaselineB cadesLevelBaselineB = new CAdESLevelBaselineB(true);
        final PAdESLevelBaselineB padesProfileB = new PAdESLevelBaselineB();

        final DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();

        SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = new SignerInfoGeneratorBuilder(digestCalculatorProvider);

        signerInfoGeneratorBuilder = signerInfoGeneratorBuilder.setSignedAttributeGenerator(new CMSAttributeTableGenerator() {
            @Override
            public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
                return padesProfileB.getSignedAttributes(params, cadesLevelBaselineB, parameters, messageDigest);
            }
        });

        signerInfoGeneratorBuilder = signerInfoGeneratorBuilder.setUnsignedAttributeGenerator(new CMSAttributeTableGenerator() {
            @Override
            public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map params) throws CMSAttributeTableGenerationException {
                return padesProfileB.getUnsignedAttributes();
            }
        });

        return signerInfoGeneratorBuilder;
    }
}

/** @see eu.europa.esig.dss.pades.signature.PAdESLevelBaselineB */
class PAdESLevelBaselineB {
    AttributeTable getSignedAttributes(@SuppressWarnings("rawtypes") Map params, 
            CAdESLevelBaselineB cadesProfile, PAdESSignatureParameters parameters, byte[] messageDigest) {
        AttributeTable signedAttributes = cadesProfile.getSignedAttributes(parameters);

        if (signedAttributes.get(CMSAttributes.contentType) == null) {
            ASN1ObjectIdentifier contentType = (ASN1ObjectIdentifier) params.get(CMSAttributeTableGenerator.CONTENT_TYPE);
            if (contentType != null) {
                signedAttributes = signedAttributes.add(CMSAttributes.contentType, contentType);
            }
        }

        if (signedAttributes.get(CMSAttributes.messageDigest) == null) {
            signedAttributes = signedAttributes.add(CMSAttributes.messageDigest, new DEROctetString(messageDigest));
        }

        return signedAttributes;
    }

    AttributeTable getUnsignedAttributes() {
        return null;
    }
}

(SplitPAdESSigning帮手类)

signHash 及其助手不依赖服务器 A 代码,因此也可以位于服务器 B 上。

这是可能的,可以在此处看到一个开源的完整实现 https://github.com/eideasy/eideasy-external-pades-digital-signatures

您需要创建与签名后格式相同的 PDF,删除所有签名 ByteRange,然后计算哈希值。

获得 CAdES 签名后,只需将其添加到 ByteRange。这将为您提供基线-T 签名。

对于基线 LT,您需要添加 DSS 以及所有使用的证书、OCSP 响应和 crls。

如果有更多问题,您可以使用我个人资料中的详细信息与我联系。

这是整个应用程序中最重要的部分,它将计算要为您签名的摘要。计算摘要时,signatureBytes 可以是 new byte[0].

    public byte[] signDetached(SignatureParameters parameters, PDDocument document, byte[] signatureBytes, OutputStream out)
        throws IOException, NoSuchAlgorithmException {

    if (document.getDocumentId() == null) {
        document.setDocumentId(parameters.getSignatureTime());
    }

    PDSignature signature = createSignatureDictionary(parameters);
    SignatureOptions options = new SignatureOptions();

    // Enough room for signature, timestamp and OCSP for baseline-LT profile.
    options.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
    document.addSignature(signature, options);
    ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(out);

    byte[] dataToSign = IOUtils.toByteArray(externalSigning.getContent());
    final MessageDigest digest = MessageDigest.getInstance("SHA-256");
    byte[] digestBytes = digest.digest(dataToSign);

    if (signatureBytes != null) {
        externalSigning.setSignature(signatureBytes);
    }

    return digestBytes;
}

private PDSignature createSignatureDictionary(final SignatureParameters parameters) {
    PDSignature signature = new PDSignature();

    signature.setType(COSName.getPDFName("Sig"));
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);

    if (notEmpty(parameters.getSignerName())) {
        signature.setName(parameters.getSignerName());
    }

    if (notEmpty(parameters.getContactInfo())) {
        signature.setContactInfo(parameters.getContactInfo());
    }

    if (notEmpty(parameters.getLocation())) {
        signature.setLocation(parameters.getLocation());
    }

    if (notEmpty(parameters.getReason())) {
        signature.setReason(parameters.getReason());
    }

    // the signing date, needed for valid signature
    final Calendar cal = Calendar.getInstance();
    final Date signingDate = new Date(parameters.getSignatureTime());
    cal.setTime(signingDate);
    signature.setSignDate(cal);

    return signature;
}

在对 github dss esig 存储库进行一些研究后,我找到了一个看起来正确的解决方案:

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

public class ServerA {
private static PAdESSignatureParameters signatureParameters;
private static DSSDocument documentToSign;
public static ExternalCMSPAdESService service;


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

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

    service = new ExternalCMSPAdESService(getOfflineCertificateVerifier());
    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);

    save(finalDoc);
}

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;
    }
}

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;
    }

}
}

使用 IAIK 在 HSM 上签名,并使用 Europa DSS 组装 PAdES 文件的示例:

1/ 准备要使用 Europa DSS 签名的数据

            CertificateFactory fact = CertificateFactory.getInstance("X.509");
            X509Certificate cer = null;
            try (FileInputStream is = new FileInputStream (certFile);) {
                cer = (X509Certificate) fact.generateCertificate(is);
            }

            CertificateToken certificateToken = new CertificateToken(cer);
            
            CertificateToken[] certificateChain = new CertificateToken[] {
                certificateToken
            };

            PAdESSignatureParameters parameters = buildPAdESSignatureParameters(certificateToken, certificateChain);
    public static PAdESSignatureParameters buildPAdESSignatureParameters(
            CertificateToken signingCertificate,
            final CertificateToken... certificateChain) {
        PAdESSignatureParameters parameters = new PAdESSignatureParameters();
        // We choose the level of the signature (-B, -T, -LT, -LTA).
//      parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B);
        parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_T);
//      parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LT);
//      parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_LTA);
        parameters.setReason("Preuve de signature");
        parameters.setLocation("PARIS");
        parameters.setSigningCertificate(signingCertificate);
        parameters.setCertificateChain(certificateChain);

        return parameters;
    }

2/ 使用 IAIK 使用 HSM(例如 Bull Proteccio)签名

            String modulePath = "C:/data/applications/HSM/europa_dss/win64/nethsm.dll";

            iaik.pkcs.pkcs11.Module module = iaik.pkcs.pkcs11.Module.getInstance(modulePath);
            module.initialize(null);

            Token token = retrieveToken(slotId, module);
            System.out.println("module: " + infoModule(module));
            
            Session session = null;
            session = openSession(token, pincode);

            // Get the SignedInfo segment that need to be signed.
            DSSDocument toSignDocument = new FileDocument(toSignFile);
            ToBeSigned dataToSign = service.getDataToSign(toSignDocument, parameters);

            byte[] content = dataToSign.getBytes();
            System.out.println("parameters: " + parameters);
            
            ByteBuffer byteBuffer = ByteBuffer.wrap(content);
            ByteBuffer signedData = signDataByKeyIdAndLabel(keyId, label, byteBuffer, session);
            byte[] cmsContent = signedData.array();
            
            System.out.println("signed data: " + Hex.encodeHexString( signedData.array() ) );
            System.out.println("signing date: " + parameters.getSigningDate());
            System.out.println("signing time: " + parameters.getSigningDate().getTime());
                        
            
            Key hsmPublicKey = findPublicKeyByIDAndLabel(session, keyId, label);
            boolean verify = verifyHSM(session, hsmPublicKey, content, cmsContent);
            System.out.println("public key: " + hsmPublicKey);
            System.out.println("public key (cer): " + cer);
            System.out.println("verify: " + verify);
            
            closeSession(session);          
    private byte[] signHSM(Session session, Key key, byte[] data) throws TokenException {
        session.signInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
        return session.sign(data);
    }
    
    private boolean verifyHSM(Session session, Key key, byte[] data, byte[] signature) {
        try {
            session.verifyInit(Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS), key);
            session.verify(data, signature);
            return true;
        } catch (TokenException e) {
            return false;
        }
    }

3/Assemble

assemble(
                    parameters,
                    cmsContent, 
                    toSignFile, 
                    signedFile,
                    service);
    public static void assemble(
            PAdESSignatureParameters parameters,
            byte[] signatureValueBytes,
            File toSignFile,
            File signedFile,
            PAdESService service) 
    throws IOException {
        DSSDocument toSignDocument = new FileDocument(toSignFile);
        
        SignatureValue signatureValue = new SignatureValue(SignatureAlgorithm.RSA_SHA256, signatureValueBytes);
        DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue);
        signedDocument.save(signedFile.getAbsolutePath());
    }
}