Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC 3161 timestamping fails #27351

Closed
nil4 opened this issue Sep 10, 2018 · 5 comments
Closed

RFC 3161 timestamping fails #27351

nil4 opened this issue Sep 10, 2018 · 5 comments
Labels
area-System.Security question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@nil4
Copy link
Contributor

nil4 commented Sep 10, 2018

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 😃

@nil4
Copy link
Contributor Author

nil4 commented Sep 11, 2018

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

@bartonjs
Copy link
Member

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.

@nil4
Copy link
Contributor Author

nil4 commented Sep 11, 2018

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.

@bartonjs
Copy link
Member

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.

@nil4
Copy link
Contributor Author

nil4 commented Sep 11, 2018

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

@nil4 nil4 closed this as completed Sep 11, 2018
@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Security question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

3 participants