Search code examples
c++pdfcertificatepdftronber

How to add OCSPs, CRLs and Certs the Document Security Store (DSS) of a PDF BEFORE Signing in C++?


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


Solution

  • 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.