Authentication with Client Certificate over HTTPS/SSL using Java – Handshake
To save somebody some time in the future, a step by step instruction is provided below:
I assume you have a valid certificate or a chain of certificates, whose root is acceptable by the server. The valid certificate contains its private key. Run the following command to verify:
keytool -list -v -keystore "your certificate file"
Entry type: PrivateKeyEntry
Import your certificate and intermediate certificates into a browser like IE or Firefox and test out the https URL. This step will validate the certificates and save you a lot of troubles down the road. Java version of the SSL implementation is not as simple/mature as the browsers'. Please make sure all the certificates have not expired.
Backup your keystore located at /your_home_directory/.keystore by default and the truststore located at somewhere similar to \Java\jre6\lib\security\cacerts
Use not-yet-commons-ssl utility to import your certificates into the Java keystore format. Sample command is:
java -cp not-yet-commons-ssl-0.3.9.jar org.apache.commons.ssl.KeyStoreBuilder
Customize the following java code, replace the static final Strings to fit in your needs. Note that this implementation forcefully use a specific alias to present the corresponding certificate/certificate chain to the server. Somehow the default KeyManager simply disqualifies my certificate to be presented to the server.
public class Main {
private static final Logger logger = Logger.getLogger(Main.class.getName());
private static final String LINE_BREAKER = System.getProperty("line.separator");
private static final String CERTIFACATE_FILE = "your keystore location";
private static final String CERTIFACATE_PASS = "changeit";
private static final String CERTIFACATE_ALIAS = "your alias";
private static final String TARGET_URL = "https://xyz.com";
public static void main(String[] args) {
String targetURL = TARGET_URL;
URL url;
HttpsURLConnection connection = null;
BufferedReader bufferedReader = null;
InputStream is = null;
try {
//Create connection
url = new URL(targetURL);
//Uncomment this in case server demands some unsafe operations
//System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Language", "en-US");
SSLSocketFactory sslSocketFactory = getFactory(new File(CERTIFACATE_FILE), CERTIFACATE_PASS, CERTIFACATE_ALIAS);
connection.setSSLSocketFactory(sslSocketFactory);
//Process response
is = connection.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer lines = new StringBuffer();
while ((line = bufferedReader.readLine()) != null) {
lines.append(line).append(LINE_BREAKER);
}
logger.info("response from " + targetURL + ":" + LINE_BREAKER + lines);
} catch (Exception e) {
...
}
}
private static SSLSocketFactory getFactory(File pKeyFile, String pKeyPassword, String certAlias) 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());
//Replace the original KeyManagers with the AliasForcingKeyManager
KeyManager[] kms = keyManagerFactory.getKeyManagers();
for (int i = 0; i < kms.length; i++) {
if (kms[i] instanceof X509KeyManager) {
kms[i] = new AliasForcingKeyManager((X509KeyManager) kms[i], certAlias);
}
}
SSLContext context = SSLContext.getInstance("TLS");
context.init(kms, null, null);
return context.getSocketFactory();
}
/*
* This wrapper class overwrites the default behavior of a X509KeyManager and
* always render a specific certificate whose alias matches that provided in the constructor
*/
private static class AliasForcingKeyManager implements X509KeyManager {
X509KeyManager baseKM = null;
String alias = null;
public AliasForcingKeyManager(X509KeyManager keyManager, String alias) {
baseKM = keyManager;
this.alias = alias;
}
/*
* Always render the specific alias provided in the constructor
*/
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return alias;
}
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return baseKM.chooseServerAlias(keyType, issuers, socket);
}
public X509Certificate[] getCertificateChain(String alias) {
return baseKM.getCertificateChain(alias);
}
public String[] getClientAliases(String keyType, Principal[] issuers) {
return baseKM.getClientAliases(keyType, issuers);
}
public PrivateKey getPrivateKey(String alias) {
return baseKM.getPrivateKey(alias);
}
public String[] getServerAliases(String keyType, Principal[] issuers) {
return baseKM.getServerAliases(keyType, issuers);
}
}
}
Try to set
-Dsun.security.ssl.allowUnsafeRenegotiation=true
if you get the error message like:
javax.net.ssl.SSLException: HelloRequest followed by an unexpected handshake message
Enabling SSL for AXIS2 service and client
We
often encounter the satuation where requirement is to consume
webservice exposed on https. In this article we will investigate how
to consume webservice exposed over https using axis2. First lets see
how to enable SSL for AXIS2 services:
Enabling SSL on server side for AXIS2 in tomcat:
You really don't need to do much enable SSL for services deplyed in AXIS2. Just follow how to enable SSL in tamcat.Add following in Server.xml of tamcat.
<Connector
port="8443" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="test.jks" keystorePass="test123"
clientAuth="false" sslProtocol="TLS"/>
Use axis2 1.5.3 in which axis2.xml has https transportReceiver enabled by default, listening on 8443 port so you don't need any configuration change in axis2.xml.
<transportReceiver name="https"
class="org.apache.axis2.transport.http.AxisServletListener">
<parameter name="port">8443</parameter>
</transportReceiver>
Prior to this version of axis2 was using org.apache.axis2.transport.nhttp.HttpCoreNIOSSLListener as transportReceive which was having issues and not generating https endpoint correctly.
SSL on client side:
Axis2 uses http commons to transfer SOAP message over http. Apache http common uses JSSE(java secure socket extension) library for SSL.
JSSE is integrated with JDK since version 1.4.
Ideally if we just provide end point URL starting with https, SSL connection will be started and we don’t need any additional configuration. Creation of secure connection will be taken care by JSSE. But then trust store and keystore used by the JSSE would be default keystores shipped with JDK.
In the practical/production scenarios user should have capability to choose his truststore/keystore.
user may decide to trust a self signed certificate and keep it his local truststore or different applications may use different keystore/truststore.
Above can be achieved by two ways:
Approach 1:
We can set truststore, password etc in system properties. This will be picked by JSSE in SSL handshake.
Ex.
System.setProperty("javax.net.ssl.trustStore","Your truststore path");
System.setProperty("javax.net.ssl.trustStorePassword","your trust store password");
This approach will not be appropriate for tooling since it sets keystore on JVM level. We should have flexibility where we could attach different keystore/truststore for different axis2 clint running in same JVM.
Approach 2:
Apache commons provide facility which allows us to customize the SSL socket factory responsible for creation of secure socket. By customization I mean ability to use user truststore/keystore in SSL handshake. To achieve it we need to extend SecureProtocolSocketFactory interface. In our custom socket factory implementation user refer its Keystore/Truststore against default keystores.
Apache commons provide a reference implementation class named AuthSSLProtocolSocketFactory for this purpose.
This class takes Truststore/Keystore as argument to constructor which will be referred later while initiating SSLContext. SSLContext is used to create SSL Socket Factory.
In your axis2 client code you need to add following:
Protocol authhttps = new Protocol ("https", new AuthSSLProtocolSocketFactory (new url("keystore URL"), "pwd", newURL("truststore URL"), "pwd"), 443);
Protocol.registerProtocol("https", authhttps);
Enabling SSL on server side for AXIS2 in tomcat:
You really don't need to do much enable SSL for services deplyed in AXIS2. Just follow how to enable SSL in tamcat.Add following in Server.xml of tamcat.
<Connector
port="8443" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="test.jks" keystorePass="test123"
clientAuth="false" sslProtocol="TLS"/>
Use axis2 1.5.3 in which axis2.xml has https transportReceiver enabled by default, listening on 8443 port so you don't need any configuration change in axis2.xml.
<transportReceiver name="https"
class="org.apache.axis2.transport.http.AxisServletListener">
<parameter name="port">8443</parameter>
</transportReceiver>
Prior to this version of axis2 was using org.apache.axis2.transport.nhttp.HttpCoreNIOSSLListener as transportReceive which was having issues and not generating https endpoint correctly.
SSL on client side:
Axis2 uses http commons to transfer SOAP message over http. Apache http common uses JSSE(java secure socket extension) library for SSL.
JSSE is integrated with JDK since version 1.4.
Ideally if we just provide end point URL starting with https, SSL connection will be started and we don’t need any additional configuration. Creation of secure connection will be taken care by JSSE. But then trust store and keystore used by the JSSE would be default keystores shipped with JDK.
In the practical/production scenarios user should have capability to choose his truststore/keystore.
user may decide to trust a self signed certificate and keep it his local truststore or different applications may use different keystore/truststore.
Above can be achieved by two ways:
Approach 1:
We can set truststore, password etc in system properties. This will be picked by JSSE in SSL handshake.
Ex.
System.setProperty("javax.net.ssl.trustStore","Your truststore path");
System.setProperty("javax.net.ssl.trustStorePassword","your trust store password");
This approach will not be appropriate for tooling since it sets keystore on JVM level. We should have flexibility where we could attach different keystore/truststore for different axis2 clint running in same JVM.
Approach 2:
Apache commons provide facility which allows us to customize the SSL socket factory responsible for creation of secure socket. By customization I mean ability to use user truststore/keystore in SSL handshake. To achieve it we need to extend SecureProtocolSocketFactory interface. In our custom socket factory implementation user refer its Keystore/Truststore against default keystores.
Apache commons provide a reference implementation class named AuthSSLProtocolSocketFactory for this purpose.
This class takes Truststore/Keystore as argument to constructor which will be referred later while initiating SSLContext. SSLContext is used to create SSL Socket Factory.
In your axis2 client code you need to add following:
Protocol authhttps = new Protocol ("https", new AuthSSLProtocolSocketFactory (new url("keystore URL"), "pwd", newURL("truststore URL"), "pwd"), 443);
Protocol.registerProtocol("https", authhttps);
package
bo.socket;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.util.Hashtable;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
public class MyCustomSSLSocketFactory extends JSSESocketFactory implements SecureSocketFactory
{ public MyCustomSSLSocketFactory(Hashtable attributes) {
super(attributes);
}
protected void initFactory() throws IOException {
try {
SSLContext context = getContext();
sslFactory = context.getSocketFactory();
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
System.out.print(e.getMessage());
throw new IOException(e.getMessage());
}
}
protected SSLContext getContext() throws Exception
{
try
{
String keystore_type = KeyStore.getDefaultType(); // "JKS"
KeyStore keyStore = KeyStore.getInstance(keystore_type);
KeyStore trustStore = KeyStore.getInstance(keystore_type);
char[] keystore_password = "DemoIdentityKeyStorePassPhrase".toCharArray();
keyStore.load(new FileInputStream("C:\\bea103\\wlserver_10.3\\server\\lib\\DemoIdentity.jks"), keystore_password);
char[] trusstore_password = "DemoTrustKeyStorePassPhrase".toCharArray();
trustStore.load(new FileInputStream("C:\\bea103\\wlserver_10.3\\server\\lib\\DemoTrust.jks"), trusstore_password);
String algorithmTrust = TrustManagerFactory.getDefaultAlgorithm(); // PKIX
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithmTrust);
tmf.init(trustStore);
String algorithmKey = KeyManagerFactory.getDefaultAlgorithm(); // "SunX509"
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithmKey);
char[] key_password = "DemoIdentityPassPhrase".toCharArray();
kmf.init(keyStore, key_password);
SSLContext sslctx = SSLContext.getInstance("SSL");
sslctx.init(kmf.getKeyManagers(),tmf.getTrustManagers(),null);
return sslctx;
}
catch (Exception e)
{ e.printStackTrace();
throw new Exception("Error creating context for SSLSocket.", e);
}
}
}
And in the main call of a WS from Client I set the new SSLSocketFactory Class:
AxisProperties.setProperty("axis.socketSecureFactory","bo.socket.MyCustomSSLSocketFactory");
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.util.Hashtable;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
public class MyCustomSSLSocketFactory extends JSSESocketFactory implements SecureSocketFactory
{ public MyCustomSSLSocketFactory(Hashtable attributes) {
super(attributes);
}
protected void initFactory() throws IOException {
try {
SSLContext context = getContext();
sslFactory = context.getSocketFactory();
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
System.out.print(e.getMessage());
throw new IOException(e.getMessage());
}
}
protected SSLContext getContext() throws Exception
{
try
{
String keystore_type = KeyStore.getDefaultType(); // "JKS"
KeyStore keyStore = KeyStore.getInstance(keystore_type);
KeyStore trustStore = KeyStore.getInstance(keystore_type);
char[] keystore_password = "DemoIdentityKeyStorePassPhrase".toCharArray();
keyStore.load(new FileInputStream("C:\\bea103\\wlserver_10.3\\server\\lib\\DemoIdentity.jks"), keystore_password);
char[] trusstore_password = "DemoTrustKeyStorePassPhrase".toCharArray();
trustStore.load(new FileInputStream("C:\\bea103\\wlserver_10.3\\server\\lib\\DemoTrust.jks"), trusstore_password);
String algorithmTrust = TrustManagerFactory.getDefaultAlgorithm(); // PKIX
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithmTrust);
tmf.init(trustStore);
String algorithmKey = KeyManagerFactory.getDefaultAlgorithm(); // "SunX509"
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithmKey);
char[] key_password = "DemoIdentityPassPhrase".toCharArray();
kmf.init(keyStore, key_password);
SSLContext sslctx = SSLContext.getInstance("SSL");
sslctx.init(kmf.getKeyManagers(),tmf.getTrustManagers(),null);
return sslctx;
}
catch (Exception e)
{ e.printStackTrace();
throw new Exception("Error creating context for SSLSocket.", e);
}
}
}
And in the main call of a WS from Client I set the new SSLSocketFactory Class:
AxisProperties.setProperty("axis.socketSecureFactory","bo.socket.MyCustomSSLSocketFactory");
openssl
- generate a new private key and matching Certificate Signing Request (eg to send to a commercial CA)
- openssl req -out MYCSR.csr -pubkey -new -keyout MYKEY.key
- add
-nodes
to create an unencrypted private key
add-config
<openssl.cnf>
if your config file has not been set in the environment - decrypt private key
- openssl rsa -in MYKEY.key >> MYKEY-NOCRYPT.key
- generate a certificate siging request for an existing private key
- openssl req -out MYCSR.csr -key MYKEY.key -new
- generate a certificate signing request based on an existing x509 certificate
- openssl x509 -x509toreq -in MYCRT.crt -out MYCSR.csr -signkey MYKEY.key
- create self-signed certificate (can be used to sign other certificates)
- openssl req -x509 -new -out MYCERT.crt -keyout MYKEY.key -days 365
- sign a Certificate Signing Request
- openssl x509 -req -in MYCSR.csr -CA MY-CA-CERT.crt -CAkey MY-CA-KEY.key -CAcreateserial -out MYCERT.crt -days 365
-days
has to be less than the validity of the CA certificate
- convert DER (.crt .cer .der) to PEM
- openssl x509 -inform der -in MYCERT.cer -out MYCERT.pem
- convert PEM to DER
- openssl x509 -outform der -in MYCERT.pem -out MYCERT.der
- convert PKCS#12 (.pfx .p12) to PEM containing both private key and certificates
- openssl pkcs12 -in KEYSTORE.pfx -out KEYSTORE.pem -nodes
- add
-nocerts
for private key only; add-nokeys
for certificates only - convert (add) a seperate key and certificate to a new keystore of type PKCS#12
- openssl pkcs12 -export -in MYCERT.crt -inkey MYKEY.key -out KEYSTORE.p12 -name "tomcat"
- convert (add) a seperate key and certificate to a new keystore of type PKCS#12 for use with a server that should send the chain too (eg Tomcat)
- openssl pkcs12 -export -in MYCERT.crt -inkey MYKEY.key -out KEYSTORE.p12 -name "tomcat" -CAfile MY-CA-CERT.crt -caname myCA -chain
- you can repeat the combination of "-CAfile" and "-caname" for each intermediate certificate
- check a private key
- openssl rsa -in MYKEY.key -check
- add
-noout
to not disclose the key - check a Certificate Signing Request
- openssl req -text -noout -verify -in MYCSR.csr
- check a certificate
- openssl x509 -in MYCERT.crt -text -noout
- check a PKCS#12 keystore
- openssl pkcs12 -info -in KEYSTORE.p12
- check a trust chain of a certificate
- openssl verify -CAfile MYCHAINFILE.pem -verbose MYCERT.crt
- trust
chain is in directory (hash
format): replace
-CAfile
with-CApath /path/to/CAchainDir/
to check for server usage:-purpose sslserver
to check for client usage:-purpose sslient
- check if public key matches the private key
- openssl
rsa -in MYKEY.key -modulus -noout
| openssl md5; /
openssl x509 -in MYCERT.crt -modulus -noout | openssl md5 - This should return the same two md5-hashes
- debug an SSL connection [server doesn't require certificate authentication]
- openssl s_client -connect idp.example.be:443
- debug an SSL connection with mutual certificate authentication
- openssl s_client -connect idp.example.be:8443 -CAfile MY-CA-CERT.crt -cert MYCERT.crt -key MYKEY.key
- trust
chain is in directory (hash
format): replace
-CAfile
with-CApath /path/to/CAchainDir/
send the starttls command (smtp or pop3 style):-starttls smtp
or-starttls pop3
keytool
keytool
does
not support management of private keys inside a keystore. You need to
use another tool for that. If you are using the JKS format, that
means you need another java-based tool. extkeytool
from
the Shibboleth distribution can do this. - Create an empty keystore
- keytool
-genkey -alias foo -keystore truststore.jks
keytool -delete -alias foo -keystore truststore.jks - Generate a private key and an initial certificate as a JKS keystore
- keytool -genkey -keyalg RSA -alias "selfsigned" -keystore KEYSTORE.jks -storepass "secret" -validity 360
- you
can also pass the data for the DN of the certificate as command-line
parameters:
-dname "CN=${pki-cn}, OU=${pki-ou}, O=${pki-o}, L=${pki-l}, S=${pki-s}, C=${pki-c}"
- Generate a secret key that can be used for symmetric encryption. For this to work, you need to make use of a JCEKS keystore.
- keytool -genseckey -alias "secret_key" -keystore KEYSTORE.jks -storepass "secret" -storetype "JCEKS"
- Generate a Certificate Signing Request for a key in a JKS keystore
- keytool -certreq -v -alias "selfsigned" -keystore KEYSTORE.jks -storepass "secret" -file MYCSR.csr
- Import a (signed) certificate into a JKS keystore
- keytool -import -keystore KEYSTORE.jks -storepass "secret" -file MYCERT.crt
- add a public certificate to a JKS keystore, eg the JVM truststore
- keytool -import -trustcacerts -alias "sensible-name-for-ca" -file CAcert.crt -keystore MYSTORE.jks
- If the JVM truststore contains your certificate or the certificate of the root CA that signed your certificate, then the JVM will trust and thus might accept your certificate. The default truststore already contains the root certificates of most commonly used sommercial CA's. Use this command to add another certificate for trust:
- keytool -import -trustcacerts -alias "sensible-name-for-ca" -file CAcert.crt -keystore $JAVA_HOME/lib/security/cacerts
- the
default password of the Java truststore is "changeit".
if $JAVA_HOME is set to the root of the JDK, then the truststore is it $JAVA_HOME/jre/lib/security/cacerts
keytool does NOT support adding trust certificates to a PKCS12 keystore (which is very unfortunate but probably a good move to promote JKS) - delete a public certificate from a JAVA keystore (JKS; eg JVM truststore)
- keytool -delete -alias "sensible-name-for-ca" -keystore $JAVA_HOME/lib/security/cacerts
- the
default password of the Java truststore is "changeit".
if $JAVA_HOME is set to the root of the JDK, then the truststore is it $JAVA_HOME/jre/lib/security/cacerts - List the certificates inside a keystore
- keytool -list -v -keystore KEYSTORE.jks
-storetype pkcs12
can be used- Get information about a stand-alone certificate
- keytool -printcert -v -file MYCERT.crt
- Convert a JKS file to PKCS12 format (Java 1.6.x and above)
- keytool -importkeystore -srckeystore KEYSTORE.jks -destkeystore KEYSTORE.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass mysecret -deststorepass mysecret -srcalias myalias -destalias myalias -srckeypass mykeypass -destkeypass mykeypass -noprompt
certutil
Important Exceptions
1.)
Unconnected
sockets not implemented
-
2.)
SSL Handshake
failure error
3.) org.apache.axis2.AxisFault: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Thanks for this, bro.
ReplyDeleteMy Self Harivigneshwaran I am trying 2 way ssl rest api i had .p12 file and implement in my coding i have 401 unauthorized error happen i am totally confused if any other file will be need kindly share valuable feed back
ReplyDelete