Search code examples
c#.netrsax509certificate2

How to persist private key of RSAParameter in C#/.net


Starting with .Net 4.7.2 (.Net Standard 2.0) it's possible to create self-signed certificates and certificate signing requests with C#/.Net only, see the MS Documentation.

While it's straight forward to create a self-signed certificate which will assert HasPrivateKey (you just call CreateSelfSigned(notBefore, notAfter)) I'm having a hard time to figure out how to get hold of the private key in general, e.g. if I want to create a certificate signed by a CA and then want to persist the certificate as a PFX file or want to persist the private key in a .PEM file or want to store it in the MS certificate store together with the private key, or when I just want to have in memory and also assert HasPrivateKey.

What I do have is a 'RSAParameters' instance which is in possession of the relevant private information, but I failed to figure out how to (easily) use that for the purpose in question (create a PFX file or PEM file or MS Certificate Store entry) without having to read through all the relevant RFCs and write a program for that on my own. (That RSAParameter instance contains the D, Exponent and Modulus, so I could try to patch this together (with the help of this answer, hopefully), but I was hoping for a C# method which will perform these tasks for me (which I could not find) by now).

Of course the idea is to do that with .Net functionality alone, as well.

Every hint on how to achieve this is appreciated.


Solution

  • If you only have Modulus, Exponent, and D you first have to recover the CRT parameters (P, Q, DP, DQ, InverseQ).

    As your other questions, you're mainly missing the cert.CopyWithPrivateKey(key) extension methods and rsa.ImportParameters(RSAParameters):

    if I want to create a certificate signed by a CA and then want to persist the certificate as a PFX file

    using (RSA rsa = RSA.Create())
    {
        rsa.ImportParameters(rsaParameters);
    
        using (X509Certificate2 caSigned = GetCASignedCert(rsa))
        using (X509Certificate2 withKey = caSigned.CopyWithPrivateKey(rsa))
        {
            File.WriteAllBytes("some.pfx", withKey.Export(X509ContentType.Pkcs12, "and a password"));
        }
    }
    

    or want to persist the private key in a .PEM file

    This one is available in .NET Core 3.0 daily builds:

    RSAParameters rsaParameters = default(RSAParameters);
    
    using (StreamWriter writer = new StreamWriter("rsa.key"))
    using (RSA rsa = RSA.Create())
    {
        rsa.ImportParameters(rsaParameters);
    
        writer.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
    
        writer.WriteLine(
            Convert.ToBase64String(
                rsa.ExportRSAPrivateKey(),
                Base64FormattingOptions.InsertLineBreaks));
    
        writer.WriteLine("-----END RSA PRIVATE KEY-----");
    }
    

    PKCS#8 and encrypted PKCS#8 are also available.

    On existing versions this requires using the RSAParameters and a ITU-T X.690 DER encoder.

    or want to store it in the MS certificate store together with the private key

    using (RSA rsa = RSA.Create())
    {
        rsa.ImportParameters(rsaParameters);
    
        using (X509Certificate2 caSigned = GetCASignedCert(rsa))
        using (X509Certificate2 withKey = caSigned.CopyWithPrivateKey(rsa))
        using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
        {
            X509Certificate2 persisted = new X509Certificate2(
                withKey.Export(X509ContentType.Pkcs12, ""),
                "",
                X509KeyStorageFlags.PersistKeySet);
    
            using (persisted)
            {
                store.Open(OpenFlags.ReadWrite);
                store.Add(persisted);
            }
        }
    }
    

    or when I just want to have in memory and also assert HasPrivateKey.

    using (RSA rsa = RSA.Create())
    {
        rsa.ImportParameters(rsaParameters);
    
        using (X509Certificate2 caSigned = GetCASignedCert(rsa))
        {
            // Yes, this value can outlive both usings
            return caSigned.CopyWithPrivateKey(rsa);
        }
    }