'Issue related to fetching certificate from Azure Keyvault using Java

I am migrating our legacy application into Azure Cloud . In the existing application we are securing our Jetty Server while starting so for that we are using jks file to secure our Jetty server .

Now we are moving into Azure Cloud so we have to fetch the .jks file from Azure keyvault . So how to fetch the complete .jks file from Azure keyvault . I am able to fetch the secrets from Keyvault but unable to fetch the certificate (which i uploaded in Azure keyvault). I am not sure whether we have any API which gives the certificate file.

Below code i am using to fetch the secrets & certificates:

import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.azure.keyvault.KeyVaultClient;
import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials;
import com.microsoft.azure.keyvault.models.CertificateBundle;
import com.microsoft.azure.keyvault.models.SecretBundle;

public class DemoTest {
    
    private static String vaultBase = "https://abc.vault.azure.net/";
    private static String ClientId = "*******************";
    private static String clientKey = "*****************";
    
    
    public static void main(String[] args) {
        
        KeyVaultClient keyVaultClient = GetKeyVaultClient();
        SecretBundle getSecret=keyVaultClient.getSecret(vaultBase, "mysecretkey");
        SecretBundle getSecret1=keyVaultClient.getSecret(vaultBase, "DB-PASSWORD-POC");
        SecretBundle getSecret2=keyVaultClient.getSecret(vaultBase, "certificate-value");
//      SecretBundle getSecret2=keyVaultClient.getSecret(vaultBase, "DB-PASSWORD-DEV");
        CertificateBundle getCertificate=keyVaultClient.getCertificate(vaultBase, "abcprod");
        CertificateBundle bundle = keyVaultClient.getCertificate("https://abc.vault.azure.net/certificates/abcprod/********386c9403bab8337ce21d27495");
        System.out.println(getSecret.value());
        System.out.println(getSecret1.value());
        System.out.println(getSecret2.value());
//      System.out.println(getCertificate.contentType());
//      System.out.println(getCertificate.id());
//      System.out.println(getCertificate.kid());
//      System.out.println(getCertificate.toString());
//      System.out.println(getCertificate.attributes().toString());
//      System.out.println(getCertificate.keyIdentifier().name());
//      System.out.println(getCertificate.sid());
//      System.out.println(getCertificate.certificateIdentifier().baseIdentifier());
//      System.out.println(bundle.cer());
//      System.out.println(bundle);
        
    }


    private static KeyVaultClient GetKeyVaultClient() {
        return new KeyVaultClient(new KeyVaultCredentials() {
            @Override
            public String doAuthenticate(String authorization, String resource, String scope) {
                String token = null;
                try {
                    AuthenticationResult authResult = getAccessToken(authorization, resource);
                    token = authResult.getAccessToken();
                } catch (Exception e) {
                    e.printStackTrace();

                }
                return token;
            }
        });
    }
    
     public static AuthenticationResult getAccessToken(String authorization, String resource) throws InterruptedException, ExecutionException, MalformedURLException {

            AuthenticationResult result = null;

            //Starts a service to fetch access token.
            ExecutorService service = null;
            try {
                service = Executors.newFixedThreadPool(1);
                AuthenticationContext context = new AuthenticationContext(authorization, false, service);

                Future<AuthenticationResult> future = null;

                //Acquires token based on client ID and client secret.
                if (ClientId != null && clientKey != null) {
                    ClientCredential credentials = new ClientCredential(ClientId, clientKey);
                    future = context.acquireToken(resource, credentials, null);
                }

                result = future.get();
            } finally {
                service.shutdown();
            }

            if (result == null) {
                throw new RuntimeException("Authentication results were null.");
            }
            return result;
        }

}

We are securing our jetty server with this code :

public class ABCSubscriber {
        private static final int Port = 9090;
        private static final String KeyStoreType = "jks";
        private static final String KeyStoreFile = "/home/abc/xyz/subscriber.jks";
        private static final String KeyStorePassword = "******";
        private static final String KeyPassword = "*******";
        private static final String ContextPath = "/";
        private static final String URLPattern = "/*";

