package org.threadly.litesockets.utils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.security.KeyFactory; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; /** * Common utilities for SSL connections. */ public class SSLUtils { public static final int MAX_PEM_FILE_SIZE = 1024*1024; public static final String SSL_HANDSHAKE_ERROR = "Problem doing SSL Handshake"; public static final String PEM_CERT_START = "-----BEGIN CERTIFICATE-----"; public static final String PEM_CERT_END = "-----END CERTIFICATE-----"; public static final String PEM_KEY_START = "-----BEGIN PRIVATE KEY-----"; public static final String PEM_KEY_END = "-----END PRIVATE KEY-----"; public static final SSLContext OPEN_SSL_CTX; static { try { //We dont allow SSL by default connections anymore OPEN_SSL_CTX = SSLContext.getInstance("SSL"); OPEN_SSL_CTX.init(null, getOpenTrustManager(), null); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (KeyManagementException e) { throw new RuntimeException(e); } } public static TrustManager[] getOpenTrustManager() { return new TrustManager [] {new SSLUtils.FullTrustManager() }; } /** * Java 7 introduced SNI by default when you establish SSl connections. * The problem is there is no way to turn it off or on at a per connection level. * So if you are knowingly going to connect to a server that has a non SNI valid cert * you have to disable SNI for the whole VM. * * The default is whatever the VM is started with you can disable it by running this method. * */ public static void disableSNI() { System.setProperty ("jsse.enableSNIExtension", "false"); } /** * Java 7 introduced SNI by default when you establish SSl connections. * The problem is there is no way to turn it off or on at a per connection level. * So if you are knowingly going to connect to a server that has a non SNI valid cert * you have to disable SNI for the whole VM. * * The default is whatever the VM is started with you can enable it by running this method. * */ public static void enableSNI() { System.setProperty ("jsse.enableSNIExtension", "true"); } public static String fileToString(File file, int max) throws IOException { if(!file.exists() && file.canRead()) { throw new IllegalStateException("File "+file.getName()+" either does not exist or can not be read"); } RandomAccessFile raf = new RandomAccessFile(file, "r"); if(raf.length() > max) { raf.close(); throw new IllegalStateException("File "+file.getName()+" is to large (>"+max); } byte[] ba = new byte[(int)raf.length()]; raf.read(ba); raf.close(); return new String(ba); } public static List<X509Certificate> getPEMFileCerts(File certFile) throws CertificateException, IOException { String certString = fileToString(certFile, MAX_PEM_FILE_SIZE); List<X509Certificate> certs = new ArrayList<X509Certificate>(); int certPos = certString.indexOf(PEM_CERT_START); CertificateFactory factory = CertificateFactory.getInstance("X.509"); while(certPos > -1) { String data = certString.substring(certPos + PEM_CERT_START.length(), certString.indexOf(PEM_CERT_END)).replace("\n", "").replace("\r", ""); X509Certificate x5c = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(Base64.decode(data))); certs.add(x5c); certPos = certString.indexOf(PEM_CERT_START, certs.size()); } return certs; } public static RSAPrivateKey getPEMFileKey(File keyFile) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { String keyString = fileToString(keyFile, MAX_PEM_FILE_SIZE); int keyPos = keyString.indexOf(PEM_KEY_START)+PEM_KEY_START.length(); int keyEnd = keyString.indexOf(PEM_KEY_END); if(keyPos == -1 || keyEnd == -1) { throw new InvalidKeySpecException("could not find key!"); } PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(Base64.decode(keyString.substring(keyPos, keyEnd).trim().replace("\n", "").replace("\r", ""))); return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keyspec); } /** * Creates a {@link KeyManagerFactory} from PEM files (something java should just do...). There * are some minor restrictions. The key must be in PKCS8 (not PKCS1). * * @param certFile the PEM encoded certificate file to use. * @param keyFile the PEM encoded key to use. * @return a {@link KeyManagerFactory} using the provided cert and file. * @throws KeyStoreException Thrown if there is any kind of error opening or parsing the PEM files. */ public static KeyManagerFactory generateKeyStoreFromPEM(File certFile, File keyFile) throws KeyStoreException{ char[] password = UUID.randomUUID().toString().toCharArray(); try { List<X509Certificate> certs = getPEMFileCerts(certFile); RSAPrivateKey key = getPEMFileKey(keyFile); KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(null); for(int i=0; i<certs.size(); i++) { keystore.setCertificateEntry("cert-"+i, certs.get(i)); } keystore.setKeyEntry("mykey", key, password, certs.toArray(new X509Certificate[certs.size()])); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(keystore, password); return kmf; } catch(Exception e) { throw new KeyStoreException(e); } } private SSLUtils(){} /** * This trust manager just trusts everyone and everything. You probably * should not be using it unless you know what your doing. * * @author lwahlmeier */ public static class FullTrustManager implements X509TrustManager, TrustManager { @Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { //No Exception means we are ok } @Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { //No Exception means we are ok } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }