azure-keyvaultpfxazure-java-sdkcertificate-store

Having trouble downloading a certificate from Azure KeyVault as a .PFX using the Java SDK


Having trouble downloading a certificate from Azure KeyVault as a .PFX using the Java SDK.

After the download, the cert is not openable like the original HEX-encoded .PFX file is , before I uploaded it to the Certificates blade in Azure KeyVault. NOTE: I tried both a PFX file with a password and one without a password.

After download, the resulting HEX file 373 lines and 14,919 chars and not openable. Before upload the .pfx hex file is 374 lines and 14,947 characters.

Anyone know what I am doing wrong? After download, with either the original password OR a blank password, I get this error: test.pfx is not a KeyStore of any of the following recognized types: JCE, JCEKS, PKCS#12, UBER or BCFKS (I use Keystore Explorer for testing)

public static void main(String[] args) throws Exception {

    System.setProperty("AZURE_CLIENT_ID", "...");
    System.setProperty("AZURE_CLIENT_SECRET", "...");
    System.setProperty("AZURE_TENANT_ID", "..."");
    final DefaultAzureCredential azureCredential = new DefaultAzureCredentialBuilder()
            .build();
    CertificateClient certificateClient = new CertificateClientBuilder()
            .vaultUrl("https://djangofan-kv-test.vault.azure.net")
            .credential(azureCredential)
            .buildClient();

    String certificateName = "test";

    KeyVaultCertificateWithPolicy certificate = certificateClient.getCertificate(certificateName);
    CertificateKeyType keyType = certificate.getPolicy().getKeyType();
    System.out.println("\nKey type: " + keyType);

    String secretIdAndUrl = certificate.getKeyId();
    System.out.println("\nSecret ID: " + secretIdAndUrl);

    String secretValue = secretClient.getSecret(certificateName).getValue();
    System.out.println("\nSecret value:\n" + secretValue);

    Base64.Decoder decoder = Base64.getDecoder();
    byte[] decodedBytes = decoder.decode(secretValue);
    System.out.println("\nSecret value decoded:\n" + Arrays.toString(decodedBytes));

    String hexData = bytesToHex(decodedBytes);
    System.out.println("\nSecret value HEX dump:\n" + hexData);
    try (BufferedWriter writer = new BufferedWriter(new FileWriter("test.pfx", StandardCharsets.UTF_8))) {
        writer.write(hexData);
    }
    System.exit(0);
}

NOTE: Manually downloading the .pfx cert from the KeyVault GUI works fine.

I also tried it this way, with SecretsClient:

SecretClient secretClient = new SecretClientBuilder()
        .vaultUrl("https://djangofan-kv-test.vault.azure.net")
        .credential(azureCredential)
        .buildClient();
String secretName = "secretnopass";
KeyVaultSecret secret = secretClient.getSecret(secretName);
String secretValue = secret.getValue();
System.out.println("\nSecret value:\n" + secretValue);
String hexData = bytesToHex(secretValue.getBytes(StandardCharsets.UTF_8));
System.out.println("\nSecret value HEX dump:\n" + hexData);

Maven dependencies:

<dependency>
  <groupId>com.azure</groupId>
  <artifactId>azure-security-keyvault-certificates</artifactId>
  <version>4.3.0</version>
</dependency>
<dependency>
  <groupId>com.azure</groupId>
  <artifactId>azure-security-keyvault-secrets</artifactId>
  <version>4.3.0</version>
</dependency>
<dependency>
  <groupId>com.azure</groupId>
  <artifactId>azure-identity</artifactId>
  <version>1.3.5</version>
</dependency>

NOTE: I'll try this alternative method but I am unsure why direct download to keyVault wouldn't work: https://youtu.be/26WGUCQ_LAI?t=618


Solution

  • Here is the solution, although it is not ideal, since I means I have to download the keystore parts and then re-assemble them in code to produce a valid keystore.

    NOTE: Unfortunately, i could find no way to just do a single download of keystore to my function. Maybe this is because the function has to "do things in memory" and it has no file storage. When I tried, all I could get is the keystore WITHOUT the private key.

    For this to work, you need to upload the 3 public certs + 1 private key into the Secrets blade of your KeyVault instance.

    You pass a KeyStore object, with pass-by-reference, into this method to re-constuct the keystore:

    private void addNonprodKeysToKeystore(KeyStore keyStore, String nonprodCertificateAlias, SecretClient secretClient) throws Exception {
        // get cert chain root cert
        KeyVaultSecret rootCertSecret = secretClient.getSecret(DIGICERT_ROOT_AZURE_PEM);
        String rootCertPEM = rootCertSecret.getValue();
        // get cert chain intermediate cert
        KeyVaultSecret intermediateCertSecret = secretClient.getSecret(DIGICERT_INTERMEDIATE_AZURE_PEM);
        String intermediateCertPEM = intermediateCertSecret.getValue();
        // get cert chain head cert
        KeyVaultSecret headCertSecret = secretClient.getSecret(HEAD_CERT_AZURE_PEM);
        String headCertPEM = headCertSecret.getValue();
        // build cert chain for keystore
        Certificate[] certificateChain = buildCertificateChain(headCertPEM, intermediateCertPEM, rootCertPEM);
    
        KeyVaultSecret privateKeySecret = secretClient.getSecret(PRIVATE_KEY_AZURE_PEM);
        String encodedPrivateKey = privateKeySecret.getValue();
        byte[] decodedPrivateKey = Base64.getDecoder().decode(encodedPrivateKey);
        String decodedPkString = new String(decodedPrivateKey, StandardCharsets.UTF_8);
        String strippedPkPEM = stripPEMHeadersFromPrivateKey(decodedPkString);
        byte[] decodedPkBody = Base64.getDecoder().decode(strippedPkPEM);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decodedPkBody);
        PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
        keyStore.setKeyEntry(nonprodCertificateAlias, privateKey, this.resolveKeystorePassword(), certificateChain);
    }
    

    And the additional utility methods:

    public Certificate[] buildCertificateChain(String headCertPEM, String intermediateCertPEM, String rootCertPEM) throws Exception {
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    
            String strippedHeadCertPEM = stripPEMHeadersFromCertificate(headCertPEM);
            byte[] decodedHeadCertificate = Base64.getDecoder().decode(strippedHeadCertPEM);
            X509Certificate headCert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decodedHeadCertificate));
    
            String strippedIntermediateCertPEM = stripPEMHeadersFromCertificate(intermediateCertPEM);
            byte[] decodedIntermediateCertificate = Base64.getDecoder().decode(strippedIntermediateCertPEM);
            X509Certificate intermediateCert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decodedIntermediateCertificate));
    
            String strippedRootCertPEM = stripPEMHeadersFromCertificate(rootCertPEM);
            byte[] decodedRootCertificate = Base64.getDecoder().decode(strippedRootCertPEM);
            X509Certificate rootCert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decodedRootCertificate));
    
            log.info("Returning newly generated cert chain.");
            return new Certificate[]{headCert, intermediateCert, rootCert};
        }
    
        public String stripPEMHeadersFromPrivateKey(String pemEncodedPrivateKey) {
            if (pemEncodedPrivateKey == null || pemEncodedPrivateKey.isEmpty()) {
                throw new IllegalArgumentException("The provided PEM string is null or empty.");
            }
            return pemEncodedPrivateKey.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", ""); // Removes all whitespaces, newlines, tabs, etc.
        }
    
        public String stripPEMHeadersFromCertificate(String pemEncodedCertificate) {
            if (pemEncodedCertificate == null || pemEncodedCertificate.isEmpty()) {
                throw new IllegalArgumentException("The provided PEM string is null or empty.");
            }
    
            return pemEncodedCertificate.replace("-----BEGIN CERTIFICATE-----", "")
                    .replace("-----END CERTIFICATE-----", "")
                    .replaceAll("\\s+", ""); // Removes all whitespaces, newlines, tabs, etc.
        }