/* * Copyright (C) 2003 Sun Microsystems, Inc. * * This 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 of the License, or * (at your option) any later version. * * This software 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ package com.iiordanov.tigervnc.rfb; import javax.net.ssl.*; import java.security.cert.*; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.io.File; import java.io.InputStream; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Collection; import android.content.DialogInterface; import android.os.Looper; import android.os.Message; import android.util.Log; import com.iiordanov.bVNC.Utils; import com.iiordanov.bVNC.RemoteCanvas; import com.iiordanov.bVNC.RemoteCanvasActivity; import com.iiordanov.bVNC.Constants; import com.iiordanov.bVNC.bVNC; import com.iiordanov.tigervnc.rdr.*; public class CSecurityTLS extends CSecurity { public static StringParameter x509ca = new StringParameter("x509ca", "X509 CA certificate", ""); //"/system/etc/security/cacerts.bks"); public static StringParameter x509crl = new StringParameter("x509crl", "X509 CRL file", ""); private void initGlobal() { try { SSLSocketFactory sslfactory; SSLContext ctx = SSLContext.getInstance("TLS"); if (anon) { ctx.init(null, null, null); } else { TrustManager[] myTM = new TrustManager[] { new MyX509TrustManager() }; ctx.init (null, myTM, null); } sslfactory = ctx.getSocketFactory(); try { ssl = (SSLSocket)sslfactory.createSocket(CConnection.sock, CConnection.sock.getInetAddress().getHostName(), CConnection.sock.getPort(), true); ssl.setTcpNoDelay(true); } catch (java.io.IOException e) { throw new Exception(e.toString()); } if (anon) { String[] supported; ArrayList<String> enabled = new ArrayList<String>(); supported = ssl.getSupportedCipherSuites(); for (int i = 0; i < supported.length; i++) { //Log.e("SUPPORTED CIPHERS", supported[i]); if (supported[i].matches("TLS_DH_anon.*")) enabled.add(supported[i]); } if (enabled.size() == 0) throw new Exception("Your device lacks support for ciphers necessary for this encryption mode " + "(Anonymous Diffie-Hellman ciphers). " + "This is a known issue with devices running Android 2.2.x and older. You can " + "work around this by using VeNCrypt with x509 certificates instead."); ssl.setEnabledCipherSuites(enabled.toArray(new String[0])); } else { ssl.setEnabledCipherSuites(ssl.getSupportedCipherSuites()); } ssl.setEnabledProtocols(ssl.getSupportedProtocols()); ssl.addHandshakeCompletedListener(new MyHandshakeListener()); } catch (java.security.GeneralSecurityException e) { vlog.error ("TLS handshake failed " + e.toString ()); return; } } public CSecurityTLS(boolean _anon, RemoteCanvas v) { vncCanvas = v; anon = _anon; setDefaults(); cafile = x509ca.getData(); crlfile = x509crl.getData(); } public static void setDefaults() { // TODO: Perhaps add x509ca and crl fields in database. String homeDir = null; if ((homeDir/*=UserPrefs.getHomeDir()*/) == null) { vlog.error("Could not obtain VNC home directory path"); return; } String vnchomedir = null; /*homeDir+UserPrefs.getFileSeparator()+".vnc"+ UserPrefs.getFileSeparator();*/ String caDefault = new String(vnchomedir+"x509_ca.pem"); String crlDefault = new String(vnchomedir+"x509_crl.pem"); if (new File(caDefault).exists()) x509ca.setDefaultStr(caDefault); if (new File(crlDefault).exists()) x509crl.setDefaultStr(crlDefault); } public boolean processMsg(CConnection cc) { is = cc.getInStream(); initGlobal(); if (!is.checkNoWait(1)) return false; if (is.readU8() == 0) { int result = is.readU32(); String reason; if (result == Security.secResultFailed || result == Security.secResultTooMany) reason = is.readString(); else reason = new String("Authentication failure (protocol error)"); throw new AuthFailureException(reason); } // SSLSocket.getSession blocks until the handshake is complete session = ssl.getSession(); if (!session.isValid()) throw new Exception("TLS Handshake failed!"); try { cc.setStreams(new JavaInStream(ssl.getInputStream()), new JavaOutStream(ssl.getOutputStream())); } catch (java.io.IOException e) { throw new Exception("Failed to set streams"); } return true; } class MyHandshakeListener implements HandshakeCompletedListener { public void handshakeCompleted(HandshakeCompletedEvent e) { vlog.info("Handshake succesful!"); vlog.info("Using cipher suite: " + e.getCipherSuite()); } } class MyX509TrustManager implements X509TrustManager { X509TrustManager tm; MyX509TrustManager() throws java.security.GeneralSecurityException { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance("BKS"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); try { ks.load(null, null); File cacert = new File(cafile); if (!cacert.exists() || !cacert.canRead()) return; InputStream caStream = new FileInputStream(cafile); X509Certificate ca = (X509Certificate)cf.generateCertificate(caStream); ks.setCertificateEntry("CA", ca); PKIXBuilderParameters params = new PKIXBuilderParameters(ks, new X509CertSelector()); File crlcert = new File(crlfile); if (!crlcert.exists() || !crlcert.canRead()) { params.setRevocationEnabled(false); } else { InputStream crlStream = new FileInputStream(crlfile); Collection<? extends CRL> crls = cf.generateCRLs(crlStream); CertStoreParameters csp = new CollectionCertStoreParameters(crls); CertStore store = CertStore.getInstance("Collection", csp); params.addCertStore(store); params.setRevocationEnabled(true); } tmf.init(new CertPathTrustManagerParameters(params)); } catch (java.io.FileNotFoundException e) { vlog.error(e.toString()); } catch (java.io.IOException e) { vlog.error(e.toString()); } tm = (X509TrustManager)tmf.getTrustManagers()[0]; } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (tm!=null) tm.checkClientTrusted(chain, authType); } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { if (tm!=null) tm.checkClientTrusted(chain, authType); else { throw new CertificateException ("The authenticity of the server's certificate could not be established."); } } catch (final java.lang.Exception e) { // Send a message containing the certificate to our handler. Message m = new Message(); m.setTarget(vncCanvas.handler); m.what = Constants.DIALOG_X509_CERT; m.obj = chain[0]; vncCanvas.handler.sendMessage(m); synchronized (vncCanvas) { // Block indefinitely until x509 cert is matched to a saved one or the user accepts it. while (!vncCanvas.isCertificateAccepted()) { try { vncCanvas.wait(); } catch (InterruptedException e1) { e1.printStackTrace(); } } } } } public X509Certificate[] getAcceptedIssuers () { if (tm!=null) return tm.getAcceptedIssuers(); else return null; } } public final int getType() { return anon ? Security.secTypeTLSNone : Security.secTypeX509None; } public final String description() { return anon ? "TLS Encryption without VncAuth" : "X509 Encryption without VncAuth"; } //protected void setParam(); //protected void checkSession(); protected CConnection cc; private boolean anon; private SSLSession session; private String cafile, crlfile; private InStream is; private SSLSocket ssl; TrustManager[] trustAllCerts; RemoteCanvas vncCanvas; static LogWriter vlog = new LogWriter("CSecurityTLS"); }