Search code examples
c#bouncycastlex509

Convert .crt + .key files to X509Certificate2 programmatically in C#


I have a .crt certificate and a .key private key file on a Linux machine. The private key is in encrypted PKCS#8 format (BEGIN ENCRYPTED PRIVATE KEY...). I would like to import these into an X509Certificate2 object for further use. Since we're on Linux, we're using .NET Core 2.2 (we cannot migrate to 3.0 yet).

I have explored a few possible solutions, detailed below:

  1. Use openssl to convert the files to a .pfx and import that using X509Certificate2
    • I do not want to use this option since I don't want to execute shell code from within C#. I would like the solution to be completely programmatically achieved in C#.
  2. Use the C# BouncyCastle libraries to do either:
    • A conversion of both the certificate and the key to .pfx (as above), or
    • Importing the certificate and private key separately and using X509Certificate2.CopyWithPrivateKey() to combine them.
    • However, I cannot find an API for the C# version of BouncyCastle, so I'm not sure what methods I could possibly use to do this.
  3. Some other programmatic method in C# that I'm missing here

Essentially, the end goal is to obtain an X509Certificate2 object from the .crt and .key files. Any help/insight into what approach to use, or even a pointer to helpful BouncyCastle documentation, would be much appreciated. Thanks!


Solution

  • This is possible, though not as friendly as it could be, in .NET Core 3.0:

    private static byte[] UnPem(string pem)
    {
        // This is a shortcut that assumes valid PEM
        // -----BEGIN words-----\nbase64\n-----END words-----
        const string Dashes = "-----";
        int index0 = pem.IndexOf(Dashes);
        int index1 = pem.IndexOf('\n', index0 + Dashes.Length);
        int index2 = pem.IndexOf(Dashes, index1 + 1);
    
        return Convert.FromBase64String(pem.Substring(index1, index2 - index1));
    }
    
    ...
    
    string keyPem = File.ReadAllText("private.key");
    byte[] keyDer = UnPem(keyPem);
    X509Certificate2 certWithKey;
    
    using (X509Certificate2 certOnly = new X509Certificate2("certificate.cer"))
    using (RSA rsa = RSA.Create())
    {
        // For "BEGIN PRIVATE KEY"
        rsa.ImportPkcs8PrivateKey(keyDer, out _);
        certWithKey = certOnly.CopyWithPrivateKey(rsa);
    }
    
    using (certWithKey)
    {
        Console.WriteLine(certWithKey.HasPrivateKey);
    }
    

    RSA private keys can be in three different formats, and you need to call the correct import for each one:

    • "BEGIN PRIVATE KEY": ImportPkcs8PrivateKey
    • "BEGIN ENCRYPTED PRIVATE KEY": ImportEncryptedPkcs8PrivateKey
    • "BEGIN RSA PRIVATE KEY": ImportRSAPrivateKey