2 * org.ibex.net.SSL - By Brian Alliet
3 * Copyright (C) 2004 Brian Alliet
5 * Based on TinySSL by Adam Megacz
6 * Copyright (C) 2003 Adam Megacz <adam@xwt.org> all rights reserved.
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
16 import org.ibex.crypto.*;
17 import java.security.SecureRandom;
19 import java.net.Socket;
20 import java.net.SocketException;
23 import java.util.Enumeration;
24 import java.util.Hashtable;
25 import java.util.Random;
26 import java.util.Vector;
28 // FEATURE: Server socket
30 public class SSL extends Socket {
31 public static final byte TLS_RSA_WITH_AES_256_CBC_SHA = 0x35;
32 public static final byte TLS_RSA_WITH_AES_128_CBC_SHA = 0x2f;
33 public static final byte SSL_RSA_WITH_RC4_128_SHA = 0x05;
34 public static final byte SSL_RSA_WITH_RC4_128_MD5 = 0x04;
36 private static final byte[] DEFAULT_CIPHER_PREFS = new byte[]{
37 TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,
38 SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5
41 private String hostname;
43 private int negotiated;
45 private boolean tls = true;
49 private final DataInputStream rawIS;
50 private final DataOutputStream rawOS;
52 private final InputStream sslIS;
53 private final OutputStream sslOS;
55 private byte[] sessionID;
57 private Digest clientWriteMACDigest;
58 private Digest serverWriteMACDigest;
59 private byte[] masterSecret;
61 private Cipher writeCipher;
62 private Cipher readCipher;
64 private long serverSequenceNumber;
65 private long clientSequenceNumber;
68 private boolean closed;
70 // These are only used during negotiation
71 private byte[] serverRandom;
72 private byte[] clientRandom;
73 private byte[] preMasterSecret;
78 private byte[] pending = new byte[16384];
79 private int pendingStart;
80 private int pendingLength;
82 private byte[] sendRecordBuf = new byte[16384];
84 private int handshakeDataStart;
85 private int handshakeDataLength;
86 private byte[] readRecordBuf = new byte[16384+20]; // 20 == sizeof(sha1 hash)
87 private byte[] readRecordScratch = new byte[16384+20];
89 // These are only uses for CBCs
90 private int blockSize; // block size (0 for stream ciphers)
91 private byte[] padBuf;
92 private byte[] prevBlock;
94 private ByteArrayOutputStream handshakesBuffer;
99 private final static byte[] pad1 = new byte[48];
100 private final static byte[] pad2 = new byte[48];
101 private final static byte[] pad1_sha = new byte[40];
102 private final static byte[] pad2_sha = new byte[40];
105 for(int i=0; i<pad1.length; i++) pad1[i] = (byte)0x36;
106 for(int i=0; i<pad2.length; i++) pad2[i] = (byte)0x5C;
107 for(int i=0; i<pad1_sha.length; i++) pad1_sha[i] = (byte)0x36;
108 for(int i=0; i<pad2_sha.length; i++) pad2_sha[i] = (byte)0x5C;
111 private final static Hashtable caKeys = new Hashtable();
112 private static VerifyCallback verifyCallback;
117 public SSL(String host) throws IOException { this(host,443); }
118 public SSL(String host, int port) throws IOException { this(host,port,true); }
119 public SSL(String host, int port, boolean negotiate) throws IOException { this(host,port,negotiate,null); }
120 public SSL(String host, int port, State state) throws IOException { this(host,port,true,state); }
121 public SSL(String host, int port, boolean negotiate, State state) throws IOException {
124 rawIS = new DataInputStream(new BufferedInputStream(super.getInputStream()));
125 rawOS = new DataOutputStream(new BufferedOutputStream(super.getOutputStream()));
126 sslIS = new SSLInputStream();
127 sslOS = new SSLOutputStream();
128 if(negotiate) negotiate(state);
131 public synchronized void setTLS(boolean b) { if(negotiated!=0) throw new IllegalStateException("already negotiated"); tls = b; }
133 public void negotiate() throws IOException { negotiate(null,null); }
134 public void negotiate(State state) throws IOException { negotiate(state,null); }
135 public void negotiate(byte[] cipherPrefs) throws IOException { negotiate(null,cipherPrefs); }
136 private void negotiate(State state, byte[] cipherPrefs) throws IOException {
137 if(negotiated != 0) throw new IllegalStateException("already negotiated");
139 handshakesBuffer = new ByteArrayOutputStream();
142 sendClientHello(state != null ? state.sessionID : null, cipherPrefs);
144 debug("sent ClientHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
146 receiveServerHello();
147 debug("got ServerHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
150 state != null && sessionID.length == state.sessionID.length &&
151 eq(state.sessionID,0,sessionID,0,sessionID.length);
154 negotiateResume(state);
158 // we're done with these now
159 clientRandom = serverRandom = preMasterSecret = null;
160 handshakesBuffer = null;
162 log("Negotiation with " + hostname + " complete (" + (tls?"TLSv1.0":"SSLv3.0")+")");
164 if((negotiated & 3) != 3) {
166 try { super.close(); } catch(IOException e) { /* ignore */ }
172 private void negotiateResume(State state) throws IOException {
173 masterSecret = state.masterSecret;
176 log("initializec crypto");
178 receiveChangeCipherSpec();
179 debug("Received ChangeCipherSpec");
182 debug("Received Finished");
184 sendChangeCipherSpec();
185 debug("Sent ChangeCipherSpec");
188 debug("Sent Finished");
191 private void negotiateNew() throws IOException {
192 X509.Certificate[] certs = receiveServerCertificates();
193 debug("got Certificate");
195 boolean gotCertificateRequest = false;
197 byte[] buf = readHandshake();
199 case 14: // ServerHelloDone
200 if(buf.length != 4) throw new Exn("ServerHelloDone contained trailing garbage");
201 debug("got ServerHelloDone");
203 case 13: // CertificateRequest
204 debug("Got a CertificateRequest message but we don't suport client certificates");
205 gotCertificateRequest = true;
208 throw new Exn("unknown handshake type " + buf[0]);
212 if(gotCertificateRequest)
213 sendHandshake((byte)11,new byte[3]); // send empty cert list
216 if(!hostname.equalsIgnoreCase(certs[0].getCN()))
217 throw new Exn("Certificate is for " + certs[0].getCN() + " not " + hostname);
220 if(verifyCallback == null) throw e;
221 synchronized(SSL.class) {
222 if(!verifyCallback.checkCerts(certs,hostname,e)) throw e;
226 computeMasterSecret();
228 sendClientKeyExchange(certs[0]);
229 debug("sent ClientKeyExchange");
233 sendChangeCipherSpec();
234 debug("sent ChangeCipherSpec");
237 debug("sent Finished");
240 receiveChangeCipherSpec();
241 debug("got ChangeCipherSpec");
244 debug("got Finished");
247 public State getSessionState() {
248 if((negotiated&3)!=3 || !closed || warnings != 0) return null;
249 return new State(sessionID,masterSecret);
251 public boolean isActive() { return !closed; }
252 public boolean isNegotiated() { return (negotiated&3) == 3; }
254 private void sendClientHello(byte[] sessionID, byte[] cipherPrefs) throws IOException {
255 if(sessionID != null && sessionID.length > 256) throw new IllegalArgumentException("sessionID");
256 if(cipherPrefs == null) cipherPrefs = DEFAULT_CIPHER_PREFS;
257 else if(cipherPrefs.length > 4) throw new IllegalArgumentException("too many cipherPrefs");
258 // 2 = version, 32 = randomvalue, 1 = sessionID size, 2 = cipher list size, 8 = the four ciphers,
259 // 2 = compression length/no compression
261 byte[] buf = new byte[2+32+1+(sessionID == null ? 0 : sessionID.length)+2+(cipherPrefs.length * 2)+2];
262 buf[p++] = 0x03; // major version
263 buf[p++] = tls ? (byte)0x01 : (byte)0x00;
265 clientRandom = new byte[32];
266 int now = (int)(System.currentTimeMillis() / 1000L);
267 new Random().nextBytes(clientRandom);
268 clientRandom[0] = (byte)(now>>>24);
269 clientRandom[1] = (byte)(now>>>16);
270 clientRandom[2] = (byte)(now>>>8);
271 clientRandom[3] = (byte)(now>>>0);
272 System.arraycopy(clientRandom,0,buf,p,32);
275 buf[p++] = sessionID != null ? (byte)sessionID.length : 0;
276 if(sessionID != null && sessionID.length != 0) System.arraycopy(sessionID,0,buf,p,sessionID.length);
277 p += sessionID != null ? sessionID.length : 0;
279 buf[p++] = 0x00; // 8 bytes of ciphers
280 buf[p++] = (byte)(cipherPrefs.length * 2);
282 for(int i=0;i<cipherPrefs.length;i++) {
284 buf[p++] = cipherPrefs[i];
287 buf[p++] = 0x01; // compression length
288 buf[p++] = 0x00; // no compression
290 sendHandshake((byte)1,buf);
294 private void receiveServerHello() throws IOException {
296 byte[] buf = readHandshake();
297 if(buf[0] != 2) throw new Exn("expected a ServerHello message");
299 if(buf.length < 6 + 32 + 1) throw new Exn("ServerHello too small");
300 if(buf.length < 6 + 32 + 1 + buf[6+32] + 3) throw new Exn("ServerHello too small " + buf.length+" "+buf[6+32]);
302 if(buf[4] != 0x03 || !(buf[5]==0x00 || buf[5]==0x01)) throw new Exn("server wants to use version " + buf[4] + "." + buf[5]);
303 tls = buf[5] == 0x01;
305 serverRandom = new byte[32];
306 System.arraycopy(buf,p,serverRandom,0,32);
308 sessionID = new byte[buf[p++]&0xff];
309 if(sessionID.length != 0) System.arraycopy(buf,p,sessionID,0,sessionID.length);
310 p += sessionID.length;
311 int cipher = ((buf[p]&0xff)<<8) | (buf[p+1]&0xff);
313 if((cipher>>>8)!=0) throw new Exn("Unsupported cipher " + cipher);
314 switch(cipher&0xff) {
315 case SSL_RSA_WITH_RC4_128_MD5: sha = false; aes=0; debug("Using SSL_RSA_WITH_RC4_128_MD5"); break;
316 case SSL_RSA_WITH_RC4_128_SHA: sha = true; aes=0; debug("Using SSL_RSA_WITH_RC4_128_SHA"); break;
317 case TLS_RSA_WITH_AES_128_CBC_SHA: sha = true; aes = 128; debug("Using TLS_RSA_WITH_AES_128_CBC_SHA"); break;
318 case TLS_RSA_WITH_AES_256_CBC_SHA: sha = true; aes = 256; debug("Using TLS_RSA_WITH_AES_256_CBC_SHA"); break;
319 default: throw new Exn("Unsupported cipher " + cipher);
321 mac = new byte[sha ? 20 : 16];
322 if(buf[p++] != 0x0) throw new Exn("unsupported compression " + buf[p-1]);
325 private X509.Certificate[] receiveServerCertificates() throws IOException {
326 byte[] buf = readHandshake();
327 if(buf[0] != 11) throw new Exn("expected a Certificate message");
328 if((((buf[4]&0xff)<<16)|((buf[5]&0xff)<<8)|((buf[6]&0xff)<<0)) != buf.length-7) throw new Exn("size mismatch in Certificate message");
334 while(p < buf.length - 3) {
335 p += 3 + ((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0);
338 if(count == 0) throw new Exn("server didn't provide any certificates");
339 X509.Certificate[] certs = new X509.Certificate[count];
342 while(p < buf.length) {
343 int len = ((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0);
345 if(p + len > buf.length) throw new Exn("Certificate message cut short");
346 certs[count++] = new X509.Certificate(new ByteArrayInputStream(buf,p,len));
352 private void sendClientKeyExchange(X509.Certificate serverCert) throws IOException {
353 byte[] encryptedPreMasterSecret;
354 RSA.PublicKey pks = serverCert.getRSAPublicKey();
355 PKCS1 pkcs1 = new PKCS1(new RSA(pks.modulus,pks.exponent,false),random);
356 encryptedPreMasterSecret = pkcs1.encode(preMasterSecret);
359 buf = new byte[encryptedPreMasterSecret.length+2];
360 buf[0] = (byte) (encryptedPreMasterSecret.length>>>8);
361 buf[1] = (byte) (encryptedPreMasterSecret.length>>>0);
362 System.arraycopy(encryptedPreMasterSecret,0,buf,2,encryptedPreMasterSecret.length);
364 // ugh... netscape didn't send the length bytes and now every SSLv3 implementation
365 // must implement this bug
366 buf = encryptedPreMasterSecret;
368 sendHandshake((byte)16,buf);
371 private void sendChangeCipherSpec() throws IOException {
372 sendRecord((byte)20,new byte[] { 0x01 });
375 private void computeMasterSecret() {
376 preMasterSecret = new byte[48];
377 preMasterSecret[0] = 0x03; // version_high
378 preMasterSecret[1] = tls ? (byte) 0x01 : (byte) 0x00; // version_low
379 randomBytes(preMasterSecret,2,46);
382 masterSecret = tlsPRF(48,preMasterSecret,getBytes("master secret"),concat(clientRandom,serverRandom));
384 masterSecret = concat(new byte[][] {
385 md5(new byte[][] { preMasterSecret,
386 sha1(new byte[][] { new byte[] { 0x41 }, preMasterSecret, clientRandom, serverRandom })}),
387 md5(new byte[][] { preMasterSecret,
388 sha1(new byte[][] { new byte[] { 0x42, 0x42 }, preMasterSecret, clientRandom, serverRandom })}),
389 md5(new byte[][] { preMasterSecret,
390 sha1(new byte[][] { new byte[] { 0x43, 0x43, 0x43 }, preMasterSecret, clientRandom, serverRandom })})
395 public void initCrypto() {
400 keyMaterial = tlsPRF(
401 (mac.length + (aes==256 ? 32 : 16) + (aes==0 ? 0 : 16))*2, // MAC len + key len + iv len
403 getBytes("key expansion"),
404 concat(serverRandom,clientRandom)
407 keyMaterial = new byte[] { };
408 for(int i=0; keyMaterial.length < 72; i++) {
409 byte[] crap = new byte[i + 1];
410 for(int j=0; j<crap.length; j++) crap[j] = (byte)(((byte)0x41) + ((byte)i));
411 keyMaterial = concat(new byte[][] { keyMaterial,
412 md5(new byte[][] { masterSecret,
413 sha1(new byte[][] { crap, masterSecret, serverRandom, clientRandom }) }) });
415 if(aes != 0) throw new Error("should never happen");
418 byte[] clientWriteMACSecret = new byte[mac.length];
419 byte[] serverWriteMACSecret = new byte[mac.length];
420 byte[] clientWriteKey = new byte[aes==256 ? 32 : 16];
421 byte[] serverWriteKey = new byte[aes==256 ? 32 : 16];
422 byte[] clientWriteIV = aes == 0 ? null : new byte[16];
423 byte[] serverWriteIV = aes == 0 ? null : new byte[16];
426 System.arraycopy(keyMaterial, p, clientWriteMACSecret, 0, mac.length); p += mac.length;
427 System.arraycopy(keyMaterial, p, serverWriteMACSecret, 0, mac.length); p += mac.length;
428 System.arraycopy(keyMaterial, p, clientWriteKey, 0, clientWriteKey.length); p += clientWriteKey.length;
429 System.arraycopy(keyMaterial, p, serverWriteKey, 0, serverWriteKey.length); p += serverWriteKey.length;
430 if(clientWriteIV != null) System.arraycopy(keyMaterial,p,clientWriteIV,0,16); p += 16;
431 if(serverWriteIV != null) System.arraycopy(keyMaterial,p,serverWriteIV,0,16); p += 16;
435 writeCipher = aes==0 ? (Cipher)new RC4(clientWriteKey) : (Cipher)new CBC(new AES(clientWriteKey,false),16,clientWriteIV,false);
436 inner = sha ? (Digest)new SHA1() : (Digest)new MD5();
437 clientWriteMACDigest = tls ? (Digest) new HMAC(inner,clientWriteMACSecret) : (Digest)new SSLv3HMAC(inner,clientWriteMACSecret);
439 readCipher = aes==0 ? (Cipher)new RC4(serverWriteKey) : (Cipher)new CBC(new AES(serverWriteKey,true),16,serverWriteIV,true);
440 inner = sha ? (Digest)new SHA1() : (Digest)new MD5();
441 serverWriteMACDigest = tls ? (Digest)new HMAC(inner,serverWriteMACSecret) : (Digest)new SSLv3HMAC(inner,serverWriteMACSecret);
444 blockSize = 16; // aes block size == 16
445 padBuf = new byte[mac.length+blockSize]; // worse case, 15 bytes of data, mac, and padding byte
449 private void sendFinished() throws IOException {
450 byte[] handshakes = handshakesBuffer.toByteArray();
452 sendHandshake((byte)20, tlsPRF(
455 getBytes("client finished"),
456 concat(md5(handshakes),sha1(handshakes))));
459 sendHandshake((byte)20, concat(new byte[][] {
460 md5(new byte[][] { masterSecret, pad2,
461 md5(new byte[][] { handshakes, new byte[] { (byte)0x43, (byte)0x4C, (byte)0x4E, (byte)0x54 },
462 masterSecret, pad1 }) }),
463 sha1(new byte[][] { masterSecret, pad2_sha,
464 sha1(new byte[][] { handshakes, new byte[] { (byte)0x43, (byte)0x4C, (byte)0x4E, (byte)0x54 },
465 masterSecret, pad1_sha } ) })
470 private void receiveChangeCipherSpec() throws IOException {
471 int size = readRecord((byte)20);
472 if(size == -1) throw new Exn("got eof when expecting a ChangeCipherSpec message");
473 if(size != 1 || readRecordBuf[0] != 0x01) throw new Exn("Invalid ChangeCipherSpec message");
476 private void receieveFinished() throws IOException {
477 byte[] handshakes = handshakesBuffer.toByteArray();
478 byte[] buf = readHandshake();
479 if(buf[0] != 20) throw new Exn("expected a Finished message");
483 if(buf.length != 4 + 12) throw new Exn("Finished message too short");
486 getBytes("server finished"),
487 concat(md5(handshakes),sha1(handshakes)));
489 if(buf.length != 4 + 16 +20) throw new Exn("Finished message too short");
490 expected = concat(new byte[][] {
491 md5(new byte[][] { masterSecret, pad2,
492 md5(new byte[][] { handshakes, new byte[] { (byte)0x53, (byte)0x52, (byte)0x56, (byte)0x52 },
493 masterSecret, pad1 }) }),
494 sha1(new byte[][] { masterSecret, pad2_sha,
495 sha1(new byte[][] { handshakes, new byte[] { (byte)0x53, (byte)0x52, (byte)0x56, (byte)0x52 },
496 masterSecret, pad1_sha } ) } ) } );
498 if(!eq(expected,0,buf,4,expected.length)) throw new Exn("server finished message mismatch");
501 private void flush() throws IOException { rawOS.flush(); }
503 private void sendHandshake(byte type, byte[] payload) throws IOException {
504 if(payload.length > (1<<24)) throw new IllegalArgumentException("payload.length");
505 byte[] buf = new byte[4+payload.length];
507 buf[1] = (byte)(payload.length>>>16);
508 buf[2] = (byte)(payload.length>>>8);
509 buf[3] = (byte)(payload.length>>>0);
510 System.arraycopy(payload,0,buf,4,payload.length);
511 handshakesBuffer.write(buf);
512 sendRecord((byte)22,buf);
515 private void sendRecord(byte proto, byte[] buf) throws IOException { sendRecord(proto,buf,0,buf.length); }
516 private void sendRecord(byte proto, byte[] payload, int off, int totalLen) throws IOException {
517 int macLength = (negotiated & 1) != 0 ? mac.length : 0;
518 while(totalLen > 0) {
519 int len = min(totalLen,16384-macLength);
520 rawOS.writeByte(proto);
521 rawOS.writeShort(tls ? 0x0301 : 0x0300);
522 if((negotiated & 1) != 0) {
523 computeMAC(proto,payload,off,len,clientWriteMACDigest,clientSequenceNumber);
524 // FEATURE: Encode in place
526 int firstShot = len & ~(blockSize-1);
527 if(firstShot != 0) writeCipher.process(payload,off,sendRecordBuf,0,firstShot);
528 int _off = off + firstShot; // offset in sendRecordBuf
529 int _len = len - firstShot; // length of data in padBuf
530 if(_len != 0) System.arraycopy(payload,_off,padBuf,0,_len);
531 System.arraycopy(mac,0,padBuf,_len,macLength);
533 int extra = blockSize - (_len&~blockSize);
534 if(extra == 0) extra = 16;
535 for(int i=0;i<extra;i++) padBuf[_len+i] = (byte)(extra-1);
537 writeCipher.process(padBuf,0,sendRecordBuf,_off,_len);
538 rawOS.writeShort(firstShot+_len);
539 rawOS.write(sendRecordBuf,0,firstShot+_len);
541 writeCipher.process(payload,off,sendRecordBuf,0,len);
542 writeCipher.process(mac,0,sendRecordBuf,len,macLength);
543 rawOS.writeShort(len + macLength);
544 rawOS.write(sendRecordBuf,0, len +macLength);
546 clientSequenceNumber++;
548 rawOS.writeShort(len);
549 rawOS.write(payload,off,len);
556 private byte[] readHandshake() throws IOException {
557 if(handshakeDataLength == 0) {
558 handshakeDataStart = 0;
559 handshakeDataLength = readRecord((byte)22);
560 if(handshakeDataLength == -1) throw new Exn("got eof when expecting a handshake packet");
562 byte[] buf = readRecordBuf;
563 int len = ((buf[handshakeDataStart+1]&0xff)<<16)|((buf[handshakeDataStart+2]&0xff)<<8)|((buf[handshakeDataStart+3]&0xff)<<0);
564 // Handshake messages can theoretically span multiple records, but in practice this does not occur
565 if(len > handshakeDataLength) {
566 sendAlert(true,10); // 10 == unexpected message
567 throw new Exn("handshake message size too large " + len + " vs " + (handshakeDataLength-handshakeDataStart));
569 byte[] ret = new byte[4+len];
570 System.arraycopy(buf,handshakeDataStart,ret,0,ret.length);
571 handshakeDataLength -= ret.length;
572 handshakeDataStart += ret.length;
573 handshakesBuffer.write(ret);
577 private int readRecord(byte reqProto) throws IOException {
578 int macLength = (negotiated & 2) != 0 ? mac.length : 0;
584 proto = rawIS.readByte();
585 } catch(EOFException e) {
586 // this may or may not be an error. it is up to the application protocol
589 throw new PrematureCloseExn();
592 version = rawIS.readShort();
593 if(version != 0x0300 && version != 0x0301) throw new Exn("invalid version ");
594 len = rawIS.readShort();
595 if(len <= 0 || len > 16384+((negotiated&2)!=0 ? macLength : 0)) throw new Exn("invalid length " + len);
596 rawIS.readFully((negotiated&2)!=0 ? readRecordScratch : readRecordBuf,0,len);
597 } catch(EOFException e) {
598 // an EOF here is always an error (we don't pass the EOF back on to the app
599 // because it isn't a "legitimate" eof)
600 throw new Exn("Hit EOF too early");
603 if((negotiated & 2) != 0) {
604 // FEATURE: Decode in place
605 if(blockSize != 0 && (len % blockSize) != 0) throw new Exn("input not a multiple of the cipher's block size");
606 readCipher.process(readRecordScratch,0,readRecordBuf,0,len);
609 if(!tls) throw new Error("should never happen");
610 int padding = readRecordBuf[len-1]&0xff;
611 if(padding >= blockSize) throw new Exn("invalid padding length: " + padding);
612 for(int i=0;i<padding;i++) if(readRecordBuf[len-padding-1] != padding) throw new Exn("bad padding");
615 if(len < macLength) throw new Exn("packet size < macLength");
617 computeMAC(proto,readRecordBuf,0,len-macLength,serverWriteMACDigest,serverSequenceNumber);
618 for(int i=0;i<macLength;i++)
619 if(mac[i] != readRecordBuf[len-macLength+i])
620 throw new Exn("mac mismatch");
622 serverSequenceNumber++;
625 if(proto == reqProto) return len;
629 if(len != 2) throw new Exn("invalid lengh for alert");
630 int level = readRecordBuf[0];
631 int desc = readRecordBuf[1];
633 if(desc == 0) { // CloseNotify
634 debug("Server requested connection closure");
637 } catch(SocketException e) { /* incomplete close, thats ok */ }
643 log("SSL ALERT WARNING: desc: " + desc);
645 } else if(level == 2) {
646 throw new Exn("SSL ALERT FATAL: desc: " +desc);
648 throw new Exn("invalid alert level");
652 case 22: { // Handshake
653 int type = readRecordBuf[0];
654 int hslen = ((readRecordBuf[1]&0xff)<<16)|((readRecordBuf[2]&0xff)<<8)|((readRecordBuf[3]&0xff)<<0);
655 if(hslen > len - 4) throw new Exn("Multiple sequential handshake messages received after negotiation");
656 if(type == 0) { // HellloRequest
657 if(tls) sendAlert(false,100); // politely refuse, 100 == NoRegnegotiation
659 throw new Exn("Unexpected Handshake type: " + type);
662 default: throw new Exn("Unexpected protocol: " + proto);
667 private static void longToBytes(long l, byte[] buf, int off) {
668 for(int i=0;i<8;i++) buf[off+i] = (byte)(l>>>(8*(7-i)));
670 private void computeMAC(byte proto, byte[] payload, int off, int len, Digest digest, long sequenceNumber) {
672 longToBytes(sequenceNumber,mac,0);
674 mac[9] = 0x03; // version
676 mac[11] = (byte)(len>>>8);
677 mac[12] = (byte)(len>>>0);
679 digest.update(mac,0,13);
680 digest.update(payload,off,len);
681 digest.doFinal(mac,0);
683 longToBytes(sequenceNumber, mac, 0);
685 mac[9] = (byte)(len>>>8);
686 mac[10] = (byte)(len>>>0);
688 digest.update(mac, 0, 11);
689 digest.update(payload, off, len);
690 digest.doFinal(mac, 0);
694 private void sendCloseNotify() throws IOException { sendRecord((byte)21, new byte[] { 0x01, 0x00 }); }
695 private void sendAlert(boolean fatal, int message) throws IOException {
696 byte[] buf = new byte[] { fatal ? (byte)2 :(byte)1, (byte)message };
697 sendRecord((byte)21,buf);
705 // Shared digest objects
706 private MD5 masterMD5 = new MD5();
707 private SHA1 masterSHA1 = new SHA1();
709 private byte[] md5(byte[] in) { return md5( new byte[][] { in }); }
710 private byte[] md5(byte[][] inputs) {
712 for(int i=0; i<inputs.length; i++) masterMD5.update(inputs[i], 0, inputs[i].length);
713 byte[] ret = new byte[masterMD5.getDigestSize()];
714 masterMD5.doFinal(ret, 0);
718 private byte[] sha1(byte[] in) { return sha1(new byte[][] { in }); }
719 private byte[] sha1(byte[][] inputs) {
721 for(int i=0; i<inputs.length; i++) masterSHA1.update(inputs[i], 0, inputs[i].length);
722 byte[] ret = new byte[masterSHA1.getDigestSize()];
723 masterSHA1.doFinal(ret, 0);
728 PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
729 L_S = length in bytes of secret;
730 L_S1 = L_S2 = ceil(L_S / 2);
732 The secret is partitioned into two halves (with the possibility of
733 one shared byte) as described above, S1 taking the first L_S1 bytes
734 and S2 the last L_S2 bytes.
736 P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
737 HMAC_hash(secret, A(2) + seed) +
738 HMAC_hash(secret, A(3) + seed) + ...
741 A(i) = HMAC_hash(secret, A(i-1))
743 private byte[] tlsPRF(int size,byte[] secret, byte[] label, byte[] seed) {
744 if(size > 140) throw new IllegalArgumentException("size > 140: " + size);
745 seed = concat(label,seed);
747 int half_length = (secret.length + 1) / 2;
748 byte[] s1 = new byte[half_length];
749 System.arraycopy(secret,0,s1,0,half_length);
750 byte[] s2 = new byte[half_length];
751 System.arraycopy(secret,secret.length - half_length, s2, 0, half_length);
753 Digest hmac_md5 = new HMAC(new MD5(),s1);
754 Digest hmac_sha = new HMAC(new SHA1(),s2);
756 byte[] md5out = new byte[144];
757 byte[] shaout = new byte[140];
758 byte[] digest = new byte[20];
762 hmac_md5.update(seed,0,seed.length);
763 hmac_md5.doFinal(digest,0);
767 hmac_md5.update(digest,0,16);
768 hmac_md5.update(seed,0,seed.length);
769 hmac_md5.doFinal(md5out,n);
770 hmac_md5.update(digest,0,16);
771 hmac_md5.doFinal(digest,0);
776 hmac_sha.update(seed,0,seed.length);
777 hmac_sha.doFinal(digest,0);
780 hmac_sha.update(digest,0,20);
781 hmac_sha.update(seed,0,seed.length);
782 hmac_sha.doFinal(shaout,n);
783 hmac_sha.update(digest,0,20);
784 hmac_sha.doFinal(digest,0);
788 byte[] ret = new byte[size];
789 for(int i=0;i<size;i++) ret[i] = (byte)(md5out[i] ^ shaout[i]);
793 public static class CBC implements Cipher {
794 private final Cipher c;
795 private final byte[] iv;
796 private final int blockSize;
797 private final boolean reverse;
798 public CBC(Cipher c, int blockSize, byte[] iv, boolean reverse) {
800 this.blockSize = blockSize;
802 this.reverse = reverse;
803 if(iv.length != blockSize) throw new IllegalArgumentException("iv.length != blockSize");
806 public void process(byte[] in, int inp, byte[] out, int outp, int len) {
807 if((len % blockSize) != 0) throw new IllegalArgumentException("buffer must be a multiple of block size");
810 for(int i=0;i<blockSize;i++) iv[i] ^= in[inp+i]; // mangle the cleartext
811 c.process(iv,0,out,outp,blockSize); // process it
812 System.arraycopy(out,outp,iv,0,blockSize); // copy the block to iv
814 c.process(in,inp,out,outp,blockSize); // process the ciphertext
815 for(int i=0;i<blockSize;i++) out[outp+i] ^= iv[i]; // mangle the cleartext
816 System.arraycopy(in,inp,iv,0,blockSize); // copy the ciphertext to iv
818 inp+=16; outp+=16; len-=16;
823 public static class SSLv3HMAC extends Digest {
824 private final Digest h;
825 private final byte[] digest;
826 private final byte[] key;
827 private final int padSize;
829 public int getDigestSize() { return h.getDigestSize(); }
831 public SSLv3HMAC(Digest h, byte[] key) {
834 switch(h.getDigestSize()) {
835 case 16: padSize = 48; break;
836 case 20: padSize = 40; break;
837 default: throw new IllegalArgumentException("unsupported digest size");
839 digest = new byte[h.getDigestSize()];
842 public void reset() {
844 h.update(key,0,key.length);
845 h.update(pad1,0,padSize);
847 public void update(byte[] b, int off, int len) { h.update(b,off,len); }
848 public void doFinal(byte[] out, int off){
850 h.update(key,0,key.length);
851 h.update(pad2,0,padSize);
852 h.update(digest,0,digest.length);
856 protected void processWord(byte[] in, int inOff) {}
857 protected void processLength(long bitLength) {}
858 protected void processBlock() {}
865 private static SecureRandom random = new SecureRandom();
866 public static synchronized void randomBytes(byte[] buf, int off, int len) {
867 byte[] bytes = new byte[len];
868 random.nextBytes(bytes);
869 System.arraycopy(bytes,0,buf,off,len);
872 public static byte[] concat(byte[] a, byte[] b) { return concat(new byte[][] { a, b }); }
873 public static byte[] concat(byte[] a, byte[] b, byte[] c) { return concat(new byte[][] { a, b, c }); }
874 public static byte[] concat(byte[][] inputs) {
876 for(int i=0; i<inputs.length; i++) total += inputs[i].length;
877 byte[] ret = new byte[total];
878 for(int i=0,pos=0; i<inputs.length;pos+=inputs[i].length,i++)
879 System.arraycopy(inputs[i], 0, ret, pos, inputs[i].length);
883 public static byte[] getBytes(String s) {
885 return s.getBytes("US-ASCII");
886 } catch (UnsupportedEncodingException e) {
887 return null; // will never happen
891 public static boolean eq(byte[] a, int aoff, byte[] b, int boff, int len){
892 for(int i=0;i<len;i++) if(a[aoff+i] != b[boff+i]) return false;
897 // InputStream/OutputStream/Socket interfaces
899 public OutputStream getOutputStream() { return sslOS; }
900 public InputStream getInputStream() { return sslIS; }
901 public synchronized void close() throws IOException {
903 if(negotiated != 0) {
906 // don't bother sending a close_notify back to the server
907 // this is an incomplete close which is allowed by the spec
914 private int read(byte[] buf, int off, int len) throws IOException {
915 if(pendingLength == 0) {
916 if(closed) return -1;
917 int readLen = readRecord((byte)23);
918 if(readLen == -1) return -1; // EOF
919 len = min(len,readLen);
920 System.arraycopy(readRecordBuf,0,buf,off,len);
921 if(readLen > len) System.arraycopy(readRecordBuf,len,pending,0,readLen-len);
923 pendingLength = readLen - len;
926 len = min(len,pendingLength);
927 System.arraycopy(pending,pendingStart,buf,off,len);
928 pendingLength -= len;
934 private void write(byte[] buf, int off, int len) throws IOException {
935 if(closed) throw new SocketException("Socket closed");
936 sendRecord((byte)23,buf,off,len);
940 private class SSLInputStream extends InputStream {
941 public int available() throws IOException {
942 synchronized(SSL.this) {
943 return negotiated != 0 ? pendingLength : rawIS.available();
946 public int read() throws IOException {
947 synchronized(SSL.this) {
948 if(negotiated==0) return rawIS.read();
949 if(pendingLength > 0) {
951 return pending[pendingStart++]&0xff;
953 byte[] buf = new byte[1];
955 return n == -1 ? -1 : buf[0]&0xff;
959 public int read(byte[] buf, int off, int len) throws IOException {
960 synchronized(SSL.this) {
961 return negotiated!=0 ? SSL.this.read(buf,off,len) : rawIS.read(buf,off,len);
964 public long skip(long n) throws IOException {
965 synchronized(SSL.this) {
966 if(negotiated==0) return rawIS.skip(n);
967 if(pendingLength > 0) {
968 n = min((int)n,pendingLength);
973 return super.skip(n);
978 private class SSLOutputStream extends OutputStream {
979 public void flush() throws IOException { rawOS.flush(); }
980 public void write(int b) throws IOException { write(new byte[] { (byte)b }); }
981 public void write(byte[] buf, int off, int len) throws IOException {
982 synchronized(SSL.this) {
984 SSL.this.write(buf,off,len);
986 rawOS.write(buf,off,len);
991 public static class Exn extends IOException { public Exn(String s) { super(s); } }
992 public static class PrematureCloseExn extends Exn {
993 public PrematureCloseExn() { super("Connection was closed by the remote WITHOUT a close_noify"); }
996 public static boolean debugOn = false;
997 private static void debug(Object o) { if(debugOn) System.err.println("[IbexSSL-Debug] " + o.toString()); }
998 private static void log(Object o) { System.err.println("[IbexSSL] " + o.toString()); }
1000 public static void verifyCerts(X509.Certificate[] certs) throws DER.Exception, Exn {
1002 verifyCerts_(certs);
1003 } catch(RuntimeException e) {
1004 e.printStackTrace();
1005 throw new Exn("Error while verifying certificates: " + e);
1009 private static void verifyCerts_(X509.Certificate[] certs) throws DER.Exception, Exn {
1010 int last = certs.length-1;
1011 for(int i=0;i<certs.length;i++) {
1012 debug("Cert " + i + ": " + certs[i].subject + " ok");
1013 if(!certs[i].isValid())
1014 throw new Exn("Certificate " + i + " in certificate chain is not valid (" + certs[i].startDate + " - " + certs[i].endDate + ")");
1016 X509.Certificate.BC bc = certs[i].basicContraints;
1021 if(!bc.isCA) throw new Exn("non-CA certificate used for signing");
1022 if(bc.pathLenConstraint != null && bc.pathLenConstraint.longValue() < i-1) throw new Exn("CA cert can't be used this deep");
1025 if(i != certs.length - 1) {
1026 if(!certs[i].issuer.equals(certs[i+1].subject))
1027 throw new Exn("Issuer for certificate " + i + " does not match next in chain");
1028 if(!certs[i].isSignedBy(certs[i+1]))
1029 throw new Exn("Certificate " + i + " in chain is not signed by the next certificate");
1033 X509.Certificate cert = certs[last];
1035 RSA.PublicKey pks = (RSA.PublicKey) caKeys.get(cert.issuer);
1036 if(pks == null) throw new Exn("Certificate is signed by an unknown CA (" + cert.issuer + ")");
1037 if(!cert.isSignedWith(pks)) throw new Exn("Certificate is not signed by its CA");
1038 log("" + cert.subject + " is signed by " + cert.issuer);
1041 public static void addCACert(byte[] b) throws IOException { addCACert(new ByteArrayInputStream(b)); }
1042 public static void addCACert(InputStream is) throws IOException { addCACert(new X509.Certificate(is)); }
1043 public static void addCACert(X509.Certificate cert) throws DER.Exception { addCAKey(cert.subject,cert.getRSAPublicKey()); }
1044 public static void addCAKey(X509.Name subject, RSA.PublicKey pks) {
1045 synchronized(caKeys) {
1046 if(caKeys.get(subject) != null)
1047 throw new IllegalArgumentException(subject.toString() + " already exists!");
1048 caKeys.put(subject,pks);
1054 // This will force a <clinit> which'll load the certs
1055 Class.forName("org.ibex.net.ssl.RootCerts");
1056 log("Loaded root keys from org.ibex.net.ssl.RootCerts");
1057 } catch(ClassNotFoundException e) {
1058 InputStream is = SSL.class.getClassLoader().getResourceAsStream("org.ibex/net/ssl/rootcerts.dat");
1061 addCompactCAKeys(is);
1062 log("Loaded root certs from rootcerts.dat");
1063 } catch(IOException e2) {
1064 log("Error loading certs from rootcerts.dat: " + e2.getMessage());
1070 public static int addCompactCAKeys(InputStream is) throws IOException {
1071 synchronized(caKeys) {
1073 Vector seq = (Vector) new DER.InputStream(is).readObject();
1074 for(Enumeration e = seq.elements(); e.hasMoreElements();) {
1075 Vector seq2 = (Vector) e.nextElement();
1076 X509.Name subject = new X509.Name(seq2.elementAt(0));
1077 RSA.PublicKey pks = new RSA.PublicKey(seq2.elementAt(1));
1078 addCAKey(subject,pks);
1081 } catch(RuntimeException e) {
1082 e.printStackTrace();
1083 throw new IOException("error while reading stream: " + e);
1088 public static synchronized void setVerifyCallback(VerifyCallback cb) { verifyCallback = cb; }
1091 public static class State {
1093 byte[] masterSecret;
1094 State(byte[] sessionID, byte[] masterSecret) {
1095 this.sessionID = sessionID;
1096 this.masterSecret = masterSecret;
1100 public interface VerifyCallback {
1101 public boolean checkCerts(X509.Certificate[] certs, String hostname, Exn exn);
1105 private static final int min(int a, int b) { return a < b ? a : b; }