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

Question: how to generate a .pem file with private key from an X509Certificate2? #51597

Closed
heng-liu opened this issue Apr 20, 2021 · 10 comments
Closed
Labels
area-System.Security question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@heng-liu
Copy link

Description

May I know if there is any way to generate a .pem file with private key from an X509Certificate2?
I tried the following:
1.Create a self-issued X509Certificate2 certificate(cert) with private key (the key generation algorithm is RSA ), saved as attached file.

byte[] certbytes = cert.Export(X509ContentType.Pfx, "password"); 
File.WriteAllBytes(path, certbytes);

2.Wrote a console to:
1).Read the bytes, create an X509Certificate2 cert, Set a password to protect the cert.
2).Try to get the certificate, public key, private key to create a .pem file.

string path = "c:\\work\\0420\\cert";
            byte[] certbytes = File.ReadAllBytes(path);

            string password = "password";
            X509Certificate2 certWithPrivateKey = new X509Certificate2(certbytes, password, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

            byte[] certificateBytes = certWithPrivateKey.RawData;
            char[] certificatePem = PemEncoding.Write("CERTIFICATE", certificateBytes);

            using (AsymmetricAlgorithm key = certWithPrivateKey.GetRSAPrivateKey())
            {
                byte[] pubKeyBytes = key.ExportSubjectPublicKeyInfo();
                char[] pubKeyPem = PemEncoding.Write("PUBLIC KEY", pubKeyBytes);

                byte[] encryptedPrivKeyBytes = key.ExportEncryptedPkcs8PrivateKey(
                    password,
                    new PbeParameters(
                        PbeEncryptionAlgorithm.Aes256Cbc,
                        HashAlgorithmName.SHA256,
                        iterationCount: 100_000));
                char[] privKeyPem = PemEncoding.Write("PRIVATE KEY", encryptedPrivKeyBytes);
            }

I could see non empty char arrays for above certificatePem, pubKeyPem and privKeyPem. But I'm not sure how to combine them into a .pem file.
I tried to convert them into string and concatenate them, but the .pem file generated in this way failed to be used in CreateFromEncryptedPemFile as CryptographicException as following:
cert = X509Certificate2.CreateFromEncryptedPemFile(options.CertificatePath, options.CertificatePassword)
The exception details is:
image

I'm wondering if you know how to generate a .pem file with private key (with or without password) from an X509Certificate2 cert? Thanks!

Attached is the cert file from step1.
cert.zip

Configuration

Regression?

Other information

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Security untriaged New issue has not been triaged by the area owner labels Apr 20, 2021
@ghost
Copy link

ghost commented Apr 20, 2021

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq, @GrabYourPitchforks
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

May I know if there is any way to generate a .pem file with private key from an X509Certificate2?
I tried the following:
1.Create a self-issued X509Certificate2 certificate(cert) with private key (the key generation algorithm is RSA ), saved as attached file.

byte[] certbytes = cert.Export(X509ContentType.Pfx, "password"); 
File.WriteAllBytes(path, certbytes);

2.Wrote a console to:
1).Read the bytes, create an X509Certificate2 cert, Set a password to protect the cert.
2).Try to get the certificate, public key, private key to create a .pem file.

