39

I'm working on some code that creates a X509certificate and a public/private key pair. The public key is added to the certificate and it is sent to an CA which signs it.

The returned certificate is then accessed through the System.Security.Cryptography.X509Certificates.X509Certificate2 class. Now I want to use this certificate to initiate a secure connection with other clients. Therefore I use the SslStream class. To start the SSL Handshake I use this method:

server.AssociatedSslStream.AuthenticateAsServer(
                        MyCertificate,                      // Client Certificate
                        true,                               // Require Certificate from connecting Peer
                        SslProtocols.Tls,                   // Use TLS 1.0
                        false                               // check Certificate revocation
                    );

This method requires that the private key is associated with the certificate. Of course the certificate returned by the CA does not contain a private key. But it is stored as .key file on the harddrive. The X509Certificate2 class has a property called PrivateKey which I guess will associate a private key with the certificate, but I can't find a way to set this property.

Is there any way I can associate the private key with the .net X509 class?

PogoMips
  • 3,497
  • 8
  • 27
  • 34
  • Does this answer your question? [Providing an EC private key to certificate for use in HttpClient C#](https://stackoverflow.com/questions/54689349/providing-an-ec-private-key-to-certificate-for-use-in-httpclient-c-sharp) – Jim G. Jan 20 '20 at 21:27

6 Answers6

52

You can save yourself the hassle of copy-pasting all that code and store the private key next to the certificate in a pfx/pkcs#12 file:

openssl pkcs12 -export -in my.cer -inkey my.key -out mycert.pfx

You'll have to supply a password, which you have to pass to the constructor of X509Certificate2:

X509Certificate2 cert = new X509Certificate2("mycert.pfx","password");
sschober
  • 2,003
  • 3
  • 24
  • 38
30

For everyone else with the same problem, I found a neat little piece of code that let's you do exactly that:

http://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back

byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
byte[] keyBuffer  = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);

X509Certificate2 certificate = new X509Certificate2(certBuffer, password);

RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;

EDIT: The code for the Helper method (which otherwise requires a codeproject login) is as follows:

public static byte[] GetBytesFromPEM(string pemString, PemStringType type)
{
    string header; string footer;
    switch (type)
    {
        case PemStringType.Certificate:
            header = "-----BEGIN CERTIFICATE-----";
            footer = "-----END CERTIFICATE-----";
            break;
        case PemStringType.RsaPrivateKey:
            header = "-----BEGIN RSA PRIVATE KEY-----";
            footer = "-----END RSA PRIVATE KEY-----";
            break;
        default:
            return null;
    }

    int start = pemString.IndexOf(header) + header.Length;
    int end = pemString.IndexOf(footer, start) - start;
    return Convert.FromBase64String(pemString.Substring(start, end));
}

Update

As of .NET 5 you can simply use CreateFromPem(ReadOnlySpan, ReadOnlySpan):

Creates a new X509 certificate from the contents of an RFC 7468 PEM-encoded certificate and private key.

example:

X509Certificate2 cert = X509Certificate2.CreateFromPem(
                certPem, // The text of the PEM-encoded X509 certificate.
                keyPem // The text of the PEM-encoded private key.
            );

Or if you have a string with both the cert and its private key, you can pass it in for both the cert arg and the key arg:

X509Certificate2 cert = X509Certificate2.CreateFromPem(
                certPem, // The text of the PEM-encoded X509 certificate.
                keyPem // The text of the PEM-encoded private key.
            );
Toolmaker
  • 554
  • 6
  • 22
