Sending client certificates on custom ClosableHttpClient for zuul - java

I'm trying to send an X.509 client certificate with outgoing requests for zuul forwardproxy. Certificate is included in the keystore which i'm loading with loadKeyMaterial() on SSLContext.
Here's the code:
#Bean
public CloseableHttpClient httpClient() throws Throwable {
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(new File(keyStorePath), keyStorePass, keyStorePass, new PrivateKeyStrategy() {
#Override
public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
return alias;
}
})
.loadTrustMaterial(new File(keyStorePath), keyStorePass, new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1.3" },
new String[] { "TLS_DHE_RSAWITH_AES_256_GCM_SHA384" },
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslsf)
.build();
}
when i make a test request i'm getting Received fatal alert: handshake_failure and on the verbose logs i see the message No X.509 certificate for client authentication, use empty Certificate message instead. How i can make the httpClient send the certificate as X.509 client certificate?

I had encountered simmilar problem in my case it was two part solution.
Import correcly certificate,
I had incorrectly created keystore:
keytool -importcert -keystore keystore.jks -alias client-cert -file client-cert.pem -storepass password
What helped me was:
openssl pkcs12 -export -chain -in client-cert.pem -inkey client-key.pem -out keystore.p12 -name client-cert -CAfile ca-cert.pem
keytool -importkeystore -destkeystore keystore.jks -srckeystore keystore.p12 -alias client-cert
I found this here:
https://blogs.oracle.com/jtc/installing-trusted-certificates-into-a-java-keystore
Problem with spring-cloud-starter-zuul 1.1.5.RELEASE. Zuul did not used my custom CloseableHttpClient, this issue was solved in https://github.com/spring-cloud/spring-cloud-netflix/issues/2026 and after upgrading to 1.4.2.RELEASE this problem was fixed.

Related

KeyStore API throws error : java.security.KeyStoreException: BCFIPS JKS store is read-only and only supports certificate entries

I have created a JKS trust-store for an application. (PKCS12 is not yet supported by the OpenJDK in production server)
I am trying to add a certificate to the truststore using the KeyStore API:
public addToTrustStore(List<String> certChain) {
String alias;
try {
KeyStore keyStore = loadTruststore();
for (String cert : certChain) {
alias = UUID.randomUUID().toString();
X509Certificate certificate = decodePEMCertificate(cert); // converts PEM format to X509Certificate
keyStore.setCertificateEntry(alias, certificate);
logger.debug("Added the certificate with DN: {0} to the "
+ "truststore with the alias: {1}", certificate.getSubjectDN());
}
} catch (KeyStoreException a) {
//process execption
}
}
Can anyone help me get past this red-only nature of this JKS?
Thanks in advance.
Solution:
Summary : Bouncycastle doesn't permit write to JKS keystores. So we need to use BCFKS format keystore.
You can use the providers:
org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider, for FIPS compliance
org.bouncycastle.jce.provider.BouncyCastleProvider
The keytool commands that worked for me:
Create keystore:
keytool -import -file pem.cert -alias "vmware" -storepass changeit -keystore truststore.bks -deststoretype BCFKS -noprompt -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath bc-fips-1.0.2.jar
Delete an entry:
keytool -delete -alias boguscert -storepass changeit -keystore truststore.bks -storetype BCFKS -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath bc-fips-1.0.2.jar

How to configure apache httpclient 4.5+ SSLContext to use mutual TLS authentication with a self signed certificate?

