'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 |