Trying to pass along x509 client certificate to second server - java

I'm trying to give server "A" the ability to connect to server "B" using the same X509 client certificate it received from the user. Here are the basics of where I am so far:
public int makeRemoteCall() {
URL url = new URL("https://host.com/service/request");
HttpsURLConnection conn = url.openConnection();
SSLSocketFactory factory = getFactoryFromSessionCert();
conn.setSSLSocketFactory(factory);
int responseCode = conn.getResponseCode();
return responseCode;
}
public static SSLSocketFactory getFactoryFromSessionCert() throws Exception {
HttpServletRequest request = getRequest();
X509Certificate[] certs = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
keyStore.setCertificateEntry("client_cert", certs[0]);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, null);
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), null, null);
return context.getSocketFactory();
}
I am able to retrieve the client's certificate without trouble, and can verify that it does indeed end up in keyStore. But the certificate doesn't seem to make it into keyManagerFactory.
I thought the issue was that I'm not providing a password in keyManagerFactory.init(keyStore, null), so I tried providing it but without success. And should I even have to? I understand that I would need a password if I were loading certificates and keys from a protected file, but here I'm just trying to pass along an already exposed public certificate.
As further background, this basic scheme works if I replace getFactoryFromSessionCert() with this:
public static SSLSocketFactory getFactory(File pKeyFile, String pKeyPassword) throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream keyInput = new FileInputStream(pKeyFile);
keyStore.load(keyInput, pKeyPassword.toCharArray());
keyInput.close();
keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
return context.getSocketFactory();
}
So, what am I not understanding? And how should I pass along a client certificate?

Related

Netty websocket client example with a given PKCS12

I have the client.p12 file and MyPassword, I am trying to establish the websocket connection using Netty code available over here. Currently I have the working example in OkHttpClient. But I am having a hard time to map that into netty.
My server gave me this domain to connect to "https://api.server.com"
In OkHttpClient the following code works
OkHttpClient client = getClient(info);
Request request = new Request.Builder().url("https://api.server.com" + "/messaging").build();
WebSocket webSocket = client.newWebSocket(request, listener);
Here the getClient code is following:
public static OkHttpClient getClient(ConnectionInfo info) {
KeyStore appKeyStore = KeyStore.getInstance("PKCS12");
appKeyStore.load(new FileInputStream("client.p12"), "MyPassword".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(appKeyStore, info.getPassword().toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException(
"Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] {trustManager}, null);
context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
OkHttpClient.Builder builder =
new OkHttpClient.Builder().sslSocketFactory(context.getSocketFactory(), trustManager);
builder.retryOnConnectionFailure(true);
return builder.build();
}
Now that code above works fine, I am trying to implement this in Netty. So looking at example code it only accepts the protocols ws and wss. While in the above example The HTTPS requests Upgraded to WebSocket using the appropriate headers. So my understanding is that If I provide the domain name as "wss:////api.server.com/messaging" Then it will first establish the https connection and then upgrade it to WebSocket.
Now I am not sure how to set the certificate and password.
// I have created a keyStore as following
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File("client.p12"));
try {
keyStore.load(instream, "MyPassword".toCharArray());
} finally {
instream.close();
}
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
// How to specify the above keystore with this client?
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
SSlContextBuilder has a method that takes a KeyManagerFactory:
SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();

Using .p12 file to execute request to rest server

I'm trying to execute requests to a server which provided me with a .p12 file in order to make secure connection with rest services, I'm doing the following in order to set the HttpClient with the key:
SSLContext sslContext =SSLContextBuilder
.create().loadKeyMaterial(ResourceUtils.getFile("classpath:keystore/file.p12"), "secret".toCharArray(), "secret".toCharArray())
.build();
return HttpClientBuilder
.create()
.setConnectionManager(connManager())
.setSSLContext(sslContext)
.setDefaultRequestConfig(requestConfig())
.build();
When I execute the request with OAuth2RestOperations I got:
401 , Non existing certificate or invalid
I recently had a similar requirement. Here is the code I used:
KeyStore clientStore = KeyStore.getInstance("PKCS12");
try {
clientStore.load(ResourceUtils.getFile("classpath:keystore/file.p12"), "secret".toCharArray());
} catch (IOException e) {
//handle exception
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, "secret".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, null, new SecureRandom());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClientBuilder builder = HttpClientBuilder.create();
return builder.setSSLSocketFactory(socketFactory).build();
I think this is actually a duplicate question.
Please see this answer for this question Java HTTPS client certificate authentication.
In all examples you need to call loadKeyMaterial method with KeyStore
public SSLContextBuilder loadKeyMaterial(KeyStore keystore,
Load the keyStore using file path, for example:
keyStore = KeyStore.getInstance("PKCS12");
FileInputStream inputStream = new FileInputStream(new File(certPath));
keyStore.load(inputStream, certPassword.toCharArray());

Java app to use two separate truststores

From a java app, I would like to use two truststores, one to connect to a jms broker, and another to connect to a web service. I know I can import the certs into one truststore, and that works. However, I was wandering whether I can pass a list of different truststores using system property javax.net.ssl.trustStore ?
No, you can't. To use different truststores you should set one of them or both programmatically.
See example below from this post :
SSLContext ssl = SSLContext.getInstance("SSLv3");
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
String password = Configuration.getConfig("keyStorePassword");
store.load(new FileInputStream(new File(Configuration.getConfig("keyStore"))), password.toCharArray());
kmf.init(store, password.toCharArray());
KeyManager[] keyManagers = new KeyManager[1];
keyManagers = kmf.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(store);
TrustManager[] trustManagers = tmf.getTrustManagers();
ssl.init(keyManagers, trustManagers, new SecureRandom());
HttpsConfigurator configurator = new HttpsConfigurator(ssl);
Integer port = Integer.parseInt(Configuration.getConfig("port"));
HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress(Configuration.getConfig("host"), port), 0);
httpsServer.setHttpsConfigurator(configurator);
Implementor implementor = new Implementor(); // class with #WebService etc.
HttpContext context = (HttpContext) httpsServer.createContext("/EventWebService");
Endpoint endpoint = Endpoint.create( implementor );
endpoint.publish(context);

SOAP with mutual SSL - how to send over credentials?

public class ResLookupGetService extends Service {
ServerServicePortType getServerServicePort();
}
public interface ServerServicePortType {
ServerServiceResponse doSoapMethod(RequestObject request, ParamObject parameters);
}
ServerServicePortType service = new ServerServiceGetService().getServerServicePort();
ServerServiceResponse response = service.doSoapMethod(request, parameters);
The above code works fine for invoking my SOAP service before mutual SSL encryption is required.
Once it's turned on, I try creating an SSL Context and setting it like so:
ServerServicePortType service = new ServerServiceGetService().getServerServicePort();
BindingProvider bindingProvider = (BindingProvider) service;
bindingProvider.getRequestContext().put(
"com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory",
getSslContext().getSocketFactory());
ServerServiceResponse response = service.doSoapMethod(request, parameters);
And the code to create the SSLContext:
public SSLContext getSslContext(String keyStorePath, String keyStoreType, String trustStorePath) {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
InputStream ksis = ClassLoader.getSystemResourceAsStream(keyStorePath);
keyStore.load(ksis, "mypassword".toCharArray());
ksis.close();
KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream tsis = ClassLoader.getSystemResourceAsStream(trustStorePath);
trustStore.load(tsis, "mypassword".toCharArray());
tsis.close();
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "mypassword".toCharArray());
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext;
}
But it doesn't seem to be passing my credentials correctly. Am I setting this correctly?
Thanks
Turns out, using the BindingProvider does nothing (or at least I couldn't use it to a point where it made a difference).
Prior to the calls invoking the web service, I simply set these system properties:
private void setSystemProps() {
String keyStoreFileName = "ssl/clientKeyStore.jks";
String keyStorePath = ClassLoader.getSystemResource(keyStoreFileName).getPath();
String keyStoreType = "JKS";
String keyStorePassword = "mypassword";
String trustStoreFileName = "ssl/clientTruststore.jks";
String trustStorePath = ClassLoader.getSystemResource(trustStoreFileName).getPath();
String trustStoreType = "JKS";
String trustStorePassword = "mypassword";
Properties systemProps = System.getProperties();
systemProps.put("javax.net.ssl.keyStore", keyStorePath);
systemProps.put("javax.net.ssl.keyStorePassword", trustStorePassword);
systemProps.put("javax.net.ssl.keyStoreType", keyStoreType);
systemProps.put("javax.net.ssl.trustStore", trustStorePath);
systemProps.put("javax.net.ssl.trustStoreType", trustStoreType);
systemProps.put("javax.net.ssl.trustStorePassword", keyStorePassword);
System.setProperties(systemProps);
}
Then I can do the service call like normal:
ServerServicePortType service = new ServerServiceGetService().getServerServicePort();
ServerServiceResponse response = service.doSoapMethod(request, parameters);
It's worth noting that when I was setting the System Properties, they accept any Object as the value, and I was incorrectly originally setting it to a URL object rather than a String.
So the trustStorePath and keyStorePath variables are being set to the .getPath() value, which is an absolute file path, such as:
"/Users/username/path/to/directory/with/ssl/clientKeyStore.jks"
Now everything works.

Reading Client Certificate in Servlet

I have a Client Server Communication scenario in JBOSS and browser as client(JAVA PROGRAM). Initially when the connection is made, Client sends its Certificate to Server. Server extracts the public key of client from certificate and thus communication will continue.
Now my question is
How to send certificate(.cer) from Client to Server?
How to receive the certificate and extract its public key in Server?
How to send certificate(.cer) from Client to Server?
Client certificate (.cer, .crt, .pem) and it's corresponding private key (.key) should be packaged into PKCS#12 (.p12, .pfx) or JKS (.jks) container first (keystore). You also should have server's CA certicate packaged as JKS (truststore).
Example using HttpClient 3.x:
HttpClient client = new HttpClient();
// truststore
KeyStore trustStore = KeyStore.getInstance("JKS", "SUN");
trustStore.load(TestSupertype.class.getResourceAsStream("/client-truststore.jks"), "amber%".toCharArray());
String alg = KeyManagerFactory.getDefaultAlgorithm();
TrustManagerFactory fac = TrustManagerFactory.getInstance(alg);
fac.init(trustStore);
// keystore
KeyStore keystore = KeyStore.getInstance("PKCS12", "SunJSSE");
keystore.load(X509Test.class.getResourceAsStream("/etomcat_client.p12"), "etomcat".toCharArray());
String keyAlg = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyFac = KeyManagerFactory.getInstance(keyAlg);
keyFac.init(keystore, "etomcat".toCharArray());
// context
SSLContext ctx = SSLContext.getInstance("TLS", "SunJSSE");
ctx.init(keyFac.getKeyManagers(), fac.getTrustManagers(), new SecureRandom());
SslContextedSecureProtocolSocketFactory secureProtocolSocketFactory = new SslContextedSecureProtocolSocketFactory(ctx);
Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) secureProtocolSocketFactory, 8443));
// test get
HttpMethod get = new GetMethod("https://127.0.0.1:8443/etomcat_x509");
client.executeMethod(get);
// get response body and do what you need with it
byte[] responseBody = get.getResponseBody();
You may find working example in this project see X509Test class.
With HttpClient 4.x configuration and syntax would be slightly different:
HttpClient httpclient = new DefaultHttpClient();
// truststore
KeyStore ts = KeyStore.getInstance("JKS", "SUN");
ts.load(PostService.class.getResourceAsStream("/truststore.jks"), "amber%".toCharArray());
// if you remove me, you've got 'javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated' on missing truststore
if(0 == ts.size()) throw new IOException("Error loading truststore");
// tmf
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
// keystore
KeyStore ks = KeyStore.getInstance("PKCS12", "SunJSSE");
ks.load(PostService.class.getResourceAsStream("/" + certName), certPwd.toCharArray());
// if you remove me, you've got 'javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated' on missing keystore
if(0 == ks.size()) throw new IOException("Error loading keystore");
// kmf
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, certPwd.toCharArray());
// SSL
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// socket
SSLSocketFactory socketFactory = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme sch = new Scheme("https", 8443, socketFactory);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);
// request
HttpMethod get = new GetMethod("https://localhost:8443/foo");
client.executeMethod(get);
IOUtils.copy(get.getResponseBodyAsStream(), System.out);
How to receive the certificate and extract its public key in Server?
You server must be configurated to require X.509 client certificate authentication. Then during SSL handshake servlet container will recieve certificate, check it against trustore and provide it to application as a request attribute.
In usual case with single certificate you could use this method in servlet environment to extract certificate:
protected X509Certificate extractCertificate(HttpServletRequest req) {
X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
if (null != certs && certs.length > 0) {
return certs[0];
}
throw new RuntimeException("No X.509 client certificate found in request");
}

Categories