Search code examples
c#pdfdigital-signaturepades

Which information is required for a valid LTV signature and how to embed it


Goal:

Embed needed LTV (Long-Term Validation) information in signatures using SignedCms and CmsSigner.

Current Problem:

I've already implemented a logic to insert the following OIDs into the UnsigedAttributes:

  • 1.2.840.113549.1.9.16.2.24 - OCSP Response
  • 1.2.840.113549.1.9.16.2.21 - Certificate References
  • 1.2.840.113549.1.9.16.2.22 - Revocation References

What confused me here is the insertion of Certificate References into the UnsignedAttributes due to CmsSigners IncludeOption set to X509IncludeOption.WholeChain, which references the whole certificate Chain into the ASN1 Structure (As far as i know and have seen)

I'm pretty much helpless right now and don't know how to continue and which OIDs i really need to implement and where to do so. It seems to be that every source of information states some different requirements and Information which needs to be implemented leading to a difficult mess of information.

Implementation:

Right now i'm creating the basic signature for my pdf-document using CmsSigner and SignedCms. This creates the basic Signature without Timestamp, LTV etc.

After creating the Signature, i'm embedding a TimeStamp from a TSA into the UnsignedAttributes of my Asn1 Structure using AsnWriter and AsnReader as explained here: CMS How to add something to UnsignedAttribute Post-Signing in .NET Framework 4.8

As far as this goes, everything works just fine.

For LongTermValidation i've created a new Class due to the high load of Code behind it.

The purpose of the Class is to put all the needed Information into my Asn1 Structure and return it for embedding the Signature into the pdf structure.

    private byte[] GetOcspData(X509Certificate2 certificate)
    {
        var cert = DotNetUtilities.FromX509Certificate(certificate);
        var issuerCert = DotNetUtilities.FromX509Certificate(GetIssuerCertificate(certificate));

        var ocspReqGen = new OcspReqGenerator();
        var certId = new CertificateID(CertificateID.HashSha1, issuerCert, cert.SerialNumber);
        ocspReqGen.AddRequest(certId);

        var ocspReq = ocspReqGen.Generate();
        var ocspReqData = ocspReq.GetEncoded();

        // OCSP-Responder-URL abrufen (Sie müssen sicherstellen, dass diese URL verfügbar ist)
        var ocspUrl = GetOcspResponderUrl(certificate);

        using var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/ocsp-response"));

        var response = httpClient.PostAsync(ocspUrl, new ByteArrayContent(ocspReqData)).Result;
        _markdownLogger.InsertInformation("CRL Data", Convert.ToBase64String(response.Content.ReadAsByteArrayAsync().Result));
        return response.Content.ReadAsByteArrayAsync().Result;
    }

