使用DSS(数字签名服务)对哈希进行签名

4
我正在尝试使用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错误:*********

在此输入图片描述


您提到您的解决方案在两个交互服务器上运行。不清楚您展示的代码的哪些部分运行在哪个服务器上。 - mkl
4个回答

5

总体来说

这篇文章是针对您的问题的原始修订版本所写的。

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

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

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

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

eSig DSS可以为您完成所有这些准备工作,如果它拥有要准备的PDF文件

因此,如果您只想从服务器A向B发送哈希值,则必须在您的服务器A上使用eSig DSS来完成大部分工作,服务器B仅充当一个愚笨的签名服务,返回已签名的哈希值或最多可用于PAdES的CMS容器。

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

然后,在服务器A和B之间运行何种协议取决于您。您只需相应地实现SignatureTokenConnection以在eSig DSS中在服务器A上与服务器B通信即可。

针对您的方法

现在,您展示了两个服务器的代码,我们可以讨论您的具体方法。

在服务器A上,您使用eSig DSS准备了一个PAdES签名,并嵌入了一个CMS签名容器,其中包含您的SignHashDocument.signHash调用返回的SignatureValue

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

SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);

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

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

如果您不知道用于签名的证书并在 service.getDataToSign 调用之前设置它,这是行不通的。

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

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

虽然这需要相当大的更改,并且您必须深入研究 PAdESService 源代码,而不是在服务器A上引用的三行代码。

  • first retrieve a PDF object library and PDF signature service

    IPdfObjFactory pdfObjFactory = new ServiceLoaderPdfObjFactory();
    PDFSignatureService pdfSignatureService = pdfObjFactory.newPAdESSignatureService();
    
  • prepare the PDF a first time for signing and calculate the document digest,

    byte[] hash = pdfSignatureService.digest(toSignDocument, parameters);
    
  • send this document digest to the backend (server B) which must create and return a special CAdES signature container, not merely naked signature bytes,

  • and prepare the PDF a second time for signing and inject this signature container:

    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方法将独立为给定文档哈希创建一个符合PAdES要求的CMS签名容器。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);
}

由于可见性受限,PadesCMSSignedDataBuilderPAdESLevelBaselineB类被复制并添加了SplitPAdESSigning方法。
/** @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上。


正如我在编辑中提到的那样,后端(服务器B)必须创建并返回一个特殊的CAdES签名容器,而不仅仅是裸签名字节,因此,是的,您必须更改该代码以创建CAdES / CMS签名容器(包括适当的证书引用),而不是您当前创建的裸签名值。 - mkl
经过一些研究,我现在可以重新集成签名,但是在 PDF 上出现了以下错误。(更新错误) - Mehdi
非常感谢你的帮助,我有一个问题,如果我想添加时间戳,你能帮我吗? - undefined
@eluish192 "我有一个问题,如果我想添加时间戳,你能帮我吗?" - 由于这个主题的问题是关于在服务器A和B上进行分割签名,你想在这些服务器中的哪一个上添加什么类型的时间戳? - undefined
@mkl 嗨,谢谢你的回复。我想在任何服务器上添加一个时间戳。我尝试在哈希签名生成的地方设置 signaturelevel 为 T,并添加一个 tspSource,但对我来说没有起作用。 - undefined
显示剩余17条评论

2

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

}
}

“这里的问题是 PAdESSignatureParameters 对象并不包含相同的值” - 很明显它必须要包含。 - mkl
由于这是在两个独立的步骤中发生的,因此我无法重复使用同一对象。 - Mehdi
你有没有关于在签名中添加时间戳的想法? - Mehdi
CMS容器中的签名时间戳?还是文档时间戳? - mkl
是的,在 cms 容器中。我想把它添加到 B 服务器上。 - Mehdi
显示剩余11条评论

0

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

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

在获取CAdES签名后,只需将其添加到ByteRange中即可得到基线-T签名。

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

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

这是完整应用程序的最重要部分,它将为您计算要签名的摘要。在计算摘要时,signatureBytes 可以是新的 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;
}

请在此提供代码,不要通过链接或单独联系方式提供。目前您的回答基本上是一个链接式回答。 - mkl
1
谢谢,这是一个很好的观点。在这里添加了代码的最重要部分。 - Margus Pala

0

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

使用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(
                    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());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接