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
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.
}