Runtime: RFC 3161 timestamping fails

Created on 10 Sep 2018  路  5Comments  路  Source: dotnet/runtime

I'm using @bartonjs's example, An overly simplistic timestamp issuance authority at https://github.com/dotnet/corefx/issues/24524#issuecomment-361337499 to timestamp a request generated by signtool timestamp, and having trouble getting it to work. Here is what I did.

One-time certificate setup

Run this PowerShell script to generate a root CA and a timestamping certificate:

$root = New-SelfSignedCertificate  `
    -Type Custom `
    -HashAlgorithm SHA256 `
    -KeyExportPolicy Exportable `
    -Subject 'CN=_TestCA' `
    -KeySpec Signature `
    -KeyUsage 'None' `
    -KeyUsageProperty All `
    -TextExtension @("2.5.29.19={text}cA=true") `
    -CertStoreLocation Cert:\CurrentUser\My `
    -NotAfter '02/01/2029 00:00:00'

$thumbprint = $root.Thumbprint
Write-Host "Created RootCA cert: ${thumbprint}"

$tscert = New-SelfSignedCertificate `
    -Type Custom `
    -HashAlgorithm SHA256 `
    -KeyExportPolicy Exportable `
    -Subject 'CN=_TestTimestamp' `
    -KeyUsage DigitalSignature,NonRepudiation `
    -KeyUsageProperty Sign `
    -CertStoreLocation Cert:CurrentUser\My `
    -TextExtension @("2.5.29.37={critical}{text}1.3.6.1.5.5.7.3.8") `
    -Signer "Cert:CurrentUser\My\${thumbprint}" `
    -NotAfter '01/01/2029 00:00:00'

$thumbprint = $tscert.Thumbprint
Write-Host "Created timestamping cert: ${thumbprint}"

The output should be similar to:

Created RootCA cert: 3E235901DC62D29F451EB3251B612AADCAEA6005
Created timestamping cert: 3877B460311CC3F281B037198493518AF14C62B5

Note/copy the timestamping certificate thumbprint (i.e. 3877B460311CC3F281B037198493518AF14C62B5).

Open certmgr.msc, Cut the _TestCA certificate from Personal, and Paste it to Trusted Root Certification Authorities.



In a command prompt, run:

> dotnet --version
2.1.401
> mkdir repro && cd repro
> dotnet new console
> dotnet add package System.Security.Cryptography.Pkcs --version 4.5.0

Replace Program.cs with:

using System;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;

namespace Repro
{
    class Program
    {
        const string CertificateThumbprint = "3877B460311CC3F281B037198493518AF14C62B5"; 

        static void Main(string[] args)
        {
            const string requestBase64 = "MDkCAQEwMTANBglghkgBZQMEAgEFAAQgZKFY5VI7xuWHNNeFb17Q60DPLlqoFtctpEHhsq1qgPwBAf8=";
            byte[] requestBytes = Convert.FromBase64String(requestBase64);

            Debug.Assert(Rfc3161TimestampRequest.TryDecode(requestBytes, out Rfc3161TimestampRequest request, out int bytesConsumed));
            Debug.Assert(bytesConsumed == requestBytes.Length);

            var tokenInfo = new Rfc3161TimestampTokenInfo(
                new Oid("2.255.329800735698586629295641978511506172919.1.4.17"), // UUID policy (no meaning)
                request.HashAlgorithmId,
                request.GetMessageHash(),
                Guid.NewGuid().ToByteArray(),
                DateTimeOffset.UtcNow,
                accuracyInMicroseconds: 3_000_000,
                nonce: request.GetNonce()
            );

            var contentInfo = new ContentInfo(new Oid("1.2.840.113549.1.9.16.1.4", "id-ct-TSTInfo"), tokenInfo.Encode());

            X509Certificate2 certificate;
            using (var store = new X509Store(StoreLocation.CurrentUser))
            {
                store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
                certificate = store.Certificates
                    .Find(X509FindType.FindByThumbprint, CertificateThumbprint, true)
                    .Cast<X509Certificate2>()
                    .Single();
            }

            var cms = new SignedCms(contentInfo);
            var signer = new CmsSigner(certificate)
            {
                DigestAlgorithm = request.HashAlgorithmId,
                IncludeOption = request.RequestSignerCertificate
                    ? X509IncludeOption.EndCertOnly
                    : X509IncludeOption.None,
            };

            // Exercise left to the reader
            //signer.SignedAttributes.Add(BuildSigningCertificateV2Attribute(certificate));

            cms.ComputeSignature(signer);
            byte[] response = cms.Encode();

            Rfc3161TimestampToken token;
            Debug.Assert(Rfc3161TimestampToken.TryDecode(response, out token, out int _));
        }
    }
}

Replace the certificate thumbprint with the value obtained in the One-time certificate setup step.

const string CertificateThumbprint = "3877B460311CC3F281B037198493518AF14C62B5"; 

dotnet run fails with:

Assertion Failed 
    at Repro.Program.Main(String[] args) in Program.cs:line 60

at:

Debug.Assert(Rfc3161TimestampToken.TryDecode(response, out token, out int _));

Replace line 60 with:

