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

X.509 Extensions #59191

Closed
amin1best opened this issue Sep 16, 2021 · 10 comments
Closed

X.509 Extensions #59191

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

Comments

@amin1best
Copy link

Hi,
The following link provides several extensions for X.509 Request:
https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/ff182332(v=ws.10)

I do not find equivalent of some of these extensions in System.Security.Cryptography.X509Certificates:

Extension System.Security.Cryptography.X509Certificates
Key Usage X509KeyUsageExtension
Enhanced Key Usage X509EnhancedKeyUsageExtension
Certificate Template Information ?
Alternative Name - User Principal Name SubjectAlternativeNameBuilder - AddUserPrincipalName
Certificate Policies ?
Subject Key Identifier X509SubjectKeyIdentifierExtension
Authority Key Identifier ?
S/MIME Capabilities ?

Is there a plan to add Certificate Template Information, Certificate Policies, Authority Key Identifier and S/MIME Capabilities extensions?

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Security untriaged New issue has not been triaged by the area owner labels Sep 16, 2021
@ghost
Copy link

ghost commented Sep 16, 2021

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq, @GrabYourPitchforks
See info in area-owners.md if you want to be subscribed.

Issue Details

Hi,
The following link provides several extensions for X.509 Request:
https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/ff182332(v=ws.10)

I do not find equivalent of some of these extensions in System.Security.Cryptography.X509Certificates:

Extension System.Security.Cryptography.X509Certificates
Key Usage X509KeyUsageExtension
Enhanced Key Usage X509EnhancedKeyUsageExtension
Certificate Template Information ?
Alternative Name - User Principal Name SubjectAlternativeNameBuilder - AddUserPrincipalName
Certificate Policies ?
Subject Key Identifier X509SubjectKeyIdentifierExtension
Authority Key Identifier ?
S/MIME Capabilities ?

Is there a plan to add Certificate Template Information, Certificate Policies, Authority Key Identifier and S/MIME Capabilities extensions?

Author: amin1best
Assignees: -
Labels:

area-System.Security, untriaged

Milestone: -

@bartonjs bartonjs added this to the Future milestone Sep 16, 2021
@bartonjs bartonjs added question Answer questions and provide assistance, not an issue with source code or documentation. and removed untriaged New issue has not been triaged by the area owner labels Sep 16, 2021
@vcsjones
Copy link
Member

Is there a plan to add Certificate Template Information, Certificate Policies, Authority Key Identifier and S/MIME Capabilities extensions?

The only one that has a tracking issue is the AKI at #50488.

Certificate Template Information

As far as I know, this is just an extension that contains a BMPString, so it's doable to create this extension with System.Formats.Asn1.

For example:

static X509Extension GetTemplateExtension(string templateName)
{
    AsnWriter writer = new(AsnEncodingRules.DER);
    writer.WriteCharacterString(UniversalTagNumber.BMPString, templateName);
    return new X509Extension("1.3.6.1.4.1.311.20.2", writer.Encode(), false);
}

@bartonjs
Copy link
Member

