Commit bb4967a6 authored by robertdavidgraham's avatar robertdavidgraham
Browse files

ssl

parent 87443acf
Loading
Loading
Loading
Loading

src/proto-ssl.c

0 → 100644
+606 −0
Original line number Diff line number Diff line
/*
    SSL parser

    This parses out the SSL "certificate" and "ephemeral keys", and
    any other information we want from SSL.

    BIZARRE CODE ALERT: This module uses "state-machines" to parse
    SSL. This has a number of advantages, such as handling TCP
    segmentation and SSL record fragmentation without having to 
    buffer any packets. But it's quite weird if you aren't used to
    it.
*/
#include "proto-ssl.h"
#include "unusedparm.h"

/**
 * Fugly macro for doing state-machine parsing
 */
#define DROPDOWN(i,length,state) (state)++;if (++(i)>=(length)) break


/***************************************************************************
       struct {
           ProtocolVersion server_version;
           Random random;
           SessionID session_id;
           CipherSuite cipher_suite;
           CompressionMethod compression_method;
       } ServerHello;
 ***************************************************************************/
static void
server_hello(
        struct Banner1 *banner1,
        void *banner1_private,
        struct Banner1State *pstate,
        const unsigned char *px, size_t length,
        char *banner, unsigned *banner_offset, size_t banner_max)
{
    struct SSL_SERVER_HELLO *hello = &pstate->sub.ssl.x.server_hello;
    unsigned state = hello->state;
    unsigned remaining = hello->remaining;
	unsigned i;
    enum {
        VERSION_MAJOR, VERSION_MINOR,
        TIME0, TIME1, TIME2, TIME3,
        RANDOM,
        SESSION_LENGTH, SESSION_ID,
        CIPHER0, CIPHER1,
        COMPRESSION,
        LENGTH0, LENGTH1,
        UNKNOWN,
    };

    UNUSEDPARM(banner1);
    UNUSEDPARM(banner);
    UNUSEDPARM(banner_offset);
    UNUSEDPARM(banner_max);
    UNUSEDPARM(banner1_private);

    for (i=0; i<length; i++) 
    switch (state) {
    case VERSION_MAJOR:
        hello->version_major = px[i];
        DROPDOWN(i,length,state);

    case VERSION_MINOR:
        hello->version_minor = px[i];
        if (hello->version_major > 3 || hello->version_minor > 4) {
            state = UNKNOWN;
            break;
        }
        hello->timestamp = 0;
        DROPDOWN(i,length,state);

    case TIME0:
        hello->timestamp <<= 8;
        hello->timestamp |= px[i];
        DROPDOWN(i,length,state);
    case TIME1:
        hello->timestamp <<= 8;
        hello->timestamp |= px[i];
        DROPDOWN(i,length,state);
    case TIME2:
        hello->timestamp <<= 8;
        hello->timestamp |= px[i];
        DROPDOWN(i,length,state);
    case TIME3:
        hello->timestamp <<= 8;
        hello->timestamp |= px[i];
        remaining = 28;
        DROPDOWN(i,length,state);
    case RANDOM:
        {
            unsigned len = (unsigned)length-i;
            if (len > remaining)
                len = remaining;

            remaining -= len;
            i += len-1;

            if (remaining != 0) {
                break;
            }
        }
        DROPDOWN(i,length,state);

    case SESSION_LENGTH:
        remaining = px[i];
        DROPDOWN(i,length,state);

    case SESSION_ID:
        {
            unsigned len = (unsigned)length-i;
            if (len > remaining)
                len = remaining;

            remaining -= len;
            i += len-1;

            if (remaining != 0) {
                break;
            }
        }
        hello->cipher_suite = 0;
        DROPDOWN(i,length,state);

    case CIPHER0:
        hello->cipher_suite <<= 8;
        hello->cipher_suite |= px[i];
        DROPDOWN(i,length,state);

    case CIPHER1:
        hello->cipher_suite <<= 8;
        hello->cipher_suite |= px[i]; /* cipher-suite recorded here */
        DROPDOWN(i,length,state);

    case COMPRESSION:
        hello->compression_method = px[i];
        DROPDOWN(i,length,state);

    case LENGTH0: 
        remaining = px[i];
        DROPDOWN(i,length,state);

    case LENGTH1:
        remaining <<= 8;
        remaining |= px[i];
        DROPDOWN(i,length,state);
        break;

    case UNKNOWN:
    default:
        i = (unsigned)length;
    }

    hello->state = state;
    hello->remaining = remaining;
}

