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 .pfx or .p12 (KeyStore) file? #96

Closed
Osiris-Team opened this issue Mar 11, 2021 · 7 comments
Closed

[Question] How to generate .pfx or .p12 (KeyStore) file? #96

Osiris-Team opened this issue Mar 11, 2021 · 7 comments

Comments

@Osiris-Team
Copy link

Osiris-Team commented Mar 11, 2021

Hey I'm not sure If this is the right place to ask, but I'm kind of stuck.
I used the org.shredzone.acme4j.example.ClientTest as base and expanded it to fit my needs.
Everything works fine and I get the certificate. Now the only thing left is to generate a .pfx file from the data.

(14.03.2021) Solution:

try {
            AL.info("Updating .pfx file...");
            // This is the certificate returned by order.getCertificate();
            X509Certificate[] chain = certificate.getCertificateChain().toArray(new X509Certificate[0]);
            PublicKey publicKey = domainKeyPair.getPublic();
            PrivateKey privateKey = domainKeyPair.getPrivate();
            PKCS12PfxPdu pfx = buildAndGetPfx(chain, publicKey, privateKey, "pwd".toCharArray());

            try (OutputStream ow = new FileOutputStream(KEYSTORE_PFX_FILE)){
                // make sure we don't include indefinite length encoding
                ow.write(pfx.getEncoded(ASN1Encoding.DL));
                ow.flush();
            }
            AL.info("Success!");
        } catch (Exception e) {
            AL.warn(e);
        }
    /**
     * Builds a {@link PKCS12PfxPdu} from the given information.
     * Original code from: https://www.bouncycastle.org/docs/pkixdocs1.5on/org/bouncycastle/pkcs/PKCS12PfxPduBuilder.html
     * Discussed in: https://github.com/shred/acme4j/issues/96
     * @param chain obtained through {@link Order#getCertificate()} -> {@link Certificate#getCertificateChain()}.
     * @param pubKey obtained from the domain keypair {@link KeyPair#getPublic()}.
     * @param privKey obtained from the domain keypair {@link KeyPair#getPrivate()}.
     * @param passwd your password.
     * @return a new {@link PKCS12PfxPdu} built, from the given information, which then can be written to file.
     * @throws Exception
     */
    public static PKCS12PfxPdu buildAndGetPfx(X509Certificate[] chain, PublicKey pubKey, PrivateKey privKey, char[] passwd) throws Exception{
        List<PKCS12SafeBag> certs = new ArrayList<>();
        for (X509Certificate cert :
                chain) {
            certs.add(new JcaPKCS12SafeBagBuilder(cert).build());
        }

        PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder();
        pfxPduBuilder.addEncryptedData(
                new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC
                , new CBCBlockCipher(new RC2Engine())).build(passwd)
                , certs.toArray(new PKCS12SafeBag[0]));

        PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd));
        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, new JcaX509ExtensionUtils().createSubjectKeyIdentifier(pubKey));

        pfxPduBuilder.addData(keyBagBuilder.build());

        return pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwd);
    }

Old code (ignore this):

/**
     * Builds a {@link PKCS12PfxPdu} from the given information.
     * Code from: https://www.bouncycastle.org/docs/pkixdocs1.5on/org/bouncycastle/pkcs/PKCS12PfxPduBuilder.html
     * @param chain
     * @param pubKey
     * @param privKey
     * @param passwd
     * @return
     * @throws Exception
     */
    public PKCS12PfxPdu buildAndGetPfx(X509Certificate[] chain, PublicKey pubKey, PrivateKey privKey, char[] passwd) throws Exception{
        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();

        PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]);

        taCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Primary Certificate"));

        PKCS12SafeBagBuilder caCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[1]);

        caCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Bouncy Intermediate Certificate"));

        PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[0]);

        eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
        eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));

        PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd));

        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));

        //
        // construct the actual key store
        //
        PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder();

        PKCS12SafeBag[] certs = new PKCS12SafeBag[3];

        certs[0] = eeCertBagBuilder.build();
        certs[1] = caCertBagBuilder.build();
        certs[2] = taCertBagBuilder.build();

        pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, new CBCBlockCipher(new RC2Engine())).build(passwd), certs);

        pfxPduBuilder.addData(keyBagBuilder.build());

        PKCS12PfxPdu pfx = pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwd);
        return pfx;
    }
