Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
viksabnis opened this issue Dec 7, 2016 · 15 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Security help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@viksabnis
Copy link

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

@bartonjs
Copy link
Member

bartonjs commented Dec 7, 2016

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

@viksabnis
Copy link
Author

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

@bartonjs
Copy link
Member

bartonjs commented Dec 7, 2016

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.

@viksabnis
Copy link
Author

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.

@bartonjs
Copy link
Member

bartonjs commented Dec 8, 2016

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.

@viksabnis
Copy link
Author

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

@bartonjs
Copy link
Member

bartonjs commented Dec 8, 2016

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 😄.

@mregen
Copy link

mregen commented Dec 8, 2016

@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..

@sandersaares
Copy link

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.

@bartonjs bartonjs self-assigned this Apr 12, 2017
@bartonjs
Copy link
Member

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

@sandersaares
Copy link

sandersaares commented Apr 19, 2017

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

@bartonjs
Copy link
Member

@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.

@bartonjs bartonjs removed their assignment Apr 24, 2017
@liamdawson
Copy link

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.

@oocx
Copy link

oocx commented Dec 31, 2019

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.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 2.0.0 milestone Jan 31, 2020
@ryanelian
Copy link

ryanelian commented Feb 2, 2020

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...

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Security help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

No branches or pull requests

8 participants