/* * Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved. * See LICENCE file for licensing information. */ package eu.emi.security.authn.x509.impl; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import eu.emi.security.authn.x509.X509CertChainValidator; import eu.emi.security.authn.x509.X509Credential; import eu.emi.security.authn.x509.helpers.ssl.HostnameToCertificateChecker; import eu.emi.security.authn.x509.helpers.ssl.SSLTrustManager; /** * Simple utility allowing programmers to quickly create SSL socket factories * using {@link X509CertChainValidator}. * * @author K. Benedyczak */ public class SocketFactoryCreator { static { CertificateUtils.configureSecProvider(); } /** * Creates a SSL trustmanager which uses the provided validator. * @param v validator to use for certificates validation * @return ready to use TrustManager */ public static X509TrustManager getSSLTrustManager(X509CertChainValidator v) { return new SSLTrustManager(v); } /** * Low level interface. It can be used to get {@link SSLContext} object initialized with the * provided credential and validator. * @param c credential to use for the created sockets. If null, then anonymous socket will be created, * what is useful only for client side. * @param v validator to use for certificates validation * @param r implementation providing random numbers * @return initialized {@link SSLContext} object */ public static SSLContext getSSLContext(X509Credential c, X509CertChainValidator v, SecureRandom r) { KeyManager[] kms = c == null ? null : new KeyManager[] {c.getKeyManager()}; SSLTrustManager tm = new SSLTrustManager(v); SSLContext sslCtx; try { sslCtx = SSLContext.getInstance("TLS"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("The TLS protocol is unsupported by the JDK, " + "a serious installation problem?", e); } try { sslCtx.init(kms, new TrustManager[] {tm}, r); } catch (KeyManagementException e) { throw new RuntimeException("Shouldn't happen - SSLContext can't be initiated?", e); } return sslCtx; } /** * Returns an {@link SSLServerSocketFactory} configured to check * client certificates with a provided validator. Server socket will use * the provided credentials. * @param c credential to use for the server socket * @param v validator to use for client's validation * @param r implementation providing random numbers * @return configured {@link SSLServerSocketFactory} */ public static SSLServerSocketFactory getServerSocketFactory(X509Credential c, X509CertChainValidator v, SecureRandom r) { return getSSLContext(c, v, r).getServerSocketFactory(); } /** * Same as {@link #getServerSocketFactory(X509Credential, X509CertChainValidator, SecureRandom)} * using {@link SecureRandom} implementation as the last argument. Note that this * method might block if the machine has not enough system entropy. It is not suggested to use * this method for setting up automatic test environments, however it is suitable for production setups. * @param c credential to use for the server socket * @param v validator to use for client's validation * @return configured {@link SSLServerSocketFactory} */ public static SSLServerSocketFactory getServerSocketFactory(X509Credential c, X509CertChainValidator v) { return getServerSocketFactory(c, v, new SecureRandom()); } /** * Returns an {@link SSLSocketFactory} configured to check * servers' certificates with a provided validator. Client socket will use * the provided credentials. * @param c credential to use for the client socket * @param v validator to use for server's validation * @param r implementation providing random numbers * @return configured {@link SSLSocketFactory} */ public static SSLSocketFactory getSocketFactory(X509Credential c, X509CertChainValidator v, SecureRandom r) { return getSSLContext(c, v, r).getSocketFactory(); } /** * Same as {@link #getSocketFactory(X509Credential, X509CertChainValidator, SecureRandom)} * using {@link SecureRandom} implementation as the last argument. Note that this * method might block if the machine has not enough system entropy. It is not suggested to use * this method for setting up automatic test environments, however it is suitable for production setups. * @param c credential to use for the client socket * @param v validator to use for server's validation * @return configured {@link SSLSocketFactory} */ public static SSLSocketFactory getSocketFactory(X509Credential c, X509CertChainValidator v) { return getSocketFactory(c, v, new SecureRandom()); } /** * This method, invoked on an initialized SSL socket will perform the initial handshake (if necessary) * and then check if the peer's hostname is matching its certificate. The reaction to a mismatch * must be handled by the provided callback. * * @param socket socket to be checked * @param callback used when there is mismatch. * @throws SSLPeerUnverifiedException if the peer was not verified */ public static void connectWithHostnameChecking(SSLSocket socket, HostnameMismatchCallback callback) throws SSLPeerUnverifiedException { HostnameToCertificateChecker checker = new HostnameToCertificateChecker(); SSLSession session = socket.getSession(); X509Certificate cert; Certificate[] serverChain = session.getPeerCertificates(); if (serverChain == null || serverChain.length == 0) throw new IllegalStateException("JDK BUG? Got null or empty peer certificate array"); if (!(serverChain[0] instanceof X509Certificate)) throw new ClassCastException("Peer certificate should be " + "an X.509 certificate, but is " + serverChain[0].getClass().getName()); cert = (X509Certificate) serverChain[0]; String hostname = socket.getInetAddress().getHostName(); try { if (!checker.checkMatching(hostname, cert)) callback.nameMismatch(socket, cert, hostname); } catch (Exception e) { throw new IllegalStateException("Can't check peer's address against its certificate", e); } } }