I have written a Java client in SAAJ which works successfully when sending SOAP messages over HTTP however when I attempt to send any SOAP messages to a web service over HTTPS that requires a client certificate it doesnt work.
At the bottom of the page at the following link - SAAJ Security - it states the following:
From the SAAJ side, all you need to do is use URLs with HTTPS as the protocol. This will work only if the certificate was successfully imported into /jre/lib/security/cacerts; otherwise JSSE will not allow the connection
I imported the client certificate along with the associated root certificate into my Java cacerts as instructed to do so above and ran the program however I get the following error:
java.net.SocketException: Connection reset
I ran a wireshark trace on the traffic and noticed that the Java code isnt presenting a client certificate when asked to do so by the server therefore I have the following questions:
1) By only passing the HTTPS URL to the soapConnection.call() method along with importing the certs to my cacerts file, is this enough for authentication to occur i.e. does SAAJ handle this automatically? Or are there more steps required that arent described in the above link?
2) By importing the certificates into the cacerts file within my JAVA_HOME, does the Java SAAJ client automatically know to look here when calling the soapConnection.call()? Or do I need to explicitly tell my code what cacerts file to use?
3) If the Java SAAJ client is automatically using the cacerts file under my JAVA_HOME then how does it know what client certificate to use? Again, shouldnt I need to explicitly code this or does SAAJ handle this automatically?
Thanks in advance
I managed to figure this out. I used the following code:
static public void doTrustToCertificates() throws Exception
{
// Set truststore that contains root / intermediary certs
System.setProperty("javax.net.ssl.trustStore", "C:\\cert\\trusted.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
// Set keystore that contains private key
File pKeyFile = new File("C:\\cert\\privatekey.pfx");
String pKeyPassword = "Password01";
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream keyInput = new FileInputStream(pKeyFile);
keyStore.load(keyInput, pKeyPassword.toCharArray());
keyInput.close();
keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());
// Set ssl context with private key and truststore details
SSLContext sc = SSLContext.getInstance("TLSv1");
sc.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLSocketFactory sockFact = sc.getSocketFactory();
// Add ssl context to https connection
HttpsURLConnection.setDefaultSSLSocketFactory(sockFact);
}
Then called the doTrustToCertificates() method just before the soapConnection.call() method of SAAJ and it worked like so:
doTrustToCertificates();
SOAPMessage soapResponse = soapConnection.call(soapMsgXml, ENDPOINT_URL);
You weren't so instructed. The instructions are misleading, but they only apply to the situation where the server certificate is self-signed, and not at all to the situation where the client needs a certificate.
In this case, you need to create a client certificate in your client's keystore. Either you create a CSR and get it signed and import that back into the same keystore using the same alias as you started with when you generated the keypair and CSR, or else you need to generate a self-signed certificate (yuck) and export it and get it imported into the server's truststore.
You don't need to write any code. All you need to do is set the system properties javax.net.ssl.keyStore and friends to tell JSSE about your client keystore.
Related
I've been at this way too long and have searched high and low for a solution.
I am trying to do mutual SSL. The target is using a certificate signed by an intermediate that is valid and trusted. My side, a Java web service client using httpclient 4.3.6, is also using a certificate signed by the intermediate.
The intermediate is in a JKS format keystore and has the full chain. My certificate is also in a JSK keystore.
I am also running Wireshark to follow the SSL process, since the Java exceptions are very unhelpful. I can see that the Server Hello finishes (a line in Wireshark called "Server Hello, Certificate, Server Key Exchange, Certificate Request, Server Hello Done"). A couple of packets later, there is a "Certificate, Client Key Exchange" line that seems to run ok. Two packets later, there is an alert about a handshake failure (40). When I go backward two packets to the "Certificate, Client Key Exchange" line and inspect it closely, I can see that while the key was sent off, there is a line that says "Certificates Length: 0" in the packet inspection window.
So it appears that my certificate is not even being sent? I saw on another question here that it may be because my side doesn't see a valid path to my cert, and so doesn't bother including it? (The answer here https://stackoverflow.com/a/14876605/5136448). I do include the trust store that has my intermediate in it when using SSLContext and SSLConnectionSocketFactory.
A colleague of mine got a successful connection using openssl when including my keystore and intermediate together, so I'm trying to add the certificate chain from the intermediate keystore to my own keystore to see if that will solve it. I have no found any successful way of doing this (openssl, keytool, Keystore Explorer), so I may not have the right idea as to properly solve this.
To sum up, using Apache http-client 4.3.6, I need to do mutual SSL to a server where the server is trusted, but the client doesn't seem to be sending its certificate (which was signed by the same entity that signed the server's certificate).
Should it be relevant, here is the client-side code:
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(new File("/path/to/test.keystore")), "abc123".toCharArray());
KeyStore trustStore = KeyStore.getInstance("PKCS12");
trustStore.load(new FileInputStream(new File("/path/to/test.truststore")), "123abc".toCharArray());
SSLContext sslContext = SSLContexts
.custom()
.loadKeyMaterial(keystore, "abc123".toCharArray(), null)
.loadTrustMaterial(trustStore, null)
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2"},
null,
SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient1 = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
I am creating a Java program to get information from a server but I have to perform a ssl handshake with the server from the Java program.
I have myfilercert.cer file certificate for authentication purpose but I have no idea how I can load that certificate in java so that the java program can perform 'handshake' with the server where I want to get information from. Where to begin?
What you need is the java keystore. The keystore is a repository of security certificates used in SSL encryption.
You can read here about the Server Authentication During SSL Handshake. This is a keystore tutorial.
As an alternative to keytool, i would suggest a tool with a Graphical User Interface called Portecle. You can use it to browse the contents of your .cer file and see what's in it.
It can be useful to know about the various certificate encodings. Also read about the X.509 standard.
This is an article on java keytool essentials (which is the oracle tool that works with the java keystore).
You can google and find a lot of resources that instruct you how to generate. I think you will want to keep the certificate at the application level.
Some SO questions that helped me along the way:
Trust Store vs Key Store - creating with keytool - important to know the difference between the trust manager and keymanager
Java HTTPS client certificate authentication
How to export private key from a keystore of self-signed certificate
What is difference between cacerts and keystore
How to connect to a secure website using SSL in Java with a pkcs12 file?
Received fatal alert: handshake_failure through SSLHandshakeException
How to configure trustStore for javax.net.ssl.trustStore on windows?
Good luck!
You can use Apache HttpClient (or just use the required classes from it to use SslContextBuilder, really), and then it'd be like so:
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
sslContextBuilder.loadTrustMaterial(new File("yourTrustStore.jks"), "thePassWord");
SSLContext sslContext = sslContextBuilder.build();
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) (new URL("https://thesite.com").openConnection());
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
But you need to create a keystore for your certificate, which can be done with keytool. If you need this for android, you'll need SpongyCastle library, and use that as a provider for KeyTool to create a BKS keystore instead of a JKS keystore; and you will need to explicitly open the KeyStore in Java.
KeyStore keyStore = KeyStore.getInstance("BKS",
BouncyCastleProvider.PROVIDER_NAME);
byteArrayInputStream = new ByteArrayInputStream(keyStoreBytes);
keyStore.load(byteArrayInputStream, keyStorePassword);
Certificate[] certificates = keyStore.getCertificateChain("theCertAlias");
Certificate certificate = certificates[0];
I am obtaining this exception (client certificate not found) when trying to
connect to a secure Web Service that requires a client certificate. I am
using a web service client automatically generated by axis2, using the
Eclipse wizard.
This is the calling code, that causes the exception in the last line:
System.setProperty("javax.net.ssl.trustStore","C:\\Archivos de programa\\Java\\jre7\\lib\\security\\cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
System.setProperty("javax.net.ssl.keyStore","D:\\Perfil Usuario\\internet\\Escritorio\\workspace\\certificados\\clientes.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
ServicioBoletinStub lala = new ServicioBoletinStub();
ConsultaDeCatalogo cons = new ConsultaDeCatalogo();
cons.setArgs0("SECCIONES");
ConsultaDeCatalogoResponse conResp = lala.consultaDeCatalogo(cons);
The client certificate is imported in the "clientes.jks" keystore, and all the
other required certificates for the authentication path are in "cacerts".
The only weird thing I had to do was to convert the client certificate from
.p12 to .cer, because keytool was complaining that the .p12 file was not an
x509 certificate. The .p12 file was encrypted with a password, but the .cer
file is not, so I am afraid that something was missing during the
conversion. I am very new to handling certificates so I do not know what I
am missing.
I also used SSLPoke to test the connection, and no errors were given.
Thank you very much.
Ok, the problem was in the import process of the client certificate, as suspected. Keytool was not importing the private key into the "clientes.jks" keystore.
I deleted everything from this keystore and followed this post (http://cunning.sharp.fm/2008/06/importing_private_keys_into_a.html) to import the client certificate plus the private key.
Anyway, I think Axis2 should have specified that the exception message was taken from the SOAP response message: I thought it was generating it by itself.
I need to make a HTTPS call from client. I do not need to send certificates to the server, just need to validate the certificate from the server.
i researched this topic and this is my understanding. Can you please confirm? I do not have a test service yet, against which to verify my code... but still need to meet deadlines.. any advice/input will be helpful.
I added this to my class:
private static String appKeyFile = "/project/src/security/cert_file.jck";
private static String key = "password";
static {
System.setProperty("javax.net.ssl.keyStore", appKeyFile);
System.setProperty("javax.net.ssl.keyStorePassword",key);
System.setProperty("javax.net.ssl.keyStoreType","JCEKS");
}
And am making the HTTPS call as follows:
config = new DefaultClientConfig();
client = Client.create(config);
service = client.resource(UriBuilder.fromUri("https://localhost:8081/TestService").build());
clientResponse = service.path("rs").path("test").path("getCustomerDetail")
.accept(MediaType.APPLICATION_XML)
.post(ClientResponse.class, customerRequestType);
if (clientResponse.getStatus() == Response.Status.OK.getStatusCode()) {
custResponseType = clientResponse.getEntity(CustResponseType.class);
System.out.println("First Name" +
custResponseType.getFirstName());
}
Is this sufficient from SSL/HTTPS/certs etc point of view (other than debugging)? Is there anything else i need to do,like loading the keystore or initializing the SSLContext?
The javax.net.ssl.keyStore* properties (the keystore) are for the keys and certificates of the party using it. That is, on the server, it should contain the server certificate and its private key; on the client, it should contain the client certificates and their private keys.
In contrast the truststore (javax.net.ssl.trustStore* properties) contains the trusted certificates used to validate the remote party's certificate. On the client, it's what's used to determine whether you trust the server certificate (normally, via a chain to a CA certificate trusted by the client); on the server, it's what's used to verify a client certificate.
Both truststore and keystore are keystore files/objects (the terminology doesn't really help).
If you set javax.net.ssl.keyStore* on the client side, it will be used by the client to present its certificate (which can only be requested by the server, and which you don't seem to be using anyway). It will still use the default truststore (shipped/configured with the JRE), and it's unlikely to contain the specific certificate in cert_file.jck (which is presumably a self-signed certificate you've generated for the server). Instead, set the javax.net.ssl.trustStore* properties to point to that file.
(If you want the default CA certificates to be available too, I'd suggest copying the certificates in the default truststore, usually from $JAVA_HOME/lib/security/jssecacerts or $JAVA_HOME/lib/security/cacerts into your own truststore.)
In case you are using self-signed certificate, you may face issues related to SSL Certificate validation. This link discusses this.
How do I perform an HTTP request and sign it with a X.509 certificate using Java?
I usually program in C#. Now, what I would like to do is something similar to the following, only in Java:
private HttpWebRequest CreateRequest(Uri uri, X509Certificate2 cert)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ClientCertificates.Add(cert);
/* ... */
return request;
}
In Java I have created a java.security.cert.X509Certificate instance but I cannot figure out how to associate it to a HTTP request. I can create a HTTP request using a java.net.URL instance, but I don't seem to be able to associate my certificate with that instance (and I'm not sure whether using java.net.URL is even appropriate).
I'm not a C# programmer, but I'm presuming that code makes a call using HTTPS/TLS and provides a client certificate for authentication? Aka, you're not asking how to use WS-Security, right?
In that case, I think the answers here and here will be of use to you. You need to use an openssl utility to import your certificate into a p12 client keystore. If your server is using a non-standard CA or self-signed cert, you'll need to set up a client truststore with those certificates as well.
At this point, look at the questions I've linked: you'll need to specify a whole slew of JVM arguments. Finally, try to make your call again (using standard Java objects or httpclient). The client should accept the server cert if your truststore is correct and the server should ask for a client cert. If your keystore is set up correctly, the client with authenticate with the X.509 client cert and you'll be good to go.
It looks like you're trying to use HTTPS with client-certificate authentication. I'm assuming that your server is configured to request this (because the client certificate can only be requested by the server).
In Java, java.security.cert.X509Certificate is really just the certificate (a public key certificate, without the private key). What you need on the client side is to configure the private key with it.
Assuming that your private key and certificate are in a keystore (to simplify, I'm assuming there's only one suitable certificate with its private key, perhaps with other certificates in the chain, in that keystore), and that you're using the default trust store:
KeyStore ks = ...
/* load the keystore */
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
URL url = new URL("https://example/");
HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory(sslContext.getSSLSocketFactory());
Other libraries will allow you to set the SSLContext or the KeyStore slightly differently, but the principles should be the same.
(You could also use the javax.net.ssl.keyStore system properties if it's appropriate.)
I would recommend the open source HttpClient library from Apache Commons. Covers this use case and many others.