string path = "c:\\work\\0420\\cert";
            byte[] certbytes = File.ReadAllBytes(path);

            string password = "password";
            X509Certificate2 certWithPrivateKey = new X509Certificate2(certbytes, password, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

            byte[] certificateBytes = certWithPrivateKey.RawData;
            char[] certificatePem = PemEncoding.Write("CERTIFICATE", certificateBytes);

            using (AsymmetricAlgorithm key = certWithPrivateKey.GetRSAPrivateKey())
            {
                byte[] pubKeyBytes = key.ExportSubjectPublicKeyInfo();
                char[] pubKeyPem = PemEncoding.Write("PUBLIC KEY", pubKeyBytes);

                byte[] encryptedPrivKeyBytes = key.ExportEncryptedPkcs8PrivateKey(
                    password,
                    new PbeParameters(
                        PbeEncryptionAlgorithm.Aes256Cbc,
                        HashAlgorithmName.SHA256,
                        iterationCount: 100_000));
                char[] privKeyPem = PemEncoding.Write("PRIVATE KEY", encryptedPrivKeyBytes);
            }

I could see non empty char arrays for above certificatePem, pubKeyPem and privKeyPem. But I'm not sure how to combine them into a .pem file.
I tried to convert them into string and concatenate them, but the .pem file generated in this way failed to be used in CreateFromEncryptedPemFile as CryptographicException as following:
cert = X509Certificate2.CreateFromEncryptedPemFile(options.CertificatePath, options.CertificatePassword)
The exception details is:
image

I'm wondering if you know how to generate a .pem file with private key (with or without password) from an X509Certificate2 cert? Thanks!

Attached is the cert file from step1.
cert.zip

Configuration

Regression?

Other information

Author: heng-liu
Assignees: -
Labels:

area-System.Security, untriaged

Milestone: -

@vcsjones
Copy link
Member

You should be able to do something like this:

string certPem = new string(PemEncoding.Write("CERTIFICATE", cert.RawData));
string keyPem;

if (cert.GetRSAPrivateKey() is RSA rsaKey)
{
    keyPem = new string(PemEncoding.Write("PRIVATE KEY", rsaKey.ExportPkcs8PrivateKey()));
}
else if (cert.GetECDsaPrivateKey() is ECDsa ecdsaKey)
{
    keyPem = new string(PemEncoding.Write("PRIVATE KEY", ecdsaKey.ExportPkcs8PrivateKey()));
}
else if (cert.GetDSAPrivateKey() is DSA dsaKey)
{
    keyPem = new string(PemEncoding.Write("PRIVATE KEY", dsaKey.ExportPkcs8PrivateKey()));
}
else
{
    throw new CryptographicException("Unknown certificate algorithm");
}

Console.WriteLine(certPem);
Console.WriteLine(keyPem);

var certAgain = X509Certificate2.CreateFromPem(certPem, keyPem);

That will export the private key in to a PEM-encoded PKCS8 private key.

@vcsjones
Copy link
Member

If you want to use ExportEncryptedPkcs8PrivateKey then the PEM label would be ENCRYPTED PRIVATE KEY:

PbeParameters pbe = new PbeParameters(PbeEncryptionAlgorithm.Aes128Cbc, HashAlgorithmName.SHA256, 600_000);
var certPem = new string(PemEncoding.Write("CERTIFICATE", cert.RawData));
string keyPem;

if (cert.GetECDsaPrivateKey() is ECDsa ecdsaKey)
{
    keyPem = new string(PemEncoding.Write("ENCRYPTED PRIVATE KEY", ecdsaKey.ExportEncryptedPkcs8PrivateKey("test", pbe)));
}
else if (cert.GetRSAPrivateKey() is RSA rsaKey)
{
    keyPem = new string(PemEncoding.Write("ENCRYPTED PRIVATE KEY", rsaKey.ExportEncryptedPkcs8PrivateKey("test", pbe)));
}
else if (cert.GetDSAPrivateKey() is DSA dsaKey)
{
    keyPem = new string(PemEncoding.Write("ENCRYPTED PRIVATE KEY", dsaKey.ExportEncryptedPkcs8PrivateKey("test", pbe)));
}
else
{
    throw new CryptographicException("Unknown certificate algorithm");
}

Console.WriteLine(certPem);
Console.WriteLine(keyPem);

var certAgain = X509Certificate2.CreateFromEncryptedPem(certPem, keyPem, "test");

@vcsjones
Copy link
Member

vcsjones commented Apr 21, 2021

And finally, if you want to combine them, one more example:

string aggregatePem = certPem + "\n" + keyPem;

var certAgain = X509Certificate2.CreateFromPem(aggregatePem, aggregatePem);

Or if you write the combined contents to a file:

string aggregatePem = certPem + "\n" + keyPem;
File.WriteAllText("combined.pem", aggregatePem);

var certAgain = X509Certificate2.CreateFromPemFile("combined.pem");

And for encrypted private key PEMs it would be the same but using the Encrypted versions of the appropriate methods.

@heng-liu
Copy link
Author

Thanks a lot for your help! @vcsjones
I tried the one with password and it worked :)

@davidfowl
Copy link
Member

@vcsjones This feels like recipe we should document somewhere

@vcsjones
Copy link
Member

This feels like recipe we should document somewhere

Yeah. Part of me is wondering though if there is a quality-of-life API that's missing. Something like:

public class X509Certificate2 {
    public string ExportCertificatePem();
    public bool TryExportCertificatePem(Span<char> buffer, out int bytesWritten);
}

public class AsymmetricAlgorithm {
    public string ExportPkcs8PrivateKeyPem();
    public bool TryExportPkcs8PrivateKeyPem(Span<char> buffer, out int bytesWritten);
    public string TryExportEncryptedPkcs8PrivateKeyPem();
    public bool TryExportEncryptedPkcs8PrivateKeyPem(Span<char> buffer, out int bytesWritten);
}

Maybe @bartonjs has more thoughts on this.

@bartonjs
Copy link
Member

We could add it, but there are some corner cases.

  • For the cert, we could easily limit it to X.509 PEM, if you want a PKCS#7 PEM use the collection.
    • But did you want the cert and key together as a multi-PEM? Should we make that easy, too? Or is that easy enough after you grab the key?
    • PFX is a no-go, it has no defined PEM header.
    • Guess we also don't care about SerializedCert and SerializedStore... so we actually maybe don't need to take the "for what format?" parameter.
  • For collections I guess PKCS#7 is the only PEM option, so that one doesn't need a format parameter, either.
  • For AsymmetricAlgorithm the encrypted versions have some bad copy-pasta 😄.
  • We've done everything else, should we also do RSA Public and RSA Private PEM?

@vcsjones
Copy link
Member

Or is that easy enough after you grab the key?

My thought was that is easy enough. Concatenating two strings doesn't seem like an especially tall order.

have some bad copy-pasta

And I thought passwordless was all the rage...

Guess we also don't care about SerializedCert and SerializedStore

Personally I have never once used those formats in conjunction with a PEM. Or really ever.

We've done everything else, should we also do RSA Public and RSA Private PEM?

Guess that can't hurt.


I will open a new issue with a better formatted proposal (with password parameters!) so I don't hijack this issue any longer.

@bartonjs bartonjs added question Answer questions and provide assistance, not an issue with source code or documentation. and removed untriaged New issue has not been triaged by the area owner labels Jul 2, 2021
@bartonjs
Copy link
Member

bartonjs commented Jul 2, 2021

The original question has been answered, and the "maybe there's room for improvement" is an API proposal, so I don't think anything's left from this issue.

@bartonjs bartonjs closed this as completed Jul 2, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Aug 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Security question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

4 participants