If I have:
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/fdms/WS1001237590._.1.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'DV8u4xRVDq');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
I'm able to open a secure connection without a problem.
However, I'd like to have the certificates stored directly in the war, so I use: (The file input stream will eventually become a resource stream, but I'm doing this to get it to work.)
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("/etc/certificates/fdms/WS1001237590._.1.ks"), "DV8u4xRVDq".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "DV8u4xRVDq".toCharArray());
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, null);
Now, if I open the same connection, I get: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
I had to do something similar a while back. I had a certificate file and I had to figure out a way to load it in and use it for an SSL connection. Hopefully what I did will help you out.
First I had to create a trust manager:
public class MyX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
MyX509TrustManager() throws Exception {
String certFile = "/certificates/MyCertFile.cer";
Certificate myCert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
keyStore.setCertificateEntry("myCert", myCert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore);
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
for(TrustManager trustManager : trustManagers) {
if(trustManager instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) trustManager;
return;
}
}
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return pkixTrustManager.getAcceptedIssuers();
}
}
After that I had to create a socket factory that used my trust manager:
public class MySSLProtocolSocketFactory implements SecureProtocolSocketFactory {
private SSLContext sslContext = null;
public MySSLProtocolSocketFactory() {
super();
}
private static SSLContext createMySSLContext() {
try {
MyX509TrustManager myX509TrustManager = new MyX509TrustManager();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new MyX509TrustManager[] { myX509TrustManager}, null);
return context;
}
catch(Exception e) {
Log.error(Log.Context.Net, e);
return null;
}
}
private SSLContext getSSLContext() {
if(this.sslContext == null) {
this.sslContext = createMySSLContext();
}
return this.sslContext;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketFactory = getSSLContext().getSocketFactory();
if(timeout == 0) {
return socketFactory.createSocket(host, port, localAddress, localPort);
}
else {
Socket socket = socketFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteAddr = new InetSocketAddress(host, port);
socket.bind(localAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(MySSLProtocolSocketFactory.class));
}
public int hashCode() {
return MySSLProtocolSocketFactory.class.hashCode();
}
}
Then I used that socket factory to send my POST:
Protocol.registerProtocol("myhttps", new Protocol("myhttps", new MySSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod("myhttps://some.url.here");
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
The only thing I couldn't figure out was how to simply add the certificate file to the regular keystore. All the example source code I found during my research pointed to creating a socket factor and then registering a protocol with that socket factory. Perhaps there is a way to simply use the socket factory to make a connection without registering a protocol; I haven't investigated that thoroughly. In my particular situation, creating a specific protocol was necessary. Hopefully this will get your further along the way. I admit it seems a bit roundabout; I felt the same way when I did it initially. But this was the only way I got it to work. Maybe other people have a better solution.
For posterity's sake, all of this was far too complicated, and we pretty much just had a check in the static block:
if( environment == 'production') {
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/prod/keystore.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'password');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
} else {
System.setProperty("javax.net.ssl.keyStore", '/etc/certificates/test/keystore.ks');
System.setProperty("javax.net.ssl.keyStorePassword", 'password');
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
}
With Axis, I think you need to configure its SSLSocketFactory via:
AxisProperties.setProperty("axis.socketSecureFactory",
"com.example.MySSLSocketFactory");
where com.example.MySSLSocketFactory is your class that implements org.apache.axis.components.net.SecureSocketFactory (you could extend org.apache.axis.components.net.JSSESocketFactory perhaps).
In the create method, create a socket using the socket factory obtained from the SSLContext you've configured.
If you want, here's an API to create SSLSocket and SSLServerSocket easily:
https://github.com/gpotter2/SSLKeystoreFactories
It does not require any other jars.... just get the files and use them like:
SSLSocket s = SSLSocketKeystoreFactory.getSocketWithCert(ip, port,
Main.class.getResourceAsStream("/mykey.jks"), "password")
Or:
SSLServerSocket s = SSLServerSocketKeystoreFactory.getSocketWithCert(port,
Main.class.getResourceAsStream("/mykey.jks"), "password")
That's much easier to use :)
I had similar problem, I solved creating a function that returns an SSL context using a keystore coming from and input stream.
protected SSLContext getSslCtx(InputStream is, String password) {
try {
// Load keystore
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(is, password.toCharArray());
// Load trust manager
TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgrFactory.init(keystore);
// Load key manager
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyMgrFactory.init(keystore, password.toCharArray());
// Create SSL context
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(keyMgrFactory.getKeyManagers(), trustMgrFactory.getTrustManagers(), null);
return ctx;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Hope this helps.
Related
Im having problem connecting to a mqtt server using self signed certificate. im using Paho client and want to connect to a server using a TLSv1.2. actually i was successful to connect in Android APIs 20+ but no success for below this version.
what ive done till now :
1- created a PKCS#12 keystore and put the .crt file and assing a password to it and save it (it will be a .pfx file)
2- added the .pfx file to raw folder of recourse in android project
3- used below code to load self signed certificate:
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
and the getSSLSocketFactory method which is the most important part is :
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
sslSockFactory = ctx.getSocketFactory();
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
this works perfectly but no success in Android APIs below 20.
finally found a solution to this.
based on this documentation, TLS 1.1 and TLS 1.2 is supported from android API level 16 (Android 4.1, Jelly Bean). But it is'nt enabled by default until API level 20+ (Android 4.4 for watch, Kitkat Watch and Android 5.0 for phone, Lollipop).
so all we need is to enable them parogrammatically in code. how we gonna do this ? there is a solution for this problem here BUT it just resolves the problem for the case that you want to accept any certificate instead.
what we need is do the same thing but with our own self signed certificate. so we do it like below. the first part is just like what i did before : (keyStoreInputStream is input stream of a .pfx file)
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
the getSSLSocketFactory method changes to:
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
sslSockFactory = new TLSSocketFactory(tm);
} else {
sslSockFactory = ctx.getSocketFactory();
}
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
and the TLSSocketFactory class is like below:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSSocketFactory(TrustManager[] trustManagers) throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, null);
internalSSLSocketFactory = context.getSocketFactory();
}
#Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
#Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}
#Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
#Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
#Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
#Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.1"});
}
return socket;
}
}
I have a fairly simple client(Android)/server app to which I am trying to add SSL. Its working fine up to Android 4.4.4 but failing with SSLHandshakeException on Android M ( haven't tested on Lollipop), the server is telling me there are no cipher suites in common.
Here's the basic code (full source available here)..
Server.java
public class Server {
public static final int PORT = 4646;
TrustManagerFactory tmf;
KeyManagerFactory kmf;
KeyStore publicKeyStore;
KeyStore privateKeyStore;
SSLServerSocket serverSocket;
public static void main(String args[]) {
Server server = new Server();
server.init();
}
private void init() {
InputStream privateKeyStoreIns;
InputStream publicKeyStoreIns;
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextInt();
privateKeyStoreIns = Server.class.getResourceAsStream("/server.private");
publicKeyStoreIns = Server.class.getResourceAsStream("/client.public");
Security.addProvider(new BouncyCastleProvider());
try {
privateKeyStore = setupKeystore(privateKeyStoreIns, "private");
publicKeyStore = setupKeystore(publicKeyStoreIns, "public");
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(publicKeyStore);
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(privateKeyStore, "private".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(),
tmf.getTrustManagers(),
secureRandom);
SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
serverSocket = (SSLServerSocket) sf.createServerSocket( PORT );
serverSocket.setNeedClientAuth(true);
Socket socket = serverSocket.accept();
ObjectInputStream objInputStream = new ObjectInputStream(socket.getInputStream());
while (objInputStream.readObject() != null) {
// do nothing
}
objInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private KeyStore setupKeystore(InputStream keyStoreInputStream, String passphrase)
throws GeneralSecurityException, IOException {
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(keyStoreInputStream, passphrase.toCharArray());
return keyStore;
}
}
MainActivity.java
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getName();
public static final int PORT = 4646;
public static final String HOST = "192.168.1.12"; //Change to IP address of server
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connect();
}
private void connect() {
new SocketConnector(this).execute();
}
static class SocketConnector extends AsyncTask<Void, Void, String> {
Activity activity;
TrustManagerFactory tmf;
KeyManagerFactory kmf;
KeyStore publicKeyStore;
KeyStore privateKeyStore;
SSLSocket socket;
public SocketConnector(Activity activity) {
this.activity = activity;
}
#Override
protected String doInBackground(Void... params) {
InputStream privateKeyStoreIns;
InputStream publicKeyStoreIns;
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextInt();
privateKeyStoreIns = activity.getResources().openRawResource(R.raw.client_private);
publicKeyStoreIns = activity.getResources().openRawResource(R.raw.server_public);
try {
privateKeyStore = setupKeystore(privateKeyStoreIns, "private");
publicKeyStore = setupKeystore(publicKeyStoreIns, "public");
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(publicKeyStore);
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(privateKeyStore, "private".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(),
tmf.getTrustManagers(),
secureRandom);
SSLSocketFactory sf = sslContext.getSocketFactory();
socket = (SSLSocket) sf.createSocket(HOST, PORT);
socket.startHandshake();
} catch (Exception e) {
e.printStackTrace();
return "Connection failure: " + e.getMessage();
}
return "Connection established!";
}
#Override
protected void onPostExecute(String s) {
TextView textView = (TextView) activity.findViewById(R.id.text_view);
textView.setText(s);
}
private KeyStore setupKeystore(InputStream keyStoreInputStream, String passphrase)
throws GeneralSecurityException, IOException {
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(keyStoreInputStream, passphrase.toCharArray());
return keyStore;
}
}
}
Exception stacktrace on Android
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:396)
at com.sslsocketpoc.app.MainActivity$SocketConnector.doInBackground(MainActivity.java:71)
at com.sslsocketpoc.app.MainActivity$SocketConnector.doInBackground(MainActivity.java:31)
at android.os.AsyncTask$2.call(AsyncTask.java:295)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x9e7a3140: Failure in SSL library, usually a protocol error
error:100c5410:SSL routines:ssl3_read_bytes:SSLV3_ALERT_HANDSHAKE_FAILURE (external/boringssl/src/ssl/s3_pkt.c:972 0xb4071d20:0x00000001)
error:100c009f:SSL routines:ssl3_get_server_hello:HANDSHAKE_FAILURE_ON_CLIENT_HELLO (external/boringssl/src/ssl/s3_clnt.c:750 0xab25d50f:0x0000
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:324)
Server side exception
javax.net.ssl.SSLHandshakeException: no cipher suites in common
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:292)
at sun.security.ssl.ServerHandshaker.chooseCipherSuite(ServerHandshaker.java:1035)
Google has forked OpenSSL in API 23 (6.x) and now uses BoringSSL with no DSA support (but ECDSA), your public client key is a DSA 1024 KEY so you need check (and change) your keys to any supported algorithm.
Look at the bottom of this question for more up to date information
I am trying to intercept all SSL handshakes (so that I can get information on them as well as present visual information to users, much like the green lock in browsers) that happen through my Jersey client. Unfortunately it does not seem like Jersey is using my SSLSocketFactory implementation, because none of the createSocket methods are called. No errors occur, it is just that nothing gets logged. The code should be clear:
Invocation + Instantiation:
this.httpClient = getHttpsClient(new DefaultSSLContextProvider());
Invocation.Builder invBuilder = httpClient.target(API_URL_PRIVATE + API_VERSION_2 + "markets").request(MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML);
invBuilder.header("Content-Type", "application/x-www-form-urlencoded");
invBuilder.header("User-Agent", USER_AGENT);
Response response = invBuilder.get();
logger.debug("response: " + response);
httpClient:
public Client getHttpsClient(SSLContextProvider sslContextProvider) throws KeyStoreException
{
ClientConfig config = new ClientConfig().connectorProvider(new HttpUrlConnectorProvider().connectionFactory(
url ->
{
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(sslContextProvider.getSSLSocketFactory());
return connection;
}));
return ClientBuilder.newBuilder()
.sslContext(sslContextProvider.getSSLContext())
.withConfig(config)
.build();
}
DefaultSSLContextProvider:
public class DefaultSSLContextProvider implements SSLContextProvider
{
private SSLContext sslContext;
private ObservableSSLSocketFactory observableSSLSocketFactory;
private static final Logger logger = LoggerFactory.getLogger(DefaultSSLContextProvider.class);
public DefaultSSLContextProvider()
{
try
{
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
sslContext = SSLContext.getInstance("SSL");
KeyStore keyStore = getKeyStore();
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
observableSSLSocketFactory = new ObservableSSLSocketFactory(sslContext);
HttpsURLConnection.setDefaultSSLSocketFactory(observableSSLSocketFactory);
SSLContext.setDefault(sslContext);
}
catch (NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
catch (KeyManagementException | KeyStoreException e)
{
logger.error("could not create DefaultSSLContextProvider", e);
throw new IllegalStateException(e);
}
}
#Override
public SSLContext getSSLContext()
{
return sslContext;
}
#Override
public SSLSocketFactory getSSLSocketFactory()
{
return observableSSLSocketFactory;
}
#Override
public KeyStore getKeyStore()
{
// snip
}
}
ObservableSSLSocketFactory:
/**
* Based heavily on:
* http://stackoverflow.com/a/23365536/3634630
*/
public class ObservableSSLSocketFactory extends SSLSocketFactory
{
private final SSLContext sslContext;
private final String[] preferredCipherSuites;
private final String[] preferredProtocols;
private static final Logger logger = LoggerFactory.getLogger(ObservableSSLSocketFactory.class);
protected ObservableSSLSocketFactory(SSLContext sslContext)
{
logger.debug("CREATING OBSERVABLE SOCKET FACTORY!");
this.sslContext = sslContext;
preferredCipherSuites = getCiphers();
preferredProtocols = getProtocols();
logger.debug("Observable socket factory created");
logger.debug("preferredCipherSuites: " + preferredCipherSuites);
logger.debug("preferredProcotols: " + preferredProtocols);
}
#Override
public String[] getDefaultCipherSuites()
{
return preferredCipherSuites;
}
#Override
public String[] getSupportedCipherSuites()
{
return preferredCipherSuites;
}
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(s, host, port, autoClose);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(address, port, localAddress, localPort);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port, localHost, localPort);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(InetAddress host, int port) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
public Socket createSocket(String host, int port) throws IOException
{
logger.debug("creating ssl socket");
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port);
sslSocket.addHandshakeCompletedListener(new HandshakeListener());
sslSocket.setEnabledProtocols(preferredProtocols);
sslSocket.setEnabledCipherSuites(preferredCipherSuites);
return sslSocket;
}
private String[] getProtocols()
{
// snip
}
private String[] getCiphers()
{
// snip
}
class HandshakeListener implements HandshakeCompletedListener
{
public HandshakeListener()
{
logger.debug("Created new HandshakeListener");
}
public void handshakeCompleted(HandshakeCompletedEvent e)
{
logger.debug("Handshake successful!");
logger.debug("using cipher suite: " + e.getCipherSuite());
}
}
}
As I said, no exceptions or errors occur (and indeed the original request goes through with no problem (HTTP 200), however the only things that are logged are:
00:01:37.867 DEBUG [ObservableSSLSocketFactory] CREATING OBSERVABLE SOCKET FACTORY!
00:01:38.072 DEBUG [ObservableSSLSocketFactory] Observable socket factory created
00:01:38.073 DEBUG [ObservableSSLSocketFactory] preferredCipherSuites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
00:01:38.073 DEBUG [ObservableSSLSocketFactory] preferredProcotols: [TLSv1, TLSv1.1, TLSv1.2]
00:01:39.435 DEBUG [Exchange] response: InboundJaxrsResponse{context=ClientResponse{method=GET, uri=https://www.bitstamp.net/api/order_book/, status=200, reason=OK}}
Nothing from createSocket()'s or the HandshakeCompletedListener.
Any help would be greatly appreciated.
Update 1:
I added some additional log statements, and the situation is indeed strange. The Jersey client is in fact calling the HttpUrlConnectorProvider implementation, and in fact an instance of ObservableSSLSocketFactory is set on the connection, it appears that when the connect method is called on the HttpsURLConnection, it does not use the socket factory.
Update 2:
I found an old bug which is titled: "HttpsURLConnection not using the set SSLSocketFactory for creating all its Sockets". This seems to be my exact problem. This bug was stated to be fixed sometime in JDK7. I am using (as stated) 8u60, which is quite beyond the time this bug was fixed. I am quite puzzled. I found the java argument -Djavax.net.debug=all and set it - I did not see any errors or anything out of place, it seems to be that the HttpsUrlConnection is not using the SSLSocketFactory set on it.
Update 3:
Taking a suggestion from wyvern on #java # irc.freenode.net, I decided to use the apache-connector instead of the JDK HttpsUrlConnection connector, and finally after some smudging and coaxing, I am presented with:
[HandshakeCompletedNotify-Thread][ObservableSSLConnectionSocketFactory] Handshake successful!
So, I guess I am all set then =).
Before making the SSL handshake make sure the client and service have the correct certificates are installed on both(client and service) the server. Refer to url:http://www.programcreek.com/java-api-examples/index.php?api=javax.net.ssl.SSLSocketFactory for SSLFactory Implementation
I want to open a secure listening socket in a Java server application. I know that the recommended way to do that is to just do this:
SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
ServerSocket ss = ssf.createServerSocket(443);
But this requires to pass the certificate of the server to the JVM when launching java. Because this would make some things in deployment more complicated for me, I would prefer to load the certificate at runtime.
So I have a key file and a password and I want a server socket. How do I get there? Well, I read the documentation and the only way I could find is this:
// these are my parameters for SSL encryption
char[] keyPassword = "P#ssw0rd!".toCharArray();
FileInputStream keyFile = new FileInputStream("ssl.key");
// init keystore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keyFile, keyPassword);
// init KeyManagerFactory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPassword);
// init KeyManager
KeyManager keyManagers[] = keyManagerFactory.getKeyManagers();
// init the SSL context
SSLContext sslContext = SSLContext.getDefault();
sslContext.init(keyManagers, null, new SecureRandom());
// get the socket factory
SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
// and finally, get the socket
ServerSocket serverSocket = socketFactory.createServerSocket(443);
And that doesn't even have any error handling. Is it really that complicated? Isn't there an easier way to do it?
But this requires to pass the certificate of the server to the JVM when launching java.
No it doesn't. Just set these system properties before you create the SSLServerSocket:
javax.net.ssl.keyStore ssl.key
javax.net.ssl.keyStorePassword P#ssw0rd!
You can do that with System.setProperties() or on the command line.
If you look at the code, you can see why it's necessarily complicated. This code decouples the implementation of the SSL protocol from:
the source of your key material (KeyStore)
certificate algorithm choice and key management (KeyManager)
management of peer trust rules (TrustManager) - not used here
secure random algorithm (SecureRandom)
NIO or socket implementation (SSLServerSocketFactory) - could use SSLEngine for NIO
Consider what your own implementation would look like if you were trying to reach the same goals!
Use this.
public class KeyMaster
{
public static SSLSocketFactory getSSLSocketFactory(KeyStore trustKey, String sslAlgorithm)
{
try
{
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustKey);
SSLContext context = SSLContext.getInstance(sslAlgorithm);//"SSL" "TLS"
context.init(null, tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
catch(Exception e)
{
Assistance.log("Err: getSSLSocketFactory(), ");
}
return null;
}
public static SSLServerSocketFactory getSSLServerSocketFactory(KeyStore trustKey, String sslAlgorithm)
{
try
{
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustKey);
SSLContext context = SSLContext.getInstance(sslAlgorithm);//"SSL" "TLS"
context.init(null, tmf.getTrustManagers(), null);
return context.getServerSocketFactory();
}
catch(Exception e)
{
Assistance.log("Err: getSSLSocketFactory(), ");
}
return null;
}
public static SSLServerSocket getSSLServerSocket(SSLServerSocketFactory socketFactory, int port)
{
try
{
return (SSLServerSocket) socketFactory.createServerSocket(port);
}
catch(Exception e)
{Assistance.log("Err: getSSLSocket(), ");}
return null;
}
public static KeyStore getFromPath(String path, String algorithm, String filePassword)//PKSC12
{
try
{
File f = new File(path);
if(!f.exists())
throw new RuntimeException("Err: File not found.");
FileInputStream keyFile = new FileInputStream(f);
KeyStore keystore = KeyStore.getInstance(algorithm);
keystore.load(keyFile, filePassword.toCharArray());
keyFile.close();
return keystore;
}
catch(Exception e)
{
Assistance.log("Err: getFromPath(), " + e.toString());
}
return null;
}
and in main.
KeyStore key = KeyMaster.getFromPath(".\\cssl.pfx", "PKCS12", "123");
SSLServerSocketFactory fac = KeyMaster.getSSLServerSocketFactory(key, "TLS");
listener = KeyMaster.getSSLServerSocket(fac, 49015);
I'm integrating with a Merchant Account called CommWeb and I'm sending an SSL post to their URL (https://migs.mastercard.com.au/vpcdps). When I try to send the post, I get the following exception:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
The code (which I didn't write, and that already exists in our codebase) that performs the post is:
public static HttpResponse sendHttpPostSSL(String url, Map<String, String> params) throws IOException {
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
StringBuilder resultBuffer = new StringBuilder();
resultBuffer.append(postMethod.getResponseBodyAsString());
return new HttpResponse(resultBuffer.toString(), "");
} else {
throw new IOException("Invalid response code: " + status);
}
}
The documentation for the Merchant Account integration says nothing about certificates. They did provide some sample JSP code that seems to blindly accept certificates:
<%! // Define Static Constants
// ***********************
public static X509TrustManager s_x509TrustManager = null;
public static SSLSocketFactory s_sslSocketFactory = null;
static {
s_x509TrustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; }
public boolean isClientTrusted(X509Certificate[] chain) { return true; }
public boolean isServerTrusted(X509Certificate[] chain) { return true; }
};
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[] { s_x509TrustManager }, null);
s_sslSocketFactory = context.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
...
...
// write output to VPC
SSLSocket ssl = (SSLSocket)s_sslSocketFactory.createSocket(s, vpc_Host, vpc_Port, true);
ssl.startHandshake();
os = ssl.getOutputStream();
// get response data from VPC
is = ssl.getInputStream();
...
...
%>
Our webapp has a keystore, and I tried adding the certificate (which I exported from firefox) using the keytool command, but that didn't work and I got the same error. I've tried solutions on the web (importing the key and using System.setProperty) but that seems kind of clunky and it didn't work (gave me a NoSuchAlgorithmError). Any help is appreciated!
Evidently the valicert class 3 CA certificate is not in your default truststore (which is probably the cacerts file in your JRE lib/security directory, but see the JSSE documentation for the full story).
You could add this certificate to the cacerts file, but I don't recommend this. Instead, I think you should create your own truststore file (which can be a copy of the cacerts file) and add the valicert root ca to this. Then point to this file with the javax.net.ssl.trustStore system property.
I figure I should update this answer with what I actually did. Using the documentation that GregS provided, I created a trust manager for valicert. In the trust manager, I load the certificate files:
public class ValicertX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
ValicertX509TrustManager() throws Exception {
String valicertFile = "/certificates/ValicertRSAPublicRootCAv1.cer";
String commwebDRFile = "/certificates/DR_10570.migs.mastercard.com.au.crt";
String commwebPRODFile = "/certificates/PROD_10549.migs.mastercard.com.au.new.crt";
Certificate valicert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
Certificate commwebDR = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebDRFile));
Certificate commwebPROD = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebPRODFile));
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
keyStore.setCertificateEntry("valicert", valicert);
keyStore.setCertificateEntry("commwebDR", commwebDR);
keyStore.setCertificateEntry("commwebPROD", commwebPROD);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore);
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
for(TrustManager trustManager : trustManagers) {
if(trustManager instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) trustManager;
return;
}
}
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return pkixTrustManager.getAcceptedIssuers();
}
}
Now, using this trust manager, I had to create a socket factory:
public class ValicertSSLProtocolSocketFactory implements ProtocolSocketFactory {
private SSLContext sslContext = null;
public ValicertSSLProtocolSocketFactory() {
super();
}
private static SSLContext createValicertSSLContext() {
try {
ValicertX509TrustManager valicertX509TrustManager = new ValicertX509TrustManager();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new ValicertX509TrustManager[] { valicertX509TrustManager}, null);
return context;
}
catch(Exception e) {
Log.error(Log.Context.Net, e);
return null;
}
}
private SSLContext getSSLContext() {
if(this.sslContext == null) {
this.sslContext = createValicertSSLContext();
}
return this.sslContext;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketFactory = getSSLContext().getSocketFactory();
if(timeout == 0) {
return socketFactory.createSocket(host, port, localAddress, localPort);
}
else {
Socket socket = socketFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteAddr = new InetSocketAddress(host, port);
socket.bind(localAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(ValicertSSLProtocolSocketFactory.class));
}
public int hashCode() {
return ValicertSSLProtocolSocketFactory.class.hashCode();
}
}
Now I just register a new protocol:
Protocol.registerProtocol("vhttps", new Protocol("vhttps", new ValicertSSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
StringBuilder resultBuffer = new StringBuilder();
resultBuffer.append(postMethod.getResponseBodyAsString());
return new HttpResponse(resultBuffer.toString(), "");
} else {
throw new IOException("Invalid response code: " + status);
}
The only disadvantage is that I had to create a specific protocol (vhttps) for this particular certificate.