Load certificate and private key into Java KeyStore

Viewed 4531

I'm trying to fetch a certificate and its private key from Azure Key Vault then call a remote server and do client certificate authentication.

The first part works well (fetching from Key Vault), however i'm completely stuck at importing the public and private material into KeyStore.

I've tried

keyStore.load(publicKey, null);
keyStore.load(new ByteArrayInputStream(privateKey.getBytes()),
    "thePassphrase".toCharArray());

but this leads to

java.io.IOException: DER input, Integer tag error
        at java.base/sun.security.util.DerInputStream.getInteger(DerInputStream.java:192)
        at java.base/sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1995)
        at java.base/sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:222)
        at java.base/java.security.KeyStore.load(KeyStore.java:1479)

Here's the full thing minus what i don't know how to implement -

DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();

SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl("https://<myvault>.vault.azure.net")
    .credential(credential)
    .buildClient();

CertificateClient certClient = new CertificateClientBuilder()
    .vaultUrl("https://<myvault>.vault.azure.net")
    .credential(credential)
    .buildClient();

// Get the public part of the cert
KeyVaultCertificateWithPolicy certificate = certClient.getCertificate("Joes-Crab-Shack");
byte[] publicKey = certificate.getCer();

// Get the private key
KeyVaultSecret secret = secretClient.getSecret(
    certificate.getName(),
    certificate.getProperties().getVersion());
String privateKey = secret.getValue();

// ***************
// How do i get the cert and its private key into KeyStore?
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// I've also tried "JKS" but that leads to
//    java.io.IOException: Invalid keystore format
keyStore.load(...)
// ***************

// Do client certificate authentication
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, null).build();

CloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
response = httpClient.execute(new HttpGet("https://remote.that.asks.for.client.cert/"));

InputStream inputStream = response.getEntity().getContent();

body = IOUtils.readInputStreamToString(inputStream, Charsets.UTF_8);

How do i get the certificate and its private key into KeyStore so i can then use it in my HTTP client?

3 Answers

It is a bit late, but I want to place a resolution that I have practiced whilst retrieving a certificate from Azure Keyvault then putting it into the java Keystore.

The dependencies that I utilized are the following.

com.azure:azure-security-keyvault-certificates:jar:4.1.3
com.azure:azure-identity:jar:1.0.4
com.azure:azure-security-keyvault-secrets:jar:4.1.1

Then, the code block is below.

public class RestTemplateProvider {

    @Value("${azure.keyvault.service.cert-alias}")
    private String alias;
    @Value("${azure.keyvault.tenant-id}")
    private String tenantId;
    @Value("${azure.keyvault.client-key}")
    private String clientSecret;
    @Value("${azure.keyvault.client-id}")
    private String clientId;
    @Value("${azure.keyvault.uri}")
    private String vaultUri;

    public RestTemplate provideRestTemplate() {
        char[] emptyPass = {};
        CloseIoStream closeableIoStream = CloseIoStream.newInstance();

        try {
            // Azure KeyVault Credentials
            ClientSecretCredential credential = new ClientSecretCredentialBuilder()
                .tenantId(tenantId)
                .clientId(clientId)
                .clientSecret(clientSecret)
                .build();

            CertificateClient certificateClient = new CertificateClientBuilder()
                .vaultUrl(vaultUri)
                .credential(credential)
                .buildClient();

            SecretClient secretClient = new SecretClientBuilder()
                .vaultUrl(vaultUri)
                .credential(credential)
                .buildClient();
            // Azure KeyVault Credentials

            // Retrieving certificate
            KeyVaultCertificateWithPolicy certificateWithPolicy = certificateClient.getCertificate(alias);
            KeyVaultSecret secret = secretClient.getSecret(alias, certificateWithPolicy.getProperties().getVersion());

            byte[] rawCertificate = certificateWithPolicy.getCer();
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            ByteArrayInputStream certificateStream = new ByteArrayInputStream(rawCertificate);
            Certificate certificate = cf.generateCertificate(certificateStream);

            close(certificateStream);
            // Retrieving certificate

            // Retrieving private key
            String base64PrivateKey = secret.getValue();
            byte[] rawPrivateKey = Base64.getDecoder().decode(base64PrivateKey);

            KeyStore rsaKeyGenerator = KeyStore.getInstance(KeyStore.getDefaultType());
            ByteArrayInputStream keyStream = new ByteArrayInputStream(rawPrivateKey);
            rsaKeyGenerator.load(keyStream, null);

            close(keyStream);

            Key rsaPrivateKey = rsaKeyGenerator.getKey(rsaKeyGenerator.aliases().nextElement(), emptyPass);
            // Retrieving private key

            // Importing certificate and private key into the KeyStore
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            keyStore.setKeyEntry(alias, rsaPrivateKey, emptyPass, new Certificate[] {certificate});
            // Importing certificate and private key into the KeyStore

            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
                new SSLContextBuilder()
                    .loadTrustMaterial(null, new TrustAllStrategy())
                    .loadKeyMaterial(keyStore, emptyPass)
                    .build(),
                NoopHostnameVerifier.INSTANCE);

            // It is just a sample, except for the sslsocketfactory please use the configuration which is right for you.
            CloseableHttpClient httpClient = HttpClients.custom()
                                                        .setSSLSocketFactory(socketFactory)
                                                        .setConnectionTimeToLive(1000, TimeUnit.MILLISECONDS)
                                                        .setKeepAliveStrategy((httpResponse, httpContext) -> 10 * 1000)
                                                        .build();

            ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
            RestTemplate restTemplate = new RestTemplate(requestFactory);

            return restTemplate;
        } catch (IOException | CertificateException
            | NoSuchAlgorithmException | UnrecoverableKeyException
            | KeyStoreException | KeyManagementException e) {
            log.error("Error!!!")
        }

