Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upCertificate Creation API #17892
Comments
bartonjs
added
the
area-System.Security
label
Apr 4, 2017
bartonjs
added this to the 2.0.0 milestone
Apr 4, 2017
bartonjs
self-assigned this
Apr 4, 2017
This comment has been minimized.
This comment has been minimized.
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);
}
}
}
} |
bartonjs
added
the
api-needs-work
label
Apr 4, 2017
This comment has been minimized.
This comment has been minimized.
@terrajobst Did you want to record the initial review notes? |
This comment has been minimized.
This comment has been minimized.
Comments:
Other than that, it looks OK to me, considering our crypto stack will never win a price for usability :-) |
terrajobst
added
api-approved
and removed
api-needs-work
labels
Apr 4, 2017
This comment has been minimized.
This comment has been minimized.
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);
}
}
}
} |
This comment has been minimized.
This comment has been minimized.
@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. |
This comment has been minimized.
This comment has been minimized.
@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. |
This comment has been minimized.
This comment has been minimized.
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) |
bartonjs
added
api-needs-work
api-ready-for-review
and removed
api-approved
api-needs-work
labels
Apr 8, 2017
This comment has been minimized.
This comment has been minimized.
Review feedback:
public 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);
} |
bartonjs commentedApr 4, 2017
•
edited
API Goals:
Non Goals:
API Proposal:
Changes from original version: