From 59803c602da44ef9f2547b9a68d9818ef0a83584 Mon Sep 17 00:00:00 2001 From: brian Date: Wed, 28 Jul 2004 11:59:39 +0000 Subject: [PATCH] aes/cbc support in ssl.java darcs-hash:20040728115939-24bed-1173bb926bc5d1f9a791421c8180744b99a2e0ea.gz --- src/org/ibex/net/SSL.java | 191 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 146 insertions(+), 45 deletions(-) diff --git a/src/org/ibex/net/SSL.java b/src/org/ibex/net/SSL.java index 04519cd..6379db3 100644 --- a/src/org/ibex/net/SSL.java +++ b/src/org/ibex/net/SSL.java @@ -28,12 +28,23 @@ import java.util.Vector; // FEATURE: Server socket public class SSL extends Socket { + public static final byte TLS_RSA_WITH_AES_256_CBC_SHA = 0x35; + public static final byte TLS_RSA_WITH_AES_128_CBC_SHA = 0x2f; + public static final byte SSL_RSA_WITH_RC4_128_SHA = 0x05; + public static final byte SSL_RSA_WITH_RC4_128_MD5 = 0x04; + + private static final byte[] DEFAULT_CIPHER_PREFS = new byte[]{ + TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA, + SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5 + }; + private String hostname; private int negotiated; private boolean tls = true; private boolean sha; + private int aes; private final DataInputStream rawIS; private final DataOutputStream rawOS; @@ -47,8 +58,8 @@ public class SSL extends Socket { private Digest serverWriteMACDigest; private byte[] masterSecret; - private RC4 writeRC4; - private RC4 readRC4; + private Cipher writeCipher; + private Cipher readCipher; private long serverSequenceNumber; private long clientSequenceNumber; @@ -75,6 +86,11 @@ public class SSL extends Socket { private byte[] readRecordBuf = new byte[16384+20]; // 20 == sizeof(sha1 hash) private byte[] readRecordScratch = new byte[16384+20]; + // These are only uses for CBCs + private int blockSize; // block size (0 for stream ciphers) + private byte[] padBuf; + private byte[] prevBlock; + private ByteArrayOutputStream handshakesBuffer; // End Buffers @@ -114,14 +130,16 @@ public class SSL extends Socket { public synchronized void setTLS(boolean b) { if(negotiated!=0) throw new IllegalStateException("already negotiated"); tls = b; } - public void negotiate() throws IOException { negotiate(null); } - public synchronized void negotiate(State state) throws IOException { + public void negotiate() throws IOException { negotiate(null,null); } + public void negotiate(State state) throws IOException { negotiate(state,null); } + public void negotiate(byte[] cipherPrefs) throws IOException { negotiate(null,cipherPrefs); } + private void negotiate(State state, byte[] cipherPrefs) throws IOException { if(negotiated != 0) throw new IllegalStateException("already negotiated"); handshakesBuffer = new ByteArrayOutputStream(); try { - sendClientHello(state != null ? state.sessionID : null); + sendClientHello(state != null ? state.sessionID : null, cipherPrefs); flush(); debug("sent ClientHello (" + (tls?"TLSv1.0":"SSLv3.0")+")"); @@ -233,12 +251,14 @@ public class SSL extends Socket { public boolean isActive() { return !closed; } public boolean isNegotiated() { return (negotiated&3) == 3; } - private void sendClientHello(byte[] sessionID) throws IOException { + private void sendClientHello(byte[] sessionID, byte[] cipherPrefs) throws IOException { if(sessionID != null && sessionID.length > 256) throw new IllegalArgumentException("sessionID"); - // 2 = version, 32 = randomvalue, 1 = sessionID size, 2 = cipher list size, 4 = the two ciphers, + if(cipherPrefs == null) cipherPrefs = DEFAULT_CIPHER_PREFS; + else if(cipherPrefs.length > 4) throw new IllegalArgumentException("too many cipherPrefs"); + // 2 = version, 32 = randomvalue, 1 = sessionID size, 2 = cipher list size, 8 = the four ciphers, // 2 = compression length/no compression int p = 0; - byte[] buf = new byte[2+32+1+(sessionID == null ? 0 : sessionID.length)+2+2+4]; + byte[] buf = new byte[2+32+1+(sessionID == null ? 0 : sessionID.length)+2+(cipherPrefs.length * 2)+2]; buf[p++] = 0x03; // major version buf[p++] = tls ? (byte)0x01 : (byte)0x00; @@ -255,16 +275,18 @@ public class SSL extends Socket { buf[p++] = sessionID != null ? (byte)sessionID.length : 0; if(sessionID != null && sessionID.length != 0) System.arraycopy(sessionID,0,buf,p,sessionID.length); p += sessionID != null ? sessionID.length : 0; - buf[p++] = 0x00; // 4 bytes of ciphers - buf[p++] = 0x04; - buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_SHA - buf[p++] = 0x05; - buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_MD5 - buf[p++] = 0x04; - - buf[p++] = 0x01; - buf[p++] = 0x00; - + + buf[p++] = 0x00; // 8 bytes of ciphers + buf[p++] = (byte)(cipherPrefs.length * 2); + + for(int i=0;i>>8)!=0) throw new Exn("Unsupported cipher " + cipher); + switch(cipher&0xff) { + case SSL_RSA_WITH_RC4_128_MD5: sha = false; aes=0; debug("Using SSL_RSA_WITH_RC4_128_MD5"); break; + case SSL_RSA_WITH_RC4_128_SHA: sha = true; aes=0; debug("Using SSL_RSA_WITH_RC4_128_SHA"); break; + case TLS_RSA_WITH_AES_128_CBC_SHA: sha = true; aes = 128; debug("Using TLS_RSA_WITH_AES_128_CBC_SHA"); break; + case TLS_RSA_WITH_AES_256_CBC_SHA: sha = true; aes = 256; debug("Using TLS_RSA_WITH_AES_256_CBC_SHA"); break; default: throw new Exn("Unsupported cipher " + cipher); } mac = new byte[sha ? 20 : 16]; @@ -363,10 +388,11 @@ public class SSL extends Socket { public void initCrypto() { byte[] keyMaterial; + byte[] ivBlock; if(tls) { keyMaterial = tlsPRF( - (mac.length + 16 + 0)*2, // MAC len + key len + iv len + (mac.length + (aes==256 ? 32 : 16) + (aes==0 ? 0 : 16))*2, // MAC len + key len + iv len masterSecret, getBytes("key expansion"), concat(serverRandom,clientRandom) @@ -380,28 +406,38 @@ public class SSL extends Socket { md5(new byte[][] { masterSecret, sha1(new byte[][] { crap, masterSecret, serverRandom, clientRandom }) }) }); } + if(aes != 0) throw new Error("should never happen"); } byte[] clientWriteMACSecret = new byte[mac.length]; byte[] serverWriteMACSecret = new byte[mac.length]; - byte[] clientWriteKey = new byte[16]; - byte[] serverWriteKey = new byte[16]; + byte[] clientWriteKey = new byte[aes==256 ? 32 : 16]; + byte[] serverWriteKey = new byte[aes==256 ? 32 : 16]; + byte[] clientWriteIV = aes == 0 ? null : new byte[16]; + byte[] serverWriteIV = aes == 0 ? null : new byte[16]; int p = 0; System.arraycopy(keyMaterial, p, clientWriteMACSecret, 0, mac.length); p += mac.length; System.arraycopy(keyMaterial, p, serverWriteMACSecret, 0, mac.length); p += mac.length; - System.arraycopy(keyMaterial, p, clientWriteKey, 0, 16); p += 16; - System.arraycopy(keyMaterial, p, serverWriteKey, 0, 16); p += 16; + System.arraycopy(keyMaterial, p, clientWriteKey, 0, clientWriteKey.length); p += clientWriteKey.length; + System.arraycopy(keyMaterial, p, serverWriteKey, 0, serverWriteKey.length); p += serverWriteKey.length; + if(clientWriteIV != null) System.arraycopy(keyMaterial,p,clientWriteIV,0,16); p += 16; + if(serverWriteIV != null) System.arraycopy(keyMaterial,p,serverWriteIV,0,16); p += 16; Digest inner; - writeRC4 = new RC4(clientWriteKey); + writeCipher = aes==0 ? (Cipher)new RC4(clientWriteKey) : (Cipher)new CBC(new AES(clientWriteKey,false),16,clientWriteIV,false); inner = sha ? (Digest)new SHA1() : (Digest)new MD5(); clientWriteMACDigest = tls ? (Digest) new HMAC(inner,clientWriteMACSecret) : (Digest)new SSLv3HMAC(inner,clientWriteMACSecret); - readRC4 = new RC4(serverWriteKey); + readCipher = aes==0 ? (Cipher)new RC4(serverWriteKey) : (Cipher)new CBC(new AES(serverWriteKey,true),16,serverWriteIV,true); inner = sha ? (Digest)new SHA1() : (Digest)new MD5(); serverWriteMACDigest = tls ? (Digest)new HMAC(inner,serverWriteMACSecret) : (Digest)new SSLv3HMAC(inner,serverWriteMACSecret); + + if(aes != 0) { + blockSize = 16; // aes block size == 16 + padBuf = new byte[mac.length+blockSize]; // worse case, 15 bytes of data, mac, and padding byte + } } private void sendFinished() throws IOException { @@ -480,10 +516,27 @@ public class SSL extends Socket { if((negotiated & 1) != 0) { computeMAC(proto,payload,off,len,clientWriteMACDigest,clientSequenceNumber); // FEATURE: Encode in place - writeRC4.process(payload,off,sendRecordBuf,0,len); - writeRC4.process(mac,0,sendRecordBuf,len,macLength); - rawOS.writeShort(len + macLength); - rawOS.write(sendRecordBuf,0, len +macLength); + if(blockSize != 0) { + int firstShot = len & ~(blockSize-1); + if(firstShot != 0) writeCipher.process(payload,off,sendRecordBuf,0,firstShot); + int _off = off + firstShot; // offset in sendRecordBuf + int _len = len - firstShot; // length of data in padBuf + if(_len != 0) System.arraycopy(payload,_off,padBuf,0,_len); + System.arraycopy(mac,0,padBuf,_len,macLength); + _len += macLength; + int extra = blockSize - (_len&~blockSize); + if(extra == 0) extra = 16; + for(int i=0;i>4]).append(hexDigit[b[i]&0xf]); + } + return sb.toString(); + } + private byte[] readHandshake() throws IOException { if(handshakeDataLength == 0) { handshakeDataStart = 0; @@ -542,9 +606,19 @@ public class SSL extends Socket { } if((negotiated & 2) != 0) { - if(len < macLength) throw new Exn("packet size < macLength"); // FEATURE: Decode in place - readRC4.process(readRecordScratch,0,readRecordBuf,0,len); + if(blockSize != 0 && (len % blockSize) != 0) throw new Exn("input not a multiple of the cipher's block size"); + readCipher.process(readRecordScratch,0,readRecordBuf,0,len); + + if(blockSize != 0) { + if(!tls) throw new Error("should never happen"); + int padding = readRecordBuf[len-1]&0xff; + if(padding >= blockSize) throw new Exn("invalid padding length: " + padding); + for(int i=0;i 112) throw new IllegalArgumentException("size > 112"); + if(size > 140) throw new IllegalArgumentException("size > 140: " + size); seed = concat(label,seed); int half_length = (secret.length + 1) / 2; @@ -684,8 +758,8 @@ public class SSL extends Socket { Digest hmac_md5 = new HMAC(new MD5(),s1); Digest hmac_sha = new HMAC(new SHA1(),s2); - byte[] md5out = new byte[112]; - byte[] shaout = new byte[120]; + byte[] md5out = new byte[144]; + byte[] shaout = new byte[140]; byte[] digest = new byte[20]; int n; @@ -721,6 +795,36 @@ public class SSL extends Socket { return ret; } + public static class CBC implements Cipher { + private final Cipher c; + private final byte[] iv; + private final int blockSize; + private final boolean reverse; + public CBC(Cipher c, int blockSize, byte[] iv, boolean reverse) { + this.c = c; + this.blockSize = blockSize; + this.iv = iv; + this.reverse = reverse; + if(iv.length != blockSize) throw new IllegalArgumentException("iv.length != blockSize"); + } + + public void process(byte[] in, int inp, byte[] out, int outp, int len) { + if((len % blockSize) != 0) throw new IllegalArgumentException("buffer must be a multiple of block size"); + while(len != 0) { + if(!reverse) { + for(int i=0;i