2003/06/18 06:22:49
[org.ibex.core.git] / src / org / xwt / TinySSL.java
index 637e41c..abb5561 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2001 Adam Megacz <adam@xwt.org> all rights reserved.
+// Copyright (C) 2002 Adam Megacz <adam@xwt.org> all rights reserved.
 //
 // You may modify, copy, and redistribute this code under the terms of
 // the GNU Library Public License version 2.1, with the exception of
@@ -31,6 +31,9 @@ import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.TBSCertificateStructure;
 import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509Extension;
+import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.xwt.util.Log;
 import java.net.*;
 import java.io.*;
@@ -81,6 +84,9 @@ import java.text.*;
    1.02 27-Mar-02  Fixed a bug which would hang the connection when more than one
                    Handshake message appeared in the same TLS Record
 
+   1.03 10-Aug-02  Fixed a vulnerability outlined at
+                   http://online.securityfocus.com/archive/1/286290
+
 */
 
 public class TinySSL extends Socket {
@@ -90,7 +96,7 @@ public class TinySSL extends Socket {
     public static void main(String[] args) {
         Log.on = true;
         try {
-            Socket s = new TinySSL("www.verisign.com", 443);
+            Socket s = new TinySSL("www.paypal.com", 443);
             PrintWriter pw = new PrintWriter(s.getOutputStream());
             BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
             pw.println("GET / HTTP/1.0");
@@ -100,7 +106,7 @@ public class TinySSL extends Socket {
             while(true) {
                 String s2 = br.readLine();
                 if (s2 == null) return;
-                System.out.println(s2);
+                Log.log(TinySSL.class, s2);
             }
             
         } catch (Exception e) {
@@ -143,6 +149,9 @@ public class TinySSL extends Socket {
 
     String hostname;
 
+    /** if true, we don't mind if the server's cert isn't signed by a CA. USE WITH CAUTION! */
+    boolean ignoreUntrustedCert = false;
+
     /** the concatenation of all the bytes of all handshake messages sent or recieved */
     public byte[] handshakes = new byte[] { };
 
@@ -152,10 +161,12 @@ public class TinySSL extends Socket {
     public InputStream getInputStream() throws IOException { return is != null ? is : super.getInputStream(); }
     public OutputStream getOutputStream() throws IOException { return os != null ? os : super.getOutputStream(); }
 
-    public TinySSL(String host, int port) throws IOException { this(host, port, true); }
-    public TinySSL(String host, int port, boolean negotiateImmediately) throws IOException {
+    public TinySSL(String host, int port) throws IOException { this(host, port, true, false); }
+    public TinySSL(String host, int port, boolean negotiateImmediately) throws IOException { this(host, port, negotiateImmediately, false); }
+    public TinySSL(String host, int port, boolean negotiateImmediately, boolean ignoreUntrustedCert) throws IOException {
         super(host, port);
         hostname = host;
+        this.ignoreUntrustedCert = ignoreUntrustedCert;
         if (negotiateImmediately) negotiate();
     }
 
@@ -364,7 +375,7 @@ public class TinySSL extends Socket {
                                 }
 
                             if (!good) throw new SSLException("server certificate does not seem to have a CN: " + CN);
-                            if (!CN.equals(hostname))
+                            if (!ignoreUntrustedCert && !CN.equals(hostname))
                                 throw new SSLException("connecting to host " + hostname + " but server certificate was issued for " + CN);
 
                             SimpleDateFormat dateF = new SimpleDateFormat("MM-dd-yy-HH-mm-ss-z");
@@ -381,13 +392,28 @@ public class TinySSL extends Socket {
                             Date endDate = dateF.parse(s, new ParsePosition(0));
 
                             Date now = new Date();
-                            if (now.after(endDate)) throw new SSLException("server certificate expired on " + endDate);
-                            if (now.before(startDate)) throw new SSLException("server certificate will not be valid until " + startDate);
+                            if (!ignoreUntrustedCert && now.after(endDate))
+                                throw new SSLException("server certificate expired on " + endDate);
+                            if (!ignoreUntrustedCert && now.before(startDate))
+                                throw new SSLException("server certificate will not be valid until " + startDate);
 
                             Log.log(this, "server cert (name, validity dates) checks out okay");
                             
-                        } else if (!isSignedBy(last_cert, this_cert.getSubjectPublicKeyInfo()))
-                            throw new SSLException("the server sent a broken chain of certificates");
+                        } else {
+
+                            // don't check the top cert since some very old root certs lack a BasicConstraints field.
+                            if (certlen + 3 + i < numcertbytes) {
+                                // defend against Mike Benham's attack
+                                X509Extension basicConstraints = this_cert.getTBSCertificate().getExtensions().getExtension(X509Extensions.BasicConstraints);
+                                if (basicConstraints == null) throw new SSLException("certificate did not contain a basic constraints block");
+                                DERInputStream dis = new DERInputStream(new ByteArrayInputStream(basicConstraints.getValue().getOctets()));
+                                BasicConstraints bc = new BasicConstraints((DERConstructedSequence)dis.readObject());
+                                if (!bc.isCA()) throw new SSLException("non-CA certificate used for signing");
+                            }
+
+                            if (!isSignedBy(last_cert, this_cert.getSubjectPublicKeyInfo()))
+                                throw new SSLException("the server sent a broken chain of certificates");
+                        }
 
                         last_cert = this_cert;
                         i += certlen + 3;
@@ -395,6 +421,8 @@ public class TinySSL extends Socket {
                     }
                     if (Log.on) Log.log(this, "  Certificate (" + numcerts + " certificates)");
 
+                    if (ignoreUntrustedCert) break;
+
                     boolean good = false;
 
                     // pass 1 -- only check CA's whose subject is a partial match
@@ -1472,20 +1500,22 @@ public class TinySSL extends Socket {
         "1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71" +
         "lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZw" +
         "IDAQAB"
-
     };
 
+    public static boolean alwaysFalse = false;
+
     static class entropySpinner extends Thread {
         volatile boolean stop = false;
         byte counter = 0;
         entropySpinner() { start(); }
         public void run() {
             while (true) {
-                // without this synchronization, GCJ will over-optimize this loop into an infinite loop. Argh.
-                synchronized(this) {
-                    counter++;
-                    if (stop) return;
-                }
+                counter++;
+
+                // without this line, GCJ will over-optimize this loop into an infinite loop. Argh.
+                if (alwaysFalse) stop = true;
+
+                if (stop) return;
             }
         }
     }
@@ -1515,6 +1545,7 @@ public class TinySSL extends Socket {
             if (Log.on) Log.log(TinySSL.class, e);
         } 
         
+        if (Log.on) Log.log(TinySSL.class, "generating entropy..."); 
         randpool = new byte[10];
         try { Thread.sleep(100); } catch (Exception e) { }
         for(int i=0; i<spinners.length; i++) {
@@ -1539,6 +1570,7 @@ public class TinySSL extends Socket {
         randpool = new byte[md5.getDigestSize()];
         md5.doFinal(randpool, 0);
 
+        if (Log.on) Log.log(TinySSL.class, "TinySSL is initialized."); 
     }