Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load a certificate with private key from PEM files in .NET standard

I'm trying to load an X509Certificate2 from PEM files in a .NET standard library.

I created a self-signed certificate using openssl like so:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj /CN=localhost -days 365

I loaded the resulting PEM files into embedded string resources within the project and am trying to load them with the following code:

private X509Certificate2 GetCertificate()
{
    try
    {
        byte[] pubPem = System.Text.Encoding.UTF8.GetBytes(Properties.Resources.DefaultPublicPem.Trim());
        var cert = new X509Certificate2(pubPem);
        var rsa = GetRSAFromPem(Properties.Resources.DefaultPrivatePem.Trim());
        cert.PrivateKey = rsa;
        return cert;
    }

    catch (Exception ex)
    {
        // ignore errors
        return null;
    }
}

public static RSA GetRSAFromPem(String pemstr)
{
    RSA rsaKey = RSA.Create();
    Func<RSA, RsaKeyParameters, RSA> MakePublicRCSP = (RSA rcsp, RsaKeyParameters rkp) =>
    {
        RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters(rkp);
        rcsp.ImportParameters(rsaParameters);
        return rsaKey;
    };

    Func<RSA, RsaPrivateCrtKeyParameters, RSA> MakePrivateRCSP = (RSA rcsp, RsaPrivateCrtKeyParameters rkp) =>
    {
        RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters(rkp);
        rcsp.ImportParameters(rsaParameters);
        return rsaKey;
    };

    PemReader reader = new PemReader(new StringReader(pemstr));
    object kp = reader.ReadObject();

    // If object has Private/Public property, we have a Private PEM
    var hasPrivate = kp.GetType().GetProperty("Private") != null;
    var isPrivate = kp is RsaPrivateCrtKeyParameters;
    return isPrivate ? MakePrivateRCSP(rsaKey, (RsaPrivateCrtKeyParameters)kp) : hasPrivate ? MakePrivateRCSP(rsaKey, (RsaPrivateCrtKeyParameters)(((AsymmetricCipherKeyPair)kp).Private)) : MakePublicRCSP(rsaKey, (RsaKeyParameters)kp);
}

I tested it on Android and it works great.

On iOS I haven't tested yet, but on UWP it fails and I get a PlatformNotSupported Exception while trying to set the PrivateKey on the certificate.

So I'm wondering what's not supported, and whether I'm not doing it right.

like image 824
Nir B. Avatar asked Dec 03 '18 07:12

Nir B.


People also ask

Does PEM include private key?

pem contains the private encryption key. cert.


2 Answers

According to this, setting the private key on an existing certificate is not supported in .net core:

The PrivateKey property will be back in netstandard2.0 (#12295), but it will throw on set for .NET Core.

set_PrivateKey has a large amount of nuance in .NET Framework (depending on how you use it you can end up with side effects that persist across machine reboots), and mirroring that level of nuance to platforms other than Windows is awfully tricky, which is why we don't support it.

The only supported way to have a cert with a private key on .NET Core is through a PFX/PKCS12 file (or the cert+key pair to already be associated via X509Store).

So one way to solve this was to merge public-private pair to a PFX file, embed it as a resource, and initialize the X509Certificate2 from that PFX.

Another way, which I ended up using, is to use the method RSACertificateExtensions.CopyWithPrivateKey on UWP.

So basically I ended up building a platform specific interface for loading the certificates. On UWP it was implemented like this:

public class UWPCertificateBuilder : ICertificateBuilder
{
    public X509Certificate2 GetCertificate(X509Certificate2 cert, RSA key)
    {
        return cert.CopyWithPrivateKey(key);
    }
}

On Android it was implemented like this:

public class DroidCertificateBuilder : ICertificateBuilder
{
    public X509Certificate2 GetCertificate(X509Certificate2 cert, RSA key)
    {
        cert.PrivateKey = key;
        return cert;
    }
}
like image 196
Nir B. Avatar answered Oct 12 '22 14:10

Nir B.


Instead of RSACryptoServiceProvider you should use base RSA class. In .NET Standard and .NET Core on Windows, RSA private key is resolved to RSACng, instead of legacy RSACryptoServiceProvider.

See this thread for more details: X509AsymmetricSecurityKey.GetAsymmetricAlgorithm returns null after .Net 4.7.2 upgrade

like image 35
Crypt32 Avatar answered Oct 12 '22 15:10

Crypt32