Azure / azure-sdk-for-java

This repository is for active development of the Azure SDK for Java. For consumers of the SDK we recommend visiting our public developer docs at https://docs.microsoft.com/java/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-java.
MIT License
2.32k stars 1.97k forks source link

[BUG] Unable to retrieve Private key for an Certificate with exportable keys #32106

Open vtapadia opened 1 year ago

vtapadia commented 1 year ago

Describe the bug Using the KeyVaultClient, we are unable to retrieve the private key from the Key Vault.

Exception or Stack Trace 2022-11-11 16:05:55,344 INFO | main | example.certificate.UserMgmtTest | Key Properties exportable ?: null 2022-11-11 16:05:55,344 INFO | main | example.certificate.UserMgmtTest | Private Key ?: false 2022-11-11 16:05:55,344 ERROR | main | com.azure.security.keyvault.keys.models.JsonWebKey | java.security.NoSuchAlgorithmException: no such algorithm: RSA for provider AzureKeyVault java.lang.IllegalStateException: java.security.NoSuchAlgorithmException: no such algorithm: RSA for provider AzureKeyVault at com.azure.security.keyvault.keys.models.JsonWebKey.getRsaPublicKey(JsonWebKey.java:583) at com.azure.security.keyvault.keys.models.JsonWebKey.toRsa(JsonWebKey.java:741)

To Reproduce Create a self signed certificate in Azure Key Vault, with Advanced option to enable export of keys. And try to download the private keys using the KeyVaultClient

Code Snippet

        KeyClient keyClient = new KeyClientBuilder()
                .credential(credential)
                .vaultUrl(kvURL)
                .buildClient();

        KeyVaultKey keyVaultKey = keyClient.getKey(keyId);
        log.info("Key Properties exportable ?: {}" , keyVaultKey.getProperties().isExportable());

        JsonWebKey webKey = keyVaultKey.getKey();
        log.info("Private Key ?: {}" , webKey.hasPrivateKey());

Expected behavior Should be able to extract the private key in this case.

Screenshots N/A Setup (please complete the following information):

If you suspect a dependency version mismatch (e.g. you see NoClassDefFoundError, NoSuchMethodError or similar), please check out Troubleshoot dependency version conflict article first. If it doesn't provide solution for the problem, please provide:

Additional context While creating the certificate, the Private key was marked as exportable image

Access permissions are set as image

Information Checklist Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

joshfree commented 1 year ago

@vcolin7 could you please follow up with @vtapadia on this issue? thanks

vcolin7 commented 1 year ago

Last I checked you cannot export keys in Key Vault instances, only in Managed HSMs. Is this right @jlichwa? Which one are you working with @vtapadia?

jlichwa commented 1 year ago

@vtapadia you should use new SDK versus deprecated KeyClient SDK.

Either way, it is certificate, certificate can be retrieved as PFX/PEM with private key using GetSecret API/SDK. I don't see sample in java but in C#: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/keyvault/samples/getcert/Program.cs#L71

vcolin7 commented 1 year ago

@jlichwa He's using the new KeyClient (the deprecated one is called KeyVaultClient). @vtapadia one thing to note is that the code sample shared only works for getting a certificate's private key.

@jlichwa, do you know if a KeyVaultKey's private key (not part of a certificate) can even be exported from a Key Vault? I thought we only supported this for Managed HSMs.

vcolin7 commented 1 year ago

@vtapadia While we wait for an answer from Jack, if I recall correctly, you will need to set a release policy for a key before being able to gain access to its private key, even if you set the exportable property to true. You can find more information on how the release operation works here and about the release policy itself here.

You can find how we do this for one of our tests here.

Adding @heaths to the conversation just in case, please feel free to correct me if I'm wrong about this.

jlichwa commented 1 year ago

@vcolin7 it is different key. You think about cryptographic key. It is private key of certificate. Certificates does not have release policy (based on screenshot it is certificate).

heaths commented 1 year ago

The private key material is never part of the certificate but is stored as a secret. Java might consider implementing a downloadCertificate methods on CertificateClient like I did in .NET; though, customers can do it now similar to: https://learn.microsoft.com/samples/azure/azure-sdk-for-net/get-certificate-private-key/

vtapadia commented 1 year ago

Thanks and sorry for the late response.

We are indeed looking for the Private key which was part of the key pair used to create the certificate. From the certificate, we get the key ID. Using the Key ID, I would expect that I could export the private key programatically for future usage if during creation, I marked the private key as exportable.

In general, we do not need to extract the private key and we don't either. But its a specific use case where the library depends on a file location for the keystore file. So we will really have to extract it.

I still feel that the exportable parameter value being null could indicate that this value is not set properly and might be the main reason for this not working as expected with the Java SDK but did not spend time to dive deep into the SDK.

jlichwa commented 1 year ago

@vtapadia that key id is only for public key. If you need private key you need to get entire certificate in pfx or pem which is available with getSecret API. If you don't see private key when you get secret (secret download) please let us know.

"Downloading as certificate means getting the public portion. If you want both the private key and public metadata then you can download it as secret." https://learn.microsoft.com/en-us/azure/key-vault/certificates/how-to-export-certificate?tabs=azure-cli#export-stored-certificates If key is marked as exportable, downloaded secret (PFX/PEM) will have private key otherwise it will have private key empty.

vtapadia commented 1 year ago

Wouldn't it be easy to support the way I have mentioned.

At least as it is mentioned as exportable, I would expect the easiest way to get the private key. Doing this as secret would need to again load it and parse it separately.

vtapadia commented 1 year ago

Also, is it possible to get some sample on how to read the key from the secret in Java. We tried few ways using BouncyCastle but those does not seam to work.

jlichwa commented 1 year ago

@vtapadia this certificate management component for TLS certs, so windows cert manager, NGiNX etc ... all cert managers requires entire certificate in pfx or pem format and API is built for that scenario. I would recommend use openssl or equivalent for any custom operations.

vtapadia commented 1 year ago

Hi @jlichwa I would argue that even the usage for TLS is not complete. I would be divulging from the original question but might be good to mention here. I could not find a way to upload a certificate with our own CA to be uploaded to the Certificate for Truststore (without Private Key) use case. It fails when we try to upload a certificate stating that we also need private key. Also, when are using it as a keystore with our client certificate, it seems it is not sending the complete chain. We are investigating this issue as well.

Maybe you have solutions or hints for these as well. Otherwise I will raise another issue for those as well.

jlichwa commented 1 year ago

@vtapadia I asked to understand your use case. Today our use case is either download entire cert with private key or without for TLS offload - which does not scale well. This is to answer why our API is designed that way. So getting just private key without certificate is likely that you are using for some specific scenario, which maybe something we did not account for.

Other than that, the solution stays, you will either do in code like in c# example (we don't have example for Java) I provided https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/keyvault/samples/getcert/Program.cs#L100 Third-party tools like openssl should also help.

For you TLS issue, yes we require to import certificate with private key. Separate issue/question has to be created.

vtapadia commented 1 year ago

Ok, clear. I did some investigation and made some small code to get a sample setup showing the working in Java. Might be useful for others also who run into similar issue.

// Reading of a certificate in memory from the Key Vault
//Create Identity Credentials
char[] NO_PASSWORD = "".toCharArray();
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
        .tenantId(tenantId).clientId(clientId).clientSecret(clientSecret)
        .build();

//Retrieve the certificate with the name "keyId"
CertificateClient certificateClient = new CertificateClientBuilder()
        .vaultUrl(kvURL)
        .credential(credential)
        .buildClient();
KeyVaultCertificateWithPolicy clientCertificate = certificateClient.getCertificate(keyId);

log.info("certificate {}", clientCertificate);

//Retrieve the certificate as a secret value
SecretClient secretClient = new SecretClientBuilder()
        .vaultUrl(kvURL)
        .credential(credential)
        .buildClient();
log.info("Secret id {}", clientCertificate.getSecretId());
KeyVaultSecret secret = secretClient.getSecret(keyId);
String secretValue = secret.getValue();
byte[] decodedSecretValue = Base64.decode(secretValue);
log.trace("Secret value {}", secretValue);

log.info("Secret value content type {}", secret.getProperties().getContentType());

//Validate the content type to be pkcs12 (application/x-pkcs12)
if (secret.getProperties().getContentType().contains("pkcs12")) {
    //Create a keystore in memory to parse the content
    KeyStore store = KeyStore.getInstance("PKCS12");
    //NOTE: remember to use the base64 decoded value in this.
    store.load(new ByteArrayInputStream(decodedSecretValue), NO_PASSWORD);

    //Retrieve the key. This would only work if the key is marked as exportable when creating the certificate
    //Otherwise this would return null.
    Key key = store.getKey(store.aliases().nextElement(), NO_PASSWORD);
    log.info("Key found :: {}", key);

    //Retrieve certificate chain
    Certificate[] certificateChain = store.getCertificateChain(store.aliases().nextElement());
    log.info("Certificate chain length {}", certificateChain.length);

    // JKS File creation, only if required
    boolean useCaseToGenerateFile = false;
    if (useCaseToGenerateFile) {
        KeyStore fileStore = KeyStore.getInstance("JKS");
        fileStore.load(null, NO_PASSWORD);

        //Add the key and the certificate chain extracted earlier
        fileStore.setKeyEntry(keyId, key, NO_PASSWORD, certificateChain);

        //JKS File path..and store
        try (FileOutputStream fos = new FileOutputStream(keyId+".jks")) {
            fileStore.store(fos, NO_PASSWORD);
        }
    }
}
vtapadia commented 1 year ago

Also, would it be an idea to not consider this as a bug, but as a feature request.