Closed bartonjs closed 4 years ago
Example usage:
using System;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509CertificateCreation;
using System.Security.Cryptography.X509Certificates;
namespace CertReq
{
public static class Samples
{
public static X509Certificate2 CreateRoot(string name)
{
// Creates a certificate roughly equivalent to
// makecert -r -n "{name}" -a sha256 -cy authority
//
using (RSA rsa = RSA.Create())
{
var generator = new RSAPkcs1X509SignatureGenerator(rsa, HashAlgorithmName.SHA256);
var request = new CertificateRequest { Subject = new X500DistinguishedName(name) };
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(true, false, 0, true));
// makecert will add an authority key identifier extension, which .NET doesn't
// have out of the box.
//
// It does not add a subject key identifier extension, so we won't, either.
return request.SelfSign(
generator,
DateTimeOffset.UtcNow,
// makecert's fixed default end-date.
new DateTimeOffset(2039, 12, 31, 23, 59, 59, TimeSpan.Zero),
X509KeyStorageFlags.DefaultKeySet);
}
}
public static X509Certificate2 CreateTlsClient(string name, X509Certificate2 issuer, SubjectAltNameBuilder altNames)
{
using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP384))
{
var selfGenerator = new ECDsaX509SignatureGenerator(ecdsa, HashAlgorithmName.SHA384);
var request = new CertificateRequest
{
Subject = new X500DistinguishedName(name),
PublicKey = selfGenerator.PublicKey,
};
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
request.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") }, false));
if (altNames != null)
{
request.CertificateExtensions.Add(altNames.BuildExtension());
}
var generator = new IssuerSignatureGenerator(issuer, HashAlgorithmName.SHA384);
byte[] serialNumber = new byte[8];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(serialNumber);
}
byte[] certBytes = request.Sign(generator, TimeSpan.FromDays(90), serialNumber);
return CertificateRequest.AssociatePrivateKey(certBytes, selfGenerator, X509KeyStorageFlags.DefaultKeySet);
}
}
public static X509Certificate2 BuildLocalhostTlsSelfSignedServer()
{
SubjectAltNameBuilder sanBuilder = new SubjectAltNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
sanBuilder.AddDnsName("localhost.localdomain");
sanBuilder.AddDnsName(Environment.MachineName);
using (RSA rsa = RSA.Create())
{
var generator = new RSAPkcs1X509SignatureGenerator(rsa, HashAlgorithmName.SHA256);
var request = new CertificateRequest { Subject = new X500DistinguishedName("CN=localhost") };
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
request.CertificateExtensions.Add(sanBuilder.BuildExtension());
return request.SelfSign(generator, TimeSpan.FromDays(90), X509KeyStorageFlags.DefaultKeySet);
}
}
public static byte[] CreateCertificateRenewal(AsymmetricAlgorithm newKey, X509Certificate2 currentCert)
{
// Getting, and persisting, `newKey` is out of scope here.
X509SignatureGenerator generator = null;
if (newKey is ECDsa)
{
generator = new ECDsaX509SignatureGenerator((ECDsa)newKey, HashAlgorithmName.SHA384);
}
else if (newKey is RSA)
{
generator = new RSAPkcs1X509SignatureGenerator((RSA)newKey, HashAlgorithmName.SHA256);
}
CertificateRequest request = new CertificateRequest { Subject = currentCert.SubjectName };
foreach (X509Extension extension in currentCert.Extensions)
{
request.CertificateExtensions.Add(extension);
}
// Send this to the CA you're requesting to sign your certificate.
return request.EncodeSigningRequest(generator);
}
public static X509Certificate2 RenewCertificate(X509Certificate2 currentCert)
{
using (RSA rsa = RSA.Create())
{
byte[] certificateSigningRequest = CreateCertificateRenewal(rsa, currentCert);
byte[] signedCertificate = SendRequestToCAAndGetResponse(certificateSigningRequest);
return CertificateRequest.AssociatePrivateKey(
signedCertificate,
new RSAPkcs1X509SignatureGenerator(rsa, HashAlgorithmName.SHA256),
X509KeyStorageFlags.DefaultKeySet);
}
}
}
}
@terrajobst Did you want to record the initial review notes?
Comments:
System.Security.Cryptography.X509Certificates
Samples.CreateRoot
a convenience API the shipped API. Reason being that most people seem to have the problem of being able to cook up cert out of thin air for testing purposes.Other than that, it looks OK to me, considering our crypto stack will never win a price for usability :-)
The samples get a lot smaller with the API revisions:
using System;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace CertReq
{
public static class Samples
{
public static X509Certificate2 CreateRoot(string name)
{
// Creates a certificate roughly equivalent to
// makecert -r -n "{name}" -a sha256 -cy authority
//
using (RSA rsa = RSA.Create())
{
var request = new CertificateRequest(name, rsa, HashAlgorithmName.SHA256);
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(true, false, 0, true));
// makecert will add an authority key identifier extension, which .NET doesn't
// have out of the box.
//
// It does not add a subject key identifier extension, so we won't, either.
return request.SelfSign(
DateTimeOffset.UtcNow,
// makecert's fixed default end-date.
new DateTimeOffset(2039, 12, 31, 23, 59, 59, TimeSpan.Zero));
}
}
public static X509Certificate2 CreateTlsClient(string name, X509Certificate2 issuer, SubjectAltNameBuilder altNames)
{
using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP384))
{
var request = new CertificateRequest(name, ecdsa, HashAlgorithmName.SHA384);
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
request.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") }, false));
if (altNames != null)
{
request.CertificateExtensions.Add(altNames.BuildExtension());
}
byte[] serialNumber = new byte[8];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(serialNumber);
}
X509Certificate2 signedCert = request.Sign(
issuer,
TimeSpan.FromDays(90),
serialNumber);
return signedCert.CreateCopyWithPrivateKey(ecdsa);
}
}
public static X509Certificate2 BuildLocalhostTlsSelfSignedServer()
{
SubjectAltNameBuilder sanBuilder = new SubjectAltNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
sanBuilder.AddDnsName("localhost.localdomain");
sanBuilder.AddDnsName(Environment.MachineName);
using (RSA rsa = RSA.Create())
{
var request = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA256);
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
request.CertificateExtensions.Add(sanBuilder.BuildExtension());
return request.SelfSign(TimeSpan.FromDays(90));
}
}
public static byte[] CreateCertificateRenewal(RSA newKey, X509Certificate2 currentCert)
{
// Getting, and persisting, `newKey` is out of scope here.
var request = new CertificateRequest(
currentCert.SubjectName,
newKey,
HashAlgorithmName.SHA256);
foreach (X509Extension extension in currentCert.Extensions)
{
request.CertificateExtensions.Add(extension);
}
// Send this to the CA you're requesting to sign your certificate.
return request.EncodePkcs10SigningRequest();
}
public static X509Certificate2 RenewCertificate(X509Certificate2 currentCert)
{
using (RSA rsa = RSA.Create())
{
byte[] certificateSigningRequest = CreateCertificateRenewal(rsa, currentCert);
X509Certificate2 signedCertificate = SendRequestToCAAndGetResponse(certificateSigningRequest);
return signedCertificate.CreateCopyWithPrivateKey(rsa);
}
}
}
}
@terrajobst Notes of what changed after the review meeting (and a couple of hours more whiteboarding with @morganbr and @ChadNedzlek) is with the updated API proposal in the top entry.
@bartonjs , this looks very good. I really like that the samples now really only contain business logic rather than ceremony to use the class for the major scenarios our users are likely to have.
Based on new data from Windows (and their lack of support for FIPS 186-3 DSA certificates) I'm going to pull the DSA typed constructor and leave DSA as a "power user" scenario (custom X509SignatureGenerator class, etc)
Review feedback:
CreateCopyWithPrivateKey
to CopyWithPrivateKey
or CreateWithPrivateKey
. CreateCopy
is a tautology.SubjectAltNameBuilder
to SubjectAlternativeNameBuilder
. We don't do abbreviations.BuildExtension
to just Build
BuildExtension
overloads with optional parameterpublic sealed partial class CertificateRequest
{
public CertificateRequest(X500DistinguishedName subjectName, ECDsa key, HashAlgorithmName hashAlgorithm);
public CertificateRequest(X500DistinguishedName subjectName, RSA key, HashAlgorithmName hashAlgorithm);
public CertificateRequest(X500DistinguishedName subjectName, PublicKey publicKey, HashAlgorithmName hashAlgorithm);
public CertificateRequest(string subjectName, ECDsa key, HashAlgorithmName hashAlgorithm);
public CertificateRequest(string subjectName, RSA key, HashAlgorithmName hashAlgorithm);
public Collection<X509Extension> CertificateExtensions { get; }
public HashAlgorithmName HashAlgorithm { get; }
public PublicKey PublicKey { get; }
public X500DistinguishedName SubjectName { get; }
public byte[] CreateSigningRequest();
public byte[] CreateSigningRequest(X509SignatureGenerator signatureGenerator);
public X509Certificate2 CreateSelfSigned(DateTimeOffset notBefore, DateTimeOffset notAfter);
public X509Certificate2 Create(X500DistinguishedName issuerName, X509SignatureGenerator generator, DateTimeOffset notBefore, DateTimeOffset notAfter, byte[] serialNumber);
public X509Certificate2 Create(X509Certificate2 issuerCertificate, DateTimeOffset notBefore, DateTimeOffset notAfter, byte[] serialNumber);
}
API Goals:
Non Goals:
API Proposal:
Changes from original version: