What is the trick to downloading a pkcs8 private key from Azure KeyVault, using Java Azure SDK?

74 Views Asked by At

So, for reference, here is what I have working:

  1. A call to a .pkcs12 stored in Certificates blade works:

       CertificateClient certificateClient = new CertificateClientBuilder()
               .vaultUrl(System.getenv("AzureWebJobsSecretStorageKeyVaultUri"))
               .credential(clientSecretCredential)
               .buildClient();
       KeyVaultCertificateWithPolicy certificate = certificateClient
         .getCertificate(certificateAzureName);
       CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
       ByteArrayInputStream inputStream = new ByteArrayInputStream(certificate
           .getCer());
       X509Certificate x509Certificate = (X509Certificate) certFactory
           .generateCertificate(inputStream);
       keyStore.setCertificateEntry(nonprodCertificateAlias, x509Certificate);
    
  2. Also, a call to get the public cert chain, stored as a secret (similar to the above but without private key). This works:

SecretClient secretClient = new SecretClientBuilder()
                    .vaultUrl(envUtil.getProperty(AppVar.AzureWebJobsSecretStorageKeyVaultUri))
                    .credential(clientSecretCredential)
                    .buildClient();
log.info("Created SecretClient.");
Objects.requireNonNull(secretClient, "Azure SecretClient is null. Could not
   be constructed.");
KeyVaultSecret secret = secretClient.getSecret(publicChainAzureName);
String encodedString = secret.getValue();
byte[] decodedCertChain = Base64.getDecoder().decode(encodedString);
....
Certificate certificate1 = certFactory
  .generateCertificate(new ByteArrayInputStream(decodedCertChain));
keyStore.setCertificateEntry(nonprodCertificateAlias, certificate1);

BUT, I cannot get a download of a private key (in pkcs8 pem format, & RSA) to work. Here is the code that just gives me the error Method threw 'java.security.spec.InvalidKeySpecException' exception: Invalid key format :

Certificate publicKeyChainCert = certFactory.generateCertificate(new ByteArrayInputStream(decodedCertChainSecret));
                
//keyStore.setCertificateEntry(pubCertChainAlias, publicKeyChainCert);
KeyVaultSecret secretKey = secretClient.getSecret(privateKeyAzureName);
String encodedPrivateKey = secretKey.getValue();
byte[] decodedPrivateKey = Base64.getDecoder().decode(encodedPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// next line throws this 
//  error: Method threw 'java.security.spec.InvalidKeySpecException' exception.
// : Invalid key format
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decodedPrivateKey);
privateKey = keyFactory.generatePrivate(privateKeySpec);
keystore.setKeyEntry(pkCertAlias, privateKey, null, 
  new Certificate[]{publicKeyChainCert});

enter image description here

NOTE: I used Keytool Explorer to export the .pkcs8 private key from my .pfx keystore. I uploaded that secret into the "Secrets" blade of my KeyVault. So, I assumed that downloading and base64 decode should get me back the exact same file but it appears it doesnt?

1

There are 1 best solutions below

0
Heath On

When you create or import a PEM file (application/x-pem-file), the secret value is not base64-encoded. It's a raw ASCII string containing the PEM using \n for new lines.

My Java is rusty (some pun intended), but you should just be able to elide several of the middle lines of your example:

Certificate publicKeyChainCert = certFactory.generateCertificate(new ByteArrayInputStream(decodedCertChainSecret));
                
//keyStore.setCertificateEntry(pubCertChainAlias, publicKeyChainCert);
KeyVaultSecret secretKey = secretClient.getSecret(privateKeyAzureName);
String privateKey = secretKey.getValue();
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKey);
privateKey = keyFactory.generatePrivate(privateKeySpec);
keystore.setKeyEntry(pkCertAlias, privateKey, null, 
  new Certificate[]{publicKeyChainCert});

This is what I do in the Key Vault .NET CertificateClient, which provides a similar DownloadCertificateAsync method that conditions base64 decoding on the certificate policy content-type, which you can get either by evaluating the certificate policy or should be set in the managed secret's contentType property - either application/x-pem-file or application/x-pkcs12.