Signing a hash with DSS (Digital Signature Service)

Viewed 2029

I am trying to sign a PDF document with DSS, and my problem is that I cannot calculate the hash of the document in server A and then sign it in server B.

Knowing that server A contains the PDF document and in server B we retrieve the certificate used for signature

My question is how i can compute the hash of the document in a server A without needing the certificate. Then send it for signature in server B ?

UPDATE :

****** Preparation and calculation of hash ********

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

****** Hash signature ********

     // 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 ERROR : *********

enter image description here

4 Answers

In general

This was written in response to the original revision of your question.

Your code mentions PAdES. Thus, I assume you mean integrated PAdES (not detached CAdES or XAdES) signatures when you say you're trying to sign a PDF document with DSS.

Creating integrated PDF signatures (like PAdES) requires first preparing the PDF to be able to carry an embedded signature, i.e. adding a signature dictionary to an existing or new signature field. This signature dictionary contains multiple information, signing time, signing reason, etc., and also a placeholder for embedding a CMS signature container later. Then this prepared PDF (except the placeholder) is hashed.

Furthermore, your code mentions that you choose the level of the signature (-B, -T, -LT, -LTA).

Creating PAdES Baseline LT and PAdES Baseline LTA signatures requires preparing a PDF with a PAdES Baseline T signature and adding a collection of additional objects, depending on the nature of the T signature.

eSig DSS can do all this preparing for you if it has the PDF to prepare.

So if you only want to send a hash value from server A to B, you have to use eSig DSS on your server A to do most of the work, and server B only serves as a dumb signing service returning a signed hash value or at most a CMS container usable for PAdES.

Whether you can do this without server A knowing about the certificate, depends on whether you want certificate details to appear in a signature widget for the new signature or not. Creating the widget appearance is part of the PDF preparation step, so if you want such a widget with certificate information, server A needs to know the certificate, usually even before requesting the signature!

What kind of protocol you run between server A and B then, is up to you. You merely have to implement SignatureTokenConnection accordingly to use on server A in eSig DSS to communicate with server B.

In case of your approach

After you now showed code from both servers, one can discuss your specific approach.

On server A you use eSig DSS to prepare a PAdES signature and embed a CMS signature container with the SignatureValue your SignHashDocument.signHash call returns:

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

SignatureValue signatureValue = SignHashDocument.signHash(dataToSign);

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

I.e. server A creates the CMS signature container and server B only supplies the signed hash.

This cannot work unless you know the certificate used for signing and set it in the parameters before the service.getDataToSign call.

The reason is that the CMS container contains references to that certificate in both the unsigned bytes and (for PAdES) the signed bytes of the container. For the reference in the unsigned bytes it theoretically would suffice to retrieve the certificate together with the signature bytes, but for the reference in the signed bytes it has to be known beforehand.

Alternatively you can try to implement the full generation of the CMS container on server B.

This requires considerable changes, though, and you have to dive quite a bit deeper into the PAdESService sources. Instead of the three lines quoted above on server A you have to:

  • 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);
    

A proof of concept

Here a proof of concept using eSig DSS 5.8:

On your server A we can essentially use your existing code:

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 test testSplitPAdESGenerationForMehdi)

The method signHash now shall independently create a CMS signature container for the given document hash, and this container shall conform to PAdES requirements. eSig DSS contains methods and classes providing this functionality but they protected or even less visible. Thus, for our POC we simply copy them into our code.

For simplicity I use hard coded SHA512withRSA as signing algorithm.

Thus:

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 method)

The helper methods getDataToSign and generateCMSSignedData are essentially copied from PAdESService; they use the padesCMSSignedDataBuilder provided by signHash (instead of a member variable you can also make it another argument of these two methods):

/** @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 methods)

The classes PadesCMSSignedDataBuilder and PAdESLevelBaselineB due to restricted visibility are copied along:

/** @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 helper classes)

signHash and its helpers do not depend on the server A code and, therefore, also can be located on server B.

After some research on the github dss esig repository I was able to find a solution that seemed correct :

********************* ON SERVER 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;
    }

}
}

And to be able to sign the calculated hash :

********************* ON SERVER 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;
    }

}
}

It's is possible and one open source full implementation can be seen in here https://github.com/eideasy/eideasy-external-pades-digital-signatures

You need to create PDF in same format as it will be after being signed, remove all signature ByteRange and then calculate hash.

After getting CAdES signature just add this to the ByteRange. This will give you up to baseline-T signature.

For baseline LT you need to add DSS with all the used certificates, OCSP responses and crls as well.

If more questions you can contact me using details in my profile.

Here is the most important part of the full application that will calculate the digest to be signed for you. When calculating digest then the signatureBytes can be 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;
}

An example using IAIK to sign on HSM, and Europa DSS to asssemble PAdES file:

1/ prepare data to be signed using 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/ sign with HSM (Bull Proteccio for example) with IAIK

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