Skip to content

Provide a way to load a CertificateRequest from a byte[] #29547

Closed

Description

Rationale

I am using the CertificateRequest.CreateSigningRequest() to get a CSR byte array that I can send to the Certificate Authority.

As far as I know, there is currently no way, on the CA side, to "deserialize" this byte array into a CertificateRequest.

Use case

I am currently building a CA in .NET, that can sign certificate for .net client applications.

The .net client generates a PKCS 10 CSR, and send it over HTTPS to the CA, which will sign it.

Proposed API

Policies and best practices dictate that we want callers to be able to provide basic certificate revocation via CRL (Certificate Revocation Lists) prior to enabling them to load a PKCS#10 signing request, so CRL building is included in this proposal.

A CRL is, essentially, a signed list of (byte[] SerialNumber, DateTimeOffset RevocationTime, X509Extension[] Extensions).

Build a CRL from nothing

namespace System.Security.Cryptography.X509Certificates
{
    public sealed partial class CertificateRevocationListBuilder
    {
        public CertificateRevocationListBuilder() { }

        public void AddEntry(
            byte[] serialNumber,
            DateTimeOffset? revocationTime = default,
            X509RevocationReason? reason = default) { }

        public void AddEntry(
            ReadOnlySpan<byte> serialNumber,
            DateTimeOffset? revocationTime = default,
            X509RevocationReason? reason = default) { }
        
        // Helper for AddEntry(certificate.SerialNumberBytes)
        public void AddEntry(
            X509Certificate2 certificate,
            DateTimeOffset? revocationTime = default,
            X509RevocationReason? reason = default) { }
       
        // Calls similar overload with thisUpdate: DateTimeOffset.UtcNow
        public byte[] Build(
            X509Certificate2 issuerCertificate,
            BigInteger crlNumber,
            DateTimeOffset nextUpdate,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding? rsaSignaturePadding = null) { throw null; }

        // Sanity checks issuerCertificate,
        // builds a X509SignatureGenerator from the cert and (for RSA) rsaSignaturePadding,
        // builds an appropriate X509AuthorityKeyIdentifierExtension,
        // then calls the most complicated overload of Build
        public byte[] Build(
            X509Certificate2 issuerCertificate,
            BigInteger crlNumber,
            DateTimeOffset nextUpdate,
            DateTimeOffset thisUpdate,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding? rsaSignaturePadding = null) { throw null; }

        // Calls similar overload with thisUpdate: DateTimeOffset.UtcNow
        public byte[] Build(
            X500DistinguishedName issuerName, 
            X509SignatureGenerator generator,
            BigInteger crlNumber,
            DateTimeOffset nextUpdate,
            HashAlgorithmName hashAlgorithm,
            X509AuthorityKeyIdentifierExtension akid) { throw null; }

        public byte[] Build(
            X500DistinguishedName issuerName, 
            X509SignatureGenerator generator,
            BigInteger crlNumber,
            DateTimeOffset nextUpdate,
            DateTimeOffset thisUpdate,
            HashAlgorithmName hashAlgorithm,
            X509AuthorityKeyIdentifierExtension akid) { throw null; }
    }

    // Names/numbers come from X.509 specification
    public enum X509RevocationReason
    {
        Unspecified = 0,
        KeyCompromise = 1,
        // Alternative: CertificateAuthorityCompromise
        CACompromise = 2,
        AffiliationChanged = 3,
        Superseded = 4,
        CessationOfOperation = 5,
        CertificateHold = 6,

        RemoveFromCrl = 8,
        PrivilegeWithdrawn = 9,
        // Alternative: AttributeAuthorityCompromise
        AACompromise = 10,
        WeakAlgorithmOrKey = 11,
    }
}

Load an existing CRL, modify it, save it back

namespace System.Security.Cryptography.X509Certificates
{
    public sealed partial class CertificateRevocationListBuilder
    {
        public static CertificateRevocationListBuilder Load(
            byte[] currentCrl,
            out BigInteger currentCrlNumber) { throw null; }

        public static CertificateRevocationListBuilder Load(
            ReadOnlySpan<byte> currentCrl,
            out BigInteger currentCrlNumber,
            out int bytesConsumed) { throw null; }