@shred
Copy link
Owner

shred commented Mar 12, 2021

I guess that PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]) is the line that is causing the exception.

Your buildAndGetPfx() method assumes that the certificate chain always consists of three certificates. However this is not always correct. For example, the Let's Encrypt staging server only returns two certificates (your own one, and a test root certificate). I assume you are testing against the staging server, and now your code breaks because it misses a third certificate.

Other CAs might even return a chain of four or more certificates, so you shouldn't make fixed assumptions on the number of certificates. 😉

I would recommend you change your code so the PKCS12SafeBag is dynamically generated from all certificates that are passed in with your certs parameter.

@Osiris-Team
Copy link
Author

Osiris-Team commented Mar 12, 2021

@shred How would that look in code?
Something like this?:

List<JcaPKCS12SafeBagBuilder> builders = new ArrayList<>();
        for (X509Certificate cert :
                chain) {
            builders.add(new JcaPKCS12SafeBagBuilder(cert));
        }

        List<PKCS12SafeBag> certs = new ArrayList<>();
        for (JcaPKCS12SafeBagBuilder builder :
                builders) {
            certs.add(builder.build());
        }

Edit1: This would be even faster requiring only one loop:

List<PKCS12SafeBag> certs = new ArrayList<>();
        for (X509Certificate cert :
                chain) {
            certs.add(new JcaPKCS12SafeBagBuilder(cert).build());
        }

@Osiris-Team
Copy link
Author

@shred So these

taCertBagBuilder.addBagAttribute(stuff...)

dont matter at all?

@Osiris-Team
Copy link
Author

@shred This is what I came up with:

public static PKCS12PfxPdu buildAndGetPfx(X509Certificate[] chain, PublicKey pubKey, PrivateKey privKey, char[] passwd) throws Exception{
        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();

        List<PKCS12SafeBag> certs = new ArrayList<>();
        for (X509Certificate cert :
                chain) {
            certs.add(new JcaPKCS12SafeBagBuilder(cert).build());
        }
        
        PKCS12SafeBag[] certsArray = new PKCS12SafeBag[certs.size()];
        for (int i = 0; i < certs.size(); i++) {
            certsArray[i] = certs.get(i);
        }

        PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder();
        pfxPduBuilder.addEncryptedData(
                new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC
                , new CBCBlockCipher(new RC2Engine())).build(passwd)
                , certsArray); // converting the certs list into an array ty using certs.toArray() didn't work :/ Thats why I used the loop above

        PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, new BcPKCS12PBEOutputEncryptorBuilder(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, new CBCBlockCipher(new DESedeEngine())).build(passwd));
        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Eric's Key"));
        keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, extUtils.createSubjectKeyIdentifier(pubKey));
        
        pfxPduBuilder.addData(keyBagBuilder.build());
        
        return pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwd);
    }

@Osiris-Team
Copy link
Author

Osiris-Team commented Mar 12, 2021

@shred Another question. Is it possible to create a org.shredzone.acme4j.Certificate from the already existing files? Or must I order a new one every time?

@shred
Copy link
Owner

shred commented Mar 14, 2021

The code is looking good, except that you don't set a pkcs_9_at_friendlyName any more. I don't know much about PFX files, so I cannot say if this is a problem, and if the generated PFX file is generally correct. BTW, instead of the second for loop you could also do PKCS12SafeBag[] certsArray = certs.toArray(new PKCS12SafeBag[0]);.

You cannot create a org.shredzone.acme4j.Certificate resource from a file, because crucial information like the certificate URL, the certificate chain, and alternate chains would be missing. What you can do is store the location of the certificate (URL certificateUrl = certificate.getLocation()). Later you would do a login.bindCertificate(certificateUrl) to recreate the Certificate resource.

@Osiris-Team
Copy link
Author

@shred Thanks for the help!

@Osiris-Team Osiris-Team changed the title [Question] How to generate .pfx file? [Question] How to generate .pfx or .p12 file? Apr 18, 2021
@Osiris-Team Osiris-Team changed the title [Question] How to generate .pfx or .p12 file? [Question] How to generate .pfx or .p12 (KeyStore) file? Apr 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants