initial import
[org.ibex.crypto.git] / src / org / ibex / net / SSL.java
1 /*
2  * org.ibex.net.SSL - By Brian Alliet
3  * Copyright (C) 2004 Brian Alliet
4  * 
5  * Based on TinySSL by Adam Megacz
6  * Copyright (C) 2003 Adam Megacz <adam@xwt.org> all rights reserved.
7  * 
8  * You may modify, copy, and redistribute this code under the terms of
9  * the GNU Lesser General Public License version 2.1, with the exception
10  * of the portion of clause 6a after the semicolon (aka the "obnoxious
11  * relink clause")
12  */
13
14 package org.ibex.net;
15
16 import org.ibex.der.DER.Exception;
17 import org.ibex.der.DER.InputStream;
18 import org.ibex.x509.X509Certificate;
19 import org.ibex.x509.RSAPublicKey;
20 import org.ibex.x509.X509Name;
21 import org.ibex.crypto.HMAC;
22 import org.ibex.crypto.PKCS1;
23 import org.ibex.crypto.RC4;
24 import org.ibex.crypto.RSA;
25 import org.ibex.crypto.Digest;
26 import org.ibex.crypto.MD5;
27 import org.ibex.crypto.SHA1;
28
29 import java.security.SecureRandom;
30
31 import java.net.Socket;
32 import java.net.SocketException;
33
34 import java.io.*;
35 import java.util.Enumeration;
36 import java.util.Hashtable;
37 import java.util.Random;
38 import java.util.Vector;
39
40 // FEATURE: Server socket
41
42 public class SSL extends Socket {
43     private String hostname;
44     
45     private int negotiated;
46     
47     private boolean tls = true;
48     private boolean sha;
49     
50     private final DataInputStream rawIS;
51     private final DataOutputStream rawOS;
52     
53     private final InputStream sslIS;
54     private final OutputStream sslOS;
55     
56     private byte[] sessionID;
57
58     private Digest clientWriteMACDigest;        
59     private Digest serverWriteMACDigest;        
60     private byte[] masterSecret;
61     
62     private RC4 writeRC4;
63     private RC4 readRC4;
64     
65     private long serverSequenceNumber;
66     private long clientSequenceNumber;
67     
68     private int warnings;
69     private boolean closed;
70     
71     // These are only used during negotiation
72     private byte[] serverRandom;
73     private byte[] clientRandom;
74     private byte[] preMasterSecret;
75     
76     // Buffers
77     private byte[] mac;
78     
79     private byte[] pending = new byte[16384];
80     private int pendingStart;
81     private int pendingLength;
82
83     private byte[] sendRecordBuf = new byte[16384];
84     
85     private int handshakeDataStart;
86     private int handshakeDataLength;
87     private byte[] readRecordBuf = new byte[16384+20];   // 20 == sizeof(sha1 hash)
88     private byte[] readRecordScratch = new byte[16384+20];
89     
90     private ByteArrayOutputStream handshakesBuffer;
91     
92     // End Buffers
93     
94     // Static variables
95     private final static byte[] pad1 = new byte[48];
96     private final static byte[] pad2 = new byte[48];
97     private final static byte[] pad1_sha = new byte[40];
98     private final static byte[] pad2_sha = new byte[40];
99     
100     static {
101         for(int i=0; i<pad1.length; i++) pad1[i] = (byte)0x36;
102         for(int i=0; i<pad2.length; i++) pad2[i] = (byte)0x5C;
103         for(int i=0; i<pad1_sha.length; i++) pad1_sha[i] = (byte)0x36;
104         for(int i=0; i<pad2_sha.length; i++) pad2_sha[i] = (byte)0x5C;
105     }
106     
107     private final static Hashtable caKeys = new Hashtable();
108     private static VerifyCallback verifyCallback;
109     
110     //
111     // Constructors
112     //
113     public SSL(String host) throws IOException { this(host,443); }
114     public SSL(String host, int port) throws IOException { this(host,port,true); }
115     public SSL(String host, int port, boolean negotiate) throws IOException { this(host,port,negotiate,null); }
116     public SSL(String host, int port, State state) throws IOException { this(host,port,true,state); }
117     public SSL(String host, int port, boolean negotiate, State state) throws IOException {
118         super(host,port);
119         hostname = host;
120         rawIS = new DataInputStream(new BufferedInputStream(super.getInputStream()));
121         rawOS = new DataOutputStream(new BufferedOutputStream(super.getOutputStream()));
122         sslIS = new SSLInputStream();
123         sslOS = new SSLOutputStream();
124         if(negotiate) negotiate(state);
125     }
126
127     public synchronized void setTLS(boolean b) { if(negotiated!=0) throw new IllegalStateException("already negotiated"); tls = b; }
128     
129     public void negotiate() throws IOException { negotiate(null); }
130     public synchronized void negotiate(State state) throws IOException {
131         if(negotiated != 0) throw new IllegalStateException("already negotiated");
132         
133         handshakesBuffer = new ByteArrayOutputStream();
134         
135         try {
136             sendClientHello(state != null ? state.sessionID : null);
137             flush();
138             debug("sent ClientHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
139             
140             receiveServerHello();
141             debug("got ServerHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
142             
143             boolean resume = 
144                 state != null && sessionID.length == state.sessionID.length && 
145                 eq(state.sessionID,0,sessionID,0,sessionID.length);
146             
147             if(resume) 
148                 negotiateResume(state);
149             else
150                 negotiateNew();
151             
152             // we're done with these now
153             clientRandom = serverRandom = preMasterSecret = null;
154             handshakesBuffer = null;
155             
156             log("Negotiation with " + hostname + " complete (" + (tls?"TLSv1.0":"SSLv3.0")+")");
157         } finally {
158             if((negotiated & 3) != 3) {
159                 negotiated = 0;
160                 try { super.close(); } catch(IOException e) { /* ignore */ }
161                 closed = true;
162             }
163         }
164     }
165     
166     private void negotiateResume(State state) throws IOException {
167         masterSecret = state.masterSecret;
168         
169         initCrypto();
170         log("initializec crypto");
171         
172         receiveChangeCipherSpec();
173         debug("Received ChangeCipherSpec");
174         negotiated |= 2;
175         receieveFinished();
176         debug("Received Finished");
177         
178         sendChangeCipherSpec();
179         debug("Sent ChangeCipherSpec");
180         negotiated |= 1;
181         sendFinished();
182         debug("Sent Finished");
183     }
184     
185     private void negotiateNew() throws IOException {
186         X509Certificate[] certs = receiveServerCertificates();
187         debug("got Certificate");
188         
189         boolean gotCertificateRequest = false;
190         OUTER: for(;;) {
191             byte[] buf = readHandshake();
192             switch(buf[0]) {
193             case 14: // ServerHelloDone
194                 if(buf.length != 4) throw new Exn("ServerHelloDone contained trailing garbage");
195                 debug("got ServerHelloDone");
196                 break OUTER;
197             case 13: // CertificateRequest
198                 debug("Got a CertificateRequest message but we don't suport client certificates");
199                 gotCertificateRequest = true;
200                 break;
201             default:
202                 throw new Exn("unknown handshake type " + buf[0]);
203             }
204         }
205         
206         if(gotCertificateRequest)
207             sendHandshake((byte)11,new byte[3]); // send empty cert list
208         
209         try {
210             if(!hostname.equalsIgnoreCase(certs[0].getCN()))
211                 throw new Exn("Certificate is for " + certs[0].getCN() + " not " + hostname);
212             verifyCerts(certs);
213         } catch(Exn e) {
214             if(verifyCallback == null) throw e;
215             synchronized(SSL.class) {
216                 if(!verifyCallback.checkCerts(certs,hostname,e)) throw e;
217             }
218         }
219         
220         computeMasterSecret();
221         
222         sendClientKeyExchange(certs[0]);
223         debug("sent ClientKeyExchange");
224         
225         initCrypto();
226         
227         sendChangeCipherSpec();
228         debug("sent ChangeCipherSpec");
229         negotiated |= 1;
230         sendFinished();
231         debug("sent Finished");
232         flush();
233         
234         receiveChangeCipherSpec();
235         debug("got ChangeCipherSpec");
236         negotiated |= 2;
237         receieveFinished();
238         debug("got Finished");
239     }
240     
241     public State getSessionState() {
242         if((negotiated&3)!=3 || !closed || warnings != 0) return null;
243         return new State(sessionID,masterSecret);
244     }
245     public boolean isActive() { return !closed; }
246     public boolean isNegotiated() { return (negotiated&3) == 3; }
247     
248     private void sendClientHello(byte[] sessionID) throws IOException {
249         if(sessionID != null && sessionID.length > 256) throw new IllegalArgumentException("sessionID");
250         // 2 = version, 32 = randomvalue, 1 = sessionID size, 2 = cipher list size, 4 = the two ciphers,
251         // 2 = compression length/no compression
252         int p = 0;
253         byte[] buf = new byte[2+32+1+(sessionID == null ? 0 : sessionID.length)+2+2+4];
254         buf[p++] = 0x03; // major version
255         buf[p++] = tls ? (byte)0x01 : (byte)0x00;
256         
257         clientRandom = new byte[32];
258         int now = (int)(System.currentTimeMillis() / 1000L);
259         new Random().nextBytes(clientRandom);
260         clientRandom[0] = (byte)(now>>>24);
261         clientRandom[1] = (byte)(now>>>16);
262         clientRandom[2] = (byte)(now>>>8);
263         clientRandom[3] = (byte)(now>>>0);
264         System.arraycopy(clientRandom,0,buf,p,32);
265         p += 32;
266         
267         buf[p++] = sessionID != null ? (byte)sessionID.length : 0;
268         if(sessionID != null && sessionID.length != 0) System.arraycopy(sessionID,0,buf,p,sessionID.length);
269         p += sessionID != null ? sessionID.length : 0;
270         buf[p++] = 0x00; // 4 bytes of ciphers
271         buf[p++] = 0x04;
272         buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_SHA
273         buf[p++] = 0x05;
274         buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_MD5
275         buf[p++] = 0x04; 
276         
277         buf[p++] = 0x01;
278         buf[p++] = 0x00;
279                 
280         sendHandshake((byte)1,buf);
281         flush();
282     }
283     
284     private void receiveServerHello() throws IOException {
285         // ServerHello
286         byte[] buf = readHandshake();
287         if(buf[0] != 2) throw new Exn("expected a ServerHello message");
288         
289         if(buf.length < 6 + 32 + 1) throw new Exn("ServerHello too small");
290         if(buf.length < 6 + 32 + 1 + buf[6+32] + 3) throw new Exn("ServerHello too small " + buf.length+" "+buf[6+32]); 
291         
292         if(buf[4] != 0x03 || !(buf[5]==0x00 || buf[5]==0x01)) throw new Exn("server wants to use version " + buf[4] + "." + buf[5]);
293         tls = buf[5] == 0x01;
294         int p = 6;
295         serverRandom = new byte[32];
296         System.arraycopy(buf,p,serverRandom,0,32);
297         p += 32;
298         sessionID = new byte[buf[p++]&0xff];
299         if(sessionID.length != 0) System.arraycopy(buf,p,sessionID,0,sessionID.length);
300         p += sessionID.length;
301         int cipher = ((buf[p]&0xff)<<8) | (buf[p+1]&0xff);
302         p += 2;
303         switch(cipher) {
304             case 0x0004: sha = false; debug("Using SSL_RSA_WITH_RC4_128_MD5"); break;
305             case 0x0005: sha = true;  debug("Using SSL_RSA_WITH_RC4_128_SHA"); break;
306             default: throw new Exn("Unsupported cipher " + cipher);
307         }
308         mac = new byte[sha ? 20 : 16];
309         if(buf[p++] != 0x0) throw new Exn("unsupported compression " + buf[p-1]);
310     }
311     
312     private X509Certificate[] receiveServerCertificates() throws IOException {
313         byte[] buf = readHandshake();
314         if(buf[0] != 11) throw new Exn("expected a Certificate message");
315         if((((buf[4]&0xff)<<16)|((buf[5]&0xff)<<8)|((buf[6]&0xff)<<0)) != buf.length-7) throw new Exn("size mismatch in Certificate message");
316         int p = 7;
317         int count = 0;
318         
319         for(int i=p;i<buf.length-3;i+=((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0)) count++;
320         if(count == 0) throw new Exn("server didn't provide any certificates");
321         X509Certificate[] certs = new X509Certificate[count];
322         count = 0;
323         while(p < buf.length) {
324             int len = ((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0);
325             p += 3;
326             if(p + len > buf.length) throw new Exn("Certificate message cut short");
327             certs[count++] = new X509Certificate(new ByteArrayInputStream(buf,p,len));
328             p += len;
329         }
330         return certs;
331     }
332     
333     private void sendClientKeyExchange(X509Certificate serverCert) throws IOException {
334         byte[] encryptedPreMasterSecret;
335         RSAPublicKey pks = serverCert.getRSAPublicKey();
336         PKCS1 pkcs1 = new PKCS1(new RSA(pks.modulus,pks.exponent,false),random);
337         encryptedPreMasterSecret = pkcs1.encode(preMasterSecret);
338         byte[] buf;
339         if(tls) {
340             buf = new byte[encryptedPreMasterSecret.length+2];
341             buf[0] = (byte) (encryptedPreMasterSecret.length>>>8);
342             buf[1] = (byte) (encryptedPreMasterSecret.length>>>0);
343             System.arraycopy(encryptedPreMasterSecret,0,buf,2,encryptedPreMasterSecret.length);
344         } else {
345             // ugh... netscape didn't send the length bytes and now every SSLv3 implementation
346             // must implement this bug
347             buf = encryptedPreMasterSecret;
348         }
349         sendHandshake((byte)16,buf);
350     }
351     
352     private void sendChangeCipherSpec() throws IOException {
353         sendRecord((byte)20,new byte[] { 0x01 });
354     }
355     
356     private void computeMasterSecret() {
357         preMasterSecret = new byte[48];
358         preMasterSecret[0] = 0x03; // version_high
359         preMasterSecret[1] = tls ? (byte) 0x01 : (byte) 0x00; // version_low
360         randomBytes(preMasterSecret,2,46);
361         
362         if(tls) {
363             masterSecret = tlsPRF(48,preMasterSecret,getBytes("master secret"),concat(clientRandom,serverRandom));
364         } else {
365             masterSecret = concat(new byte[][] {
366                     md5(new byte[][] { preMasterSecret,
367                             sha1(new byte[][] { new byte[] { 0x41 }, preMasterSecret, clientRandom, serverRandom })}),
368                             md5(new byte[][] { preMasterSecret,
369                                     sha1(new byte[][] { new byte[] { 0x42, 0x42 }, preMasterSecret, clientRandom, serverRandom })}),
370                                     md5(new byte[][] { preMasterSecret,
371                                             sha1(new byte[][] { new byte[] { 0x43, 0x43, 0x43 }, preMasterSecret, clientRandom, serverRandom })})
372             } );    
373         }
374     }
375     
376     public void initCrypto() {
377         byte[] keyMaterial;
378         
379         if(tls) {
380             keyMaterial = tlsPRF(
381                     (mac.length + 16 + 0)*2, // MAC len + key len + iv len
382                     masterSecret,
383                     getBytes("key expansion"),
384                     concat(serverRandom,clientRandom)
385             );
386         } else {
387             keyMaterial = new byte[] { };
388             for(int i=0; keyMaterial.length < 72; i++) {
389                 byte[] crap = new byte[i + 1];
390                 for(int j=0; j<crap.length; j++) crap[j] = (byte)(((byte)0x41) + ((byte)i));
391                 keyMaterial = concat(new byte[][] { keyMaterial,
392                         md5(new byte[][] { masterSecret,
393                                 sha1(new byte[][] { crap, masterSecret, serverRandom, clientRandom }) }) });
394             }            
395         }
396
397         byte[] clientWriteMACSecret = new byte[mac.length];
398         byte[] serverWriteMACSecret = new byte[mac.length];
399         byte[] clientWriteKey = new byte[16];
400         byte[] serverWriteKey = new byte[16];
401         
402         int p = 0;
403         System.arraycopy(keyMaterial, p, clientWriteMACSecret, 0, mac.length); p += mac.length;
404         System.arraycopy(keyMaterial, p, serverWriteMACSecret, 0, mac.length); p += mac.length;
405         System.arraycopy(keyMaterial, p, clientWriteKey, 0, 16); p += 16; 
406         System.arraycopy(keyMaterial, p, serverWriteKey, 0, 16); p += 16;
407         
408         Digest inner;
409         
410         writeRC4 = new RC4(clientWriteKey);
411         inner = sha ? (Digest)new SHA1() : (Digest)new MD5();
412         clientWriteMACDigest = tls ? (Digest) new HMAC(inner,clientWriteMACSecret) : (Digest)new SSLv3HMAC(inner,clientWriteMACSecret);
413         
414         readRC4 = new RC4(serverWriteKey);
415         inner = sha ? (Digest)new SHA1() : (Digest)new MD5();
416         serverWriteMACDigest = tls ? (Digest)new HMAC(inner,serverWriteMACSecret) : (Digest)new SSLv3HMAC(inner,serverWriteMACSecret);
417     }
418     
419     private void sendFinished() throws IOException {
420         byte[] handshakes = handshakesBuffer.toByteArray();
421         if(tls) {
422             sendHandshake((byte)20, tlsPRF(
423                     12,
424                     masterSecret,
425                     getBytes("client finished"),
426                     concat(md5(handshakes),sha1(handshakes))));
427             
428         } else {
429             sendHandshake((byte)20, concat(new byte[][] { 
430                     md5(new byte[][] { masterSecret, pad2, 
431                                        md5(new byte[][] { handshakes, new byte[] { (byte)0x43, (byte)0x4C, (byte)0x4E, (byte)0x54 },
432                                                           masterSecret, pad1 }) }),
433                     sha1(new byte[][] { masterSecret, pad2_sha,
434                                        sha1(new byte[][] { handshakes, new byte[] { (byte)0x43, (byte)0x4C, (byte)0x4E, (byte)0x54 },
435                                                           masterSecret, pad1_sha } ) })
436                 }));
437         }
438     }
439         
440     private void receiveChangeCipherSpec() throws IOException {    
441         int size = readRecord((byte)20);
442         if(size == -1) throw new Exn("got eof when expecting a ChangeCipherSpec message");
443         if(size != 1 || readRecordBuf[0] != 0x01) throw new Exn("Invalid ChangeCipherSpec message");
444     }
445     
446     private void receieveFinished() throws IOException {
447         byte[] handshakes = handshakesBuffer.toByteArray();
448         byte[] buf = readHandshake();
449         if(buf[0] != 20) throw new Exn("expected a Finished message");
450         byte[] expected;
451         
452         if(tls) {
453             if(buf.length != 4 + 12) throw new Exn("Finished message too short");
454             expected = tlsPRF(
455                     12,masterSecret,
456                     getBytes("server finished"),
457                     concat(md5(handshakes),sha1(handshakes)));
458         } else {
459             if(buf.length != 4 + 16 +20) throw new Exn("Finished message too short");
460             expected = concat(new byte[][] {
461                     md5(new byte[][] { masterSecret, pad2,
462                             md5(new byte[][] { handshakes, new byte[] { (byte)0x53, (byte)0x52, (byte)0x56, (byte)0x52 },
463                                     masterSecret, pad1 }) }),
464                                     sha1(new byte[][] { masterSecret, pad2_sha,
465                                             sha1(new byte[][] { handshakes, new byte[] { (byte)0x53, (byte)0x52, (byte)0x56, (byte)0x52 },
466                                                     masterSecret, pad1_sha } ) } ) } );
467         }
468         if(!eq(expected,0,buf,4,expected.length)) throw new Exn("server finished message mismatch");
469     }
470     
471     private void flush() throws IOException { rawOS.flush(); }
472
473     private void sendHandshake(byte type, byte[] payload) throws IOException {
474         if(payload.length > (1<<24)) throw new IllegalArgumentException("payload.length");
475         byte[] buf = new byte[4+payload.length];
476         buf[0] = type;
477         buf[1] = (byte)(payload.length>>>16);
478         buf[2] = (byte)(payload.length>>>8);
479         buf[3] = (byte)(payload.length>>>0);
480         System.arraycopy(payload,0,buf,4,payload.length);
481         handshakesBuffer.write(buf);
482         sendRecord((byte)22,buf);
483     }
484     
485     private void sendRecord(byte proto, byte[] buf) throws IOException { sendRecord(proto,buf,0,buf.length); }
486     private void sendRecord(byte proto, byte[] payload, int off, int totalLen) throws IOException {
487         int macLength = (negotiated & 1) != 0 ? mac.length : 0;
488         while(totalLen > 0) {
489             int len = min(totalLen,16384-macLength);
490             rawOS.writeByte(proto);
491             rawOS.writeShort(tls ? 0x0301 : 0x0300);
492             if((negotiated & 1) != 0) {
493                 computeMAC(proto,payload,off,len,clientWriteMACDigest,clientSequenceNumber);
494                 // FEATURE: Encode in place
495                 writeRC4.process(payload,off,sendRecordBuf,0,len);
496                 writeRC4.process(mac,0,sendRecordBuf,len,macLength);
497                 rawOS.writeShort(len + macLength);
498                 rawOS.write(sendRecordBuf,0, len +macLength);
499                 clientSequenceNumber++;
500             } else {
501                 rawOS.writeShort(len);
502                 rawOS.write(payload,off,len);
503             }
504             totalLen -= len;
505             off += len;
506         }
507     }
508     
509     private byte[] readHandshake() throws IOException {
510         if(handshakeDataLength == 0) {
511             handshakeDataStart = 0;
512             handshakeDataLength = readRecord((byte)22);
513             if(handshakeDataLength == -1) throw new Exn("got eof when expecting a handshake packet");
514         }
515         byte[] buf = readRecordBuf;
516         int len = ((buf[handshakeDataStart+1]&0xff)<<16)|((buf[handshakeDataStart+2]&0xff)<<8)|((buf[handshakeDataStart+3]&0xff)<<0);
517         // Handshake messages can theoretically span multiple records, but in practice this does not occur
518         if(len > handshakeDataLength) {
519             sendAlert(true,10); // 10 == unexpected message
520             throw new Exn("handshake message size too large " + len + " vs " + (handshakeDataLength-handshakeDataStart));
521         }
522         byte[] ret = new byte[4+len];
523         System.arraycopy(buf,handshakeDataStart,ret,0,ret.length);
524         handshakeDataLength -= ret.length;
525         handshakeDataStart += ret.length;
526         handshakesBuffer.write(ret);
527         return ret;
528     }
529     
530     private int readRecord(byte reqProto) throws IOException {
531         int macLength = (negotiated & 2) != 0 ? mac.length : 0;
532         for(;;) {
533             byte proto;
534             int version, len;
535             
536             try {
537                 proto = rawIS.readByte();
538             } catch(EOFException e) {
539                 // this may or may not be an error. it is up to the application protocol
540                 closed = true;
541                 super.close();
542                 throw new PrematureCloseExn();
543             }
544             try {
545                 version = rawIS.readShort();
546                 if(version != 0x0300 && version != 0x0301) throw new Exn("invalid version ");
547                 len = rawIS.readShort();
548                 if(len <= 0 || len > 16384+((negotiated&2)!=0 ? macLength : 0)) throw new Exn("invalid length " + len);
549                 rawIS.readFully((negotiated&2)!=0 ? readRecordScratch : readRecordBuf,0,len);
550             } catch(EOFException e) {
551                 // an EOF here is always an error (we don't pass the EOF back on to the app
552                 // because it isn't a "legitimate" eof)
553                 throw new Exn("Hit EOF too early");
554             }
555             
556             if((negotiated & 2) != 0) {
557                 if(len < macLength) throw new Exn("packet size < macLength");
558                 // FEATURE: Decode in place
559                 readRC4.process(readRecordScratch,0,readRecordBuf,0,len);
560                 computeMAC(proto,readRecordBuf,0,len-macLength,serverWriteMACDigest,serverSequenceNumber);
561                 for(int i=0;i<macLength;i++)
562                     if(mac[i] != readRecordBuf[len-macLength+i])
563                         throw new Exn("mac mismatch");
564                 len -= macLength;
565                 serverSequenceNumber++;
566             }
567             
568             if(proto == reqProto) return len;
569             
570             switch(proto) {
571                 case 21: { // ALERT
572                     if(len != 2) throw new Exn("invalid lengh for alert");
573                     int level = readRecordBuf[0];
574                     int desc = readRecordBuf[1];
575                     if(level == 1) {
576                         if(desc == 0) { // CloseNotify
577                             debug("Server requested connection closure");
578                             try {
579                                 sendCloseNotify();
580                             } catch(SocketException e) { /* incomplete close, thats ok */ }
581                             closed = true;
582                             super.close();
583                             return -1;
584                         } else {
585                             warnings++;
586                             log("SSL ALERT WARNING: desc: " + desc);
587                         }
588                     } else if(level == 2) {
589                         throw new Exn("SSL ALERT FATAL: desc: " +desc);
590                     } else {
591                         throw new Exn("invalid alert level");
592                     }
593                     break;
594                 }
595                 case 22: { // Handshake
596                     int type = readRecordBuf[0];
597                     int hslen = ((readRecordBuf[1]&0xff)<<16)|((readRecordBuf[2]&0xff)<<8)|((readRecordBuf[3]&0xff)<<0);
598                     if(hslen > len - 4) throw new Exn("Multiple sequential handshake messages received after negotiation");
599                     if(type == 0) { // HellloRequest
600                         if(tls) sendAlert(false,100); // politely refuse, 100 == NoRegnegotiation
601                     } else {
602                         throw new Exn("Unexpected Handshake type: " + type);
603                     }
604                 }
605                 default: throw new Exn("Unexpected protocol: " + proto);
606             }
607         }
608     }
609     
610     private static void longToBytes(long l, byte[] buf, int off) {
611         for(int i=0;i<8;i++) buf[off+i] = (byte)(l>>>(8*(7-i)));
612     }
613     private void computeMAC(byte proto, byte[] payload, int off, int len, Digest digest, long sequenceNumber) {
614         if(tls) {
615             longToBytes(sequenceNumber,mac,0);
616             mac[8] = proto;
617             mac[9] = 0x03; // version
618             mac[10] = 0x01;
619             mac[11] = (byte)(len>>>8);
620             mac[12] = (byte)(len>>>0);
621             
622             digest.update(mac,0,13);
623             digest.update(payload,off,len);
624             digest.doFinal(mac,0);
625         } else {
626             longToBytes(sequenceNumber, mac, 0);
627             mac[8] = proto;
628             mac[9] = (byte)(len>>>8);
629             mac[10] = (byte)(len>>>0);
630             
631             digest.update(mac, 0, 11);
632             digest.update(payload, off, len);
633             digest.doFinal(mac, 0);
634         }
635     }
636     
637     private void sendCloseNotify() throws IOException { sendRecord((byte)21, new byte[] { 0x01, 0x00 }); }
638     private void sendAlert(boolean fatal, int message) throws IOException {
639         byte[] buf = new byte[] { fatal ? (byte)2 :(byte)1, (byte)message };
640         sendRecord((byte)21,buf);
641         flush();
642     }
643     
644     //
645     // Hash functions
646     //
647     
648     // Shared digest objects
649     private MD5 masterMD5 = new MD5();
650     private SHA1 masterSHA1 = new SHA1();
651     
652     private byte[] md5(byte[] in) { return md5( new byte[][] { in }); }
653     private byte[] md5(byte[][] inputs) {
654         masterMD5.reset();
655         for(int i=0; i<inputs.length; i++) masterMD5.update(inputs[i], 0, inputs[i].length);
656         byte[] ret = new byte[masterMD5.getDigestSize()];
657         masterMD5.doFinal(ret, 0);
658         return ret;
659     }
660     
661     private byte[] sha1(byte[] in)  { return sha1(new byte[][] { in }); }
662     private byte[] sha1(byte[][] inputs) {
663         masterSHA1.reset();
664         for(int i=0; i<inputs.length; i++) masterSHA1.update(inputs[i], 0, inputs[i].length);
665         byte[] ret = new byte[masterSHA1.getDigestSize()];
666         masterSHA1.doFinal(ret, 0);
667         return ret;
668     }
669     
670     /*  RFC-2246
671      PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
672      L_S = length in bytes of secret;
673      L_S1 = L_S2 = ceil(L_S / 2);
674      
675      The secret is partitioned into two halves (with the possibility of
676      one shared byte) as described above, S1 taking the first L_S1 bytes
677      and S2 the last L_S2 bytes.
678      
679      P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
680      HMAC_hash(secret, A(2) + seed) +
681      HMAC_hash(secret, A(3) + seed) + ...
682      
683      A(0) = seed
684      A(i) = HMAC_hash(secret, A(i-1))
685      */           
686     private byte[] tlsPRF(int size,byte[] secret, byte[] label, byte[] seed) {
687         if(size > 112) throw new IllegalArgumentException("size > 112");
688         seed = concat(label,seed);
689         
690         int half_length = (secret.length + 1) / 2;
691         byte[] s1 = new byte[half_length];
692         System.arraycopy(secret,0,s1,0,half_length);
693         byte[] s2 = new byte[half_length];
694         System.arraycopy(secret,secret.length - half_length, s2, 0, half_length);
695
696         Digest hmac_md5 = new HMAC(new MD5(),s1);
697         Digest hmac_sha = new HMAC(new SHA1(),s2);
698         
699         byte[] md5out = new byte[112];
700         byte[] shaout = new byte[120];
701         byte[] digest = new byte[20];
702         int n;
703         
704         n = 0;
705         hmac_md5.update(seed,0,seed.length);
706         hmac_md5.doFinal(digest,0);
707         
708         // digest == md5_a_1
709         while(n < size) {
710             hmac_md5.update(digest,0,16);
711             hmac_md5.update(seed,0,seed.length);
712             hmac_md5.doFinal(md5out,n);
713             hmac_md5.update(digest,0,16);
714             hmac_md5.doFinal(digest,0);
715             n += 16;
716         }
717         
718         n = 0;
719         hmac_sha.update(seed,0,seed.length);
720         hmac_sha.doFinal(digest,0);
721         
722         while(n < size) {
723             hmac_sha.update(digest,0,20);
724             hmac_sha.update(seed,0,seed.length);
725             hmac_sha.doFinal(shaout,n);
726             hmac_sha.update(digest,0,20);
727             hmac_sha.doFinal(digest,0);
728             n += 20;
729          }
730             
731         byte[] ret = new byte[size];
732         for(int i=0;i<size;i++) ret[i] = (byte)(md5out[i] ^ shaout[i]);
733         return ret;
734     }
735
736     public static class SSLv3HMAC implements Digest {
737         private final Digest h;
738         private final byte[] digest;
739         private final byte[] key;
740         private final int padSize;
741         
742         public int getDigestSize() { return h.getDigestSize(); }
743         
744         public SSLv3HMAC(Digest h, byte[] key) {
745             this.h = h;
746             this.key = key;
747             switch(h.getDigestSize()) {
748                 case 16: padSize = 48; break;
749                 case 20: padSize = 40; break;
750                 default: throw new IllegalArgumentException("unsupported digest size");
751             }
752             digest = new byte[h.getDigestSize()];
753             reset();
754         }
755         public void reset() {
756             h.reset();
757             h.update(key,0,key.length);
758             h.update(pad1,0,padSize);
759         }
760         public void update(byte[] b, int off, int len) { h.update(b,off,len); }
761         public void doFinal(byte[] out, int off){
762             h.doFinal(digest,0);
763             h.update(key,0,key.length);
764             h.update(pad2,0,padSize);
765             h.update(digest,0,digest.length);
766             h.doFinal(out,off);
767             reset();
768         }
769     }
770     
771     //
772     // Static Methods
773     //
774     
775     private static SecureRandom random = new SecureRandom();
776     public static synchronized void randomBytes(byte[] buf, int off, int len) {
777         byte[] bytes =  new byte[len];
778         random.nextBytes(bytes);
779         System.arraycopy(bytes,0,buf,off,len);
780     }
781     
782     public static byte[] concat(byte[] a, byte[] b) { return concat(new byte[][] { a, b }); }
783     public static byte[] concat(byte[] a, byte[] b, byte[] c) { return concat(new byte[][] { a, b, c }); }
784     public static byte[] concat(byte[][] inputs) {
785         int total = 0;
786         for(int i=0; i<inputs.length; i++) total += inputs[i].length;
787         byte[] ret = new byte[total];
788         for(int i=0,pos=0; i<inputs.length;pos+=inputs[i].length,i++)
789             System.arraycopy(inputs[i], 0, ret, pos, inputs[i].length);
790         return ret;
791     }
792     
793     public static byte[] getBytes(String s) {
794         try {
795             return s.getBytes("US-ASCII");
796         } catch (UnsupportedEncodingException e) {
797             return null; // will never happen
798         }
799     }
800     
801     public static boolean eq(byte[] a, int aoff, byte[] b, int boff, int len){
802         for(int i=0;i<len;i++) if(a[aoff+i] != b[boff+i]) return false;
803         return true;
804     }
805     
806     //
807     // InputStream/OutputStream/Socket interfaces
808     //
809     public OutputStream getOutputStream() { return sslOS; }
810     public InputStream getInputStream() { return sslIS; }
811     public synchronized void close() throws IOException {
812         if(!closed) {
813             if(negotiated != 0) {
814                 sendCloseNotify();
815                 flush();
816                 // don't bother sending a close_notify back to the server 
817                 // this is an incomplete close which is allowed by the spec
818             }
819             super.close();
820             closed = true;
821         }
822     }
823     
824     private int read(byte[] buf, int off, int len) throws IOException {
825         if(pendingLength == 0) {
826             if(closed) return -1;
827             int readLen = readRecord((byte)23);
828             if(readLen == -1) return -1; // EOF
829             len = min(len,readLen);
830             System.arraycopy(readRecordBuf,0,buf,off,len);
831             if(readLen > len) System.arraycopy(readRecordBuf,len,pending,0,readLen-len);
832             pendingStart = 0;
833             pendingLength = readLen - len;
834             return len;
835         } else {
836             len = min(len,pendingLength);
837             System.arraycopy(pending,pendingStart,buf,off,len);
838             pendingLength -= len;
839             pendingStart += len;
840             return len;
841         }
842     }
843     
844     private void write(byte[] buf, int off, int len) throws IOException {
845         if(closed) throw new SocketException("Socket closed");
846         sendRecord((byte)23,buf,off,len);
847         flush();
848     }
849     
850     private class SSLInputStream extends InputStream {
851         public int available() throws IOException {
852             synchronized(SSL.this) {
853                 return negotiated != 0 ? pendingLength : rawIS.available();
854             }
855         }
856         public int read() throws IOException {
857             synchronized(SSL.this) {
858                 if(negotiated==0) return rawIS.read();
859                 if(pendingLength > 0) {
860                     pendingLength--;
861                     return pending[pendingStart++];
862                 } else {
863                     byte[] buf = new byte[1];
864                     int n = read(buf);
865                     return n == -1 ? -1 : buf[0]&0xff;
866                 }
867             }
868         }
869         public int read(byte[] buf, int off, int len) throws IOException {
870             synchronized(SSL.this) {
871                 return negotiated!=0 ? SSL.this.read(buf,off,len) : rawIS.read(buf,off,len);
872             }
873         }
874         public long skip(long n) throws IOException {
875             synchronized(SSL.this) {
876                 if(negotiated==0) return rawIS.skip(n);
877                 if(pendingLength > 0) {
878                     n = min((int)n,pendingLength);
879                     pendingLength -= n;
880                     pendingStart += n;
881                     return n;
882                 }
883                 return super.skip(n);
884             }
885         }
886     }
887     
888     private class SSLOutputStream extends OutputStream {
889         public void flush() throws IOException { rawOS.flush(); }
890         public void write(int b) throws IOException { write(new byte[] { (byte)b }); }
891         public void write(byte[] buf, int off, int len) throws IOException {
892             synchronized(SSL.this) {
893                 if(negotiated!=0)
894                     SSL.this.write(buf,off,len);
895                 else
896                     rawOS.write(buf,off,len);
897             }
898         }
899     }
900     
901     public static class Exn extends IOException { public Exn(String s) { super(s); } }
902     public static class PrematureCloseExn extends Exn {
903         public PrematureCloseExn() { super("Connection was closed by the remote WITHOUT a close_noify"); }
904     }
905     
906     public static boolean debugOn = false;
907     private static void debug(Object o) { if(debugOn) System.err.println("[BriSSL-Debug] " + o.toString()); }
908     private static void log(Object o) { System.err.println("[BriSSL] " + o.toString()); }
909             
910     private static void verifyCerts(X509Certificate[] certs) throws DER.Exception, Exn {
911         try {
912             verifyCerts_(certs);
913         } catch(RuntimeException e) {
914             e.printStackTrace();
915             throw new Exn("Error while verifying certificates: " + e);
916         }
917     }
918     
919     private static void verifyCerts_(X509Certificate[] certs) throws DER.Exception, Exn {
920         boolean ignoreLast = false;
921         for(int i=0;i<certs.length;i++) {
922             debug("Cert " + i + ": " + certs[i].subject + " ok");
923             if(!certs[i].isValid())
924                 throw new Exn("Certificate " + i + " in certificate chain is not valid (" + certs[i].startDate + " - " + certs[i].endDate + ")");
925             if(i != 0) {
926                 X509Certificate.BC bc = certs[i].basicContraints;
927                 if(bc == null) {
928                     if(i == certs.length - 1) {
929                         ignoreLast = true;
930                         break;
931                     }
932                     throw new Exn("CA-cert lacks Basic Constraints");
933                 } else {
934                     if(!bc.isCA) throw new Exn("non-CA certificate used for signing");
935                     if(bc.pathLenConstraint != null && bc.pathLenConstraint.longValue() < i-1) throw new Exn("CA cert can't be used this deep");
936                 }
937             }
938             if(i != certs.length - 1) {
939                 if(!certs[i].issuer.equals(certs[i+1].subject))
940                     throw new Exn("Issuer for certificate " + i + " does not match next in chain");
941                 if(!certs[i].isSignedBy(certs[i+1]))
942                     throw new Exn("Certificate " + i + " in chain is not signed by the next certificate");
943             }
944         }
945         
946         X509Certificate cert = certs[ignoreLast ? certs.length - 2 : certs.length-1];
947         
948         RSAPublicKey pks = (RSAPublicKey) caKeys.get(cert.issuer);
949         if(pks == null) throw new Exn("Certificate is signed by an unknown CA (" + cert.issuer + ")");
950         if(!cert.isSignedWith(pks)) throw new Exn("Certificate is not signed by its CA");
951         log("" + cert.subject + " is signed by " + cert.issuer);
952     }
953     
954     public static void addCACert(byte[] b) throws IOException { addCACert(new ByteArrayInputStream(b)); }
955     public static void addCACert(InputStream is) throws IOException { addCACert(new X509Certificate(is)); }
956     public static void addCACert(X509Certificate cert) throws DER.Exception { addCAKey(cert.subject,cert.getRSAPublicKey()); }
957     public static void addCAKey(X509Name subject, RSAPublicKey pks)  {
958         synchronized(caKeys) {
959             if(caKeys.get(subject) != null)
960                 throw new IllegalArgumentException(subject.toString() + " already exists!");
961             caKeys.put(subject,pks);
962         }
963     }
964     
965     static {
966         try {
967             // This will force a <clinit> which'll load the certs
968             Class.forName("org.ibex.net.ssl.RootCerts");
969             log("Loaded root keys from org.ibex.net.ssl.RootCerts");
970         } catch(ClassNotFoundException e) {
971             InputStream is = SSL.class.getClassLoader().getResourceAsStream("org.ibex/net/ssl/rootcerts.dat");
972             if(is != null) {
973                 try {
974                     addCompactCAKeys(is);
975                     log("Loaded root certs from rootcerts.dat");
976                 } catch(IOException e2) {
977                     log("Error loading certs from rootcerts.dat: " + e2.getMessage()); 
978                 }
979             }
980         }
981     }
982         
983     public static int addCompactCAKeys(InputStream is) throws IOException {
984         synchronized(caKeys) {
985             try {
986                 Vector seq = (Vector) new DER.InputStream(is).readObject();
987                 for(Enumeration e = seq.elements(); e.hasMoreElements();) {
988                     Vector seq2 = (Vector) e.nextElement();
989                     X509Name subject = new X509Name(seq2.elementAt(0));
990                     RSAPublicKey pks = new RSAPublicKey(seq2.elementAt(1));
991                     addCAKey(subject,pks);
992                 }
993                 return seq.size();
994             } catch(RuntimeException e) {
995                 e.printStackTrace();
996                 throw new IOException("error while reading stream: " + e);
997             }
998         }
999     }
1000     
1001     public static synchronized void setVerifyCallback(VerifyCallback cb) { verifyCallback = cb; }
1002     
1003     // State Info
1004     public static class State {
1005         byte[] sessionID;
1006         byte[] masterSecret;
1007         State(byte[] sessionID, byte[] masterSecret) {
1008             this.sessionID = sessionID;
1009             this.masterSecret = masterSecret;
1010         }
1011     }
1012     
1013     public interface VerifyCallback {
1014         public boolean checkCerts(X509Certificate[] certs, String hostname, Exn exn);
1015     }
1016     
1017     // Helper methods
1018     private static final int min(int a, int b) { return a < b ? a : b; }
1019 }