Guy Lowe Guy Lowe - 2 months ago 61
C# Question

Add Private Key to X509 Certificate before export to p12 in c#

I'm trying to programmatically export a certificate to a p12 with its private key without having to import it into a certificate store first.

The flow I am trying to replicate is as follows:

  1. On a Mac create a certificate signing request using Keychain.

  2. Use this to create a provisioning certificate for iOS apps using
    Apple's portal.

  3. I then download the new .cer file that Apple has
    generated from my csr.

  4. Typically what you would then do is double click on the .cer and it
    would import into KeyChain Access and appear with and as as a part
    of the original private key that was created.

  5. You can then right click on the new certificate entry and export
    this as a .p12.

I need to replicate the last 2 steps in c#. I have a .cer from Apple, I have the public and private keys and I somehow need to apply the private key in such a way so that when I programmatically export it as a p12 it matches the one that I do manually above.

I can import the .cer like so:

X509Certificate2 originalCert = new X509Certificate2(@"c:\temp\aps.cer");
//then export it like so
byte[] p12 = originalCert.Export(X509ContentType.Pkcs12, "password");

But when I compare it with the one I manually create out of KeyChain it does not match i.e:

byte[] p12Bytes = File.ReadAllBytes(@"c:\temp\manual.p12");
string manual = Convert.ToBase64String(p12Bytes);
string generated = Convert.ToBase64String(p12);
Assert.IsTrue(manual == generated);//this fails

There may of course be a problem with my understanding of how this all works :)

I have tried using the BouncyCastle libraries to do this but the following code does nothing to the output at the end.

Org.BouncyCastle.X509.X509Certificate cert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(originalCert);
X509CertificateEntry[] chain = new X509CertificateEntry[1];
X509CertificateEntry entry = new X509CertificateEntry(cert);
chain[0] = entry;

AsymmetricKeyEntry keyPair;
using (StreamReader reader = File.OpenText(@"c:\temp\EXPORTED PRIVATE KEY.p12"))
Pkcs12Store store = new Pkcs12Store(reader.BaseStream, "Password".ToCharArray());
foreach (string n in store.Aliases)
if (store.IsKeyEntry(n))
AsymmetricKeyEntry key = store.GetKey(n);

if (key.Key.IsPrivate)
keyPair = key;
pfx.SetKeyEntry("TEST CERT", key, chain);

Can anyone please point me in the right direction?

EDIT: I've made one more change which looks promising as the lengths of the keys almost match now. I am now exporting the p12 via BouncyCastle like so:

using (MemoryStream stream = new MemoryStream())
pfx.Save(stream, "password".ToCharArray(), new SecureRandom());

byte[] p12Bytes = File.ReadAllBytes(@"c:\temp\newexported.p12");
string realp12 = Convert.ToBase64String(p12Bytes);

using (BinaryReader reader = new BinaryReader(stream))
byte[] p12 = stream.ToArray();
string generated12 = Convert.ToBase64String(p12);
Assert.IsTrue(realp12.Length == generated12.Length);

It seems to use randomly generated bytes to perform the operation (see new SecuredRandom()) and I'm now confused as to how the decryption occurs if it is randomised somewhat?



First I would like to thank bartonjs above for his help solving this. The different lengths got me thinking about this in a different way.

My edit above solved it. I assumed that by calling:

 pfx.SetKeyEntry("MyKey", key, chain);

it would set the key into the certificates in the chain. Instead I had to export them like so:

using (MemoryStream stream = new MemoryStream())
     pfx.Save(stream, "Password".ToCharArray(), new SecureRandom());//The SecureRandom must be responsible for the different strings/lengths when re-generated.
     using (BinaryReader reader = new BinaryReader(stream))
         byte[] p12 = stream.ToArray();
         //then I can save this off somewhere - in my case the db.

Now I have a p12 with a certificate and private key attached and it all works with Apple's push notification system.