简体   繁体   中英

Associate a private key with the X509Certificate2 class in .net

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?

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

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

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

I struggled with the same problem as the original poster. I didn't found acceptable and complete solutions, so finally I will post my solution here (for demonstration everything is in one method):

public byte[] CreateCertificatePair(FileInfo signedP7bCertificate)
{
    X509Certificate2 selfSignedCertificate = GetSelfSignedCertificate();

    var certificateChain = new X509Certificate2Collection();
    certificateChain.Import(signedP7bCertificate.FullName);

    // Get the signed client certificate from certificate chain
    // (for certificate chain with intermediate certificate it would be position [2])
    X509Certificate2 x509Certificate2 = certificateChain[1];

    // Load the private key from the keypair needed for the certificate request
    RSA rsaPrivateKey = selfSignedCertificate.GetRSAPrivateKey();
    RSAParameters rsaParameters = rsaPrivateKey.ExportParameters(true);
    var cryptoServiceProvider = new RSACryptoServiceProvider();
    cryptoServiceProvider.ImportParameters(rsaParameters);

    // Add the private key to the signed certificate
    x509Certificate2.PrivateKey = cryptoServiceProvider;

    // Export the newly created keypair
    byte[] pkcs12KeyPair = x509Certificate2.Export(X509ContentType.Pkcs12);
    return pkcs12KeyPair;
}

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM