1
10package org.jstk.ssl;
11
12import java.net.*;
13import java.io.*;
14import java.util.Collection;
15import java.util.Iterator;
16import java.security.cert.CertificateException;
17import java.security.cert.X509Certificate;
18import java.security.cert.CertificateFactory;
19import org.jstk.JSTKUtil;
20
21public class SSLAnalyzer implements ProtocolAnalyzer {
22 public static abstract class SSLProtocolMessage implements Cloneable {
23 protected static String[] isa = new String[] { "", " ", " ", " ", " ", " ", " " };
25 protected int consumed;
26 public abstract boolean parse(byte[] buf, int offset, int n);
27 public abstract void print(int indent);
28 public int bytesConsumed(){
29 return consumed;
30 }
31 public int int24(byte[] buf, int off){
32 return ((buf[off] & 0x000000ff) << 16) |
33 ((buf[off + 1] & 0x000000ff) << 8) |
34 (buf[off + 2] & 0x000000ff);
35 }
36 public int int16(byte[] buf, int off){
37 return ((buf[off] & 0x000000ff) << 8) | (buf[off + 1] & 0x000000ff);
38 }
39 public void printHexData(int indent, String label, byte[] buf){
40 System.out.println(isa[indent] + label + "[" + buf.length + "]:");
41 String[] sa = JSTKUtil.hexStringArrayFromBytes(buf, 16);
42 for (int i = 0; i < sa.length; i++)
43 System.out.println(isa[indent] + " " + sa[i]);
44 }
45
46 public boolean matchHeader(byte[] buf, int off, int n, byte type){
47 if (buf[off] != type)
48 return false;
49 int length = int24(buf, off + 1);
50 if (off + 4 + length > n)
51 return false;
52 return true;
53 }
54 }
55
56 public static class SSLv2ClientHelloMessage extends SSLProtocolMessage {
57 private static String[] SSLv2CipherSuites = new String[] {
58 "Unknown Cipher Suite",
59 "SSL_RC4_128_WITH_MD5",
60 "SSL_RC4_128_EXPORT40_WITH_MD5",
61 "SSL_RC2_CBC_128_CBC_WITH_MD5",
62 "SSL_RC2_CBC_128_CBC_EXPORT40_WITH_MD5",
63 "SSL_IDEA_128_CBC_WITH_MD5",
64 "SSL_DES_64_CBC_WITH_MD5",
65 "SSL_DES_192_EDE3_CBC_WITH_MD5" };
66 private static String[] TLSv1CipherSuites = new String[] {
67 "Unknown Cipher Suite",
68 "TLS_RSA_WITH_NULL_MD5",
69 "TLS_RSA_WITH_NULL_SHA",
70 "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
71 "TLS_RSA_WITH_RC4_128_MD5",
72 "TLS_RSA_WITH_RC4_128_SHA",
73 "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
74 "TLS_RSA_WITH_IDEA_CBC_SHA",
75 "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
76 "TLS_RSA_WITH_DES_CBC_SHA",
77 "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
78 "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
79 "TLS_DH_DSS_WITH_DES_CBC_SHA",
80 "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
81 "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
82 "TLS_DH_RSA_WITH_DES_CBC_SHA",
83 "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
84 "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
85 "TLS_DHE_DSS_WITH_DES_CBC_SHA",
86 "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
87 "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
88 "TLS_DHE_RSA_WITH_DES_CBC_SHA",
89 "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
90 "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
91 "TLS_DH_anon_WITH_RC4_128_MD5",
92 "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
93 "TLS_DH_anon_WITH_DES_CBC_SHA",
94 "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" };
95
96 int majorVersion;
97 int minorVersion;
98 int cipherSpecsLen;
99 int sessionIdLen;
00 int challengeLen;
01 byte[] cipherSpecsData;
02 byte[] sessionIdData;
03 byte[] challengeData;
04 public boolean parse(byte[] buf, int offset, int n){
05 int recordLength;
06 consumed = 0;
07 if ((buf[offset] & (byte)0x80) != 0x00){ recordLength = ((int)(buf[offset] & 0x7f)) << 8 | buf[offset + 1];
09 if (recordLength > n - 2)
10 return false;
11 consumed += 2;
12 if (buf[offset + consumed] != 0x01)
13 return false;
14 consumed += 1;
15 majorVersion = buf[offset + consumed];
16 minorVersion = buf[offset + consumed + 1];
17 consumed += 2;
18 cipherSpecsLen = ((int)(buf[offset + consumed])) << 8 | buf[offset + consumed + 1];
19 consumed += 2;
20 sessionIdLen = ((int)(buf[offset + consumed])) << 8 | buf[offset + consumed + 1];
21 consumed += 2;
22 challengeLen = ((int)(buf[offset + consumed])) << 8 | buf[offset + consumed + 1];
23 consumed += 2;
24 if (offset + consumed + cipherSpecsLen + sessionIdLen + challengeLen > n)
25 return false;
26
27 cipherSpecsData = new byte[cipherSpecsLen];
28 System.arraycopy(buf, offset + consumed, cipherSpecsData, 0, cipherSpecsLen);
29 consumed += cipherSpecsLen;
30
31 sessionIdData = new byte[sessionIdLen];
32 System.arraycopy(buf, offset + consumed, sessionIdData, 0, sessionIdLen);
33 consumed += sessionIdLen;
34
35 challengeData = new byte[challengeLen];
36 System.arraycopy(buf, offset + consumed, challengeData, 0, challengeLen);
37 consumed += challengeLen;
38 return true;
39 }
40 return false;
41 }
42 private String cipherSuite(byte[] ba){
43 if (ba[0] == 0x00 && ba[1] == 0x00){ int cipherSuiteIndex = (int)ba[2];
45 if (cipherSuiteIndex < TLSv1CipherSuites.length)
46 return TLSv1CipherSuites[cipherSuiteIndex];
47 } else if (ba[1] == 0x00) { int cipherSuiteIndex = (int)ba[0];
49 if (cipherSuiteIndex < SSLv2CipherSuites.length)
50 return SSLv2CipherSuites[cipherSuiteIndex];
51 }
52 return "Unknown Cipher Suite";
53 }
54 public void print(int indent){
55 System.out.println(isa[indent] + "ClientHello (SSLv2 format mapped to TLSv1 fields)");
56 System.out.println(isa[indent] + " Version " + majorVersion + "." + minorVersion);
57 System.out.println(isa[indent] + " Random[" + challengeLen + "]:");
58 int noCipherSuites = cipherSpecsLen/3;
59 System.out.println(isa[indent] + " Cipher Suites[" + noCipherSuites + "]:");
60 for (int i = 0; i < noCipherSuites; i++){
61 byte[] ba = new byte[3];
62 System.arraycopy(cipherSpecsData, i*3, ba, 0, 3);
63 System.out.println(isa[indent] + " " + cipherSuite(ba) +
64 " (0x" + JSTKUtil.hexStringFromBytes(ba) + ")");
65 }
66 }
67 }
68
69 public static class ProtocolVersion {
70 private int majorVersion;
71 private int minorVersion;
72 public ProtocolVersion(byte[] buf, int offset){
73 majorVersion = buf[offset];
74 minorVersion = buf[offset + 1];
75 }
76 public String toString(){
77 return "" + majorVersion + "." + minorVersion;
78 }
79 }
80
81 public static class SSLRecordHeader extends SSLProtocolMessage {
82 public static final byte CHANGE_CIPHER_SPEC = 0x14;
83 public static final byte ALERT = 0x15;
84 public static final byte HAND_SHAKE = 0x16;
85 public static final byte APPLICATION_DATA = 0x17;
86 byte contentType;
87 ProtocolVersion version;
88 int length;
89
90 public boolean parse(byte[] buf, int offset, int n){
91 consumed = 0;
92 contentType = buf[offset + consumed];
93
94 if (contentType < 0x14 || contentType > 0x17)
95 return false;
96 ++consumed;
97 version = new ProtocolVersion(buf, offset + consumed);
98 consumed += 2;
99 length = int16(buf, offset + consumed);
00 consumed += 2;
01 if (offset + consumed + length > n)
02 return false;
03 return true;
04 }
05 public byte getContentType(){
06 return contentType;
07 }
08 public String getContentTypeAsString(){
09 switch (contentType){
10 case CHANGE_CIPHER_SPEC:
11 return "ChangeCipherSpec";
12 case ALERT:
13 return "Alert";
14 case HAND_SHAKE:
15 return "HandShake";
16 case APPLICATION_DATA:
17 return "ApplicationData";
18 }
19 return "UnRecognized";
20 }
21 public int getContentLength(){
22 return length;
23 }
24 public void print(int indent){
25 System.out.println(isa[indent] + "Record Header. " +
26 "(ContentType: " + getContentTypeAsString() +
27 ", Version: " + version +
28 ", Len: " + length + ")");
29 }
30 }
31
32 public static class ServerHello extends SSLProtocolMessage {
33 public static byte SERVER_HELLO = 2;
34 int length;
35 ProtocolVersion version;
36
37 byte[] random = new byte[32];
38 byte[] sessionId = null;
39 int cipherSuite;
40 byte compMethod;
41
42 public boolean parse(byte[] buf, int offset, int n){
43 if (!matchHeader(buf, offset, n, SERVER_HELLO))
44 return false;
45 consumed = 4;
46 version = new ProtocolVersion(buf, offset + consumed);
47
48 consumed += 2;
49 System.arraycopy(buf, offset + consumed, random, 0, 32);
50 consumed += 32;
51
52 int sessionIdLen = buf[offset + consumed] & 0x000000ff;
53 if (sessionIdLen > 32)
54 return false;
55 ++consumed;
56 sessionId = new byte[sessionIdLen];
57 System.arraycopy(buf, offset + consumed, sessionId, 0, sessionIdLen);
58 consumed += sessionIdLen;
59
60 cipherSuite = int16(buf, offset + consumed);
61 consumed += 2;
62
63 compMethod = buf[offset + consumed];
64 ++consumed;
65 return true;
66 }
67 public void print(int indent){
68 System.out.println(isa[indent] + "ServerHello. (Version: " + version + ", Len: " + length +")");
69
70 printHexData(indent + 2, "Random", random);
71 printHexData(indent + 2, "Session Id", sessionId);
72
73 System.out.println(isa[indent] + " Cipher Suite: " + cipherSuite);
74 System.out.println(isa[indent] + " Compression Method: " + (int)compMethod);
75 }
76 }
77
78 public static class SSLCertificate extends SSLProtocolMessage {
79 public static byte CERTIFICATE = 11;
80 byte[] cert = null;
81
82 public boolean parse(byte[] buf, int offset, int n){
83 if (!matchHeader(buf, offset, n, CERTIFICATE))
84 return false;
85 consumed = 4;
86
87 int certLen = int24(buf, offset + consumed);
88 consumed += 3;
89
90 cert = new byte[certLen];
91 System.arraycopy(buf, offset + consumed, cert, 0, certLen);
92 consumed += certLen;
93 return true;
94 }
95 public void print(int indent){
96 System.out.println(isa[indent] + "Certificate. (Bytes: " + cert.length +")");
97 try {
99 CertificateFactory cf = CertificateFactory.getInstance("X.509");
00 ByteArrayInputStream bais = new ByteArrayInputStream(cert);
01
02 int index = 0;
03 while (bais.available() > 0){
04 byte[] ba = new byte[3];
05 bais.read(ba, 0, 3); int len = int24(ba, 0);
07 X509Certificate c = (X509Certificate)cf.generateCertificate(bais);
08 System.out.println(isa[indent] + "Certificate[" + index + "]:");
09 System.out.println(isa[indent] + " Data:");
10 System.out.println(isa[indent] + " Version: " + c.getVersion());
11 System.out.println(isa[indent] + " Serial Number: " + c.getSerialNumber());
12 System.out.println(isa[indent] + " Signature Algorithm: " + c.getSigAlgName());
13 System.out.println(isa[indent] + " Issuer: " + c.getIssuerX500Principal());
14 System.out.println(isa[indent] + " Validity:");
15 System.out.println(isa[indent] + " Not Before: " + c.getNotBefore());
16 System.out.println(isa[indent] + " Not After: " + c.getNotAfter());
17 System.out.println(isa[indent] + " Subject: " + c.getSubjectX500Principal());
18 System.out.println(isa[indent] + " Extensions:");
19 System.out.println(isa[indent] + " Basic Constraints: " + c.getBasicConstraints());
20 ++index;
21 }
22 } catch (CertificateException ce) {
23 System.out.println("Cannot parse input as Certificate");
24 }
25 }
26 }
27
28 public static class ServerHelloDone extends SSLProtocolMessage {
29 public static byte SERVER_HELLO_DONE = 14;
30
31 public boolean parse(byte[] buf, int offset, int n){
32 if (!matchHeader(buf, offset, n, SERVER_HELLO_DONE))
33 return false;
34 consumed = 4;
35 return true;
36 }
37 public void print(int indent){
38 System.out.println(isa[indent] + "ServerHelloDone.");
39 }
40 }
41
42 public static class ClientKeyExchange extends SSLProtocolMessage {
43 public static byte CLIENT_KEY_EXCHANGE = 16;
44 byte[] data = null;
45
46 public boolean parse(byte[] buf, int offset, int n){
47 if (!matchHeader(buf, offset, n, CLIENT_KEY_EXCHANGE))
48 return false;
49 consumed = 4;
50 int length = int24(buf, offset + 1);
51 data = new byte[length];
52 System.arraycopy(buf, offset + consumed, data, 0, length);
53 consumed += length;
54 return true;
55 }
56 public void print(int indent){
57 System.out.println(isa[indent] + "ClientKeyExchange.");
58 printHexData(indent + 2, "Client Key Exchange Data", data);
59 }
60 }
61
62 public static class ServerKeyExchange extends SSLProtocolMessage {
63 public static byte SERVER_KEY_EXCHANGE = 12;
64 byte[] data = null;
65
66 public boolean parse(byte[] buf, int offset, int n){
67 if (!matchHeader(buf, offset, n, SERVER_KEY_EXCHANGE))
68 return false;
69 consumed = 4;
70 int length = int24(buf, offset + 1);
71 data = new byte[length];
72 System.arraycopy(buf, offset + consumed, data, 0, length);
73 consumed += length;
74 return true;
75 }
76 public void print(int indent){
77 System.out.println(isa[indent] + "ServerKeyExchange.");
78 printHexData(indent + 2, "Server Key Excahnge Data", data);
79 }
80 }
81
82 public static class Finished extends SSLProtocolMessage {
83 public static byte FINISHED = 20;
84 byte[] verifyData = null;
85 byte[] md5Hash = null;
86 byte[] shaHash = null;
87
88 public boolean parse(byte[] buf, int offset, int n){
89 if (!matchHeader(buf, offset, n, FINISHED))
90 return false;
91 consumed = 4;
92 int length = int24(buf, offset + 1);
93
94 verifyData = new byte[length];
95 System.arraycopy(buf, offset + consumed, verifyData, 0, length);
96 consumed += length;
97 return true;
98 }
99 public void print(int indent){
00 System.out.println(isa[indent] + "Finished.");
01 printHexData(indent + 2, "Verify Data", verifyData);
02 }
03 }
04
05 public static class SSLProtocolMessageFactory {
06 private static SSLProtocolMessage[] msgs = new SSLProtocolMessage[] {
07 new ServerHello(),
08 new SSLCertificate(),
09 new ServerHelloDone(),
10 new ServerKeyExchange(),
11 new ClientKeyExchange(),
12 new Finished(),
13 new SSLRecordHeader()
14 };
15
16 public static SSLProtocolMessage createProtocolMessage(byte[] buf, int offset, int n){
17 for (int i = 0; i < msgs.length; i++){
18 if (msgs[i].parse(buf, offset, n))
19 return msgs[i];
20 }
21 return null;
22 }
23 }
24
25
26 private String label = null;
27 public SSLAnalyzer(String label) {
28 this.label = label;
29 }
30
31 public void analyze(JSTKBuffer buf){
32 int n = buf.getNBytes();
33 byte[] tbuf = buf.getByteArray();
34 System.out.println("[SSL] C " + label + " S (" + buf.getNBytes() + " bytes)");
35 SSLv2ClientHelloMessage clientHello = new SSLv2ClientHelloMessage();
36 SSLRecordHeader recordHeader = new SSLRecordHeader();
37
38 if (clientHello.parse(tbuf, 0, n)){
39 clientHello.print(2);
40 } else {
41 int offset = 0;
42 while (offset < n){
43 if (recordHeader.parse(tbuf, offset, n)){
44 recordHeader.print(2);
45 offset += recordHeader.bytesConsumed();
46 int recordLimit = offset + recordHeader.getContentLength();
47 while (offset < recordLimit) {
48 if (recordHeader.getContentType() == recordHeader.HAND_SHAKE){
49 SSLProtocolMessage sslPM = SSLProtocolMessageFactory.createProtocolMessage(tbuf, offset, n);
50 if (sslPM != null){
51 sslPM.print(4);
52 offset += sslPM.bytesConsumed();
53 } else {
54 System.out.println("Encrypted Handshake Message");
55 offset += recordHeader.getContentLength();
56 }
57 } else {
58 System.out.println("Encrypted " + recordHeader.getContentTypeAsString() + " Message");
59 offset += recordHeader.getContentLength();
60 }
61 }
62 } else {
63 System.out.println("Unknown format.");
64 offset = n;
65 } } } }
69}
70