/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ /* * Copyright(C) 2006 Cameron Rich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * A wrapper around the unmanaged interface to give a semi-decent Java API */ package totalcross.net.ssl; import java.io.ByteArrayInputStream; import java.net.InetSocketAddress; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.*; import javax.net.ssl.KeyManager; 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 totalcross.crypto.*; import totalcross.crypto.NoSuchAlgorithmException; import totalcross.io.IOException; import totalcross.net.Socket; import totalcross.sys.Vm; import totalcross.util.Vector; /** * A base object for SSLServer/SSLClient. */ public class SSLCTX { int options; int num_sessions; boolean dontFinalize; //flsobral@tc114_36: finalize support. private SSLContext ctx_ssl; private TrustManager[] trustMgrs; private Vector CACerts = new Vector(); private Vector Certs = new Vector(); private Vector Keys = new Vector(); public static final String toSign = "TotalCross"; /** * Establish a new client/server context. * * This function is called before any client/server SSL connections are * made. If multiple threads are used, then each thread will have its * own SSLCTX context. Any number of connections may be made with a single * context. * * Each new connection will use the this context's private key and * certificate chain. If a different certificate chain is required, then a * different context needs to be be used. * * @param options [in] Any particular options. At present the options * supported are: * - SSL_SERVER_VERIFY_LATER (client only): Don't stop a handshake if the * server authentication fails. The certificate can be authenticated * later with a call to verifyCert(). * - SSL_CLIENT_AUTHENTICATION (server only): Enforce client authentication * i.e. each handshake will include a "certificate request" message * from the server. * - SSL_NO_DEFAULT_KEY: Don't use the default key/certificate. The user * will load the key/certificate explicitly. * - SSL_DISPLAY_BYTES (full mode build only): Display the byte sequences * during the handshake. * - SSL_DISPLAY_STATES (full mode build only): Display the state changes * during the handshake. * - SSL_DISPLAY_CERTS (full mode build only): Display the certificates that * are passed during a handshake. * - SSL_DISPLAY_RSA (full mode build only): Display the RSA key details * that are passed during a handshake. * * @param num_sessions [in] The number of sessions to be used for session * caching. If this value is 0, then there is no session caching. * * If this option is null, then the default internal private key/ * certificate pair is used (if CONFIG_SSL_USE_DEFAULT_KEY is set). * * The resources used by this object are automatically freed. * @throws NoSuchAlgorithmException */ protected SSLCTX(int options, int num_sessions) throws NoSuchAlgorithmException { this.options = options; this.num_sessions = num_sessions; try { Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); ctx_ssl = SSLContext.getInstance("TLS"); } catch (java.security.NoSuchAlgorithmException e) { throw new NoSuchAlgorithmException(e.getMessage()); } } /** * Remove a client/server context. * Frees any used resources used by this context. Each connection will be * sent a "Close Notify" alert (if possible). */ final public void dispose() { } /** * Find an ssl object based on a Socket reference. * * Goes through the list of SSL objects maintained in a client/server * context to look for a socket match. * @param s [in] A reference to a totalcross.net.Socket object. * @return A reference to the SSL object. Returns null if the object * could not be found. */ final public SSL find(Socket s) { return SSL.cacheGetSSL(s); } /** * Load security material (CA, Cert or private key) in binary DER or ASCII PEM format. * These information are used during the SSL protocol to authentication of SSL parts * and cyphering/uncyphering of the data exchanged between the two parts. * @param obj_type [in] The format of the file. Can be one of: * - SSL_OBJ_X509_CERT (no password required). * - SSL_OBJ_X509_CACERT (no password required). * - SSL_OBJ_RSA_KEY (AES128/AES256 PEM encryption supported). (not supported on Desktop) * - SSL_OBJ_P8 (RC4-128 encrypted data supported). (password protection not supported on Desktop) * - SSL_OBJ_P12 (RC4-128 encrypted data supported). * PEM encoded files are automatically detected and may contain several material, * whereas DER encoding only support one single material. * @param material [in] security material input stream. * @param password [in] The password used. Can be null if not required. * @return SSL_OK if the call succeeded * @throws CryptoException * @throws NoSuchAlgorithmException */ final public int objLoad(int obj_type, totalcross.io.Stream material, String password) throws IOException, NoSuchAlgorithmException, CryptoException { byte buffer[] = new byte[1024]; byte bytes[] = new byte[0]; int count; while ((count = material.readBytes(buffer,0,buffer.length)) > 0) { byte[] temp = new byte[bytes.length + count]; Vm.arrayCopy(bytes, 0, temp, 0, bytes.length); Vm.arrayCopy(buffer, 0, temp, bytes.length, count); bytes = temp; } if (bytes != null) return objLoad(obj_type, bytes, bytes.length, password); return Constants.SSL_NOT_OK; } private final int objLoadOne(int obj_type, byte[] data, int len, String password) throws NoSuchAlgorithmException, CryptoException { try { if (obj_type == Constants.SSL_OBJ_PKCS8) { byte[] pkcs8_bytes = new byte[len]; Vm.arrayCopy(data, 0, pkcs8_bytes, 0, len); PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(pkcs8_bytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec); Keys.push(privKey); return Constants.SSL_OK; } else if (obj_type == Constants.SSL_OBJ_PKCS12) { KeyStore ks12 = KeyStore.getInstance("pkcs12"); ks12.load(new ByteArrayInputStream(data, 0, len), password.toCharArray()); java.util.Enumeration<?> aliases = ks12.aliases(); while (aliases.hasMoreElements()) { String alias = (String) aliases.nextElement(); java.security.cert.Certificate[] certchain = null; certchain = ks12.getCertificateChain(alias); if (certchain != null) { for (int k = 0; k < certchain.length; k++) Certs.push((X509Certificate) certchain[k]); } PrivateKey pke = null; pke = (PrivateKey) ks12.getKey(alias, password.toCharArray()); if (pke != null) Keys.push(pke); } return Constants.SSL_OK; } else if (obj_type == Constants.SSL_OBJ_RSA_KEY) { throw new UnsupportedOperationException("Only pkcs8/pkcs12 encoding are supported for private keys"); } else if (obj_type == Constants.SSL_OBJ_X509_CACERT || obj_type == Constants.SSL_OBJ_X509_CERT) { java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X509"); java.security.cert.X509Certificate x509certificate = (java.security.cert.X509Certificate) cf .generateCertificate(new ByteArrayInputStream(data, 0, len)); if (x509certificate != null) { if (obj_type == Constants.SSL_OBJ_X509_CACERT) { if (CACerts.size() < SSLUtil.CONFIG_X509_MAX_CA_CERTS) CACerts.push(x509certificate); } else { if (Certs.size() < SSLUtil.CONFIG_SSL_MAX_CERTS) Certs.push(x509certificate); } } return Constants.SSL_OK; } } catch (java.security.NoSuchAlgorithmException e) { throw new NoSuchAlgorithmException(e.getMessage()); } catch (java.security.GeneralSecurityException e) { throw new CryptoException(e.getMessage()); } catch (java.io.IOException e) { throw new CryptoException(e.getMessage()); } return Constants.SSL_NOT_OK; } /** * Load security material (CA, Cert or private key) in binary DER or ASCII PEM format. * These information are used during the SSL protocol to authenticate an SSL part * and cyphering/uncyphering of the data exchanged between the two parts. * @param obj_type [in] The format of the memory data. * - SSL_OBJ_X509_CERT (no password required). * - SSL_OBJ_X509_CACERT (no password required). * - SSL_OBJ_RSA_KEY (AES128/AES256 PEM encryption supported). (not supported on Desktop) * - SSL_OBJ_P8 (RC4-128 encrypted data supported). (password protection not supported on Desktop) * - SSL_OBJ_P12 (RC4-128 encrypted data supported). * PEM encoded data is automatically detected and may contain several material, * whereas DER encoding only support one single material. * @param data [in] The binary data to be loaded. * @param len [in] The amount of data to be loaded. * @param password [in] The password used. Can be null if not required. * @return SSL_OK if the call succeeded * @throws CryptoException * @throws NoSuchAlgorithmException */ final public int objLoad(int obj_type, byte[] data, int len, String password) throws NoSuchAlgorithmException, CryptoException { if (ctx_ssl == null) return -1; int ret = Constants.SSL_OK; /* check if the data is a PEM content */ boolean pem = false; String str_content = new String(data, 0, len); int start, end = -1; do { start = str_content.indexOf(SECTION_BEGIN); if (start > 0) { end = str_content.indexOf(SECTION_END); if (end > 0) { pem = true; int nl = str_content.indexOf('\n', end); String pem_data; if (nl > 0) { pem_data = str_content.substring(start, nl); str_content = str_content.substring(nl+1); } else { pem_data = str_content.substring(start); str_content = ""; } data = pem_data.getBytes(); ret = objLoadOne(obj_type, data, data.length, password); if (ret != Constants.SSL_OK) Vm.debug("Failed to load certificate:\r\n" + pem_data); } } } while (start > 0 && end > 0); if (!pem) // if no PEM format has been found, try another format ret = objLoadOne(obj_type, data, len, password); return ret; } private final static String SECTION_BEGIN = "-----BEGIN "; private final static String SECTION_END = "-----END "; private final static char[] ks_pass = "123456".toCharArray(); final public SSL newClient(Socket socket, byte[] session_id) throws IOException, NoSuchAlgorithmException, CryptoException { SSLSocketFactory sf = prepareSecurity(); java.net.Socket ns = (java.net.Socket)socket.getNativeSocket(); SSLSocket ssl_socket = null; try { InetSocketAddress inet = (InetSocketAddress)ns.getRemoteSocketAddress(); ssl_socket = (SSLSocket)sf.createSocket(ns, inet.getHostName(), inet.getPort(), false); if (ssl_socket != null) { SSL ssl = new SSL(ssl_socket, socket); ssl._trustMgrs = trustMgrs; ssl_socket.setUseClientMode(true); ssl.renegotiate(); return ssl; } } catch (java.io.IOException e) { throw new IOException(e.getMessage()); } return null; } final public SSL newServer(Socket socket) throws IOException, NoSuchAlgorithmException, CryptoException { SSLSocketFactory sf = prepareSecurity(); java.net.Socket ns = (java.net.Socket)socket.getNativeSocket(); SSLSocket ssl_socket = null; try { InetSocketAddress inet = (InetSocketAddress)ns.getRemoteSocketAddress(); ssl_socket = (SSLSocket)sf.createSocket(ns, inet.getHostName(), inet.getPort(), false); if (ssl_socket != null) { SSL ssl = new SSL(ssl_socket, socket); ssl._trustMgrs = trustMgrs; ssl_socket.setUseClientMode(false); ssl_socket.setNeedClientAuth((options & Constants.SSL_CLIENT_AUTHENTICATION) != 0); ssl.renegotiate(); return ssl; } } catch (java.io.IOException e) { throw new IOException(e.getMessage()); } return null; } private SSLSocketFactory prepareSecurity() throws IOException, NoSuchAlgorithmException, CryptoException { try { if (ctx_ssl == null) return null; KeyStore ts = null; KeyStore ks = null; ts = KeyStore.getInstance("JKS"); //ts.load(new FileInputStream("truststore.ks"), password); ts.load(null, null); ks = KeyStore.getInstance("JKS"); //ks.load(new FileInputStream("keystore.ks"), password); ks.load(null, null); //KeyStore inputKeyStore = KeyStore.getInstance("PKCS12"); for (int i = CACerts.size() - 1; i >= 0; i--) { java.security.cert.X509Certificate caCert = (java.security.cert.X509Certificate) (CACerts.items[i]); ts.setCertificateEntry("ca" + i, caCert); ks.setCertificateEntry("ca" + i, caCert); } for (int i = Certs.size() - 1; i >= 0; i--) { java.security.cert.X509Certificate cert = (java.security.cert.X509Certificate) (Certs.items[i]); ks.setCertificateEntry("cert", cert); } for (int i = Keys.size() - 1; i >= 0; i--) { PrivateKey pkey = (PrivateKey) Keys.items[i]; java.security.cert.Certificate[] certChain = buildChain(pkey, CACerts, Certs); if (certChain != null) ks.setKeyEntry("cert", pkey, ks_pass, certChain); } /* store both stores for checking... try { ts.store(new FileOutputStream("truststore.ks"), ks_pass); ks.store(new FileOutputStream("keystore.ks"), ks_pass); } catch (Exception e) { e.printStackTrace(); } */ TrustManager[] tm = null; TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); if (tmf != null) { tmf.init(ts); trustMgrs = tmf.getTrustManagers(); } if ((options & Constants.SSL_SERVER_VERIFY_LATER) != 0) tm = new TrustManager[] { // no check! trusts everyone new X509TrustManager() { /** * Doesn't throw an exception, so this is how it approves a certificate. * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], String) **/ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } /** * Doesn't throw an exception, so this is how it approves a certificate. * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], String) **/ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } /** * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() **/ public X509Certificate[] getAcceptedIssuers() { return null; } } }; else tm = trustMgrs; KeyManager[] km = null; KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); if (kmf != null) { kmf.init(ks, ks_pass); km = kmf.getKeyManagers(); } ctx_ssl.init(km, tm, new java.security.SecureRandom()); return (SSLSocketFactory) ctx_ssl.getSocketFactory(); } catch (java.security.NoSuchAlgorithmException e) { throw new NoSuchAlgorithmException(e.getMessage()); } catch (java.security.GeneralSecurityException e) { throw new CryptoException(e.getMessage()); } catch (java.io.IOException e) { throw new IOException(e.getMessage()); } } private static X509Certificate findIssuer(X509Certificate cert, Vector all, Vector chain) { for (int i = all.size()-1; i >= 0; i--) { java.security.cert.X509Certificate issuerCert = (java.security.cert.X509Certificate)all.items[i]; java.security.PublicKey publickey = issuerCert.getPublicKey(); try { cert.verify(publickey); // System.out.println("Subject: " + subject); // System.out.println("Issuer: " + issuer); chain.addElement(cert); return issuerCert; } catch (java.security.GeneralSecurityException e) { // keep searching } } return null; } private static Certificate[] buildChain(PrivateKey pkey, Vector cacerts, Vector certs) throws NoSuchAlgorithmException, CryptoException { Vector chain = new Vector(); X509Certificate cert = null; try { Signature sigInst = Signature.getInstance("MD5withRSA"); for (int i = certs.size()-1; i >= 0; i--) { sigInst.initSign(pkey); sigInst.update(toSign.getBytes()); byte sign[] = sigInst.sign(); sigInst.initVerify((Certificate)certs.items[i]); sigInst.update(toSign.getBytes()); if (sigInst.verify(sign)) { cert = (X509Certificate)certs.items[i]; break; } } } catch (java.security.NoSuchAlgorithmException e) { throw new NoSuchAlgorithmException(e.getMessage()); } catch (java.security.GeneralSecurityException e) { throw new CryptoException(e.getMessage()); } X509Certificate issuerCert; do { issuerCert = null; java.security.Principal subject = cert.getSubjectDN(); java.security.Principal issuer = cert.getIssuerDN(); // System.out.println("Subject: " + subject); // System.out.println("Issuer: " + issuer); if (subject.equals(issuer)) { chain.addElement(cert); Certificate[] ch = new Certificate[chain.size()]; chain.copyInto(ch); return ch; } issuerCert = findIssuer(cert, certs, chain); if (issuerCert == null) issuerCert = findIssuer(cert, cacerts, chain); cert = issuerCert; } while (issuerCert != null); return null; } protected final void finalize() //flsobral@tc114_36: finalize support. { try { if (dontFinalize != true) dispose(); } catch (Throwable t) { } } }