Skip to content

ETSI BASELINE T LT LTA

Aleksandar Gyonov edited this page Feb 15, 2024 · 5 revisions

Preface

EU has standardized the Advanced Digital signatures in ETSI TS 119 182-1.

In this standardization there are four levels of signatures.

  • Baseline-B level provides requirements for the incorporation of signed header parameters and some unsigned components within the etsiU unsigned header parameter when the signature is generated.
  • Baseline-T level provides requirements for the generation and inclusion, for an existing signature, of a trusted token proving that the signature itself actually existed at a certain date and time.
  • Baseline-LT level provides requirements for the incorporation of all the material required for validating the signature in the signature document. This level aims to tackle the long-term availability of the validation material.
  • Baseline-LTA level provides requirements for the incorporation of electronic time-stamps that allow validation of the signature long time after its generation. This level aims to tackle the long-term availability and integrity of the validation material.

With this library you can create Baseline-B, Baseline-T, Baseline-LT and Baseline-LTA level signatures.

Tested & verified with DSS Demonstration WebApp.

How to use

XML Signature

In the previous article ETSI XML Sigantures and detached content, you can see how to create an XML signature of a level Baseline-B. You must start from there, because the general idea is to have a baseline signature and then to enhance it with a:

  • timestamp (for Baseline-T)
  • additional certificates and OCSP responses (for Baseline-LT)
  • archive timestamp (for Baseline-LTA)

For this purpose there are three additional methods in the ETSISignedXml class:

/// <summary>
/// Add timestamping. Mainly to produce the XADES BASELINE-T signature
/// </summary>
/// <param name="funcAsync">Async function that calls Timestamping server, with input data and returns 
/// response from the server
/// </param>
/// <param name="signedDoc">The signed document</param>
public async Task AddTimestampAsync(Func<byte[], CancellationToken, Task<byte[]>> funcAsync, XmlDocument signedDoc, CancellationToken ct = default);

/// <summary>
/// Add some additional data objects for validation. Mainly to produce the XADES BASELINE-LT signature
/// </summary>
/// <param name="additionalCerts">Additional certificates, not included up until now</param>
/// <param name="ocspVals">Revocation status values, for all certificates (signer and chain, timestamp and chain). Raw RFC 6960 responses</param>
/// <param name="signedDoc">The signed document</param>
public void AddValidatingMaterial(XmlDocument signedDoc, X509Certificate2[] additionalCerts, List<byte[]>? ocspVals = null);

/// <summary>
/// Add archive timestamping. Mainly to produce XADES BASELINE-LTA signature
/// </summary>
/// <param name="funcAsync">Async function that calls Timestamping server, with input data and returns 
/// response from the server</param>
/// <param name="signedDoc">The signed document</param>
/// <param name="attachement">In case of detached signature, with no payload option, provide the attachment, to be used as payload</param>
/// <remarks>NB. This implementation only supports 1 (one) Transformation per Reference XML Element. For more complex scenarious,
///  with more tham one Transfromations per Reference, you shall extend the ETSISignedXml class and override the current method.</remarks>
public virtual async Task AddArchiveTimestampAsync(Func<byte[], CancellationToken, Task<byte[]>> funcAsync, XmlDocument signedDoc, byte[]? attachement = null, CancellationToken ct = default);

As you can see, the AddTimestampAsync method is used to add a timestamp to the signature (thus producing Baseline-T signature).

The AddValidatingMaterial method is used to add additional certificates and OCSP responses to the signature (thus producing Baseline-LT signature).

The AddArchiveTimestampAsync method is used to add archive timestamp to the signature (thus producing Baseline-LTA signature).

In the test project you can find examples how to use the methods. The examples are in the CryptoEx.Tests.TestETSIXml file.

For example here is how to sign and verify an enveloped XML document to Baseline-LT level:

public async Task Test_XML_RSA_Enveloped_Timestamped_LT()
{
    // Try get certificate
    X509Certificate2? cert = GetCertificateOnWindows(CertType.RSA, out X509Certificate2[] issuers);
    if (cert == null) {
        Assert.Fail("NO RSA certificate available");
    }

    // Get CA certificates for the timestamping server
    X509Certificate2[] timeStampCerts = GetCertificatesTimeStamp();

    // Get RSA private key
    RSA? rsaKey = cert.GetRSAPrivateKey();
    if (rsaKey != null) {
        // Get payload 
        var doc = new XmlDocument();
        doc.LoadXml(message.Trim());

        // Create signer 
        ETSISignedXml signer = new ETSISignedXml(rsaKey, HashAlgorithmName.SHA512);

        // Sign payload
        XmlElement signature = signer.Sign(doc, cert);

        // Prepare enveloped data
        doc.DocumentElement!.AppendChild(doc.ImportNode(signature, true));

        // Add timestamp
        await signer.AddTimestampAsync(CreateRfc3161RequestAsync, doc);

        // UP TO HERE WE HAVE BASELINE T !!!

        // Get OCSPs for the signer
        List<byte[]> ocsps = GetOCSPs(cert, issuers);

        // Get OCSPs for the timestamp
        List<byte[]> ts_ocsp = [];
        if (_TS_x509Certificate2s != null) {
            var ls_ts = _TS_x509Certificate2s.ToList();
            ls_ts.AddRange(timeStampCerts);
            X509Certificate2[] tsCerts = ls_ts.ToArray();
            if (tsCerts.Length > 1) {
                ts_ocsp = GetOCSPs(tsCerts[0], tsCerts[1..]);
            } else if (tsCerts.Length > 0) {
                ts_ocsp = GetOCSPs(tsCerts[0], Array.Empty<X509Certificate2>());
            }
        }

        // add all certs in one place
        X509Certificate2[] all = new X509Certificate2[issuers.Length + timeStampCerts.Length];
        issuers.CopyTo(all, 0);
        timeStampCerts.CopyTo(all, issuers.Length);

        // add all OCSPs in one place
        ocsps.AddRange(ts_ocsp);

        // Add validating material - chain certificates and timestamp root and OCSPs
        // NB! In the response of the timestamp server there shall be also a certificate chain
        signer.AddValidatingMaterial(doc, all, ocsps);

        // UP TO HERE WE HAVE BASELINE LT !!!

        // Add archive timestamp
        await signer.AddArchiveTimestampAsync(CreateRfc3161RequestAsync, doc);

        // UP TO HERE WE HAVE BASELINE LTA !!!

        // Verify signature
        Assert.True(signer.Verify(doc, out ETSIContextInfo cInfo)
                    && (cInfo.IsSigningTimeInValidityPeriod.HasValue && cInfo.IsSigningTimeInValidityPeriod.Value)
                    && (cInfo.IsSigningCertDigestValid.HasValue && cInfo.IsSigningCertDigestValid.Value));
    } else {
        Assert.Fail("NO RSA certificate available");
    }
}

Basically you:

  1. Get the signing key and/or certificate (from somewhere)
  2. Get the timestamp server's CA certificates (from somewhere)
  3. Open a stream for the external, detached content
  4. Create a source XML document, with embedded content
  5. Create an instance of the ETSISignedXml class, with the signing key and hash algorithm you wish
  6. Sign the source XML document with the detached content
  7. Append the signature to the source XML document
  8. Call the AddTimestampAsync method to add a timestamp to the signature NB! At this point you have a Baseline-T signature
  9. Get the OCSP responses for the signer and the timestamp
  10. Attach the OCSP responses and the additional certificates to the signature, using method AddValidatingMaterial NB! At this point you have a Baseline-LT signature
  11. Call the AddArchiveTimestampAsync method to add an archive timestamp to the signature NB! At this point you have a Baseline-LTA signature
  12. OPTIONAL: Verify the signature. The method returns true / false, based only on cryptographic verification of the digest and the digital signature.