The Codepiece above retrieves the OCSP Response of the issuer Certificate (I've read somewhere that this is needed for the issuer, not the signing certificate itself which makes no sense in my opinion)

    private byte[] GetCertificateRefs(X509Certificate2 signingCertificate)
    {
        var chain = new X509Chain();
        chain.Build(signingCertificate);

        using var sha256 = SHA256.Create();

        var certificateRefs = new List<byte[]>();
        foreach (var chainElement in chain.ChainElements)
        {
            var certHash = sha256.ComputeHash(chainElement.Certificate.RawData);
            certificateRefs.Add(certHash);
        }

        // serialization of Certificate Chain for `id-aa-ets-certificateRefs`
        return SerializeRefs(certificateRefs);
    }

The above piece of code first gathers all the certificates inside the CertificateChain, serializes it and returns it for further usage.

    private byte[] GetRevocationReferences(X509Certificate2 signingCertificate)
    {
        var chain = new X509Chain();
        chain.Build(signingCertificate);

        var revocationRefs = new List<byte[]>();
        foreach (var chainElement in chain.ChainElements)
        {
            var revocationData = GetRevocationData(chainElement.Certificate);
            if (revocationData.Length > 0)
            {
                revocationRefs.Add(revocationData);
            }
        }

        return SerializeRefs(revocationRefs);
    }

The above piece of code gathers the revocation Data from every Certificate in the Certificate Chain (either OCSP or CRL) and returns its serialized List of revocation Data.

I didn't put every piece of Code here on purpose because i don't know what is needed exactly and i don't want this to get too long. If something more is needed, i'd be happy to submit it later

Conclusion

I'm pretty unsure what exactly is needed for LTV and if i inserted it in the correct Place inside the Asn1 Structure. The implementation of Information Gathering like Ocsp for example is tested and i've got my responses.

I'd be happy to either get additional information which helps me to understand the requirements for LTV and needed Informations to implement or get some Code which does it for me.

New Insights

I am implementing the CAdES standard and aim to achieve the "LTV enabled" (Long-Term Validation) indicator in Adobe Acrobat and other PDF validation tools.

After studying RFC 5126, which defines the CAdES standard, I have learned about the various signature profiles.

Initially, my digital signature was at the CAdES-T (Timestamped) level. However, I have since realized that achieving long-term validity requires moving to a much higher level—specifically, CAdES-A (Archival level). This is necessary to ensure the digital signature remains valid over an extended period, even as cryptographic algorithms evolve or certificates expire.

What I've implemented now is:

  • 1.2.840.113549.1.9.16.2.21 - id-aa-ets-certificateRefs

  • 1.2.840.113549.1.9.16.2.22 - id-aa-ets-revocationRefs
    To reach CAdES-C

  • 1.2.840.113549.1.9.16.2.25 - id-aa-ets-escTimeStamp
    Second Timestamp to reach CAdES-X

  • 1.2.840.113549.1.9.16.2.23 - id-aa-ets-certValues

  • 1.2.840.113549.1.9.16.2.24 - id-aa-ets-revocationValues
    To reach CAdES-X Long Type 1

  • 1.2.840.113549.1.9.16.2.48 - id-aa-ets-archiveTimeStampV2
    Archival TimeStamp to reach CAdES-A.

According to RFC 5126, the CAdES-A level is specifically designed for long-term valid signatures and extended archival periods (typically exceeding 10 years, provided the archival timestamp is periodically renewed). Based on this, I have concluded that CAdES-A is the appropriate signature level to implement in order to achieve the desired goal of an LTV-enabled signature.

This is the new Information i gathered / clarification to your questions.

Any insights into potential issues of my implementation or missed steps would also be a lot of help


Solution

  • In an edit to your question you clarify that you

    aim to achieve the "LTV enabled" (Long-Term Validation) indicator in Adobe Acrobat and other PDF validation tools.

    So the focus is on PDF signatures and you want PDF validators, in particular Adobe Acrobat, to report your signature as "LTV enabled".

    I'll try to clarify how all the terms you used when you described your approach relate to that, and finally summarize where you should put validation related information.

    "LTV enabled"

    The term "LTV enabled" in Acrobat has been introduced by Adobe because "Our customers asked that we clearly identify a PDF that contained LTV (vs. one that did not). That was that term that we determined was simple and clear in conveying that message" (Leonard Rosenthol, Senior Principal Architect for PDF at Adobe).

    Unfortunately, the 'definition' of that term is a mere "the PDF is signed correctly and contains all necessary certificates, a valid CRL or OSCP response for every certificate [except for the root certificate]" (also Leonard Rosenthol). As the exact signature validation policy used by Adobe Acrobat is not published, this means that it is not exactly clear to third party developers what embedded data are needed.

    Nonetheless, usually one can achieve a "LTV enabled" status by adding all certificates involved and sufficient revocation information for the certificates of all signatures involved (remember, revocation information and certificates are signed, too!). Digital timestamps appear not to be necessary for Adobe's validation policy (unless configured otherwise in the preferences).

    An important detail here, though, is that the "LTV enabled" is not bound to CAdES; the validation related information is not expected in CAdES specific attributes!

    Where PDF signature validation related information are put

    The first place to check is the PDF specification, ISO 32000, part 1 and 2.

    They describe where validation related information can be put in a PDF.

    Cert entry in signature dictionary

    In case (and only in case) of plain PKCS#1 signatures (SubFilter adbe.x509.rsa_sha1) certificates can be put in Cert entry of the signature dictionary.

    This type of signatures in PDF is less often used and even deprecated in PDF 2.0.

    SignedData certificates collection

    In case of signatures based on PKCS#7/CMS signature containers, certificates can be put into the signature container certificate collection.

    Adobe's Revocation Information attribute

    This option has been used since the early beginnings of PDF signing.

    Adobe has defined a signed (!) attribute to hold revocation information for the signature, see section 12.8.3.3.2 of ISO 32000 part 1 or 2. In particular the OID shall be

    adbe-revocationInfoArchival OBJECT IDENTIFIER::=
                {adbe(1.2.840.113583) acrobat(1) security(1) 8}
    

    and the attribute value structure is RevocationInfoArchival with

    RevocationInfoArchival::= SEQUENCE {
        crl [0] EXPLICIT SEQUENCE of CRLs, OPTIONAL
        ocsp [1] EXPLICIT SEQUENCE of OCSPResponse, OPTIONAL
        otherRevInfo [2] EXPLICIT SEQUENCE of OtherRevInfo, OPTIONAL
    }
    OtherRevInfo::= SEQUENCE {
        Type OBJECT IDENTIFIER
        Value OCTET STRING
    }
    

    Document Security Store (DSS)

    Originally specified for PAdES signatures, ISO 32000 part 2 adopts the definition of the DSS dictionaries for the long term validation of signatures in general. According to section 12.8.4.3 of ISO 32000-2,

    This dictionary may contain:

    • an array of all certificates used for the signatures, including timestamp signatures, that occur in the document. It shall also hold all the auxiliary certificates required to validate the certificates participating in certificate chain validations.
    • an array of all Certificate Revocation Lists (CRL) (see Internet RFC 5280) used for some of the signatures, and
    • an array of all Certificate Status Protocol (OCSP) responses (see Internet RFC 6960) used for some of the signatures.
    • a VRI key whose value shall be a dictionary containing one VRI dictionary (validation-related information) for each signature represented in CMS format.

    Concerning your case, therefore

    Your current approach is to try and use the options defined for CAdES (ETSI EN 319 122 part 1 and 2) to embed all kinds of validation related information into the CMS container you eventually want to embed into a PDF to create an embedded PDF signature with "LTV enabled".

    But this is not how adding validation related information to PDF signatures is specified.

    What you should do, therefore, is put all the relevant certificates into the CMS signature container certificate collection and all the revocation information either into the Adobe Revocation Information signed attribute or the DSS document security store PDF dictionary.

    If you create adbe.pkcs7.detached (or adbe.pkcs7.sha1) signatures, the signed attribute is the preferred option, but adding further stuff in the DSS is ok.

    If you create ETSI.CAdES.detached signatures or ETSI.RFC3161 document timestamps, you should definitively use the DSS dictionary.

    Current Adobe Acrobat versions don't care which option you chose and will use information both from the signed attribute or the DSS dictionary. They won't look for revocation information in the attributes defined for CAdES, though.

    Other validators may be more strict and ignore the signed attribute for PAdES signatures. Or they are based on the old specification ISO 32000-1 alone and don't know the DSS yet.