enum {
    CERT_COPY_FINISH=0,
    CERT_COPY_START=1,
};



/***************************************************************************
 ***************************************************************************/
static void
out_b64(unsigned x, char *banner, unsigned *banner_offset, size_t banner_max)
{
    static const char *b64 =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789"
        "+/";
    if (*banner_offset < banner_max)
        banner[(*banner_offset)++] = b64[(x>>18)&0x3F];
    if (*banner_offset < banner_max)
        banner[(*banner_offset)++] = b64[(x>>12)&0x3F];
    if (*banner_offset < banner_max)
        banner[(*banner_offset)++] = b64[(x>> 6)&0x3F];
    if (*banner_offset < banner_max)
        banner[(*banner_offset)++] = b64[(x>> 0)&0x3F];
}

/***************************************************************************
 ***************************************************************************/
static void
server_cert_copy(   struct SSL_SERVER_CERT *data,
                    const unsigned char *px,
                    unsigned length, 
                    char *banner, unsigned *banner_offset, size_t banner_max)
{
    unsigned state = data->state;
    unsigned b64x = data->b64x;
    unsigned i;

    /*
     * Initialize
     */
    if (px == 0 && length == CERT_COPY_START) {
        data->cert_state = 0;
        data->b64x = 0;
        return;
    }

    /*
     * Convert to base64
     */
    for (i=0; i<length; i++)
    switch (state) {
    case 0:
        b64x = px[i];
        DROPDOWN(i,length,state);
    case 1:
        b64x = b64x * 256 + px[i];
        DROPDOWN(i,length,state);
    case 2:
        b64x = b64x * 256 + px[i];
        state = 0;
        out_b64(b64x, banner, banner_offset, banner_max);
    }

    /*
     * Finalize: we need to put the final touches on the
     * base64 encoding
     */
    if (px == 0) {
        unsigned zzz = (*banner_offset) - 1;
        switch (state) {
        case 0:
            break;
        case 1:
            b64x *= 256;
        case 2:
            b64x *= 256;
        }

        out_b64(b64x, banner, banner_offset, banner_max);

        switch (state) {
        case 0:
            break;
        case 1:
            banner[zzz--] = '=';
        case 2:
            banner[zzz--] = '=';
        }
    }

    data->state = state;
    data->b64x = b64x;
}

/***************************************************************************
 ***************************************************************************/
static void
server_cert(
        struct Banner1 *banner1,
        void *banner1_private,
        struct Banner1State *pstate,
        const unsigned char *px, size_t length,
        char *banner, unsigned *banner_offset, size_t banner_max)
{
    struct SSL_SERVER_CERT *data = &pstate->sub.ssl.x.server_cert;
    unsigned state = data->state;
    unsigned remaining = data->remaining;
    unsigned cert_remaining = data->cert_remaining;
	unsigned i;
    enum {
        LEN0, LEN1, LEN2,
        CLEN0, CLEN1, CLEN2,
        CERT,
        UNKNOWN,
    };

    UNUSEDPARM(banner1);
    UNUSEDPARM(banner1_private);

    for (i=0; i<length; i++) 
    switch (state) {
    case LEN0:
        remaining = px[i];
        DROPDOWN(i,length,state);
    case LEN1:
        remaining = remaining * 256 + px[i];
        DROPDOWN(i,length,state);
    case LEN2:
        remaining = remaining * 256 + px[i];
        DROPDOWN(i,length,state);

    case CLEN0:
        if (remaining < 3) {
            state = UNKNOWN;
            continue;
        }
        cert_remaining = px[i];
        DROPDOWN(i,length,state);
    case CLEN1:
        cert_remaining = cert_remaining * 256 + px[i];
        DROPDOWN(i,length,state);
    case CLEN2:
        cert_remaining = cert_remaining * 256 + px[i];
        DROPDOWN(i,length,state);
        data->cert_state = 0;
        server_cert_copy(data,  0,1,  0,0,0);

    case CERT:
        {
            unsigned len = (unsigned)length-i;
            if (len > remaining)
                len = remaining;
            if (len > cert_remaining)
                len = cert_remaining;

            /* parse the certificate */
            server_cert_copy(data, px+i, len, banner, banner_offset, banner_max);


            remaining -= len;
            cert_remaining -= len;
            i += len-1;

            if (cert_remaining == 0) {
                /* We've reached the end of the certificate, so make
                 * a record of it */
                server_cert_copy(data,  0,1,  0,0,0);
                state = CLEN0;
            }
        }
        break;


    case UNKNOWN:
    default:
        i = (unsigned)length;
    }

    data->state = state;
    data->remaining = remaining;
    data->cert_remaining = cert_remaining;
}

