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

[Improvement] Exporting x509 certs/keys as PEM #122

Open
christhefmdev opened this issue Jun 14, 2021 · 4 comments
Open

[Improvement] Exporting x509 certs/keys as PEM #122

christhefmdev opened this issue Jun 14, 2021 · 4 comments
Assignees
Labels
question Further information is requested

Comments

@christhefmdev
Copy link

We are using the modules on stand-alone windows machines that are running FileMaker Server. Our scripting is setup to download a new certificate about every 53 days and then install the certificate on the following Sunday.

In the current implementation we are exporting the certificate as a PFX then parsing the client and chain certs and also the key. One client we have fails to get the chain certificate from the PFX. This prompted some investigating on my part and I see that there are functions for getting the entire PEM using the State 'GetOrderCertificate' function. Is there a similar function that I can use to export the certificate key? I do not see a similar function for the AcmePSKey class and am not sure how to use any of the AcmePSKey's export functions.

Thanks

@christhefmdev christhefmdev added the question Further information is requested label Jun 14, 2021
@glatzert
Copy link
Collaborator

Hi essentially you are asking for a feature, that allows to Export the certificate as PEM, right?
I looked into that some time ago and discarded it, since I did not want to import something like "BouncyCastle" (which essentially allows cert.pem and key.pem to be created more easily).

The PEM parts are covered, since I store the original certificate (and also X509Certificate2 has an export function, which allows to create the Base64 Encoded contents for the cert).
The key.pem parts are not covered yet. The key is not stored in PEM format, but in the format .NET uses.
Since it's only the key left to do, it might be a good time to look into exporting it again (can't be that hard ^^).

@glatzert glatzert changed the title Exporting x509 certs/keys [Improvement] Exporting x509 certs/keys as PEM Jun 15, 2021
@glatzert
Copy link
Collaborator

There seems to be an easy way - but it's only available in .NET Core and thus PS Core 7.
(ExportPkcs8PrivateKey() on AsymmetricKey)

@JohnLBevan
Copy link
Contributor

JohnLBevan commented Mar 13, 2023

There's some good info on these pieces here:

From working through those & using a bit of reverse engineering I've come up with the code below. It's not there yet; but getting close (sorry it's a bit of a mess; I've been hacking away trying to figure things out & haven't yet tidied it into a more usable piece). I'll try to progress further, but sharing this attempt so far for now as I don't have much time this week / this may help someone else progress further...

Side note: the code I developed below was based on Scott's example which is an RSA key; whilst I'd been testing with a regular private key...

using System;
using System.Linq;
using System.IO;
using System.Numerics;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

X509Certificate2 GetPfxCert () {
	/* here I'm just loading a dummy cert from file for demo purposes */
	var pfxFilename = @"c:\temp\certs\myDemoCert.pfx";
	var pfxPrivateKeyExportPassword = "myExportPassword";
	return new X509Certificate2(pfxFilename, pfxPrivateKeyExportPassword, X509KeyStorageFlags.Exportable); //note: the PFX must have the private key flagged as exportable when the PFX is created; and we must use X509KeyStorageFlags.Exportable to open it with the key exportable: https://stackoverflow.com/a/9373407/361842
}
string ConvertDER2PEM(IEnumerable<byte> bytes) {
	return Convert.ToBase64String(bytes.ToArray());
}
string FormatPrivateKeyAsPEM(string pem) {
	return $"-----BEGIN PRIVATE KEY-----\n{pem}-----END PRIVATE KEY-----";
}
// info on the makup of DER encoding here: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
const byte ASN1_TAG_SEQUENCE = 0x30; // i.e. everything after this is part of a complex structure
const byte ASN1_TAG_INTEGER = 0x02;  // i.e. the value following this is an integer
const byte ASN1_TAG_NULL = 0x00;  // i.e. end of sequence
IEnumerable<byte> GetPrivateKeyInDER(X509Certificate2 certificate) {
	var privateKey = ((RSACryptoServiceProvider)certificate.PrivateKey).ExportParameters(true);
	var sequenceItems = new Queue<byte>();
	foreach (var b in ToDerInt(privateKey.Modulus)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.Exponent)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.D)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.P)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.Q)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.DP)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.DQ)) {sequenceItems.Enqueue(b);}
	foreach (var b in ToDerInt(privateKey.InverseQ)) {sequenceItems.Enqueue(b);}
	yield return ASN1_TAG_SEQUENCE;
	foreach(var b in ToDerLength(sequenceItems.Count)){yield return b;};
	foreach(var b in sequenceItems){yield return b;};
}

IEnumerable<byte> ToDerLength(int len) {
	if (len < 0x80) {
		yield return (byte)len;
	} else {
		var lengthInBytes = BitConverter.GetBytes(len);
		var result = new Queue<byte>();
		var sigFig = false;
		for (var i = lengthInBytes.Length - 1; i>=0; i--) { // reverse the array, trimming off "leading zeros"
			if (lengthInBytes[i] != 0 || sigFig) {
				sigFig = true;
				result.Enqueue(lengthInBytes[i]);
			}
		}
		yield return (byte)(result.Count + 0x80); // return the length of the length; note: we  add 0x80 to show that this is the length-of-the-length-of-the-data; not just the length-of-the-data
		foreach (var b in result) {yield return b;}
	}
}
IEnumerable<byte> ToDerInt (byte[] bigint) {
	if (bigint != null) {
		yield return ASN1_TAG_INTEGER;
		foreach (var b in ToDerLength(bigint.Length)) {yield return b;}
		for (var i = bigint.Length-1; i>=0; i--) {
			yield return bigint[i];
		}		
	}
}

void Main() {
	var pfx = GetPfxCert();	
	var privateKeyDer = GetPrivateKeyInDER(pfx);
	var privateKeyPem = ConvertDER2PEM(privateKeyDer);
	var keyFileContents = FormatPrivateKeyAsPEM(privateKeyPem);
	//Console.WriteLine(privateKeyDer);
	Console.WriteLine(keyFileContents);	
}

@glatzert
Copy link
Collaborator

Well executed, sir!

If you like to do the heavy lifting, I'll happily include it in the module (don't forget ecdsa then ;)).
If not, I'll probably resort to calling .net core code, when available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants