1 /*
2  * @(#) $Id: SSLAnalyzer.java,v 1.4 2003/07/08 08:13:53 pankaj Exp $
3  *
4  * Copyright (c) 2002-03 by Pankaj Kumar (http://www.pankaj-k.net). 
5  * All rights reserved.
6  *
7  * The license governing the use of this file can be found in the 
8  * root directory of the containing software.
9  */
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[] { // Indent String Array
24            "", " ", "  ", "   ", "    ", "     ", "      " };
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){    // 2 byte record header. No padding.
08                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){    // TLSv1 cipher suite
44                int cipherSuiteIndex = (int)ba[2];
45                if (cipherSuiteIndex < TLSv1CipherSuites.length)
46                    return TLSv1CipherSuites[cipherSuiteIndex];
47            } else if (ba[1] == 0x00) { // Could be SSLv2 cipher suite
48                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            //printHexData(indent + 2, "Certificate", cert);
98            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);        // Need to eat up these 3 bytes.
06                    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                } // if(recordHeader.parse(...
66            } // while
67        } // if (clientHello.parse( ...
68    }
69}
70