They all (along with the rest of https://datatracker.ietf.org/doc/html/rfc5280#section-4.2) seem generally reasonable and on-theme for improving the support for creating (and then reading) Certification Signing Requests.

@bartonjs
Copy link
Member

@vcsjones You must have found a different certificate template than I did. I found 1.3.6.1.4.1.311.21.7, which has structure 😄

@vcsjones
Copy link
Member

vcsjones commented Sep 16, 2021

@vcsjones You must have found a different certificate template than I did. I found 1.3.6.1.4.1.311.21.7, which has structure 😄

I used the one from the cited link https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/ff182332(v=ws.10).

I found 1.3.6.1.4.1.311.21.7

I think that is "V2 Template Names".

@amin1best
Copy link
Author

amin1best commented Sep 18, 2021

Certificate Template Information

As far as I know, this is just an extension that contains a BMPString, so it's doable to create this extension with System.Formats.Asn1.

For example:

static X509Extension GetTemplateExtension(string templateName)
{
    AsnWriter writer = new(AsnEncodingRules.DER);
    writer.WriteCharacterString(UniversalTagNumber.BMPString, templateName);
    return new X509Extension("1.3.6.1.4.1.311.20.2", writer.Encode(), false);
}

Thanks,
Microsoft.com Certificate:

Certificate Template Information

Items include following:
Template
Major Version Number
Minor Version Number

How can i do that?
@vcsjones

@vcsjones
Copy link
Member

vcsjones commented Sep 18, 2021

How can i do that?

Let's look at the ASN.1 syntax that @bartonjs linked to:

 CertificateTemplateOID ::= SEQUENCE {
         templateID              OBJECT IDENTIFIER,
         templateMajorVersion    INTEGER (0..4294967295) OPTIONAL,
         templateMinorVersion    INTEGER (0..4294967295) OPTIONAL
     } --#public

We can build this with AsnWriter. The extension contents are a sequence of the template OID (the 1.3.6.1.4.etc.etc) string, with the major version and minor version optionally written. If a minor version is specified, then a major version has to be, too.

We can package this nicely up in to our own X509Extension class. Something like this:

public sealed class CertificateTemplate2X509Extension : X509Extension
{
    public Oid TemplateOid { get; }
    public long? MajorVersion { get; }
    public long? MinorVersion { get; }

    public CertificateTemplate2X509Extension(Oid templateOid, long? majorVersion = null, long? minorVersion = null)
        : base("1.3.6.1.4.1.311.21.7", Encode(templateOid, majorVersion, minorVersion), critical: false)
    {
        TemplateOid = templateOid;
        MajorVersion = majorVersion;
        MinorVersion = minorVersion;
    }

    private static byte[] Encode(
        Oid templateOid,
        long? majorVersion,
        long? minorVersion)
    {
        if (templateOid is null || templateOid.Value is null)
            throw new ArgumentException(nameof(templateOid));
        if (minorVersion is not null && majorVersion is null)
            throw new ArgumentException("Specifying a minor version requires a major version", nameof(minorVersion));
        if (majorVersion is < 0 or > uint.MaxValue)
            throw new ArgumentOutOfRangeException(nameof(majorVersion));
        if (minorVersion is < 0 or > uint.MaxValue)
            throw new ArgumentOutOfRangeException(nameof(minorVersion));

        AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);

        using (writer.PushSequence())
        {
            writer.WriteObjectIdentifier(templateOid.Value);
            
            if (majorVersion is not null)
            {
                writer.WriteInteger(majorVersion.Value);
            }

            if (minorVersion is not null)
            {
                writer.WriteInteger(minorVersion.Value);
            }
        }

        return writer.Encode();
    }
}

And then you can add it to your CertificateExtensions in a CertificateRequest:

CertificateRequest req = new("CN=blah", ecdsa, HashAlgorithmName.SHA256);
Oid templateOid = new Oid("1.3.6.1.2.3.4.5.6.7", null);
req.CertificateExtensions.Add(new CertificateTemplate2X509Extension(templateOid, majorVersion: 100, minorVersion: 37));
using X509Certificate2 cert = req.CreateSelfSigned(DateTime.Now, DateTime.Now);

That gives us
image


You can follow this pattern for any X509Extension that is not included within .NET itself. If you need to be able to parse the extension, you can do that with AsnReader.

@amin1best
Copy link
Author

... You can follow this pattern for any X509Extension that is not included within .NET itself. If you need to be able to parse the extension, you can do that with AsnReader.

Very thanks @vcsjones
It was very practical.
Can I request that you parse 'Certificate Template Information' extension using AsnReader like this Code for reading this extension?

@vcsjones
Copy link
Member

Can I request that you parse 'Certificate Template Information' extension using AsnReader like this Code for reading this extension?

Sure. Here is a complete example of creating a certificate with the extension, then parsing it back out.

#nullable enable
using System;
using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using ECDsa ecdsa = ECDsa.Create();
CertificateRequest req = new CertificateRequest("CN=hello", ecdsa, HashAlgorithmName.SHA256);
req.CertificateExtensions.Add(new CertificateTemplate2X509Extension(new Oid("1.3.6.1.2.3.4", null), 70, 35));
X509Certificate2 cert = req.CreateSelfSigned(DateTime.Now, DateTime.Now);

foreach (X509Extension ext in cert.Extensions)
{
    if (ext.Oid?.Value == CertificateTemplate2X509Extension.ExtensionOid)
    {
        CertificateTemplate2X509Extension template2X509Extension = CertificateTemplate2X509Extension.Parse(ext.RawData);
        Console.WriteLine(template2X509Extension.TemplateOid.Value);
        Console.WriteLine(template2X509Extension.MajorVersion?.ToString() ?? "no major");
        Console.WriteLine(template2X509Extension.MinorVersion?.ToString() ?? "no minor");
    }
}


public sealed class CertificateTemplate2X509Extension : X509Extension
{
    public const string ExtensionOid = "1.3.6.1.4.1.311.21.7";
    public Oid TemplateOid { get; }
    public long? MajorVersion { get; }
    public long? MinorVersion { get; }

    public CertificateTemplate2X509Extension(Oid templateOid, long? majorVersion = null, long? minorVersion = null)
        : base(ExtensionOid, Encode(templateOid, majorVersion, minorVersion), critical: false)
    {
        TemplateOid = templateOid;
        MajorVersion = majorVersion;
        MinorVersion = minorVersion;
    }

    private static byte[] Encode(
        Oid templateOid,
        long? majorVersion,
        long? minorVersion)
    {
        if (templateOid is null || templateOid.Value is null)
            throw new ArgumentException(nameof(templateOid));
        if (minorVersion is not null && majorVersion is null)
            throw new ArgumentException("Specifying a minor version requires a major version", nameof(minorVersion));
        if (majorVersion is < 0 or > uint.MaxValue)
            throw new ArgumentOutOfRangeException(nameof(majorVersion));
        if (minorVersion is < 0 or > uint.MaxValue)
            throw new ArgumentOutOfRangeException(nameof(minorVersion));

        AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);

        using (writer.PushSequence())
        {
            writer.WriteObjectIdentifier(templateOid.Value);
            
            if (majorVersion is not null)
            {
                writer.WriteInteger(majorVersion.Value);
            }

            if (minorVersion is not null)
            {
                writer.WriteInteger(minorVersion.Value);
            }
        }

        return writer.Encode();
    }

    public static CertificateTemplate2X509Extension Parse(byte[] extensionDer)
    {
        AsnReader reader = new AsnReader(extensionDer, AsnEncodingRules.DER);
        AsnReader sequenceReader = reader.ReadSequence();
        reader.ThrowIfNotEmpty(); // Validate there is no data after the outer sequence.
        string templateId = sequenceReader.ReadObjectIdentifier();
        long? major = null, minor = null;

        if (sequenceReader.HasData) // Is there optional data? Then we have at least a major
        {
            // Read the major version. We know there is more data, so if it fails,
            // either the number is out-of-bounds or it is the wrong tag.
            if (!sequenceReader.TryReadUInt32(out uint parsedMajor))
            {
                throw new InvalidOperationException("Major version is invalid.");
            }

            major = parsedMajor;
        }

        if (sequenceReader.HasData) // Is there still  more optional data? Then we have a minor
        {
            if (!sequenceReader.TryReadUInt32(out uint parsedMinor))
            {
                throw new InvalidOperationException("Minor version is invalid.");
            }

            minor = parsedMinor;
        }

        // We've read everything we can possibly read. Let's validate the SEQUENCE is empty
        // because if it isn't, we don't know what to do with the rest of it and the extension
        // is malformed.
        sequenceReader.ThrowIfNotEmpty();

        return new CertificateTemplate2X509Extension(new Oid(templateId, null), major, minor);
    }
}

@bartonjs
Copy link
Member

bartonjs commented Mar 5, 2022

I believe all questions have been answered, and that the actionable pieces are already tracked by other issues. Closing.

@bartonjs bartonjs closed this as completed Mar 5, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Apr 4, 2022
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