SOAP with mutual SSL - how to send over credentials? - java

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.

Related

Java how to pass certyficate to OkHttpClient

i want to pass my certyficate to post request. I need to pass it becouse it wont work without this.
String pass = "password";
File file = new File("C:\\Users\\Test\\Desktop\\Program\\Certs\\TLS.p12");
InputStream stream = new FileInputStream(file);
KeyStore store = KeyStore.getInstance("PKCS12");
store.load(stream, pass.toCharArray());
PrivateKey key = (PrivateKey)store.getKey("TLS_CERT", pass.toCharArray());
System.out.println("Success");
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(store);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(store, "AGSZKeCnantL".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), new SecureRandom());
OkHttpClient client = new OkHttpClient();
OkHttpClient.Builder builder = client.newBuilder();
builder.sslSocketFactory(sslContext.getSocketFactory()).build();
This is what i recive, i know i can disable TLS but server dont let me connect
Exception in thread "main" java.lang.UnsupportedOperationException: clientBuilder.sslSocketFactory(SSLSocketFactory) not supported on JDK 9+
at okhttp3.internal.platform.Jdk9Platform.trustManager(Jdk9Platform.kt:61)
at okhttp3.OkHttpClient$Builder.sslSocketFactory(OkHttpClient.kt:751)
at Program.main(Program.java:71)
Can someone explain me how to fix this?

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();

Jersey Client with SSL Mutual Authentication

Im have a code write in java and im using jersey client and i tried to do a mutual certification, so i also have a .jks that contains my certificates signed by the CA, so this is my code
#SuppressWarnings("static-access")
#POST
#Path("PruebaPlumaCalva")
public Response testPlumaCalva(String jsonObject)
{
// ClientBuilder.newClient().
logger.debug("Test");
Map<String, Object> payload = new HashMap<String, Object>();
payload.put("documentType", x);
payload.put("documentNumber", xxxxxx);
payload.put("partner", "xxx");
payload.put("transactionId", "xxxxx");
logger.debug("Mis parametros son:" + payload);
String json = null;
try
{
json = (new ObjectMapper()).writeValueAsString(payload);
}
catch (JsonProcessingException e)
{
}
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("xxxxx", "xxxx");
//SSLContext scl = SslConfigurator.newInstance().trustStoreFile("C:/Users/juan.rojas/Documents/Juan José/Proyectos/PagosMoviles/KeyStore.jks").trustStorePassword("123456").keyStoreFile("C:/Users/juan.rojas/Documents/Juan José/Proyectos/PagosMoviles/KeyStore.jks").keyPassword("123456").createSSLContext();
SSLContext scl = SslConfigurator.newInstance().trustStoreFile("/cdrive/f_drive/Pos.jks").trustStorePassword("123456").keyStoreFile("/cdrive/f_drive/Pos.jks").keyPassword("test").createSSLContext();
Client client = ClientBuilder.newBuilder()
.sslContext(scl)
.build();
client.register(feature);
WebTarget webTarget = client.target("xxxxxxxx");
Invocation.Builder invocationB = webTarget.request(MediaType.APPLICATION_JSON_TYPE);
Response response = invocationB.post(Entity.json(json));
JsonNode jsonNode = bonusUtilities.createJsonNode(response);
logger.debug("JsonNode Answer" + jsonNode);
int x = 0;
return response;
}
So when i execute that code with only 1 certificate he works good, but when i have 2 certificates he dont works, i think that the problem is that the .jks dont know which certicate he must use, but i dont know how to specify which one he must use, i already look a lot of forums but i cant see someone that provides me the solution to my problem
I had the same problem and could solve it thanks to this answer.
Basically, there's no way to do this "out of the box", you have to implement you own KeyManager.
I simplified the linked answer a bit more and the code looks like this:
public class FilteredKeyManager implements X509KeyManager {
private final X509KeyManager originatingKeyManager;
public FilteredKeyManager(X509KeyManager originatingKeyManager) {
this.originatingKeyManager = originatingKeyManager;
}
#Override
public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
return "yourAliasHere";
}
}
For the rest of the overriden methods, just call the originatingKeyManager.
To create the SSLContext, I'm not using the SSLConfigurator, but the parameters are the same (path and password).
// Init keystore
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream ksFile = new FileInputStream("yourKeystorePath");
ks.load(ksFile, "keystorePassword".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, pass);
// Init truststore
KeyStore trustKeystore = KeyStore.getInstance("JKS"));
FileInputStream tsFile = new FileInputStream("yourTruststorePath"));
trustKeystore.load(tsFile, "truststorePassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustKeystore);
// Create instance of custom KeyManager
KeyManager[] km = new KeyManager[] { new FilteredKeyManager((X509KeyManager) kmf.getKeyManagers()[0]) };
// Create SSLContext using custom KeyManager
SSLContext context = SSLContext.getInstance("TLSv1");
context.init(km, ts, new SecureRandom());

Trying to pass along x509 client certificate to second server

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?

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);

Categories