Search code examples
c#x509certificate2

How to provide X509KeyStorageFlags to CertificateRequest


It is possible to specify X509KeyStorageFlags in X509Certificate2 constructor what would look like this:

    var cert = new X509Certificate2(bytes, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

But in my case I use CertificateRequest to create self signed certificate so I do not have a certificate constructor:

var rsaKeyPair = RSA.Create();
var request = new CertificateRequest(
                      $"cn = {Environment.MachineName}",
                      rsaKeyPair,
                      HashAlgorithmName.SHA256,
                      RSASignaturePadding.Pkcs);

request.CertificateExtensions.Add(
                new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment |
                                          X509KeyUsageFlags.DigitalSignature, true));

var certificate = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow),
                                           new DateTimeOffset(DateTime.UtcNow.AddYears(2)));

Is there a way to add KeyStorageFags to request.CertificateExtension or something like that?


Solution

  • How to provide X509KeyStorageFlags to CertificateRequest

    CertificateRequest doesn't understand anything about key storage. All it does is build the certificate, then call cert.CopyWithPrivateKey(privateKey).

    Moving the target:

    How to provide X509KeyStorageFlags to CopyWithPrivateKey

    CopyWithPrivateKey copies the key in its current state. Ephemeral keys stay ephemeral. Persisted keys stay persisted.

    Moving the target:

    How to provide X509KeyStorageFlags to RSA.Create()

    RSA.Create() always creates ephemeral keys, it doesn't understand key storage at all. You'd have to use a specific RSA implementation that understands persistence -- but that then locks you into Windows (of course, you could have your calling code vary on OS).

    RSA rsaKeyPair = BuildMachinePersistedKeyPair(2048);
    
    ...
    
    private static RSA BuildMachinePersistedKeyPair(int keySize)
    {
        CngKeyCreationParameters creationParameters = new CngKeyCreationParameters()
        {
            ExportPolicy = CngExportPolicies.Exportable,
            Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
            KeyCreationOptions =
                CngKeyCreationOptions.OverwriteExistingKey |
                CngKeyCreationOptions.MachineKey,
        };
    
        CngProperty keySizeProperty = new CngProperty(
            KeyPropertyName.Length,
            BitConverter.GetBytes(keySize),
            CngPropertyOptions.None);
    
        creationParameters.Parameters.Add(keySizeProperty);
    
        using (CngKey cngKey = CngKey.Create(CngAlgorithm.Rsa, Guid.NewGuid().ToString(), creationParameters))
        {
            return new RSACng(cngKey);
        }
    }
    

    Is there an easier way?

    Sure, just export/import the ephemeral one.

    X509Certificate2 certificate;
    
    using (X509Certificate2 ephemeral = request.CreateSelfSigned(...))
    {
        certificate = new X509Certificate2(
            ephemeral.Export(X509ContentType.Pkcs12),
            string.Empty,
            X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
    }