Azure/azure-sdk-for-java

[Bug] Use key vault certificate to use as authentication source in Spring/webflux WebClient

rmshnair opened this issue · 8 comments

Query/Question
unable to choose the alias key for keymanager for the request causing authentication failure on requests thats uses client certificate for authentication/authorization.

AzureKeyVaultKeyManager always returns the first alias it found (which is from JVM Keystore)

Why is this not a Bug or a feature Request?
A clear explanation of why is this not a bug or a feature request?

Setup (please complete the following information if applicable):

  • OS: windows
  • IDE: IntelliJ
  • Library/Libraries:

    com.azure.spring
    azure-spring-boot-starter-keyvault-certificates
    3.14.0

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

  • [x ] Query Added
  • Setup information Added

@backwind1233 could you please follow up with @rmshnair on this github issue?

Hi @rmshnair, thanks for reaching out, could you add more context about your issue scenario?
I am not quite sure about your problem, you want to choose the alias of keyvault certificates?
could you check this link, does it meet your needs?

@backwind1233, i looked at the link that you have provided. if i understand correctly, the configuration is meant to select a certificate from the vault to secure the server with SSL and additionally, we could use for mutual authentication on channel creation and trust anchor for validating client certificate.

My scenario is, my client is trying to connect to a server and add a chosen alias from the keyvault part of HTTP packets as client certificate, so that server can read the key from Request.ClientCertificate to authenticate and authorize my client to perform the requested api function.

in order to do this, i do following code to create a SSLContext when i work with Apache/RestTemplate.

//SSLContext creation for RestTemplate (org.apache)
SSLContexts.custom()
                                        .loadKeyMaterial(azureKeyVaultKeyStore, null,
                                                (map, socket) -> "myalias")
                                        .build()

Note: i noticed that the like that you have provided has an example to cover the above scenario for RestTemplate.

however, with webclient uses, Keymanager apis, i do not have an method to choose the alias.

//SslContext creation for WebClient (io.reactor)
SslContext sslContext = SslContextBuilder.forClient()
                                        .keyManager(manager)
                                        .build();

Please note, if i just add trustmanager, channel will be established, but no certificate will be added to the http request, hence the authentication fails with 401 error.

is there any similar configuration available to select the alias on keymanager.

Also wondering about what was the reason behind on below sceanrios

  1. keyvaultKeystore is loading the local JVM keystore certificates. i understand the reason. but wondering why it is added prior to KV certificate.
  2. KeyvaultKeymanager returns null as alias if the provider is AzureKeyVault
  3. KeyvaultKeymanager returns first alias name it finds as Client alias irrespective of inputs (always)

@backwind1233, i looked at the link that you have provided. if i understand correctly, the configuration is meant to select a certificate from the vault to secure the server with SSL and additionally, we could use for mutual authentication on channel creation and trust anchor for validating client certificate.

Yes, you are right.

My scenario is, my client is trying to connect to a server and add a chosen alias from the keyvault part of HTTP packets as client certificate, so that server can read the key from Request.ClientCertificate to authenticate and authorize my client to perform the requested api function.

If i understand correctly , you want to create mutual Tls connections.

in order to do this, i do following code to create a SSLContext when i work with Apache/RestTemplate.

//SSLContext creation for RestTemplate (org.apache)
SSLContexts.custom()
                                        .loadKeyMaterial(azureKeyVaultKeyStore, null,
                                                (map, socket) -> "myalias")
                                        .build()

Note: i noticed that the like that you have provided has an example to cover the above scenario for RestTemplate.

however, with webclient uses, Keymanager apis, i do not have an method to choose the alias.

//SslContext creation for WebClient (io.reactor)
SslContext sslContext = SslContextBuilder.forClient()
                                        .keyManager(manager)
                                        .build();

Please note, if i just add trustmanager, channel will be established, but no certificate will be added to the http request, hence the authentication fails with 401 error.

is there any similar configuration available to select the alias on keymanager.

Hi @rmshnair
I think there is a workaround you can refer.
I create a webclient to use the alias, you may try to refer this code here.
You can find both restTemplate and webclient configurations in the two different files: SampleApplicationConfiguration.java and WebClientApplicationConfiguration.java

I have tested in my machine, so it should work.
image

Please note the workaround, you need add a new class ConfigurableAliasKeyManager.

public WebClient webClientWithMTLS() throws Exception {
    KeyStore azureKeyVaultKeyStore = buildAzureKeyVaultKeyStore();
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(azureKeyVaultKeyStore);

    KeyManager keyManager = buildKeyManager(azureKeyVaultKeyStore, ALIAS);

    SslContext context = SslContextBuilder.forClient()
                                          .keyManager(keyManager)
                                          .trustManager(trustManagerFactory)
                                          .build();

    HttpClient httpClient = HttpClient.create().secure(sslSpec -> sslSpec.sslContext(context));

    return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();
}

private static KeyManager buildKeyManager(KeyStore azureKeyVaultKeyStore, String alias) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(azureKeyVaultKeyStore, "".toCharArray());
    for (KeyManager keyManager : keyManagerFactory.getKeyManagers()) {
        if (keyManager instanceof KeyVaultKeyManager) {
            return new ConfigurableAliasKeyManager((KeyVaultKeyManager) keyManager, alias);
        }
    }
    return null;
}

Thanks for the thoughts. i am trying the first method, while i have implemented the second method already before i raise this request.

Context

KeyVaultKeyManager is a class extends X509ExtendedKeyManager in azure-security-keyvault-jca, it has several apis, and there are 4 apis' implementations in the KeyVaultKeyManager need to be reconsidered.

public String chooseClientAlias(String[] keyType, Principal[] issuers,
      Socket socket);

public String chooseServerAlias(String keyType, Principal[] issuers,
    Socket socket);

    /**
     * Choose an alias to authenticate the client side of an
     * <code>SSLEngine</code> connection given the public key type
     * and the list of certificate issuer authorities recognized by
     * the peer (if any).
    / * 
public String chooseEngineClientAlias(String[] keyType,
          Principal[] issuers, SSLEngine engine);

   /**
     * Choose an alias to authenticate the server side of an
     * <code>SSLEngine</code> connection given the public key type
     * and the list of certificate issuer authorities recognized by
     * the peer (if any).
     * /
public String chooseEngineServerAlias(String keyType,
            Principal[] issuers, SSLEngine engine);

Problem

  1. Currently, the chooseClientAlias and chooseServerAlias will always return null with KeyVaultKeyStore.
  2. chooseEngineClientAlias and chooseEngineServerAlias have default implementations return null, we need to implement the logic using KeyVault.

Goal

  1. Fix logic in chooseClientAlias chooseServerAlias
  2. Follow the java doc to implement the logic in chooseEngineClientAlias and chooseEngineServerAlias.

Hi @rmshnair, while investigating your issue, we found some other issues in our library, since it's all related with enabling tls/mTls with keyVault JCA, we would like to use your issue to track the issues, you may also get the updates.