dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.

Home Page:https://docs.microsoft.com/dotnet/core/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Create a X509Certificate2 from raw PEM files with Certificate and RSA Private Key

viksabnis opened this issue · comments

the example is described in 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;

certs.zip

@VikSab I'm not sure what change you're hoping for?

Currently i am only able to create the
X509Certificate2 certificate - but it has no PrivateKey.

The Example in the link is able to merge the 2 certificates (in the attachment) and produce a X509Certificate2 with both a public and private keys

The PrivateKey property will be back in netstandard2.0 (https://github.com/dotnet/corefx/issues/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).

openssl pkcs12 -in publicCert.pem -inkey privateKey.pem -export -out merged.pfx

To support this behavior we'd probably want to make a new API and decide on what level of side effects we're willing to accept with it. And any new API would have to go through the API review process.

so this will be a gap from previous .net releases (or from the full version of .net) -

the current API allows for getting Private and Private keys with ECDSA and RSA methods (using extension methods GetECDsaPrivateKey and GetRSAPrivateKey) but there is no mechanism to change these keys after creation.

Is this something that is being considered post .netcore 2.

It is a gap we are aware of, but closing that gap is not on our radar. (You're the first person to ever ask about it).

We'd need a new API for it (which anyone can propose) and it should be decided how it would handle the following cases (all of which act differently with the current API on Windows):

  • Providing the wrong key (new key's public key doesn't match the cert public key)
  • Providing a public key as the private key
  • Calling SetKey on a cert which already has a private key
    • What if it's the exact same key? (persisted address is the same)
    • What if it's only mathematically the same?
  • Calling SetKey on a cert that was loaded from an X509Store
  • Calling SetKey on a cert then saving it into an X509Store.
  • Calling SetKey on a cert with a persisted key
  • Calling SetKey on a cert with an ephemeral key
  • Should it mutate the current instance (which has consequences for many of the questions above), or return a new one?

For a one (or two) input method this is a lot of things to consider, and this is just the list off the top of my head. While most people seem to want it as an ephemeral cert instance with an ephemeral key, the consequences of the other cases need to be considered.

However - this is a solved problem - case in point (openssl pkcs12 -in publicCert.pem -inkey privateKey.pem -export -out merged.pfx)

The current solution creates an Immutable X509Certificate2 - as its constructor only the public key

How about a constructor that creates an Immutable by giving both the public and private key, which could mimic that behaviour

I understand if you would want to close this issue
Thanks

How about a constructor that creates an Immutable by giving both the public and private key, which could mimic that behavior

The behavior would still be something that needs to be worked out if the cert instance is later added to a cert store. If not for the cert stores it'd be a relatively easy problem 😄.

@VikSab I just wrote a function to solve this problem with bouncy castle: CertificateFactory. The function CreateCertificate wraps the cert and private key in a PKCS#12 store to to return a X509Certificate with PrivateKey. Maybe this helps..

As Linux extensively uses PEM file pairs with the certificate and private key, I second the desire for such a feature. It is downright critical for cross-platform code.

closing that gap is not on our radar. (You're the first person to ever ask about it).

I would hypothesize that the reason you do not see feature requests for this (and other cryptographic features) is that, to date, multiplatform .NET code has been few and far in between. People probably give up on their cryptographic ambitions when they see the sorry state of Mono crypto support! We actually offload crypto from a Linux service to a Windows machine because of Mono's lack of functionality in this area...

Being able to close the gap would with .NET Core be very welcome.

This ended up being needed anyways for a different 2.0 feature, but it won't allow mutation, still.

X509Certificate2 certWithPrivateKey = cert.CopyWithPrivateKey(privateKey);

if (cert.HasPrivateKey) throw new UhOh();
if (!certWithPrivateKey.HasPrivateKey) throw new DifferentUhOh();

Part of dotnet/corefx#17892

Do I understand it right that CopyWithPrivateKey is essentially the replacement for certificate.PrivateKey = prov; in the issue description?

@sandersaares Yeah. There are quite a number of mutation-based side-effects that CopyWithPrivateKey avoids, and CopyWithPrivateKey works for more than RSACryptoServiceProvider/DSACryptoServiceProvider; but it fills the role of letting a cert and a key be combined.

I admit that it doesn't quite hit "open cert.key and do something useful with it" mark, since the key file parsing is left as an exercise to the reader; but it has at least made it possible to do.

If you were hoping for something like RSAParameters.FromPkcs8(keyfile) (or some other form of inbox loading of a .key file) that's perfectly reasonable, but would be great if you could open a new issue for.

Another point - when provisioning certificates from the Azure Key Vault onto a Linux VM, they come down as PEM files. I'd prefer not to have to run a separate step of combining them again if they're already there.

I'm using cert-manager in my kubernetes cluster with a let's encrypt issuer. Cert-manager also provides the certificate and theprivate key in separate pem files. There should be something like new X509Certificate2(string pathToPEMCertificate, string pathToPEMPRivateKey) in .NET that does not require additional libraries or tools.

Got bitten with this issue earlier. Pasting this code for future people who needs loading pem certificates generated by Kubernetes cert-manager:

        public static async Task LoadPemCertificate(string certificatePath, string privateKeyPath)
        {
            using var publicKey = new X509Certificate2(certificatePath);

            var privateKeyText = await File.ReadAllTextAsync(privateKeyPath);
            var privateKeyBlocks = privateKeyText.Split("-", StringSplitOptions.RemoveEmptyEntries);
            var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
            using var rsa = RSA.Create();

            if (privateKeyBlocks[0] == "BEGIN PRIVATE KEY")
            {
                rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
            }
            else if (privateKeyBlocks[0] == "BEGIN RSA PRIVATE KEY")
            {
                rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
            }

            var keyPair = publicKey.CopyWithPrivateKey(rsa);
            Certificate = new X509Certificate2(keyPair.Export(X509ContentType.Pfx));
        }

        public static async Task LoadCertificates()
        {
            var configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: false)
                .AddJsonFile($"appsettings.{EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables()
                .Build();

            var certificateConfiguration = configuration.GetSection("Certificate");
            var folderPath = certificateConfiguration["Folder"];
            var certificateFileName = certificateConfiguration["PemFileName"];
            var privateKeyFileName = certificateConfiguration["PrivateKeyFileName"];

            // OpenSSL / Cert-Manager / Kubernetes-style Certificates
            if (certificateFileName != null && privateKeyFileName != null)
            {
                var certificatePath = Path.Combine(folderPath, certificateFileName);
                var privateKeyPath = Path.Combine(folderPath, privateKeyFileName);
                await LoadPemCertificate(certificatePath, privateKeyPath);
                return;
            }
            else // Windows-style Certificate
            {
                var pfxPath = Path.Combine(folderPath, certificateConfiguration["PfxFileName"]);
                var pfxPassword = certificateConfiguration["PfxPassword"];
                Certificate = new X509Certificate2(pfxPath, pfxPassword);
            }
        }
{
  "Certificate": {
    "Folder": "D:\\VS\\MyApp",
    "PemFileName": "localhost+2.pem",
    "PrivateKeyFileName": "localhost+2-key.pem",
    "PfxFileName": null,
    "PfxPassword": null
  }
}

There should be a built-in .NET method to load these cert files...