Please refer to the test class TestETSIXml. There are several test methods that show how to use the class for different scenarios - Baseline-T (with timestamp) and Baseline-LT (with timestamp and a LT-level). You can use them as a reference and you can see how to get the rest of CA certificates and OCSP data (as these generally fall outside of the scope of the library).

JSON Signature

In the previous article ETSI JSON Signatures, you can see how to create a JSON signature of a level Baseline-B. You must start from there, because the general idea is to have a baseline signature and then to enhance it with a:

  • timestamp (for Baseline-T)
  • additional certificates and OCSP responses (for Baseline-LT)
  • archive timestamp (for Baseline-LTA)

For this purpose there are three additional methods in the ETSISigner class:

/// <summary>
/// Add timestamping. Mainly to produce the XADES BASELINE-T signature
/// </summary>
/// <param name="funcAsync">Async function that calls Timestamping server, with input data and returns 
/// response from the server
/// </param>
public async Task AddTimestampAsync(Func<byte[], CancellationToken, Task<byte[]>> funcAsync, CancellationToken ct = default);

/// <summary>
/// Add some additional data objects for validation. Mainly to produce the XADES BASELINE-LT signature
/// </summary>
/// <param name="additionalCerts">Additional certificates, not included up until now</param>
/// <param name="rVals">Revocation status values, for all certificates (signer and chain, timestamp and chain)</param>
public void AddValidatingMaterial(X509Certificate2[] additionalCerts, ETSIrVals? rVals = null);

/// <summary>
/// Add archive timestamping. Mainly to produce the JADES BASELINE-LTA signature
/// </summary>
/// <param name="funcAsync">Async function that calls Timestamping server, with input data and returns 
/// response from the server</param>
/// <param name="attachement">In case of detached signature, with no payload option, provide the attachment, to be used as payload</param>
public async Task AddArchiveTimestampAsync(Func<byte[], CancellationToken, Task<byte[]>> funcAsync, byte[]? attachement = null, CancellationToken ct = default);

As you can see, the AddTimestampAsync method is used to add a timestamp to the signature (thus producing Baseline-T signature).

The AddValidatingMaterial method is used to add additional certificates and OCSP responses to the signature (thus producing Baseline-LT signature).

The AddArchiveTimestampAsync method is used to add archive timestamp to the signature (thus producing Baseline-LTA signature).

In the test project you can find examples how to use the methods. The examples are in the CryptoEx.Tests.TestETSI file.

For example here is how to sign and verify JSON to Baseline-LT level:

