0

I need some help to generate PKCS12 pfx file with bouncycastle.

I am using fallowing commands to generate PKCS12 pfx file:

keytool -genkey -storetype PKCS12 -dname "CN=%CN, OU=%OU, O=Company, L=City, ST=State, C=US" -alias clientcert -keyalg RSA -keysize 2048 -keystore %keystore_name% -storepass %default_keystore_pwd% -keypass %default_keystore_pwd%

Importing ca.crt as root:

keytool -import -trustcacerts -noprompt -alias root -file ca.crt -keystore %keystore_name% -storepass %default_keystore_pwd% 

ca.crt is root certificate used to sign generated CSR

keytool -certreq -alias clientcert -keystore %keystore_name% -file clientcert.csr -keypass %default_keystore_pwd% -storepass %default_keystore_pwd%

At this point, I get CSR which I sign on a dedicated server with ca.crt

Then I import signed certificate to pfx:

keytool -import -alias clientcert -file signed.crt -keystore %keystore_name% -storepass %default_keystore_pwd% -keypass %default_keystore_pwd%

Using bouncycastle library I am creating CSR and private key. Then I sign CSR on server with ca.crt.

There are 3 files in final folder:

  • ca.crt
  • signed.crt - certificate signed with ca.crt
  • private_key.key (not encrypted RSA key)

With commands I provided earlier final pfx file, when extracted looks like this:

keytool -list -rfc -keystore client_keystore.pfx
Enter keystore password:
Keystore type: jks
Keystore provider: SUN

Your keystore contains 2 entries

Alias name: clientcert
Creation date: Mar 22, 2019
Entry type: PrivateKeyEntry
Certificate chain length: 2
Certificate[1]:
-----BEGIN CERTIFICATE-----
//removed
-----END CERTIFICATE-----
Certificate[2]:
-----BEGIN CERTIFICATE-----
//removed
-----END CERTIFICATE-----


*******************************************
*******************************************


Alias name: root
Creation date: Apr 3, 2019
Entry type: trustedCertEntry

-----BEGIN CERTIFICATE-----
//removed
-----END CERTIFICATE-----


*******************************************
*******************************************

I already have signed ca.crt, certificate.crt and private.key. How can I create same structure pfx file using bouncycastle library?

Generating CSR and key:

public void TDE(string CName, string OUnit, string Country, string State, string City, string EmailAdr, string password)
        {



            AsymmetricCipherKeyPair pair;
            Pkcs10CertificationRequest csr;
            Asn1SignatureFactory signatureFactory;
            var random = new SecureRandom(new CryptoApiRandomGenerator());

            var values = new Dictionary<DerObjectIdentifier, string>
            {
                {X509Name.CN, CName},
                {X509Name.OU, OUnit},
                {X509Name.O, "Company"},
                {X509Name.L, City},
                {X509Name.ST, State},
                {X509Name.C, Country},
                {X509Name.EmailAddress, EmailAdr },
            };


            var extensions = new Dictionary<DerObjectIdentifier, X509Extension>()
            {
                {X509Extensions.BasicConstraints, new X509Extension(true, new DerOctetString(new BasicConstraints(false)))},
                {X509Extensions.KeyUsage, new X509Extension(true, new DerOctetString(new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment | KeyUsage.DataEncipherment | KeyUsage.NonRepudiation)))},
                {X509Extensions.ExtendedKeyUsage, new X509Extension(false, new DerOctetString(new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth, KeyPurposeID.IdKPClientAuth)))},
            };

            var subject = new X509Name(values.Keys.Reverse().ToList(), values);


            var gen = new RsaKeyPairGenerator();
            gen.Init(new KeyGenerationParameters(random, 2048));


            pair = gen.GenerateKeyPair();
            signatureFactory = new Asn1SignatureFactory("SHA256withRSA", pair.Private);

            extensions.Add(X509Extensions.SubjectKeyIdentifier, new X509Extension(false, new DerOctetString(new SubjectKeyIdentifierStructure(pair.Public))));
            csr = new Pkcs10CertificationRequest(signatureFactory, subject, pair.Public, new DerSet(new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(new X509Extensions(extensions)))), pair.Private);


            //Convert BouncyCastle csr to .PEM file.
            var csrPem = new StringBuilder();
            var csrPemWriter = new PemWriter(new StringWriter(csrPem));
            csrPemWriter.WriteObject(csr);
            csrPemWriter.Writer.Flush();

            //Writes password to file
            Directory.CreateDirectory(Environment.CurrentDirectory + "\\" + CName + "_" + OUnit);
            File.AppendAllText(Environment.CurrentDirectory + "\\" + CName + "_" + OUnit + "\\key_password.txt", password);

            //writes CSR to file
            File.AppendAllText(Environment.CurrentDirectory + "\\" + CName + "_" + OUnit + "\\" + CName + "_csr", csrPem.ToString());


            //Convert BouncyCastle Private Key to .PEM file.
            var privateKeyPem = new StringBuilder();
            var privateKeyPemWriter = new PemWriter(new StringWriter(privateKeyPem));
            privateKeyPemWriter.WriteObject(pair.Private);
            privateKeyPemWriter.Writer.Flush();

            //privateKeyPem.ToString();
            File.AppendAllText(Environment.CurrentDirectory + "\\" + CName + "_" + OUnit + "\\" + CName + "_" + OUnit + "_prvNE.key", privateKeyPem.ToString());



        }

Thank you

Useme Alehosaini
  • 2,998
  • 6
  • 18
  • 26

1 Answers1

0

This problem took longer to overcome than I had anticipated quite honestly. Obviously, there are couple of ways to export PFX or P12 binary format using various ways, but to keep it simple I'm going to use BouncyCastle library.

Firstly, I was using an answer from Cristoph and you can find the link here https://stackoverflow.com/a/44798441/5797504. Essentially, it uses Pkcs12StoreBuilder which is neat and quite clean, and MemoryStream which I don't know why, but I'm still gonna use it anyway.

Secondly, I modified the answer to accomodate Certificate Chain and some other things which kinda obvious.

The most important part is to create an array of X509CertificateEntry[] and create a chain of certificate from your primary/end certificate, intermediate CA (sub-CA), all the way to your root CA certificate. That array will need to go into the SetKeyEntry method argument.

            // create chain certificate
            var rootCert = DotNetUtilities.FromX509Certificate(rootCA);
            var endCert = DotNetUtilities.FromX509Certificate(x509Cert);

            X509CertificateEntry[] chains = new X509CertificateEntry[2];
            chains[0] = new X509CertificateEntry(endCert);
            chains[1] = new X509CertificateEntry(rootCert);
            ////....
            store.SetKeyEntry(commonName, new AsymmetricKeyEntry(privateKey), chains);

The following section shows a complete method on how I am creating a PKCS12 File be that as it PFX file or P12 file.

I am giving credit where credit is due to Cristoph as the original writer.

public static byte[] CreatePkcs12File(X509Certificate2 x509Cert, X509Certificate2 rootCA, AsymmetricKeyParameter privateKey, string passphrase)
        {
            // create store entry
            string commonName = x509Cert.GetNameInfo(X509NameType.SimpleName, false);

            // create chain certificate
            var rootCert = DotNetUtilities.FromX509Certificate(rootCA);
            var endCert = DotNetUtilities.FromX509Certificate(x509Cert);
   
            X509CertificateEntry[] chains = new X509CertificateEntry[2];
            chains[0] = new X509CertificateEntry(endCert);
            chains[1] = new X509CertificateEntry(rootCert);

            // create certificate entry
            var certEntry = new X509CertificateEntry(endCert);
            string friendlyName = endCert.SubjectDN.ToString();

            // get bytes of private key.
            PrivateKeyInfo keyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
            byte[] keyBytes = keyInfo.ToAsn1Object().GetEncoded();

            var builder = new Pkcs12StoreBuilder();
            builder.SetUseDerEncoding(true);
            var store = builder.Build();            
            
            store.SetKeyEntry(commonName, new AsymmetricKeyEntry(privateKey), chains);
            byte[] pfxBytes = null;

            var password = passphrase;
            
            using (MemoryStream stream = new MemoryStream())
            {
                store.Save(stream, password.ToCharArray(), new SecureRandom());
                pfxBytes = stream.ToArray();
            }

            var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes);
            return result;
        }
  • Hello, thanks for answer, but on more issue remains. store.SetKeyEntry("clientcert", new AsymmetricKeyEntry(privateKey), chains) creates certificate chain with signed cert, rootCA and private key. I still need to add one more chain, which contains only rootCA without prvate key. I have tried store.SetCertificateEntry("root", rootCAentry); but it is not showing up in final PKSC12 keystore. – Egidijus Lukošius Apr 16 '20 at 14:00
  • Hi, from what I understand. You were trying to create a p12/pfx file containing your own certificate and its root. Now you're trying to add an intermediate CA (known as sub-CA) between your root and your own certificate? is that correct? If so, just add another chain (chain[1]) between your own certificate (endCert) (chain[0]) and move your rootCert at the end (chain[2]). – wahyuagungs Apr 17 '20 at 06:07
  • So that In my understanding, you want to create a PKCS#12 with the structure as follows: (1) Your own certificate (2) Intermediate CA (3) Root CA. For num (1). you would need (1.a) Your private key (1.b) Your signed certificate - by the intermediate CA also contains public key. On the other hand, for num (2) and (3) you ONLY NEED their certificate (PUBLIC KEY) WITHOUT THEIR PRIVATE KEY. sorry for the caps – wahyuagungs Apr 17 '20 at 06:11
  • Not exactly. Please take a look in my question. Second chain is trustedCAentry Alias name: root Entry type: trustedCertEntry -----BEGIN CERTIFICATE----- //removed -----END CERTIFICATE----- This second chain contains SAME rootCA as first chain second entry: chain[0] - signed cert chain[1] - rootCA used to sign shain[0] so second chain I need to add is same as chain[1], but without any private key: chain1[0] == chain[1] – Egidijus Lukošius Apr 17 '20 at 06:49