1

I am trying to have my PDF’s certification LTV enabled along with the document permission of PDF::DigitalSignatureField::e_no_changes_allowed since this two requirements are a MUST HAVE on the app we are building. As far as I have learned, there is no way to do this IF the necessary data for LTV enabling (OCSPs, CRLs, Certs) is not included in the signed attributes of the certification BEFORE signing/certifying the PDF. My guess is that, it should be around this classic line of the certification examples from Apryse/PDFTron:

// Optionally, you can add a custom signed attribute at this point, such as one of the PAdES ESS attributes.
vector<UChar> pades_versioned_stuff(digsig_field.GenerateESSSigningCertPAdESAttribute(signer_cert, Crypto::DigestAlgorithm::e_SHA256));

So I am thinking that there should be a way of doing this by adding the BER encoded CRLs, OCSPs and Certs to the pades_versioned_stuff vector and then:

vector<UChar> signedAttrs(DigitalSignatureField::GenerateCMSSignedAttributes(pdf_digest, pades_versioned_stuff));

As briefly suggested on the documentation (DigitalSignatureField::GenerateCMSSignedAttributes)

All this sounds good in theory but doing so in C++ it’s quite the challenge and I have very little idea how to begin with this task.

Questions arised:

  1. How do I BER encode anything really? does it have to be in an ASN.1 format before encoding?
  2. What exactly needs BER encoding, other resources say OCSP and CRL response, but what exactly is that?
  3. How do I add the BER encoded data gather somewhere else to the vector pades_versioned_stuff variable?

Is there anyway I can achieve this? Is there a better way to go about this? any pointers or suggestions are welcomed. Just take into account:

  1. LTV must be enabled (regardless of the controversy and actual utility of it)
  2. The PDF document must have it's permission set on NO ALLOWED CHANGES
  3. Solution must be in C++

Thank you

Santiago G.

I have tried the "normal" way of enabling LTV according to PDFTron examples and documentation. It works as long as the permissions are set to "e_formfilling_signing_allowed", which we can't have.

EDIT: I am going to add my full code (well almost). And some images of the results I am getting.