public async Task Test_ETSI_RSA_Timestamp_LT_Enveloped()
{
    // Try get certificate
    X509Certificate2? cert = GetCertificateOnWindows(CertType.RSA, out X509Certificate2[] issuers); 
    if (cert == null) {
        Assert.Fail("NO RSA certificate available");
    }

    // Get CA certificates of the timestamping server
    X509Certificate2[] timeStampCerts = GetCertificatesTimeStamp();

    // Get RSA private key
    RSA? rsaKey = cert.GetRSAPrivateKey();
    if (rsaKey != null) {
        // Create signer 
        ETSISigner signer = new ETSISigner(rsaKey, HashAlgorithmName.SHA512);

        // Get payload 
        signer.AttachSignersCertificate(cert, issuers);
        signer.Sign(Encoding.UTF8.GetBytes(message), "text/json", JWSConstants.JOSE_JSON);
        await signer.AddTimestampAsync(CreateRfc3161RequestAsync);

        // UP TO HERE WE HAVE BASELINE T !!!

        // Get OCSPs for the signer
        List<byte[]> ocsps = GetOCSPs(cert, issuers);

        // Get OCSPs for the timestamp
        List<byte[]> ts_ocsp = [];
        if (_TS_x509Certificate2s != null ) {
            var ls_ts = _TS_x509Certificate2s.ToList();
            ls_ts.AddRange(timeStampCerts);
            X509Certificate2[] tsCerts = ls_ts.ToArray();
            if (tsCerts.Length > 1) {
                ts_ocsp = GetOCSPs(tsCerts[0], tsCerts[1..]);
            } else if(tsCerts.Length > 0) {
                ts_ocsp = GetOCSPs(tsCerts[0], Array.Empty<X509Certificate2>());
            }
        }

        // Prepare rVals result
        ETSIrVals eTSIrVals = new();
        if (ts_ocsp.Count == 0) {
            eTSIrVals.RVals = new ETSIrVal()
                {
                    OcspVals = (from o in ocsps
                                select new ETSIPkiOb()
                                {
                                    Val = Convert.ToBase64String(o)
                                }).ToArray()
                };

            
        } else {
            eTSIrVals.RVals = new ETSIrVal()
                {
                    OcspVals = (from o in ocsps
                                select new ETSIPkiOb()
                                {
                                    Val = Convert.ToBase64String(o)
                                }).Union(
                                 from o in ts_ocsp
                                 select new ETSIPkiOb()
                                 {
                                     Val = Convert.ToBase64String(o)
                                 }
                                ).ToArray()
                };
        }

        // Add validating material - timestamp root and OCSPs
        // NB! The rest of the certificates are in the chain of the signer - see 'AttachSignersCertificate' few lines above
        // and in the response of the timestamp server and in the timestamp itself
        signer.AddValidatingMaterial(timeStampCerts, eTSIrVals);

        // UP TO HERE WE HAVE BASELINE LT !!!

        // Add archive timestamp
        await signer.AddArchiveTimestampAsync(CreateRfc3161RequestAsync);

        // UP TO HERE WE HAVE BASELINE LTA !!!

        // Encode - produce JWS
        var jSign = signer.Encode(JWSEncodeTypeEnum.Full);

        // Decode & verify
        Assert.True(signer.Verify(jSign, out byte[] payload, out ETSIContextInfo cInfo));
    } else {
        Assert.Fail("NO RSA certificate available");
    }
}

Basically you:

  1. Get the signing key and/or certificate (from somewhere)
  2. Get the timestamping server's CA certificates (from somewhere)
  3. Create the ETSISigner instance
  4. Attach the certificate to the JWS
  5. Sign the payload
  6. Call the AddTimestampAsync method to add a timestamp to the signature NB! At this point you have a Baseline-T signature
  7. Get the OCSP responses for the signer and the timestamp
  8. Attach the OCSP responses and the additional certificates to the signature, using method AddValidatingMaterial NB! At this point you have a Baseline-LT signature
  9. Call the AddArchiveTimestampAsync method to add an archive timestamp to the signature NB! At this point you have a Baseline-LTA signature
  10. Encode the JWS
  11. Optionally - Verify the JWS

Please refer to the test class TestETSI. There are several test methods that show how to use the class for different scenarios - Baseline-T (with timestamp) and Baseline-LT (with timestamp and a LT-level). You can use them as a reference and you can see how to get the rest of CA certificates and OCSP data (as these generally fall outside of the scope of the library).

Some implementation details

The thing with the Baseline-T, Baseline-LT and Baseline-LTA levels is that you need to have a timestamp and additional certificates and OCSP responses.

The timestamp is a separate thing and you can get it from a timestamping server.

The additional certificates and OCSP responses are generally not part of the signature itself, but are part of the validation process.

The acquisition of the timestamp and the OCSP responses is generally outside of the scope of the library. But for testing purposes and for the examples in the test project, I have included some methods that show how to get the OCSP responses and how to create a timestamp request and get the response.

This methods are available only in the test project as they are not part of the library's scope! You can see the code in Github, but you are encouraged to use/create your on program logic that fulfills these step and that probably will better suit you case.