        public static void main(String[] args) throws Exception {
              
        Server server = new Server();
        HttpConfiguration http_config = new HttpConfiguration();
        http_config.setSecureScheme("https");
        http_config.setSecurePort(Port);
        http_config.setRequestHeaderSize(8192);

        // HTTP connector
        ServerConnector http = new ServerConnector(server,
                new HttpConnectionFactory(http_config));
        http.setPort(9091);
        http.setIdleTimeout(30000);

        // SSL Context Factory
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStoreType(KeyStoreType);
        sslContextFactory.setKeyStorePath(KeyStoreFile);
        sslContextFactory.setKeyStorePassword(KeyStorePassword);
        sslContextFactory.setKeyManagerPassword(KeyPassword);

        // sslContextFactory.setTrustStorePath(ncm.getKSFile());
        // sslContextFactory.setTrustStorePassword("changeit");
        sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
                "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA",
                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");

        // SSL HTTP Configuration
        HttpConfiguration https_config = new HttpConfiguration(http_config);
        https_config.addCustomizer(new SecureRequestCustomizer());

        // SSL Connector
        ServerConnector sslConnector = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
            new HttpConnectionFactory(https_config));
        sslConnector.setPort(Port);
        server.addConnector(sslConnector);

        /**Disable and enable protocols*/
                String[] includeProtocols = {"TLSv1.1","TLSv1.2"};
        sslContextFactory.addExcludeProtocols("TLSv1.0");
        sslContextFactory.setIncludeProtocols(includeProtocols);
     
        /**End Disable and enable protocols*/

        // HTTPS Configuration
        ServerConnector https = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
                new HttpConnectionFactory(https_config));
        https.setPort(Port);
        https.setIdleTimeout(30000);
        //server.setConnectors(new Connector[] { http, https });
        server.setConnectors(new Connector[] {  https });
                ServletContextHandler ctxt = new ServletContextHandler(0);
                ctxt.setContextPath(ContextPath);
                server.setHandler(ctxt);

                ctxt.addServlet(new ServletHolder(new ABCServlet()), "/*");

                try {
                    server.start();
                } catch ( Exception e ) {
                        e.getLocalizedMessage();
                };
        server.join();

        }
}

So , Is there any way to get the certificate file from Azure keyvault? If not then how can we use certificate to secure the server ?

Can anyone please help me on this ?

Thanks in Advance !!!



Solution 1:[1]

You need to download the private key of the certificate as a secret. Getting the secret using the more obvious GetCertificate will only return the public key part of the certificate.

I know this is C# in the code example below, but that is how I get the certificate out of Key Vault, I hope you can get an idea on how to do the same in Java:

        /// <summary>
        /// Helper method to get a certificate
        /// 
        /// Source https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs
        /// </summary>
        /// <param name="certificateClient"></param>
        /// <param name="secretClient"></param>
        /// <param name="certificateName"></param>
        /// <returns></returns>
        private static X509Certificate2 GetCertificateAsync(CertificateClient certificateClient,
                                                                SecretClient secretClient,
                                                                string certificateName)
        {

            KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName);

            // Return a certificate with only the public key if the private key is not exportable.
            if (certificate.Policy?.Exportable != true)
            {
                return new X509Certificate2(certificate.Cer);
            }

            // Parse the secret ID and version to retrieve the private key.
            string[] segments = certificate.SecretId.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length != 3)
            {
                throw new InvalidOperationException($"Number of segments is incorrect: {segments.Length}, URI: {certificate.SecretId}");
            }

            string secretName = segments[1];
            string secretVersion = segments[2];

            KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion);

            // For PEM, you'll need to extract the base64-encoded message body.
            // .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier.
            if ("application/x-pkcs12".Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase))
            {
                byte[] pfx = Convert.FromBase64String(secret.Value);
                return new X509Certificate2(pfx);
            }

            throw new NotSupportedException($"Only PKCS#12 is supported. Found Content-Type: {secret.Properties.ContentType}");
        }
    }
}

Solution 2:[2]

Here is the Java code equivalent to answer posted by @Tore Nestenius

public byte[] getPkcsFromAzureKeyVault(String certificateName) throws InvalidDataException {
    String keyVaultName = System.getenv("KEY_VAULT_NAME");
    String keyVaultUri = "https://" + keyVaultName + ".vault.azure.net";

    CertificateClient certificateClient = new CertificateClientBuilder().vaultUrl(keyVaultUri)
            .credential(new DefaultAzureCredentialBuilder().build()).buildClient();

    KeyVaultCertificateWithPolicy certPol = certificateClient.getCertificate(certificateName);

    SecretClient secretClient = new SecretClientBuilder().vaultUrl(keyVaultUri)
            .credential(new DefaultAzureCredentialBuilder().build()).buildClient();
    KeyVaultSecret secret = secretClient.getSecret(certPol.getProperties().getName(),
            certPol.getProperties().getVersion());

    if ("application/x-pkcs12".equalsIgnoreCase(secret.getProperties().getContentType())) {
        return Base64.getDecoder().decode(secret.getValue());
    }
    throw new InvalidDataException();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tore Nestenius
Solution 2 Joe