        return null;
    }

    private void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // log
            }
        }
    }
}

I hope someone looking for a solution around using Azure Keyvault certificates in java Keystores can benefit from it.

It sounds to me that the import is a "one time job" and do not need to get solved programmatically. I recommend (same as @pedrofb) that you use the Keystore Explorer for this job - it worked perfectly in my testcase:

Here are my steps to do the import, all files are available in my GitHub-Repo (https://github.com/java-crypto/Stackoverflow/tree/master/Load_certificate_and_private_key_into_Java_KeyStore):

  1. create the private key + certificate file with open ssl:

openssl req -x509 -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem

This will create the 2 files key.pem (encrypted private key) and cert.pem (certificate with public key). I used some sample data and the password for key.pem is 123456.

  1. download the Keystore Explorer from https://keystore-explorer.org/downloads.html

  2. run explorer and create a new KeyStore

create new keystore

  1. select KeyStore type = PKCS12

select Key Store type

  1. Import KeyPair with "Tools" - "Import Key Pair"

import Key Pair

  1. Select Key Pair Type as PKCS #8

select Key Pair type

  1. Set the password (123456) and choose the key- and cert-file and press "Import"

set the password

  1. Choose the alias for the key (default is the given email in the certificate

choose an alias

  1. Set a Key Pair Entry password: kpe123456

set Key Pair Entry password

  1. Message: Key Pair Import successful

Message Key Pair Import successful

save your new keystore

  1. save your new keystore with "File" - "Save as"

save your new Key Store

  1. Set a password for the Key Store: ks123456

Set a password for the Key Store

  1. choose path + filename: keystore.p12

  2. ready - your private and certificate are imported in the new created Key Store keystore.p12

When searching via Google and "azure key vault get private key" I found a GitHub-issue that describes in detail how to retrieve a private key from Azure Key Vault:

https://github.com/Azure/azure-sdk-for-js/issues/7647

Scrolling down to answer https://github.com/Azure/azure-sdk-for-js/issues/7647#issuecomment-594935307 there is this statement from one of the Dev's:

How to obtain the private key

Knowing that the private key is stored in a KeyVault Secret, with the public certificate included, we can retrieve it by using the KeyVault-Secrets client, as follows:

// Using the same credential object we used before,
// and the same keyVaultUrl,
// let's create a SecretClient
const secretClient = new SecretClient(keyVaultUrl, credential);

// Assuming you've already created a KeyVault certificate,
// and that certificateName contains the name of your certificate
const certificateSecret = await secretClient.getSecret(certificateName);

// Here we can find both the private key and the public certificate, in PKCS 12 format:
const PKCS12Certificate = certificateSecret.value!;

// You can write this into a file:
fs.writeFileSync("myCertificate.p12", PKCS12Certificate);

Note that, by default, the contentType of the certificates is PKCS 12. Though specifying the content type of your certificate will make it trivial to get it's secret key in PEM format, let's explore how to retrieve a PEM secret key from a PKCS 12 certificate first.

Using openssl, you can retrieve the public certificate in PEM format by using the following command:
openssl pkcs12 -in myCertificate.p12 -out myCertificate.crt.pem -clcerts -nokeys
You can also use openssl to retrieve the private key, as follows:
openssl pkcs12 -in myCertificate.p12 -out myCertificate.key.pem -nocerts -nodes

In short: You do not need to "rebuild" a PKCS12-keystore from separate "files" (private key-PEM and certificate-PEM) as you get the PKCS12/P12-keystore as shown in the example.

Related