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.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;
29 import java.security.SecureRandom;
31 import java.net.Socket;
32 import java.net.SocketException;
35 import java.util.Enumeration;
36 import java.util.Hashtable;
37 import java.util.Random;
38 import java.util.Vector;
40 // FEATURE: Server socket
42 public class SSL extends Socket {
43 private String hostname;
45 private int negotiated;
47 private boolean tls = true;
50 private final DataInputStream rawIS;
51 private final DataOutputStream rawOS;
53 private final InputStream sslIS;
54 private final OutputStream sslOS;
56 private byte[] sessionID;
58 private Digest clientWriteMACDigest;
59 private Digest serverWriteMACDigest;
60 private byte[] masterSecret;
65 private long serverSequenceNumber;
66 private long clientSequenceNumber;
69 private boolean closed;
71 // These are only used during negotiation
72 private byte[] serverRandom;
73 private byte[] clientRandom;
74 private byte[] preMasterSecret;
79 private byte[] pending = new byte[16384];
80 private int pendingStart;
81 private int pendingLength;
83 private byte[] sendRecordBuf = new byte[16384];
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];
90 private ByteArrayOutputStream handshakesBuffer;
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];
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;
107 private final static Hashtable caKeys = new Hashtable();
108 private static VerifyCallback verifyCallback;
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 {
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);
127 public synchronized void setTLS(boolean b) { if(negotiated!=0) throw new IllegalStateException("already negotiated"); tls = b; }
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");
133 handshakesBuffer = new ByteArrayOutputStream();
136 sendClientHello(state != null ? state.sessionID : null);
138 debug("sent ClientHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
140 receiveServerHello();
141 debug("got ServerHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
144 state != null && sessionID.length == state.sessionID.length &&
145 eq(state.sessionID,0,sessionID,0,sessionID.length);
148 negotiateResume(state);
152 // we're done with these now
153 clientRandom = serverRandom = preMasterSecret = null;
154 handshakesBuffer = null;
156 log("Negotiation with " + hostname + " complete (" + (tls?"TLSv1.0":"SSLv3.0")+")");
158 if((negotiated & 3) != 3) {
160 try { super.close(); } catch(IOException e) { /* ignore */ }
166 private void negotiateResume(State state) throws IOException {
167 masterSecret = state.masterSecret;
170 log("initializec crypto");
172 receiveChangeCipherSpec();
173 debug("Received ChangeCipherSpec");
176 debug("Received Finished");
178 sendChangeCipherSpec();
179 debug("Sent ChangeCipherSpec");
182 debug("Sent Finished");
185 private void negotiateNew() throws IOException {
186 X509Certificate[] certs = receiveServerCertificates();
187 debug("got Certificate");
189 boolean gotCertificateRequest = false;
191 byte[] buf = readHandshake();
193 case 14: // ServerHelloDone
194 if(buf.length != 4) throw new Exn("ServerHelloDone contained trailing garbage");
195 debug("got ServerHelloDone");
197 case 13: // CertificateRequest
198 debug("Got a CertificateRequest message but we don't suport client certificates");
199 gotCertificateRequest = true;
202 throw new Exn("unknown handshake type " + buf[0]);
206 if(gotCertificateRequest)
207 sendHandshake((byte)11,new byte[3]); // send empty cert list
210 if(!hostname.equalsIgnoreCase(certs[0].getCN()))
211 throw new Exn("Certificate is for " + certs[0].getCN() + " not " + hostname);
214 if(verifyCallback == null) throw e;
215 synchronized(SSL.class) {
216 if(!verifyCallback.checkCerts(certs,hostname,e)) throw e;
220 computeMasterSecret();
222 sendClientKeyExchange(certs[0]);
223 debug("sent ClientKeyExchange");
227 sendChangeCipherSpec();
228 debug("sent ChangeCipherSpec");
231 debug("sent Finished");
234 receiveChangeCipherSpec();
235 debug("got ChangeCipherSpec");
238 debug("got Finished");
241 public State getSessionState() {
242 if((negotiated&3)!=3 || !closed || warnings != 0) return null;
243 return new State(sessionID,masterSecret);
245 public boolean isActive() { return !closed; }
246 public boolean isNegotiated() { return (negotiated&3) == 3; }
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
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;
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);
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
272 buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_SHA
274 buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_MD5
280 sendHandshake((byte)1,buf);
284 private void receiveServerHello() throws IOException {
286 byte[] buf = readHandshake();
287 if(buf[0] != 2) throw new Exn("expected a ServerHello message");
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]);
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;
295 serverRandom = new byte[32];
296 System.arraycopy(buf,p,serverRandom,0,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);
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);
308 mac = new byte[sha ? 20 : 16];
309 if(buf[p++] != 0x0) throw new Exn("unsupported compression " + buf[p-1]);
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");
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];
323 while(p < buf.length) {
324 int len = ((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0);
326 if(p + len > buf.length) throw new Exn("Certificate message cut short");
327 certs[count++] = new X509Certificate(new ByteArrayInputStream(buf,p,len));
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);
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);
345 // ugh... netscape didn't send the length bytes and now every SSLv3 implementation
346 // must implement this bug
347 buf = encryptedPreMasterSecret;
349 sendHandshake((byte)16,buf);
352 private void sendChangeCipherSpec() throws IOException {
353 sendRecord((byte)20,new byte[] { 0x01 });
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);
363 masterSecret = tlsPRF(48,preMasterSecret,getBytes("master secret"),concat(clientRandom,serverRandom));
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 })})
376 public void initCrypto() {
380 keyMaterial = tlsPRF(
381 (mac.length + 16 + 0)*2, // MAC len + key len + iv len
383 getBytes("key expansion"),
384 concat(serverRandom,clientRandom)
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 }) }) });
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];
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;
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);
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);
419 private void sendFinished() throws IOException {
420 byte[] handshakes = handshakesBuffer.toByteArray();
422 sendHandshake((byte)20, tlsPRF(
425 getBytes("client finished"),
426 concat(md5(handshakes),sha1(handshakes))));
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 } ) })
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");
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");
453 if(buf.length != 4 + 12) throw new Exn("Finished message too short");
456 getBytes("server finished"),
457 concat(md5(handshakes),sha1(handshakes)));
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 } ) } ) } );
468 if(!eq(expected,0,buf,4,expected.length)) throw new Exn("server finished message mismatch");
471 private void flush() throws IOException { rawOS.flush(); }
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];
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);
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++;
501 rawOS.writeShort(len);
502 rawOS.write(payload,off,len);
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");
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));
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);
530 private int readRecord(byte reqProto) throws IOException {
531 int macLength = (negotiated & 2) != 0 ? mac.length : 0;
537 proto = rawIS.readByte();
538 } catch(EOFException e) {
539 // this may or may not be an error. it is up to the application protocol
542 throw new PrematureCloseExn();
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");
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");
565 serverSequenceNumber++;
568 if(proto == reqProto) return len;
572 if(len != 2) throw new Exn("invalid lengh for alert");
573 int level = readRecordBuf[0];
574 int desc = readRecordBuf[1];
576 if(desc == 0) { // CloseNotify
577 debug("Server requested connection closure");
580 } catch(SocketException e) { /* incomplete close, thats ok */ }
586 log("SSL ALERT WARNING: desc: " + desc);
588 } else if(level == 2) {
589 throw new Exn("SSL ALERT FATAL: desc: " +desc);
591 throw new Exn("invalid alert level");
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
602 throw new Exn("Unexpected Handshake type: " + type);
605 default: throw new Exn("Unexpected protocol: " + proto);
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)));
613 private void computeMAC(byte proto, byte[] payload, int off, int len, Digest digest, long sequenceNumber) {
615 longToBytes(sequenceNumber,mac,0);
617 mac[9] = 0x03; // version
619 mac[11] = (byte)(len>>>8);
620 mac[12] = (byte)(len>>>0);
622 digest.update(mac,0,13);
623 digest.update(payload,off,len);
624 digest.doFinal(mac,0);
626 longToBytes(sequenceNumber, mac, 0);
628 mac[9] = (byte)(len>>>8);
629 mac[10] = (byte)(len>>>0);
631 digest.update(mac, 0, 11);
632 digest.update(payload, off, len);
633 digest.doFinal(mac, 0);
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);
648 // Shared digest objects
649 private MD5 masterMD5 = new MD5();
650 private SHA1 masterSHA1 = new SHA1();
652 private byte[] md5(byte[] in) { return md5( new byte[][] { in }); }
653 private byte[] md5(byte[][] inputs) {
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);
661 private byte[] sha1(byte[] in) { return sha1(new byte[][] { in }); }
662 private byte[] sha1(byte[][] inputs) {
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);
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);
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.
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) + ...
684 A(i) = HMAC_hash(secret, A(i-1))
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);
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);
696 Digest hmac_md5 = new HMAC(new MD5(),s1);
697 Digest hmac_sha = new HMAC(new SHA1(),s2);
699 byte[] md5out = new byte[112];
700 byte[] shaout = new byte[120];
701 byte[] digest = new byte[20];
705 hmac_md5.update(seed,0,seed.length);
706 hmac_md5.doFinal(digest,0);
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);
719 hmac_sha.update(seed,0,seed.length);
720 hmac_sha.doFinal(digest,0);
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);
731 byte[] ret = new byte[size];
732 for(int i=0;i<size;i++) ret[i] = (byte)(md5out[i] ^ shaout[i]);
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;
742 public int getDigestSize() { return h.getDigestSize(); }
744 public SSLv3HMAC(Digest h, byte[] 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");
752 digest = new byte[h.getDigestSize()];
755 public void reset() {
757 h.update(key,0,key.length);
758 h.update(pad1,0,padSize);
760 public void update(byte[] b, int off, int len) { h.update(b,off,len); }
761 public void doFinal(byte[] out, int off){
763 h.update(key,0,key.length);
764 h.update(pad2,0,padSize);
765 h.update(digest,0,digest.length);
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);
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) {
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);
793 public static byte[] getBytes(String s) {
795 return s.getBytes("US-ASCII");
796 } catch (UnsupportedEncodingException e) {
797 return null; // will never happen
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;
807 // InputStream/OutputStream/Socket interfaces
809 public OutputStream getOutputStream() { return sslOS; }
810 public InputStream getInputStream() { return sslIS; }
811 public synchronized void close() throws IOException {
813 if(negotiated != 0) {
816 // don't bother sending a close_notify back to the server
817 // this is an incomplete close which is allowed by the spec
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);
833 pendingLength = readLen - len;
836 len = min(len,pendingLength);
837 System.arraycopy(pending,pendingStart,buf,off,len);
838 pendingLength -= len;
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);
850 private class SSLInputStream extends InputStream {
851 public int available() throws IOException {
852 synchronized(SSL.this) {
853 return negotiated != 0 ? pendingLength : rawIS.available();
856 public int read() throws IOException {
857 synchronized(SSL.this) {
858 if(negotiated==0) return rawIS.read();
859 if(pendingLength > 0) {
861 return pending[pendingStart++];
863 byte[] buf = new byte[1];
865 return n == -1 ? -1 : buf[0]&0xff;
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);
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);
883 return super.skip(n);
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) {
894 SSL.this.write(buf,off,len);
896 rawOS.write(buf,off,len);
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"); }
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()); }
910 private static void verifyCerts(X509Certificate[] certs) throws DER.Exception, Exn {
913 } catch(RuntimeException e) {
915 throw new Exn("Error while verifying certificates: " + e);
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 + ")");
926 X509Certificate.BC bc = certs[i].basicContraints;
928 if(i == certs.length - 1) {
932 throw new Exn("CA-cert lacks Basic Constraints");
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");
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");
946 X509Certificate cert = certs[ignoreLast ? certs.length - 2 : certs.length-1];
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);
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);
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");
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());
983 public static int addCompactCAKeys(InputStream is) throws IOException {
984 synchronized(caKeys) {
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);
994 } catch(RuntimeException e) {
996 throw new IOException("error while reading stream: " + e);
1001 public static synchronized void setVerifyCallback(VerifyCallback cb) { verifyCallback = cb; }
1004 public static class State {
1006 byte[] masterSecret;
1007 State(byte[] sessionID, byte[] masterSecret) {
1008 this.sessionID = sessionID;
1009 this.masterSecret = masterSecret;
1013 public interface VerifyCallback {
1014 public boolean checkCerts(X509Certificate[] certs, String hostname, Exn exn);
1018 private static final int min(int a, int b) { return a < b ? a : b; }