I am trying to configure a CloseableHttpClient from httpclient 4.5 with mutual TLS authentication. The server certificate is self-signed.
Here is the code I use (inpired by various StackOverflow posts):
KeyStore trustStore = KeyStore.getInstance("pkcs12");
try (InputStream fis = new FileInputStream(ssl_cert_file)) {
trustStore.load(fis, password.toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(trustStore, password.toCharArray(), (map, socket) -> "client")
.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier());
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", socketFactory).build();
connectionManager = new PoolingHttpClientConnectionManager(registry);
I Got
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
Which looks like that the CA certificate I provide is not used I think.
Any idea of what could be wrong ?
The CA's certificate, client's certificate and client's private key are in a pkcs12 file
generated with
openssl pkcs12 -export -in client-cert.pem -inkey private/client-key.pem -certfile cacert.pem -name "client" -out client-cert.p12
I tried openssl s_client to check the certificate return the expected output (it does). I suspect the problem is coming from my code rather than the client-cert.p12 file.
openssl s_client -connect host:port -tls1 -cert client-cert.pem -key private/client-key.pem -CAfile cacert.pem
As pointed by #Gimby The problem was my CA's certificate was not recognized as a TrustCertEntry in my keystore.
My workaround is to generate a jks file just for the CA's certificate:
keytool -import -alias client -file cacert.pem -storetype JKS -keystore cacert.jks
Build a KeyStore object from it and use it for SSLContext.loadTrustMaterial.

Use Java commons-httpclient with cacert, cert and key extracted from .pfx

I have a .pfx file on input.
I extracted CA certificate, client certificate and key from it using following commands:
openssl pkcs12 -in input.pfx -clcerts -nokeys -out client-cert.pem
openssl pkcs12 -in input.pfx -out ca-cert.pem -nodes -nokeys -cacerts
openssl pkcs12 -in input.pfx -nocerts -nodes -out client.key
Now I'm able to execute following request using curl:
curl --cacert ./ca-cert.pem --key ./client.key --cert ./client-cert.pem -i -X POST --data-binary '#./my-data.txt' https://my-target.url
Now I would like to execute same request in Java using commons-httpclient
As far as I got it, I should convert my keys to "keystore.jks" and "truststore.jks" and use them as follwoing:
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(new File("truststore.jks"), PASS.toCharArray())
.loadKeyMaterial(new File("keystore.jks"), PASS.toCharArray(), PASS.toCharArray())
.build();
CloseableHttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
HttpPost post = new HttpPost("https://my-target.url");
post.setEntity(new ByteArrayEntity(FileUtils.readFileToByteArray(new File("my-data.txt"))));
client.execute(post);
Is there any simpler way to pass certificates to POST ?
If not, what are correct way to converting my .pfx to .jks?
I tried
keytool -importkeystore -srckeystore input.pfx -srcstoretype pkcs12 -destkeystore keystore.jks -deststoretype JKS
but end up with
java.security.UnrecoverableKeyException: Cannot recover key
Final solution was to load pfx file directly and trust certificates there.
final KeyStore store = KeyStore.getInstance("PKCS12");
try (FileInputStream stream = new FileInputStream(new File("file.pfx"))) {
store.load(stream, PASS.toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(store, PASS.toCharArray())
.loadTrustMaterial(store, TrustSelfSignedStrategy.INSTANCE)
.build();

How to setup basic Jersey/Grizzly 2.21 SSL startup configuration

I'm trying to get a very basic Grizzly server up and running to allow for one-way SSL (HTTPS) connections to access jax-rs REST services. Eventually I want two-way SSL security.
I've gone through many of the examples and I just can't get anything to work. I keep running into a SSL Handshake error. Clearly I must be doing something stupid. Any help is appreciated.
Here is my code to start my embedded Grizzly server using the Jersey wrapper classes:
public static HttpServer startHttpsServer(URI listenerURI) throws IOException {
ResourceConfig resourceConfig = new ResourceConfig().packages("ws.argo.experiment.ssl");
// First I tried this configuration using the certs from the Jersey sample code
// Grizzly ssl configuration
SSLContextConfigurator sslContext = new SSLContextConfigurator();
// set up security context
sslContext.setKeyStoreFile("./src/main/resources/keystore_server"); // contains server keypair
sslContext.setKeyStorePass("asdfgh");
sslContext.setTrustStoreFile("./src/main/resources/truststore_server"); // contains client certificate
sslContext.setTrustStorePass("asdfgh");
// Then I tried just using a default config - didn't work either
// sslContext = SSLContextConfigurator.DEFAULT_CONFIG;
if (!sslContext.validateConfiguration(true)) {
LOGGER.severe("Context is not valid");
}
LOGGER.finer("Starting Jersey-Grizzly2 JAX-RS secure server...");
HttpServer httpServer; //= GrizzlyHttpServerFactory.createHttpServer(listenerURI, resourceConfig, false);
httpServer= GrizzlyHttpServerFactory.createHttpServer(
listenerURI,
resourceConfig,
true,
new SSLEngineConfigurator(sslContext).setClientMode(false).setNeedClientAuth(false)
);
httpServer.getServerConfiguration().setName("Test HTTPS Server");
httpServer.start();
LOGGER.info("Started Jersey-Grizzly2 JAX-RS secure server.");
return httpServer;
}
I also tried replaced SSLEngineConfigurator(sslContext).setClientMode(false).setNeedClientAuth(false) with null to see if that would help. Nope.
I always get the following error:
grizzly-nio-kernel(3) SelectorRunner, fatal error: 40: no cipher suites in common
javax.net.ssl.SSLHandshakeException: no cipher suites in common
%% Invalidated: [Session-2, SSL_NULL_WITH_NULL_NULL]
grizzly-nio-kernel(3) SelectorRunner, SEND TLSv1.2 ALERT: fatal, description = handshake_failure
grizzly-nio-kernel(3) SelectorRunner, WRITE: TLSv1.2 Alert, length = 2
grizzly-nio-kernel(3) SelectorRunner, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common
To add on top of JMS comment, his answer solve my problem too .
Here is the command i used to generate the RSA certificate .
keytool -genkey -keystore ./keystore_client -alias clientKey -keyalg RSA -keypass changeit -storepass changeit -dname "CN=Client, OU=Jersey, O=changeit, L=KL, ST=SEL, C=MY"
keytool -export -alias clientKey -storepass changeit -keystore ./keystore_client -file ./client.cert
keytool -import -alias clientCert -file ./client.cert -storepass changeit -keystore ./truststore_server
keytool -genkey -keystore ./keystore_server -alias serverKey -keyalg RSA -keyalg RSA -keypass changeit -storepass changeit -dname "CN=changeit, OU=Jersey, O=changeit, L=KL, ST=SEL, C=MY"
keytool -export -alias serverKey -storepass changeit -keystore ./keystore_server -file ./server.cert
keytool -import -alias serverCert -file ./server.cert -storepass changeit -keystore ./truststore_client
I have seen this type of handshake issue come up in other posts while trying to run this issue to ground. In all of these handshake posts, the server key algorithm was never discussed - I wish it had been. It would have saved me a couple of hours. The issue that caused the above errors stemmed from assuming that the keystores that were created as part of the Jersey sample project would work. The server key was the problem.
The sample server certs are generated using the DSA algorithm. Apparently this is an issue.
I recreated the server keys using a RSA algorithm and 2048 bit strength. I restarted the server and the everything started to work like one would expect.
The error was that I assumed that the "sample" keys would work. Oops.

HttpAsyncClient SSL with client authentication

I want to use a HttpAsyncClient using SSL and with Client authentication (in addition to Server auth).
I had some problems, but finally I found the right way. I show you how to do it:
Generate keystore from PEM (PEM -> PKCS#12 keystore -> JKS keystore)
Create PKCS12 keystore from private key and public certificate:
sudo openssl pkcs12 -export -name myservercert -in selfsigned.crt -inkey server.key -out keystore.p12
Convert PKCS12 keystore into a JKS keystore:
sudo keytool -importkeystore -destkeystore mykeystore.jks -srckeystore keystore.p12 -srcstoretype pkcs12 -alias myservercert
List the contents of the JKS keystore:
sudo keytool -list -v -keystore mykeystore.jks
Your local JVM should trust the server Certificate. If its self-signed, add it to the cacerts (trusted certificates list; its default password is 'changeit'):
sudo keytool -import -alias alias_serv_cert -file /var/tmp/CERT_SERVER.cert -keystore /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.25.x86_64/jre/lib/security/cacerts
Java code to generate the client and do the POST:
char[] keystorePass = "MY PASSWORD".toCharArray();
FileInputStream fis = null;
//Loading KEYSTORE in JKS format
KeyStore keyStorePci = KeyStore.getInstance(KeyStore.getDefaultType());
try {
fis = new FileInputStream(keystoreDirectory + keystoreFilename);
keyStorePci.load(fis, keystorePass);
} catch (Exception e) {
LOG.error("Error loading keystore: " + keystoreDirectory+ keystoreFilename);
} finally {
if (fis != null) {
fis.close();
}
}
//Setting JKS keystore in SSL Context (I do not reccomend pass a 3rd argument PrivateKeyStrategy!)
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStorePci, keystorePass).build();
//Creating Async HTTP client with SSL
CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom().setSSLContext(sslcontext).build();
//Executing POST method
try {
httpclient.start();
future = httpclient.execute(httppost, new MyCustomAsyncResultManager(transactionId, transactionToken));
HttpResponse response = future.get();
LOG.info("result: " + response.getStatusLine());
} finally {httpclient.close();}
I hope this could be helpful for you.

Categories

Resources