        public static CertificateRevocationListBuilder LoadPem(
            string currentCrl,
            out BigInteger currentCrlNumber) { throw null; }

        public static CertificateRevocationListBuilder LoadPem(
            ReadOnlySpan<char> currentCrl,
            out BigInteger currentCrlNumber) { throw null; }


        // As proposed, this type doesn't allow enumerating the contents, but this method
        // permits a blanket removal policy, e.g.
        //   builder.ExpireEntries(DateTimeOffset.UtcNow - MaximumValidity)
        // to purge off all old entries (if desired)
        public void ExpireEntries(DateTimeOffset oldestRevocationTimeToKeep) { }

        public void RemoveEntry(byte[] serialNumber) { }
        public void RemoveEntry(ReadOnlySpan<byte> serialNumber) { }
    }
}

Creating a CRL Distribution Points Extension for Generated Certificates

At this time we don't think it's important to expose a way to read these back, and we don't support generating them in their full complexity, so we provide a helper-builder on the closest appropriate type (without burning the best type name for if we add a rich type later).

namespace System.Security.Cryptography.X509Certificates
{
    public sealed partial class CertificateRevocationListBuilder
    {
        public static X509Extension BuildCrlDistributionPointExtension(
            IEnumerable<string> uris,
            bool critical = false) { throw null; }
    }
}

Support PKCS#10 attributes, load a PKCS#10

namespace System.Security.Cryptography.X509Certificates
{
    public sealed partial class CertificateRequest
    {
        // add a new ctor with a (defaulted) RSASignaturePadding
        public CertificateRequest(
            X500DistinguishedName subjectName,
            X509Certificates.PublicKey publicKey,
            HashAlgorithmName hashAlgorithm,
            RSASignaturePadding? rsaSignaturePadding = default) { }

        // PKCS#10 Challenge Password, and friends
        public Collection<AsnEncodedData> OtherRequestAttributes { get; }

        public static CertificateRequest LoadSigningRequest(
            byte[] pkcs10,
            HashAlgorithmName signerHashAlgorithm,
            bool skipSignatureValidation = false,
            bool unsafeLoadCertificateExtensions = false,
            RSASignaturePadding? signerSignaturePadding = null) { throw null; }

        public static CertificateRequest LoadSigningRequest(
           ReadOnlySpan<byte> pkcs10,
           HashAlgorithmName signerHashAlgorithm,
           out int bytesConsumed,
           bool skipSignatureValidation = false,
           bool unsafeLoadCertificateExtensions = false,
           RSASignaturePadding? signerSignaturePadding = null) { throw null; }

        public static CertificateRequest LoadSigningRequestPem(
            ReadOnlySpan<char> pkcs10Pem,
            HashAlgorithmName signerHashAlgorithm,
            bool skipSignatureValidation = false,
            bool unsafeLoadCertificateExtensions = false,
            RSASignaturePadding? signerSignaturePadding = null) { throw null; }

        public static CertificateRequest LoadSigningRequestPem(
            string pkcs10Pem,
            HashAlgorithmName signerHashAlgorithm,
            bool skipSignatureValidation = false,
            bool unsafeLoadCertificateExtensions = false,
            RSASignaturePadding? signerSignaturePadding = null) { throw null; }

        public string CreateSigningRequestPem() { throw null; }
        public string CreateSigningRequestPem(X509SignatureGenerator signatureGenerator) { throw null; }
    }
}

Usability/Understanding Improvement in X509BasicConstraintsExtension

namespace System.Security.Cryptography.X509Certificates
{
    partial class X509BasicConstraintsExtension
    {
        // Named wrapper for
        //   new X509BasicConstraintsExtension(
        //     true,
        //     pathLengthConstraint.HasValue,
        //     pathLengthConstraint.GetValueOrDefault(),
        //     true);
        public static X509BasicConstraintsExtension CreateForCertificateAuthority(int? pathLengthConstraint = default(int?)) { throw null; }

        // Named wrapper for
        //   new X509BasicConstraintsExtension(
        //     false,
        //     false,
        //     0,
        //     critical);
        public static X509BasicConstraintsExtension CreateForEndEntity(bool critical = false) { throw null; }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.SecurityblockingMarks issues that we want to fast track in order to unblock other important work

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions