Associate a private key with the X509Certificate2 class in .net

62,134

Solution 1

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));
}

Solution 2

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");

Solution 3

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);
Share:
62,134
PogoMips
Author by

PogoMips

Updated on October 02, 2021

Comments

  • PogoMips
    PogoMips over 2 years

    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?

  • Dan Hastings
    Dan Hastings almost 9 years
    this saved me! created the pfx, but didnt realize i also needed to provide that password to the constructor.
  • Alejandro
    Alejandro over 7 years
    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.
  • sschober
    sschober over 7 years
    If you'd be so kind as to enlighten us about the nature of this superiority?
  • Poul K. Sørensen
    Poul K. Sørensen about 7 years
    do include the code for the helper methods, annoying to have to create a user on an external site to get access.
  • Traderhut Games
    Traderhut Games almost 7 years
    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?
  • starmandeluxe
    starmandeluxe almost 7 years
    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: stackoverflow.com/questions/1162504/…
  • Traderhut Games
    Traderhut Games almost 7 years
    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);
  • starmandeluxe
    starmandeluxe almost 7 years
    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.
  • Sean
    Sean over 6 years
    Outstanding answer! I've used this to avoid the hassle of registering the certificate via the MMC plugin.
  • Peter Waher
    Peter Waher almost 6 years
    I get a PlatformNotSupportedException thrown when trying to set the private key of a certificate in a .NET Core 2 console application.
  • Nate Hammond
    Nate Hammond about 5 years
    You can avoid the password by adding "-passout pass:" to the openSSL command
  • Chris Jensen
    Chris Jensen almost 5 years
    This should be marked as the real answer, as it is the real way to do it.
  • Chad
    Chad over 4 years
    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 }
  • user1034912
    user1034912 over 3 years
    where do i get openssl?
  • Jaans
    Jaans over 2 years
    ImportPkcs8PrivateKey is .NET Core - not available in .NET Framework 4.8?
  • lin
    lin over 2 years
  • lukaszFD
    lukaszFD about 2 years
    @lin, Your solution helped to eliminate my stackoverflow.com/q/71343850/7038630 error. The only change I made was to replace "ImportPkcs8PrivateKey" with "ImportRSAPrivateKey". And here stackoverflow.com/a/70132607/7038630 is the answer why I changed "ImportPkcs8PrivateKey" to "ImportRSAPrivateKey".