/***************************************************************************
 ***************************************************************************/
static void
content_parse(
        struct Banner1 *banner1,
        void *banner1_private,
        struct Banner1State *pstate,
        const unsigned char *px, size_t length,
        char *banner, unsigned *banner_offset, size_t banner_max)
{
    struct SSLRECORD *ssl = &pstate->sub.ssl;
    unsigned state = ssl->record.state;
    unsigned remaining = ssl->record.remaining;
	unsigned i;
    enum {
        START,
        LENGTH0, LENGTH1, LENGTH2,
        CONTENTS,
        UNKNOWN,
    };

    for (i=0; i<length; i++) 
    switch (state) {
    case START:
        if (px[i] & 0x80) {
            state = UNKNOWN;
            break;
        }
        remaining = 0;
        ssl->record.type = px[i];
        ssl->x.all.state = 0;
        DROPDOWN(i,length,state);

    case LENGTH0: 
        remaining = px[i];
        DROPDOWN(i,length,state);

    case LENGTH1:
        remaining <<= 8;
        remaining |= px[i];
        DROPDOWN(i,length,state);

    case LENGTH2:
        remaining <<= 8;
        remaining |= px[i];
        DROPDOWN(i,length,state);

    case CONTENTS:
        {
            unsigned len = (unsigned)length-i;
            if (len > remaining)
                len = remaining;

            switch (ssl->record.type) {
            case 0x02: /* server hello */
                server_hello(      banner1,
                                    banner1_private,
                                    pstate,
                                    px+i, len,
                                    banner, banner_offset, banner_max);
                break;
            case 0x0b: /* server certificate */
                server_cert(        banner1,
                                    banner1_private,
                                    pstate,
                                    px+i, len,
                                    banner, banner_offset, banner_max);
                break;
            }

            remaining -= len;
            i += len-1;

            if (remaining == 0)
                state = START;
        }

        break;
    case UNKNOWN:
    default:
        i = (unsigned)length;
    }

    ssl->record.state = state;
    ssl->record.remaining = remaining;
}

/***************************************************************************
 * Parse just the outer record, then hands down the contents to the
 * a subparser at "content_parse()"
 ***************************************************************************/
static void
ssl_parse(
        struct Banner1 *banner1,
        void *banner1_private,
        struct Banner1State *pstate,
        const unsigned char *px, size_t length,
        char *banner, unsigned *banner_offset, size_t banner_max)
{
    unsigned state = pstate->state;
    unsigned remaining = pstate->remaining;
    struct SSLRECORD *ssl = &pstate->sub.ssl;
	unsigned i;
    enum {
        START,
        VERSION_MAJOR,
        VERSION_MINOR,
        LENGTH0, LENGTH1,
        CONTENTS,
        UNKNOWN,
    };
	
    for (i=0; i<length; i++) 
    switch (state) {
    case START:
        if (px[i] & 0x80) {
            state = UNKNOWN;
            break;
        }
        if (ssl->content_type != px[i]) {
            ssl->content_type = px[i];
            ssl->record.state = 0;
        }
        remaining = 0;
        DROPDOWN(i,length,state);

    case VERSION_MAJOR:
        ssl->version_major = px[i];
        DROPDOWN(i,length,state);

    case VERSION_MINOR:
        ssl->version_minor = px[i];
        DROPDOWN(i,length,state);

    case LENGTH0: 
        remaining = px[i]<<8;
        DROPDOWN(i,length,state);

    case LENGTH1:
        remaining |= px[i];
        DROPDOWN(i,length,state);
        ssl->record.state = 0;

    case CONTENTS:
        {
            unsigned len = (unsigned)length - i;
            if (len > remaining)
                len = remaining;

            /*
             * Parse the contents of a record
             */
            content_parse(      banner1,
                                banner1_private,
                                pstate,
                                px+i, len,
                                banner, banner_offset, banner_max);

            remaining -= len;
            i += len-1;

            if (remaining == 0)
                state = START;
        }

        break;
    case UNKNOWN:
    default:
        i = (unsigned)length;
    }

    pstate->state = state;
    pstate->remaining = remaining;
}

/***************************************************************************
 ***************************************************************************/
static void *
ssl_init(struct Banner1 *banner1)
{
    UNUSEDPARM(banner1);
    return 0;
}

/***************************************************************************
 ***************************************************************************/
static const char 
ssl_hello[] = 
"\x16\x03\x02\x01\x6f"          /* TLSv1.1 record layer */
"\x01" /* type = client-hello */
"\x00\x01\x6b" /* length = 363 */
"\x03\x02"      /* version = 3.02 (TLS 1.1) */

"\x52\x48\xc5\x1a\x23\xf7\x3a\x4e\xdf\xe2\xb4\x82\x2f\xff\x09\x54" /* random */
"\x9f\xa7\xc4\x79\xb0\x68\xc6\x13\x8c\xa4\x1c\x3d\x22\xe1\x1a\x98" /* TODO: re-randomize for each request, or at least on startup */

"\x20" /* session-id-length = 32 */
"\x84\xb4\x2c\x85\xaf\x6e\xe3\x59\xbb\x62\x68\x6c\xff\x28\x3d\x27"  /* random */
"\x3a\xa9\x82\xd9\x6f\xc8\xa2\xd7\x93\x98\xb4\xef\x80\xe5\xb9\x90"  /* TODO: re-randomize for each request, or at least on startup */

"\x00\x28" /* cipher suites length */
"\xc0\x0a\xc0\x14\x00\x39\x00\x6b\x00\x35\x00\x3d\xc0\x07\xc0\x09"
"\xc0\x23\xc0\x11\xc0\x13\xc0\x27\x00\x33\x00\x67\x00\x32\x00\x05"
"\x00\x04\x00\x2f\x00\x3c\x00\x0a"

"\x01" /* compression-methods-length = 1 */
"\x00" 

"\x00\xfa" /* extensions lkength */

/* server name */
"\x00\x00"
"\x00\x1a"
"\x00\x18\x00\x00\x15\x73\x79\x6e\x64\x69\x63\x61\x74\x69\x6f\x6e"
"\x2e\x74\x77\x69\x6d\x67\x2e\x63\x6f\x6d"

"\xff\x01"
"\x00\x01"
"\x00"

"\x00\x0a"
"\x00\x08"
"\x00\x06\x00\x17\x00\x18\x00\x19"

"\x00\x0b"
"\x00\x02"
"\x01\x00"

"\x00\x23"
"\x00\xb0"
"\x81\x01\x19\x67\x60\x1e\x04\x42\x9a\xf3\xe2\x3c\x86\x58\x4f\x87"
"\x69\x44\xb0\x1d\x8e\x01\xfa\xa5\x87\x3d\x5d\xdc\x16\x4c\xb4\x20"
"\xda\xd3\x42\xb0\x88\xec\x0a\x13\xc3\xc6\x4c\x44\x74\x7d\xf5\x83"
"\x93\xeb\x16\x60\x7e\x47\x07\x15\xae\x68\x3f\x32\xfc\x28\x71\xdd"
"\x8d\x2a\xe0\x9e\x03\xad\x28\xd9\x89\x2f\x0f\x07\xaf\xc1\x27\x8e"
"\xf1\x57\xfb\xc6\xc4\xd4\x56\x3a\xf6\xed\x59\x61\x4a\x17\x14\x0b"
"\xd7\x7c\xae\xfe\x55\xd9\x7a\xa6\xf6\xc6\x57\xb5\x3c\xed\x78\x9d"
"\xee\x39\xd8\x67\x02\x09\x92\xcb\xa5\x66\xa3\x48\x3d\x06\xed\xa5"
"\x02\x2e\x9b\x16\xf6\x2b\xe7\x3f\x79\x65\x1a\xcb\x6c\x5c\xbd\x6b"
"\xad\x11\xde\xbe\xdf\x35\xdb\x0b\xff\x2c\x90\x94\x32\xb5\x94\x57"
"\x3d\x5e\x25\xd2\x1b\xd2\x44\x85\x96\x31\x28\x69\xd7\x4a\x13\x0a"
"\x33\x74\x00\x00\x75\x4f\x00\x00\x00\x05\x00\x05\x01\x00\x00\x00"
"\x00"
;

/***************************************************************************
 ***************************************************************************/
static int
ssl_selftest(void)
{
    return 0;
}

/***************************************************************************
 ***************************************************************************/
struct Banner1Stream banner_ssl = {
    "ssl", 443, ssl_hello, sizeof(ssl_hello)-1,
    ssl_selftest,
    ssl_init,
    ssl_parse,
};

src/proto-ssl.h

0 → 100644
+7 −0
Original line number Diff line number Diff line
#ifndef PROTO_SSL_H
#define PROTO_SSL_H
#include "proto-banner1.h"

extern struct Banner1Stream banner_ssl;

#endif