platform/android/Rhodes/src/com/rhomobile/rhodes/socket/SSLImpl.java in rhodes-3.5.1.12 vs platform/android/Rhodes/src/com/rhomobile/rhodes/socket/SSLImpl.java in rhodes-5.5.0

- old
+ new

@@ -24,33 +24,56 @@ * http://rhomobile.com *------------------------------------------------------------------------*/ package com.rhomobile.rhodes.socket; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.net.Socket; import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import android.util.Base64; + import com.rhomobile.rhodes.Logger; +import com.rhomobile.rhodes.RhoConf; +import com.rhomobile.rhodes.file.RhoFileApi; +import java.util.StringTokenizer; + + public class SSLImpl { private static final String TAG = "SSLImplJava"; private static SSLSocketFactory factory = null; + private static SSLSocketFactory secureFactory = null; + private static SSLSocketFactory mutualAuthFactory = null; + private SSLSocket sock; //Used from jni @SuppressWarnings("unused") @@ -80,33 +103,280 @@ return new X509Certificate[0]; } }; + private static class MySecureTrustManager implements X509TrustManager { + private X509TrustManager mSysTrustManager; + private X509TrustManager mCustomTrustManager; + + public MySecureTrustManager( X509TrustManager sysTrustManager, X509TrustManager customTrustManager ) { + mSysTrustManager = sysTrustManager; + mCustomTrustManager = customTrustManager; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + if ( mCustomTrustManager != null ) { + mCustomTrustManager.checkClientTrusted(chain, authType); + } + } catch ( CertificateException e ) { + if ( mSysTrustManager != null ) { + mSysTrustManager.checkClientTrusted(chain, authType); + } + } + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + if ( mCustomTrustManager != null ) { + mCustomTrustManager.checkServerTrusted(chain, authType); + } + } catch ( CertificateException e ) { + if ( mSysTrustManager != null ) { + mSysTrustManager.checkServerTrusted(chain, authType); + } + } + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate ret[] = null; + + X509Certificate customAcceptedIssuers[] = (mCustomTrustManager!=null)?mCustomTrustManager.getAcceptedIssuers():new X509Certificate[0]; + X509Certificate sysAcceptedIssuers[] = (mSysTrustManager!=null)?mSysTrustManager.getAcceptedIssuers():new X509Certificate[0]; + + if ( customAcceptedIssuers == null ) { + customAcceptedIssuers = new X509Certificate[0]; + } + + if ( sysAcceptedIssuers == null ) { + sysAcceptedIssuers = new X509Certificate[0]; + } + + int size = customAcceptedIssuers.length + sysAcceptedIssuers.length; + + if ( size > 0 ) { + + ret = new X509Certificate[ size ]; + + System.arraycopy(sysAcceptedIssuers, 0, ret, 0, sysAcceptedIssuers.length); + System.arraycopy(customAcceptedIssuers, 0, ret, sysAcceptedIssuers.length, customAcceptedIssuers.length); + } + + return ret; + } + + }; + private static void reportFail(String name, Exception e) { Logger.E(TAG, "Call of \"" + name + "\" failed: " + e.getClass().getSimpleName() + ": " + e.getMessage()); } + + private static byte[] fileToBytes (File file) throws IOException { + RandomAccessFile f = new RandomAccessFile(file, "r"); + + try { + long longlength = f.length(); + int length = (int) longlength; + if (length != longlength) throw new IOException("File size >= 2 GB"); + + byte[] data = new byte[length]; + f.readFully(data); + return data; + } + finally { + f.close(); + } + } - private static SSLSocketFactory getFactory(boolean verify) throws NoSuchAlgorithmException, KeyManagementException { - if (verify) - return (SSLSocketFactory)SSLSocketFactory.getDefault(); + private static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) { + String data = new String(pem); + String[] tokens = data.split(beginDelimiter); + tokens = tokens[1].split(endDelimiter); + return Base64.decode(tokens[0],Base64.DEFAULT); + } + + protected static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(certBytes)); + } + + private static Certificate loadCertificate( File f ) { + X509Certificate cert = null; + + Logger.I( TAG, "Loading SSL certificate from PEM file: " + f.getAbsolutePath() ); + + try { + + byte[] fileBuf = fileToBytes( f ); + byte[] certBytes = parseDERFromPEM(fileBuf, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----"); + cert = generateCertificateFromDER(certBytes); + + Logger.I( TAG, "SSL certificate loaded successfully" ); + + + } catch( IOException e ) { + Logger.E( TAG, "Reading certificate file failed: " + e.getClass().getSimpleName() + ": " + e.getMessage() ); + } catch ( CertificateException e ) { + Logger.E( TAG, "Certificate generation failed: " + e.getClass().getSimpleName() + ": " + e.getMessage() ); + } + + return cert; + } + + private static List<Certificate> loadAllCertificates() { + List<Certificate> certs = new ArrayList<Certificate>(); + + Logger.I(TAG, "Loading all SSL certificates from config"); + + + if ( RhoConf.isExist( "CAFile" ) ) { + + String caFilePath = RhoConf.getString( "CAFile" ); + + Logger.I(TAG, "CAFile found in config: loading certificate: " + caFilePath); + + File caFile = new File( caFilePath ); + + if ( caFile.exists() ) { + Certificate c = loadCertificate(caFile); + if ( c != null ) { + certs.add( c ); + } + } else { + Logger.W(TAG, "CAFile config parameter exists, but file " + caFilePath + " not found." ); + } + } + + if ( RhoConf.isExist( "CAPath" ) ) { + String caFolderPath = RhoConf.getString( "CAPath" ); + + Logger.I(TAG, "CAPath found in config: loading all certificates from " + caFolderPath); + + File caFolder = new File( caFolderPath ); + + if ( caFolder.isDirectory() ) { + File list[] = caFolder.listFiles(); + for ( File f : list ) { + Certificate c = loadCertificate(f); + if ( c != null ) { + certs.add( c ); + } + } + + } else { + Logger.W(TAG, "CAPath config parameter exists, but folder " + caFolderPath + " not found." ); + } + } + + Logger.I(TAG, "SSL certificates loaded: " + String.valueOf(certs.size()) ); + + + return certs; + } + + private static SSLSocketFactory getSecureFactory() throws NoSuchAlgorithmException, KeyManagementException, CertificateException, KeyStoreException, IOException, UnrecoverableKeyException { + Logger.I(TAG, "Creating secure SSL factory"); + + SSLContext context = SSLContext.getInstance("TLS"); + + // First, load all system installed certificates + Logger.I(TAG, "Creating TrustManager for system certificates"); + TrustManagerFactory systemTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + systemTmf.init((KeyStore)null); + X509TrustManager systemTrustManager = (X509TrustManager)systemTmf.getTrustManagers()[0]; + + // Create keystore for custom certificates + KeyStore keystore = KeyStore.getInstance( KeyStore.getDefaultType() ); + keystore.load(null); + + List<Certificate> certs = loadAllCertificates(); + + // Add loaded custom certificates to keystore + if ( certs != null ) { + for ( int i = 0; i < certs.size(); ++i ) { + keystore.setCertificateEntry("cert-alias"+ String.valueOf(i),certs.get(i)); + } + } + + + Logger.I(TAG, "Creating TrustManager for custom certificates"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(keystore); + X509TrustManager customTrustManager = (X509TrustManager)tmf.getTrustManagers()[0]; + + KeyManagerFactory kmf = null; + + if ( RhoConf.isExist("clientSSLCertificate")) { + String clientCertPath = RhoConf.getString("clientSSLCertificate"); + + Logger.I(TAG, "clientSSLCertificate is " + clientCertPath ); + + + if ( clientCertPath.length() > 0 ) { + Logger.I(TAG, "Creating KeyManager for client certificates"); + kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm() ); + + String password = ""; + if (RhoConf.isExist("clientSSLCertificatePassword")) { + password = RhoConf.getString("clientSSLCertificatePassword"); + } + + KeyStore clientKeystore = KeyStore.getInstance( "pkcs12" ); + clientKeystore.load( RhoFileApi.open(clientCertPath), password.toCharArray() ); + kmf.init(clientKeystore, password.toCharArray()); + } + } + + /* + * this really works only with first provided TrustManager, + * so we make our own wrapper which encapsulates both system installed and custom provided certificates + */ + context.init( + (kmf==null)?null:kmf.getKeyManagers(), + new TrustManager[] { new MySecureTrustManager( systemTrustManager, customTrustManager ) }, + new SecureRandom() + ); + + Logger.I(TAG, "Secure SSL factory initialization completed"); + + return (SSLSocketFactory)context.getSocketFactory(); + + } + + private static SSLSocketFactory getFactory(boolean verify) throws NoSuchAlgorithmException, KeyManagementException, CertificateException, KeyStoreException, IOException, UnrecoverableKeyException { + if (verify) { + //if ( secureFactory == null ) { + secureFactory = getSecureFactory(); + //} + return secureFactory; + } if (factory == null) { SSLContext context = SSLContext.getInstance("TLS"); TrustManager[] managers = {new MyTrustManager()}; context.init(null, managers, new SecureRandom()); - factory = context.getSocketFactory(); + factory = context.getSocketFactory(); } return factory; } - public boolean connect(int fd, boolean sslVerifyPeer) { + public boolean connect(int fd, boolean sslVerifyPeer, String hostname ) { try { + Logger.I(TAG, "SSL connect to " + hostname); + RhoSockAddr remote = getRemoteSockAddr(fd); Socket s = new RhoSocket(fd, remote); SSLSocketFactory f = getFactory(sslVerifyPeer); - SSLSocket aSock = (SSLSocket)f.createSocket(s, remote.host.toString(), remote.port, true); + StringTokenizer st = new StringTokenizer( hostname, ":" ); + String host = st.nextToken(); + + SSLSocket aSock = (SSLSocket)f.createSocket(s, host, remote.port, true); aSock.setUseClientMode(true); synchronized (this) { sock = aSock; os = sock.getOutputStream();