PogoMips
  • 3,497
  • 8
  • 27
  • 34
  • 1
    This is a better way than to bundle the private key and certificate together, I've used this option and find it a superior alternative. – Alejandro Sep 02 '16 at 13:42
  • 1
    If you'd be so kind as to enlighten us about the nature of this superiority? – sschober Nov 18 '16 at 20:35
  • 2
    do include the code for the helper methods, annoying to have to create a user on an external site to get access. – Poul K. Sørensen Feb 10 '17 at 22:50
  • 11
    Trying to use this, but (of course) there is no Crypto Class (there is one in System.Web.Helpers - pretty sure not it..) Got a using missing here? – Traderhut Games Jun 21 '17 at 21:53
  • 1
    Too short of an answer (and yes having to register to get the rest of the code is unacceptable), but this is a good starting point. And @TraderhutGames I did some digging and here's what we're looking for: https://stackoverflow.com/questions/1162504/decrypting-with-private-key-from-pem-file-in-c-sharp-with-net-crypto-library – starmandeluxe Jun 28 '17 at 05:47
  • I ended up getting the Secret from the Azure Key Vault (where I was trying to make my certificate from) and then simply doing this: X509Certificate2 cert = new X509Certificate2(Convert.FromBase64String(value), "", X509KeyStorageFlags.MachineKeySet |X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); – Traderhut Games Jun 28 '17 at 14:58
  • What is "value" in your constructor? I'm guessing the private key? Azure Key Vault is a great option and was initially what I also wanted to use, but there is a huge limitation in that your AD application must be in the same directory as the Key Vault itself. My circumstances don't allow this unfortunately. – starmandeluxe Jun 29 '17 at 03:43
  • 6
    I get a `PlatformNotSupportedException` thrown when trying to set the private key of a certificate in a .NET Core 2 console application. – Peter Waher May 26 '18 at 10:24
  • As stated above, you may want to bundle the private key and certificate together. However, if you do need to employ this solution, you may need the following enumeration to get it to work: `public enum PemStringType { Certificate = 1, RsaPrivateKey = 2 }` – Chad Aug 12 '19 at 15:42
8

my solution

 byte[] PublicCertificate = Encoding.Unicode.GetBytes("-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----");
 var publicCertificate = new X509Certificate2(PublicCertificate );
 byte[] PrivateKey = Convert.FromBase64String("MIIEvQIBA...=");
 using var rsa = RSA.Create();
 rsa.ImportPkcs8PrivateKey(PrivateKey, out _);
 publicCertificate = publicCertificate.CopyWithPrivateKey(rsa);
 publicCertificate = new X509Certificate2(publicCertificate.Export(X509ContentType.Pkcs12));

 var client = new RestClient("api_url");
 client.ClientCertificates = new X509Certificate2Collection();
 client.ClientCertificates.Add(publicCertificate);
lin
  • 153
  • 1
  • 10
  • 1
    `ImportPkcs8PrivateKey` is .NET Core - not available in .NET Framework 4.8? – Jaans Aug 30 '21 at 11:17
  • @Jaans https://learn.microsoft.com/pt-br/dotnet/api/system.security.cryptography.rsa.importpkcs8privatekey?view=net-5.0 – lin Aug 30 '21 at 16:39
  • 2
    @lin, Your solution helped to eliminate my https://stackoverflow.com/q/71343850/7038630 error. The only change I made was to replace "ImportPkcs8PrivateKey" with "ImportRSAPrivateKey". And here https://stackoverflow.com/a/70132607/7038630 is the answer why I changed "ImportPkcs8PrivateKey" to "ImportRSAPrivateKey". – lukaszFD Mar 04 '22 at 12:33
1

for .NET Framework 4.8

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

...

private X509Certificate2 GetCert(string clientCertFile, string privateKeyFile) {
    var tempCertificate = new X509Certificate2(clientCertFile);

    StreamReader reader = new StreamReader(privateKeyFile);
    PemReader pemReader = new PemReader(reader);
    AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
    AsymmetricKeyParameter privateKey = keyPair.Private;
    RSA rsa = DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters) privateKey);
            
    tempCertificate = tempCertificate.CopyWithPrivateKey(rsa);

    return new X509Certificate2(tempCertificate.Export(X509ContentType.Pkcs12));
}
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
Neano
  • 11
  • 1
0

My .net 45 solution thanks to Lee Taylor and Neano

using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Parameters;
using System.IO;
using System.Security.Cryptography.X509Certificates;

private X509Certificate2 GetCert(string  certPath,string keyPath)
{
  X509Certificate2 cert = new X509Certificate2(certPath);
  StreamReader reader = new StreamReader(keyPath);
  PemReader pemReader = new PemReader(reader);
  RsaPrivateCrtKeyParameters keyPair=(RsaPrivateCrtKeyParameters)pemReader.ReadObject();
  RSA rsa = DotNetUtilities.ToRSA(keyPair);
  cert.PrivateKey = rsa;
  return new X509Certificate2(cert.Export(X509ContentType.Pfx));
}