0

I am working on a solution that allows signing using p12 certificate on a remote server.

First, I have the digest of the document which is calculated on a server and then I send it for signature on another server.

Here are the PDF files, you will find two PDF versions. The "CURRENT_SIGNATURE.pdf" file is the result I got using the code below. And the "TARGER_SIGNATUREPDF.pdf" is the target I want to have. As you can see the difference the TARGET file says "certificate revocation list embedded in signature." on the other hand the CURRENT indicates that "the list of revocation of the certificates incorporated in the document." In addition, the TARGET file there is only one signature and no revision added : https://www.grosfichiers.com/i4fmqCz43is

Result vérification :

enter image description here

My goal now is to add a LTV verification, knowing that I am signing on the server part using: PadesCMSSignedDataBuilder

********************* ON SERVER A **********************

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


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

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

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

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

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


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

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


        save(finalDoc);
        save2(extendedDocument);
    }

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

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

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

    private static class ExternalCMSPAdESService extends PAdESService {

        private static final long serialVersionUID = -2003453716888412577L;

        private byte[] cmsSignedData;

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

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

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

    }
}

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

}
}
Mehdi
  • 99
  • 2
  • 10
  • I still have the same problem ! does anyone have a solution for this ?? – Mehdi Jan 28 '22 at 15:50
  • Neither of the PDFs you attached in [PDFs.zip](https://www.grosfichiers.com/i4fmqCz43is) validates positively in Adobe Reader with a default configuration; for both one gets the *"Signer's identity is unknown because it has not been included in your list of trusted certificates and none of its parent certificates are trusted certificates."* If you get something else, you have a non-default validation configuration of Adobe Reader. To discuss validation results, you have to disclose such non-standard settings. TARGER_SIGNATUREPDF.pdf is even worse, no certificate chain can be built. – mkl Feb 02 '22 at 10:18
  • For CURRENT_SIGNATURE.pdf, if I configure Adobe Reader to trust the root certificate "Sunnystamp Root CA G2" of the chain to the signer certificate, the signature becomes "LTV enabled" and the Revocation tabs for the signer and CA certificates say "The selected certificate is considered valid because it does not appear in the Certificate Revocation List (CRL) that is embedded in the document." For TARGER_SIGNATUREPDF.pdf the situation is more difficult as the AIA extension of the signer certificate only has an _OCSP_ method but no _Certification Authority Issuer_ method. Thus, no chain. – mkl Feb 02 '22 at 10:33
  • Another issue in the TARGER_SIGNATUREPDF.pdf signature: It claims to be a PAdES signature (using **ETSI.CAdES.detached** as **SubFilter**) but it embeds validation information in the Adobe _adbe-revocationInfoArchival_ signed attribute. That attribute is only specified for **adbe.pkcs7.detached** and **adbe.pkcs7.sha1** signatures. Adobe Reader uses revocation information from wherever it gets them but a strict validator may reject them. – mkl Feb 02 '22 at 11:06
  • The file TARGER_SIGNATUREPDF.pdf mentions that it has been created using "iText-7.1.16-SNAPSHOT for .NET". Indeed, the iText API can be used to put an _adbe-revocationInfoArchival_ signed attribute into PAdES signatures. Nonetheless, this combination is questionable; when interpreting specifications strictly, it's forbidden. All told I really wonder why *the "TARGER_SIGNATUREPDF.pdf" is the target you want to have*... – mkl Feb 02 '22 at 11:10
  • and if i also want to embed the revocation validation in the signature like the TARGET_SIGNATURE.pdf how i can do ? – Mehdi Feb 02 '22 at 11:13
  • *"if i also want to embed the revocation validation in the signature like the TARGET_SIGNATURE.pdf how i can do?"* - eSig DSS aims for creating valid [CPX]AdES signatures. Thus, by default it does not support embedding that attribute. You can of course try to patch it. But it most likely is easier to not use DSS at all on Server B and build the signature container using the iText PdfPKCS7 class or plain BouncyCastle. Unfortunately embedding revocation information in the signature container will also invalidate DSS' expectation of the size thereof, so you'll also have to adjust code on server A – mkl Feb 02 '22 at 11:23
  • if I understand correctly the **_CURRENT_SIGNATURE.pdf_** file is better than the **_TARGET_SIGNATURE.pdf_** ? However, can we avoid the revision that is added to the **_CURRENT_SIGNATURE.pdf_** because the signature will be already timestamped? If yes how can i do that ? – Mehdi Feb 02 '22 at 12:21
  • *"if I understand correctly the CURRENT_SIGNATURE.pdf file is better than the TARGET_SIGNATURE.pdf ?"* - Yes. At least in respect to where the revocation information are put. I haven't checked other stuff yet. *However, can we avoid the revision that is added to the CURRENT_SIGNATURE.pdf because the signature will be already timestamped?* - No. At least not if you are trying to create PAdES BASELINE. There you first have a T type (timestamp either in signature container or as document timestamp) to which you add revocation information in an incremental update which forms a new revision. – mkl Feb 02 '22 at 14:15

1 Answers1

1

In comments to an answer to another question a discussion started in which you pointed to this question and asked for help. In that discussion it became apparent that you do not exactly know yet what you are trying to achieve. Thus, let's clarify that a bit.

LTV Verification

You say you want to add LTV verification to your signatures. Let's first take a look at what that means.

LTV is short for Long Term Validation. The goal it stands for is ensuring that a signature can still be verified in a number of years.

The challenges this goal tries to overcome are that information a verifier needs will not be available online in the long run and that algorithms used will eventually not be considered secure anymore.

The means are to retrieve the required information once and bundle them with the signature in a trustable way, and to apply digital time stamps to attest that a certain set of data, signature, and extra information existed and was bundled at a given time (e.g. when the used signing algorithm still was considered strong).

So far, so good.

Adobe early on (before PDF had become an ISO standard) defined one mechanism to achieve LTV: They specified a specific signed attribute into which one should collect the data required for validation right before signing and they recommended applying a time stamp to the embedded signature container.

Since then, though, this mechanism has turned out to be too simple and static. Depending on the validation model in use information collected before signing are not good enough: To check whether a given certificate is valid at signing time, one strictly speaking needs information generated after signing time. And to deal with algorithms getting weak one may need to time stamp the whole document again and again.

To deal with this ETSI (an European norming organization) specified alternative ways to add validation related information to a document and a way to add extra time stamps covering the whole document (and not only the embedded signature container). These mechanisms do not change the original signature container but add the information in incremental updates to the original document. Meanwhile these mechanisms have been added to the international PDF standard in ISO 32000-2. They are subsumed under the term PAdES.

ETSI also defined standard schemes how to use these new mechanisms to enhance signatures in an interoperable way, the PAdES BASELINE profiles:

  • The B level only contains a basic signature container profiled to in particular include an ESS certificate ID attribute.
  • The T level is based on the B level but additionally requires an after-signing time stamp. This time stamp may be applied as signature time stamp to the original signature container or as document time stamp in an extra incremental update of the document.
  • The LT level is based on the T level and requires missing intermediate certificates and required revocation information to be added in a new incremental update.
  • The LTA level is based on the LT level and requires another document time stamp added in yet another incremental update.

To achieve long term validation the additions as for LT and LTA may be repeated to provide validation information for the previous time stamp and to document that the used algorithms were applied at a time when they were still strong.

Adobe has established their own "LTV-enabled" profile which assumes less strict requirements on the validation data (no time stamps needed) and does not care about algorithms becoming weak. They essentially collect all the validation related information they find in the document and use them as-is. (More exactly this is the behavior for the standard settings of Adobe Acrobat. You can configure Acrobat differently to change requirements, e.g. to make certain time stamps do matter. Thus, when talking about "LTV-enabled" signatures always make sure you have the same set of settings in mind as your discussion partner...)

Extending Using eSig DSS

If you want to extend a PDF signature using eSig DSS on server A, simply take the finalDoc and

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

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

where

  • certificateVerifier is a CommonCertificateVerifier initialized for online resources,
  • tspSource is a OnlineTSPSource initialized for your time stamp service of choice, and
  • extensionLevel is the desired level, e.g. SignatureLevel.PAdES_BASELINE_LT.

The result in extendedDocument should contain the required validation related information.

mkl
  • 90,588
  • 15
  • 125
  • 265
  • For now LT. But will this verification allow me to have the message that is in the question at the top (New UPDATE) on the PDF ?? – Mehdi Feb 01 '22 at 10:18
  • *"But will this verification allow me to have the message that is in the question at the top"* - My French is too rusty. Please set your Adobe Reader to English and show that message. – mkl Feb 01 '22 at 10:22
  • here is the translation of the displayed message : "The selected certificate is considered valid because it is not in the certificate revocation list embedded in the signature." – Mehdi Feb 01 '22 at 10:32
  • Whether Adobe Acrobat uses information from an embedded CRL or whether it, in spite of having an embedded CRL, retrieves an online resource, is up to its own code and may change from version to version. E.g. I've recently come across a case where in spite of embedded CRLs Adobe Reader tried to receive OCSP responses first, and because of limited connectivity from the intranet in question that led to waiting periods until the OCSP request timed out. Thus, embedding revocation information does not by itself prevent Adobe Acrobat from retrieving and using online information. – mkl Feb 01 '22 at 10:36
  • Ah yes, I understood. But except for the code mentioned above I haven't really found a concrete example of integration of the CRL check. Do you have any ideas on this? – Mehdi Feb 01 '22 at 10:40
  • In this context it would be of interest if your certificate includes the addresses where revocation information can be requested. – mkl Feb 01 '22 at 10:40
  • Yes of course it is. The certificate contains the CRL distribution point (the field is called like this) This is the URI = http://psce.baridesign.ma/ACDeleguees_Externes/crl/Classe3__AC-crl-2.crl This is the OSCP URL : Méthode = OCSP URI = http://psce.baridesign.ma/ACDeleguees_Externes/ocsp/Classe3__OCSP – Mehdi Feb 01 '22 at 10:43
  • Ok, because the URLs _are_ included, Adobe Acrobat _may_ look there, too. Thus, no one can guarantee that the sentence from the screen shot will be there for signatures with your certificate. That been said, if the signature is BASELINE-LT and LTV-enabled, chances are good that you get to see it. But promising that, in particular for future versions of Acrobat... no, no one in their right mind will do. – mkl Feb 01 '22 at 11:02
  • now the problem is that i can't find a way to ensure the lt or ltv signature. as you can see my code only allows signing in B. that's what I'm looking for – Mehdi Feb 01 '22 at 11:11
  • do you have any suggestions to evolve my code and take into consideration LT LTA ? – Mehdi Feb 01 '22 at 14:13
  • Thanks for your help, just how can i initiate tspSource while I’m using timestamp in server B ? – Mehdi Feb 01 '22 at 17:50
  • Well, `OnlineTSPSource` has a constructor that accepts an URL string. This of course assumes that the time stamp server on server B can be accessed by server A via http/https and is rfc 3161 conform. – mkl Feb 01 '22 at 17:59
  • My timestamp is accessible on https but whit authentification certificate and password. Is OnlineTSPSource permit that ? – Mehdi Feb 01 '22 at 18:10
  • *"My timestamp is accessible on https but whit authentification certificate and password. Is OnlineTSPSource permit that ?"* - I've no idea. You can instantiate `OnlineTSPSource` with a custom `DataLoader`. Maybe that could support such an authentication mode. – mkl Feb 01 '22 at 18:41
  • I have just updated my code on the server A to add the recommended elements but when I proceed to the signature on the document section revocation signed PDF I always have the following message: _"The selected certificate is not linked to a certificate designated as an approval anchor (for details, see the Approval panel). Therefore, this certificate has not undergone any revocation checking."_ – Mehdi Feb 01 '22 at 18:56
  • Please share the PDF in question for analysis. – mkl Feb 01 '22 at 21:01
  • I just updated the question by adding the target PDFs and the current one – Mehdi Feb 01 '22 at 23:06
  • Do you have any idea on how I can check whether a revocation has been made or not. Knowing that the verification program is based on iText ? – Mehdi Feb 10 '22 at 13:06
  • *"Do you have any idea on how I can check whether a revocation has been made or not."* - You mean you want to check whether the signer certificate has been revoked or not? Well, you retrieve revocation information (a CRL or an OCSP response) and check whether it indicates that the certificate in question is revoked. *"Knowing that the verification program is based on iText ?"* - Which verification program? You mean you want to base your revocation check on iText? – mkl Feb 11 '22 at 10:00
  • No, the objective is just to verify the validity of the signature afterwards. I found these functions: [https://github.com/mkl-public/testarea-itext5/blob/master/src/test/java/mkl/testarea/itext5/signature/VerifySignature.java] but they do not allow to check if a revocation exists or not. Since the revocation in my case is in the document not in the signature – Mehdi Feb 11 '22 at 10:18
  • iText 7 includes a `LtvVerifier`, you can try it but do `setOnlineCheckingAllowed(false)` as you want to try validation without online requests. I myself haven't used it, as far as validation is concerned I rely on eSig DSS and the inhouse validation code of my employer because we had and still have to follow quite specific validation related laws and regulations going beyond iText's generic classes. – mkl Feb 11 '22 at 11:32
  • exactly, the idea for me is to adapt my verifier so that it can work with the iText and Dss verifiers. my problem now is that my partner tells me that the revocation check has not been done since they check in the signature using pkcs7.getCrls() and not in the dictionary. do you have please any idea how to solve this problem ? – Mehdi Feb 11 '22 at 11:52
  • now my goal is to help him adapt his verifier so that he can make sure that the revocation check has been done – Mehdi Feb 11 '22 at 11:54
  • As mentioned, do try the `LtvVerifier`. If it doesn't work as desired, you can create a new question here focusing on that issue, including current examples. It is a topic of its own. – mkl Feb 11 '22 at 12:02
  • thank you very much for your usual help! I hope you can help me on this point also here is the link of the question: https://stackoverflow.com/questions/71080881/itext-verify-signature-with-checking-crl – Mehdi Feb 11 '22 at 13:42
  • @Mehdi *" I hope you can help me on this point also here is the link of the question:"* - I just wrote an answer to that question. – mkl Feb 14 '22 at 11:28