dev-crypto mailing list while I was writing this.
Revision History:
+
1.0 07-Dec-01 Initial Release
+
1.01 15-Mar-02 Added PKCS1 class to avoid dependancy on java.security.SecureRandom
+ 1.02 27-Mar-02 Fixed a bug which would hang the connection when more than one
+ Handshake message appeared in the same TLS Record
+
*/
public class TinySSL extends Socket {
public static void main(String[] args) {
Log.on = true;
try {
- Socket s = new TinySSL("www.paypal.com", 443);
+ Socket s = new TinySSL("www.verisign.com", 443);
PrintWriter pw = new PrintWriter(s.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
pw.println("GET / HTTP/1.0");
public InputStream getInputStream() { return is; }
public OutputStream getOutputStream() { return os; }
- public TinySSL(String host, int port) throws IOException {
+ public TinySSL(String host, int port) throws IOException { this(host, port, true); }
+ public TinySSL(String host, int port, boolean negotiateImmediately) throws IOException {
super(host, port);
hostname = host;
+ if (negotiateImmediately) negotiate();
+ }
+
+ /** negotiates the SSL connection */
+ public void negotiate() throws IOException {
os = new SSLOutputStream(super.getOutputStream());
is = new SSLInputStream(super.getInputStream());
-
os.writeClientHello();
is.readServerHandshakes();
os.sendClientHandshakes();
public int read(byte[] b, int off, int len) throws IOException {
if (pendlen == 0) {
- pend = readRecord(false);
+ pend = readRecord();
if (pend == null) return -1;
pendstart = 0;
pendlen = pend.length;
}
/** reads and decrypts exactly one record; blocks if unavailable */
- public byte[] readRecord(boolean returnHandshakes) throws IOException {
+ public byte[] readRecord() throws IOException {
// we only catch EOFException here, because anywhere else
// would be "unusual", and we *want* and EOFException in
// those cases
byte type;
- try { type = raw.readByte(); } catch (EOFException e) { return null; }
+ try { type = raw.readByte();
+ } catch (EOFException e) {
+ if (Log.on) Log.log(this, "got EOFException reading packet type");
+ return null;
+ }
byte ver_major = raw.readByte();
byte ver_minor = raw.readByte();
raw.readFully(ret);
// simply ignore ChangeCipherSpec messages -- we change as soon as we send ours
- if (type == 20) { seq_num = 0; return readRecord(returnHandshakes); }
+ if (type == 20) {
+ if (Log.on) Log.log(this, "got ChangeCipherSpec; ignoring");
+ seq_num = 0;
+ return readRecord();
+ }
byte[] decrypted_payload;
}
if (type == 21) {
- if (decrypted_payload[1] != 0)
+ if (decrypted_payload[1] > 1) {
throw new SSLException("got SSL ALERT message, level=" + decrypted_payload[0] + " code=" + decrypted_payload[1]);
- return null;
+ } else if (decrypted_payload[1] == 0) {
+ if (Log.on) Log.log(this, "server requested connection closure; returning null");
+ return null;
+ } else {
+ if (Log.on) Log.log(this, "got SSL ALERT message, level=" + decrypted_payload[0] + " code=" + decrypted_payload[1]);
+ return readRecord();
+ }
} else if (type == 22) {
- if (!returnHandshakes) {
- if (Log.on) Log.log(this, "after completion of handshake, server sent another handshake message!");
- return readRecord(false);
- }
+ if (Log.on) Log.log(this, "read a handshake");
} else if (type != 23) {
if (Log.on) Log.log(this, "unexpected record type: " + type + "; skipping");
- return readRecord(returnHandshakes);
+ return readRecord();
}
if (Log.on) Log.log(this, " returning " + decrypted_payload.length + " byte record payload");
return decrypted_payload;
}
-
+
+ private byte[] readHandshake() throws IOException {
+ // acquire a handshake message
+ byte type = (byte)read();
+ int len = ((read() & 0xff) << 16) | ((read() & 0xff) << 8) | (read() & 0xff);
+ byte[] rec = new byte[len + 4];
+ rec[0] = type;
+ rec[1] = (byte)(((len & 0x00ff0000) >> 16) & 0xff);
+ rec[2] = (byte)(((len & 0x0000ff00) >> 8) & 0xff);
+ rec[3] = (byte)((len & 0x000000ff) & 0xff);
+ if (len > 0) read(rec, 4, len);
+ return rec;
+ }
+
/** This reads the ServerHello, Certificate, and ServerHelloDone handshake messages */
public void readServerHandshakes() throws IOException {
for(;;) {
- byte[] rec = readRecord(true);
+
+ byte[] rec = readHandshake();
handshakes = concat(new byte[][] { handshakes, rec });
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(rec, 4, rec.length - 4));
switch(rec[0]) {
case 2: // ServerHello
+ if (Log.on) Log.log(this, "got ServerHello");
byte ver_major = rec[4];
byte ver_minor = rec[5];
System.arraycopy(rec, 6, server_random, 0, server_random.length);
break;
case 11: // Server's certificate(s)
+ if (Log.on) Log.log(this, "got Server Certificate(s)");
int numcertbytes = ((rec[4] & 0xff) << 16) | ((rec[5] & 0xff) << 8) | (rec[6] & 0xff);
int numcerts = 0;
X509CertificateStructure last_cert = null;
break;
case 12:
+ if (Log.on) Log.log(this, "got ServerKeyExchange");
serverKeyExchange = rec;
break;
- case 13: cert_requested = true; break;
+ case 13:
+ if (Log.on) Log.log(this, "got Request for Client Certificates");
+ cert_requested = true;
+ break;
+
case 14: if (Log.on) Log.log(this, " ServerHelloDone"); return;
default: throw new SSLException("unknown handshake of type " + rec[0]);
}
}
public void readServerFinished() throws IOException {
- byte[] rec = readRecord(true);
+
+ byte[] rec = readHandshake();
if (rec[0] != 20) throw new SSLException("expecting server Finished message, but got message of type " + rec[0]);
byte[] expectedFinished = concat(new byte[][] {
}
public int getInputBlockSize() { return engine.getInputBlockSize() - (forEncryption ? HEADER_LENGTH : 0); }
- public int getOutputBlockSize() { return engine.getInputBlockSize() - (forEncryption ? 0 : HEADER_LENGTH); }
+ public int getOutputBlockSize() { return engine.getOutputBlockSize() - (forEncryption ? 0 : HEADER_LENGTH); }
public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
return forEncryption ? encodeBlock(in, inOff, inLen) : decodeBlock(in, inOff, inLen);