- Debug.Assert(Rfc3161TimestampToken.TryDecode(response, out token, out int _));
+ token = request.ProcessResponse(response, out int _);

dotnet run now fails with:

Unhandled Exception: System.Security.Cryptography.CryptographicException: Unable to set field Status on type System.Security.Cryptography.Pkcs.Asn1.Rfc3161TimeStampResp. ---> System.Security.Cryptography.CryptographicException: ASN1 corrupted data.
   at System.Security.Cryptography.Asn1.AsnReader.CheckExpectedTag(Asn1Tag tag, Asn1Tag expectedTag, UniversalTagNumber tagNumber)
   at System.Security.Cryptography.Asn1.AsnReader.ReadSequence(Asn1Tag expectedTag)
   at System.Security.Cryptography.Asn1.AsnSerializer.DeserializeCustomType(AsnReader reader, Type typeT, Asn1Tag expectedTag)
   at System.Security.Cryptography.Asn1.AsnSerializer.DeserializeCustomType(AsnReader reader, Type typeT, Asn1Tag expectedTag)
   --- End of inner exception stack trace ---
   at System.Security.Cryptography.Asn1.AsnSerializer.DeserializeCustomType(AsnReader reader, Type typeT, Asn1Tag expectedTag)
   at System.Security.Cryptography.Asn1.AsnSerializer.Deserialize[T](ReadOnlyMemory`1 source, AsnEncodingRules ruleSet, Int32& bytesRead)
   at System.Security.Cryptography.Pkcs.Rfc3161TimestampRequest.ProcessResponse(ReadOnlyMemory`1 source, Rfc3161TimestampToken& token, Rfc3161RequestResponseStatus& status, Int32& bytesConsumed, Boolean shouldThrow)
   at System.Security.Cryptography.Pkcs.Rfc3161TimestampRequest.ProcessResponse(ReadOnlyMemory`1 source, Int32& bytesConsumed)
   at Repro.Program.Main(String[] args) in Program.cs:line 61

I'm very likely missing out some subtle detail, I just can't figure out what it is. Or perhaps it's a actual bug. I would appreciate feedback in either case 馃槂

area-System.Security question

Most helpful comment

Making AsnReader/AsnWriter public is on my TODO list, though not with a specific timeline. We've probably stopped changing it enough that it's almost time to go forward.

AsnSerializer has a lot of value, but a lot of cost, too. We've just removed the last usage of it in .NET Core, using generated code instead (solves some perf problems, some reflection/runtime problems, and some debuggability problems). It probably won't survive to be public.

All 5 comments

Building corefx locally and debugging through the Rfc3161TimestampToken.TryDecode code, it turns out the decode fails at:

https://github.com/dotnet/corefx/blob/8060e77b96fc31d9f301e2519275eb2236dedd0d/src/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Rfc3161TimestampToken.cs#L353-L355

because the expected signed attributes are missing: SigningCertificate and/or SigningCertificateV2.

So it looks like what I am missing is how to fill-in this blank:

// Exercise left to the reader
// signer.SignedAttributes.Add(BuildSigningCertificateV2Attribute(certificate));

And here I seem to run into a wall: the seemingly-relevant structs/classes to build these attributes are all internal, including CryptographicAttributeObject, SigningCertificateAsn, EssCertId, AsnSerializer, AsnWriter, etc.

The only example I could find builds them from hard-coded byte arrays, in the test code, here:

https://github.com/dotnet/corefx/blob/dea42667cf35e1c0ed4724229203d895db706246/src/System.Security.Cryptography.Pkcs/tests/Rfc3161/TimestampTokenTests.cs#L857-L887

Is there a higher-level API available (or planned to be exposed) that allows building the SigningCertificate and SigningCertificateV2 attributes? /cc @bartonjs @filipnavara

Is there a higher-level API available (or planned to be exposed) that allows building the SigningCertificate and SigningCertificateV2 attributes?

Not presently, no. The API was designed to read/verify/process timestamps, building timestamps is mainly expected to be done by trusted timestamping authorities. (Though test scenarios certainly do exist.)

The linked test code there builds a valid SigningCertificate (v1) attribute. Slightly lower is code to build a V2 attribute, though it's certainly a lot trickier.

Thank you @bartonjs, that's understandable given that 99% of scenarios would be covered by reading them.

I'll see what I can piece together from this point on. It would be really helpful if at least AsnWriter would become a publicly exposed API.

Using AsnSerializer is a joy, but probably that would require exposing too many internals to be realistic.

Making AsnReader/AsnWriter public is on my TODO list, though not with a specific timeline. We've probably stopped changing it enough that it's almost time to go forward.

AsnSerializer has a lot of value, but a lot of cost, too. We've just removed the last usage of it in .NET Core, using generated code instead (solves some perf problems, some reflection/runtime problems, and some debuggability problems). It probably won't survive to be public.

@bartonjs sounds good, looking forward to that (you're likely referring to https://github.com/dotnet/corefx/issues/21833).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

noahfalk picture noahfalk  路  3Comments

nalywa picture nalywa  路  3Comments

bencz picture bencz  路  3Comments

iCodeWebApps picture iCodeWebApps  路  3Comments

yahorsi picture yahorsi  路  3Comments