简体   繁体   中英

Combining Public Certificate with Private RSA Key in C#

I received from a client public certificate and private RSA key, as follows (only partially displayed here):

-----BEGIN CERTIFICATE-----
MIIFKDCCAxACAQEwDQYJKoZIhvcNAQELBQAwbjELMAkGA1UEBhMCVVMxETAPBgNV
BAgMCElsbGlub2lzMRAwDgYDVQQHDAdDaGljYWdvMRMwEQYDVQQKDApDbG91ZFF1
YW50MSUwIwYJKoZIhvcNAQkBFhZjcWFpb3BzQGNsb3VkcXVhbnQuY29tMB4XDTIw
MDkyMzIwMTgyN1oXDTQwMDkxODIwMTgyN1owRjELMAkGA1UEBhMCVVMxETAPBgNV
iESEKmYvylUwce7TcOuVnLtufyXxr8egu43jPvWDHsK0QvhMbx0q2KvyxGneQJ5E
...........
U0oIrq7M0qZTAf1BXEw9wgfQlIKfLzWDbIYKIg==
-----END CERTIFICATE-----

-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAuxHk8lBZaqRTzhi/jvlj59pehTkK/u2fVHLuHWZPGOvPiAjq
HqrAgM1urPpmQPC2QuWObmhvQ1uluo/tq6V56WZNUEYALZnXhGvVNrvnYfoFooI0
F+1dJI2xQ8AC11Khinj+yAPtKymMhZFOHzQaFnGvjhKWTn/I0MaoJc+TQ8JKbDQ0
ycvopM2finujmIb62cxxhkhkEC5T+yMUJ8MbCnfmWpYux/oU2CXSHrnPympCbx7x
cReH0BeZEZPMI7+1Yi7U+XKGc7RP+zt9AoIBAQDE0BZb3WfertNqmyj9TF73VAwq
SDtSa6MTC8bOdtQewCz9/zC13MSI+jZGRDKSh7nxNL+bgM0OgmM6n/5B/61SIRjM
hswgOU9AHewIFSB/5C/ZcxWqM+PrgYXXYfOl9ZeWs1x+YRKuqk/CW/Z2rHJXykNx
.............
czG95J+2TWdGAjPuFLA596PRXT5KN2ITOWXUym3UksHmonbJ9om+k0ckPr4J
-----END RSA PRIVATE KEY-----

Initially, I scanned many postings here and realized that the easiest way to combine the puplic PEM certificate with private RSA key (PEM format) is by using .NET 5:

// Needs .NET 5, that is .NET Core and VS2019 16.8 or higher
X509Certificate2 X509Cert = X509Certificate2.CreateFromPemFile("cert.cer", "cert.key");

X509Certificate2 cert = new X509Certificate2(mX509Cert.Export(X509ContentType.Pfx, "12345678"), "12345678");

This worked great. However, later on I realized that the type of project I am targeting (VSTO Excel Add-In) is not supporting .NET 5 yet... Therefore, I changed the above code to the following, (which also worked) not realizing that it also requires .NET 5...

            X509Certificate2 pubOnly = new X509Certificate2("cert.cer");

            RSA rsa = RSA.Create();
            rsa.ImportRSAPrivateKey(keyBuffer, out _);
            X509Certificate2 pubPrivEphemeral = pubOnly.CopyWithPrivateKey(rsa);

            X509Certificate2 cert = new X509Certificate2(pubPrivEphemeral.Export(X509ContentType.Pfx, "12345678"), "12345678");

Then when I finally switched to .NET 4.7.2 and now 4.8 I was looking for compatible way to do the same, and tired several methods offered here, but in vain, none did not work - they had complex routines that threw exceptions...

So I am trying now BouncyCastle that helped a lot in the past and came up with this code:

                string cerFilePath = Path.Combine(InstallDir, "cert.cer");
                X509Certificate2 dotnetCertificate2 = new X509Certificate2(cerFilePath);

                string keyFilePath = Path.Combine(InstallDir, "cert.key");
                var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(File.OpenText(keyFilePath));
                var pemObject = pemReader.ReadObject();
                var rsa = DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)pemObject);

Unfortunately, the last line throws exception - there is something incompatible here and I do not know what it is... The exception message states:

"Unable to cast object of type 'Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair' to type 'Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters'."

So if anyone can either correct me with the above BouncyCastle code or show me pure .NET 4.6 - 4.8 code - I'd really appreciate.

EDIT: So I did find another code from here

private X509Certificate2 CreateCertWithKey()
    {
        using (Log.VerboseCall())
        {
            X509Certificate2 x509Cert = null;

            try
            {
                string cerFilePath = Path.Combine(InstallDir, "cert.cer");
                using (TextReader tr = new StreamReader(cerFilePath))
                {
                    publicPemCert = tr.ReadToEnd();
                }

                string keyFilePath = Path.Combine(InstallDir, "cert.key");
                using (TextReader tr = new StreamReader(keyFilePath))
                {
                    privatePemKey = tr.ReadToEnd();
                }

                var keyPair = (AsymmetricCipherKeyPair)new PemReader(new StringReader(privatePemKey)).ReadObject();
                var cert = (Org.BouncyCastle.X509.X509Certificate)new PemReader(new StringReader(publicPemCert)).ReadObject();

                var builder = new Pkcs12StoreBuilder();
                builder.SetUseDerEncoding(true);
                var store = builder.Build();

                var certEntry = new X509CertificateEntry(cert);
                store.SetCertificateEntry("", certEntry);
                store.SetKeyEntry("", new AsymmetricKeyEntry(keyPair.Private), new[] { certEntry });

                byte[] data;
                using (var ms = new MemoryStream())
                {
                    //store.Save(ms, Array.Empty<char>(), new SecureRandom());
                    data = ms.ToArray();

                    x509Cert = new X509Certificate2(data);
                }
            }
            catch(Exception ex)
            {
                Log.Verbose(ex);
            }

            return x509Cert;
        }
    }

It runs without problem but with the generated certificate my HttpWebRequest does not work, claiming that TSL/SSL connection cannot be created (and I tries 1.1, 1.2, and 1.3, although I recall that just Tls negotiates the version by itself... ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

The .NET 5 code ran without a problem with exactly the same code for HttpWebRequest...

Just came across this while working through similar issues and I found a way to create the X509Certificate2 object that includes the private key (which I use for XML Signing). It requires that you create a PFX record using the public/private keys, I used OpenSSL:

openssl pkcs12 -export -inkey private-key.pem -in cert.pem -out cert.pfx

You can either open just instantiate the instance directly with the file name:

var cert = new X509Certificate2(filePath);

Or if you have already opened the file somewhere and stored it in base64 format, you can use the bytes:

var bytes = Convert.FromBase64String(pfx_base64);
var cert = new X509Certificate2(bytes);

A lot of credit for this goes to reading Scott Brady's blog.

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