void pdfCertification (
        const UString& in_docpath,
        std::string in_scriptpath) {

    PDFDoc doc(in_docpath);
    doc.FlattenAnnotations();
    Page lastPage = doc.GetPage(doc.GetPageCount());

    // Create a digital signature field and associated widget.
    PDF::DigitalSignatureField digsig_field = doc.CreateDigitalSignatureField("SIGNATUREFIELD_NAME");
    Annots::SignatureWidget widgetAnnot = Annots::SignatureWidget::Create(doc, Rect(70.0, 680.0, 290.0, 740.0), digsig_field);
    lastPage.AnnotPushBack(widgetAnnot);

    // Add the logo as the front of the signature widget annotation
    PDF::Image img = PDF::Image::Create(doc, "SIGNATUREFIELD_IMAGE");
    widgetAnnot.CreateSignatureAppearance(img);
    //digsig_field.SetDocumentPermissions(PDF::DigitalSignatureField::e_formfilling_signing_allowed);
    digsig_field.SetDocumentPermissions(PDF::DigitalSignatureField::e_no_changes_allowed);

    // Create a digital signature dictionary inside the digital signature field, in preparation for signing.
    digsig_field.CreateSigDictForCustomCertification("Adobe.PPKLite", PDF::DigitalSignatureField::SubFilterType::e_adbe_pkcs7_detached, 23000);
    // For security reasons, set the contents size to a value greater than but as close as possible to the size you expect your final signature to be, in bytes.

    // (OPTIONAL) Add more information to the signature dictionary.
    digsig_field.SetLocation("COMPANY_LOCATION");
    digsig_field.SetReason("REASON");

    // (OPTIONAL) Set the signing time in the signature dictionary, if no secure embedded timestamping support is available from your signing provider.
    Date current_date;
    current_date.SetCurrentTime();
    digsig_field.SetSigDictTimeOfSigning(current_date);

    // Save the document incrementally to avoid invalidating any previous signatures.
    doc.Save(in_docpath, SDFDoc::e_incremental);

    // Digest the relevant bytes of the document in accordance with ByteRanges surrounding the signature.
    vector<UChar> pdf_digest(digsig_field.CalculateDigest(Crypto::DigestAlgorithm::e_SHA256));

    Crypto::X509Certificate signer_cert("CERT");
    Crypto::X509Certificate root_cert("ROOT");
    Crypto::X509Certificate inter_cert("INTER");

    // Optionally, you can add a custom signed attribute at this point, such as one of the PAdES ESS attributes.
    // The function we provide takes care of generating the correct PAdES ESS attribute depending on your digest algorithm.
    vector<UChar> pades_versioned_ess_signing_cert_attribute(digsig_field.GenerateESSSigningCertPAdESAttribute(signer_cert, Crypto::DigestAlgorithm::e_SHA256));
    // Generate the signedAttrs component of CMS, passing any optional custom signedAttrs (e.g. PAdES ESS).
    // The signedAttrs are certain attributes that become protected by their inclusion in the signature.

    vector<UChar> signedAttrs(DigitalSignatureField::GenerateCMSSignedAttributes(pdf_digest, pades_versioned_ess_signing_cert_attribute));

    // Calculate the digest of the signedAttrs (i.e. not the PDF digest, this time).
    vector<UChar> signedAttrs_digest(Crypto::DigestAlgorithm::CalculateDigest(Crypto::DigestAlgorithm::e_SHA256, signedAttrs));

    vector<UChar> signature_value = getDataFromSM();

    // Then, load all your chain certificates into a container of X509Certificate.
    vector<Crypto::X509Certificate> chain_certs = {root_cert, inter_cert};

    // Then, create ObjectIdentifiers for the algorithms you have used.
    Crypto::ObjectIdentifier digest_algorithm_oid(Crypto::ObjectIdentifier::e_SHA256);
    Crypto::ObjectIdentifier signature_algorithm_oid(Crypto::ObjectIdentifier::e_RSA_encryption_PKCS1);

    // Then, put the CMS signature components together.
    vector<UChar> cms_signature(DigitalSignatureField::GenerateCMSSignature(signer_cert, chain_certs, digest_algorithm_oid, signature_algorithm_oid, signature_value, signedAttrs));

    // Write the signature to the document.
    doc.SaveCustomSignature(cms_signature.data(), cms_signature.size(), digsig_field, in_docpath);

    // Stamping & LTV enabling the PDF
    // Generally speaking, unsigned attributes (like timestamping) can and should be inserted after signing.
    // Be aware that function CreateSigDictForCustomCertification explicity defines the amount of data in bytes that ccan be added as unsigned attributes
    // If not enough space is available there will be an error on the command
    PDF::TimestampingConfiguration timeStampConfiguration("TIMESTAMP_SERVER");
    PDF::VerificationOptions verificationOpts(VerificationOptions::e_compatibility_and_archiving);
    verificationOpts.AddTrustedCertificate("PDFCERT_ROOT", VerificationOptions::e_default_trust | VerificationOptions::e_certification_trust);
    verificationOpts.EnableOnlineCRLRevocationChecking(true);
    verificationOpts.EnableOnlineOCSPRevocationChecking(true);
    verificationOpts.EnableTrustVerification(true);
    verificationOpts.EnableOnlineRevocationChecking(true);

    // enable LTV
    PDF::VerificationResult verificationResultsLTV = digsig_field.Verify(verificationOpts);
    if (!digsig_field.EnableLTVOfflineVerification(verificationResultsLTV)) {
        cout << "The freacking LTV thing did not worked (frustation intended)" << endl;
    }else{
        //if I save, signature will be invalid,LTV enabled
        //if I don't save, LTV will not be enabled, signature will be valid
        //doc.Save(in_docpath, SDFDoc::e_incremental);
    }

    //with LTV enabled, generate the timestamp
    PDF::TimestampingResult resultTimeStamp(digsig_field.GenerateContentsWithEmbeddedTimestamp(timeStampConfiguration, verificationOpts));
    if (resultTimeStamp.GetStatus()) {
        std::vector<UChar> cms_with_timestamp(resultTimeStamp.GetData());
        doc.SaveCustomSignature(cms_with_timestamp.data(), cms_with_timestamp.size(), digsig_field, in_docpath);
        cout << "Timestamping Locked and loaded" << endl;
    } else {
        cout << "Embedded timestamping failed: " << endl;
        puts(resultTimeStamp.GetString().ConvertToUtf8().c_str());
        if (resultTimeStamp.HasResponseVerificationResult()) {
            PDF::EmbeddedTimestampVerificationResult tstResult(resultTimeStamp.GetResponseVerificationResult());
            printf("CMS digest status: %s\n", tstResult.GetCMSDigestStatusAsString().ConvertToUtf8().c_str());
            printf("Message digest status: %s\n", tstResult.GetMessageImprintDigestStatusAsString().ConvertToUtf8().c_str());
            printf("Trust status: %s\n", tstResult.GetTrustStatusAsString().ConvertToUtf8().c_str());
        }
    }
    // end of LTV enabling & stamping PDF
}

Here are images of both results options:

LTV enabled, but invalid signature LTV enabled, but invalid signature

LTV NOT enabled, but valid signature LTV NOT enabled, but valid signature

anakin
  • 25
  • 3
  • 12

3 Answers3

1

Please see this forum post, which shows how to add digital signature and LTV sign it. https://community.apryse.com/t/how-do-i-create-and-sign-a-digital-signature-with-ltv/7315

The only change you need to make (beyond porting from C# to C++) is to add the following line of code.

digsig_field.SetDocumentPermissions(DigitalSignatureField.DocumentPermissions.e_no_changes_allowed);

For instance, something like.

DigitalSignatureField digsig_field = doc.CreateDigitalSignatureField(in_approval_field_name);
digsig_field.SetDocumentPermissions(DigitalSignatureField.DocumentPermissions.e_no_changes_allowed);

You of course need to update the code with your own private key, but below is what I see in Adobe (after adding the sample key to the Adobe CA).

enter image description here

Ryan
  • 2,473
  • 1
  • 11
  • 14
  • 1
    *"Please see this forum post"* - you appear to have forgotten to add the link. – mkl Mar 17 '23 at 06:35
  • I added the full code I am using (I believe I am already doing what is suggested here) and also added some images of the results – anakin Mar 17 '23 at 16:29
  • Also can you show me the version of that PDF on the image? Can it be a PDF version related issue? – anakin Mar 17 '23 at 17:06
  • @anakin I added the link to my answer. That forum post has C# code in an attachment for you to try out. – Ryan Mar 17 '23 at 19:16
  • Thank you for your response @Ryan, but I already had found that post (I was crossing my fingers for it to be a newer/different one) and if you look at my full code posted upon edition it's pretty much the exact same thing. Could you please take a look at my code, maybe I am missing something. Or if you have another idea I could follow, would be great. Thanks – anakin Mar 17 '23 at 19:21
1

I have been able to achieve what I set out to do. By that, I mean adding the DSS dictionary and the OCSP responses array to the PDF structure tree, using low level functions of the PDFtron library on C++.

I created this function:

PDFDoc enableLTVbyMe(PDFDoc doc, string oscpRevocationCertFilePath, string oscpRevocationInterFilePath) {
    SDF::Obj root = doc.GetRoot();
    SDF::Obj dssDictionary = doc.CreateIndirectDict();
    SDF::Obj oscpArray = doc.CreateIndirectArray();

    Filters::MappedFile oscpCertFile(oscpRevocationCertFilePath);
    Filters::FilterReader oscpCertFileReader(oscpCertFile);
    SDF::Obj oscpCertStream = doc.CreateIndirectStream(oscpCertFileReader);

    Filters::MappedFile oscpInterFile(oscpRevocationInterFilePath);
    Filters::FilterReader oscpInterFileReader(oscpInterFile);
    SDF::Obj oscpInterStream = doc.CreateIndirectStream(oscpInterFileReader);

    oscpArray.Insert(0, oscpCertStream);
    oscpArray.Insert(0, oscpInterStream);
    dssDictionary.Put("OCSPs", oscpArray);
    root.Put("DSS", dssDictionary);

    return doc;
}

And that's it. It seemed so impossible just a weeks ago. By the way this function assumes that you already got OCSP responses with OpenSSL and saved them in DER encoded files.

Only with OCSPs I already got the LTV Enabled on Adobe Reader, but I might add CRLs and VRI down the line to make sure LTV is enabled to a broader audience.

Santiago G.

anakin
  • 25
  • 3
  • 12
  • *"I might add CRLs and VRI down the line to make sure LTV is enabled to a broader audience"* - There is no need to add **VRI**. In the early days of the "LTV enabled" profile VRI dictionaries were required by Adobe but they aren't anymore. – mkl Mar 28 '23 at 07:02
  • thanks @mkl that makes things easier – anakin Mar 31 '23 at 00:31
0

I used in C++ the openssl library to get the stack of OCSP responses for the certificate chain. I cannot paste the code calling openssl, because it is closed source. But as a hint i can paste openssl functions, which were used. When you google with those you can find all you need to put it together.

  • X509_get1_ocsp
  • OCSP_parse_url
  • OCSP_response_get1_basic
  • sk_OCSP_CERTID_value
  • OCSP_response_status
  • OCSP_sendreq_bio

For PDF processing and populating DSS and OCDP folder i used the CIB pdf toolbox pdf structures.

PatrickF
  • 594
  • 2
  • 11