diff --git a/platform/src/ssl_utils.cc b/platform/src/ssl_utils.cc index ed129d1..5549eb0 100644 --- a/platform/src/ssl_utils.cc +++ b/platform/src/ssl_utils.cc @@ -1644,444 +1644,485 @@ int ssl_is_ocspreq(const unsigned char * buf, size_t sz) return 1; } -/* - * Ugly hack to manually parse a clientHello message from a memory buffer. - * This is needed in order to be able to support SNI and STARTTLS. - * - * The OpenSSL SNI API only allows to read the indicated server name at the - * time when we have to provide the server cert. OpenSSL does not - * allow to asynchroniously read the indicated server name, wait for some - * unrelated event to happen, and then later to provide the server cert - * to use and continue the handshake. Therefore we resort to parsing the - * server name from the ClientHello manually before OpenSSL gets to work on it. - * - * For STARTTLS support in autossl mode, we need to peek into the buffer of - * received octets and decide whether we have something that resembles a - * (possibly incomplete) ClientHello message, so we can upgrade the connection - * to SSL automatically. - * - * This function takes a buffer containing (part of) a ClientHello message as - * seen on the network as input. - * - * Returns: - * 1 if buf does not contain a complete ClientHello message; - * *clienthello may point to the start of a truncated ClientHello message, - * indicating that the caller should retry later with more bytes available - * 0 if buf contains a complete ClientHello message; - * *clienthello will point to the start of the complete ClientHello message - * - * If a servername pointer was supplied by the caller, and a server name - * extension was found and parsed, the server name is returned in *servername - * as a newly allocated string that must be freed by the caller. This may - * only occur for a return value of 0. - * - * If search is non-zero, then the buffer will be searched for a ClientHello - * message beginning at offsets >= 0, whereas if search is zero, only - * ClientHello messages starting at offset 0 will be considered. - * - * This code currently supports SSL 2.0, SSL 3.0 and TLS 1.0-1.2. - * - * References: - * draft-hickman-netscape-ssl-00: The SSL Protocol - * RFC 6101: The Secure Sockets Layer (SSL) Protocol Version 3.0 - * RFC 2246: The TLS Protocol Version 1.0 - * RFC 3546: Transport Layer Security (TLS) Extensions - * RFC 4346: The Transport Layer Security (TLS) Protocol Version 1.1 - * RFC 4366: Transport Layer Security (TLS) Extensions - * RFC 5246: The Transport Layer Security (TLS) Protocol Version 1.2 - * RFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions - */ -int -ssl_tls_clienthello_parse( - const unsigned char * buf, ssize_t sz, int search, - const unsigned char ** clienthello, char ** servername) + +struct cipher_suite { -#ifdef DEBUG_CLIENTHELLO_PARSER -#define DBG_printf(...) log_dbg_printf("ClientHello parser: " __VA_ARGS__) -#else /* !DEBUG_CLIENTHELLO_PARSER */ -#define DBG_printf(...) -#endif /* !DEBUG_CLIENTHELLO_PARSER */ - const unsigned char * p = buf; - ssize_t n = sz; - char * sn = NULL; + int value; + char* name; +}; - ssize_t tlsextslen; - ssize_t sidlen; - ssize_t suiteslen; - ssize_t compslen; - ssize_t msglen; - ssize_t recordlen; +struct cipher_suite cipher_suite_list[] = +{ + {0xC030, "ECDHE-RSA-AES256-GCM-SHA384"}, + {0xC02C, "ECDHE-ECDSA-AES256-GCM-SHA384"}, + {0xC028, "ECDHE-RSA-AES256-SHA384"}, + {0xC024, "ECDHE-ECDSA-AES256-SHA384"}, + {0xC014, "ECDHE-RSA-AES256-SHA"}, + {0xC00A, "ECDHE-ECDSA-AES256-SHA"}, + {0x00A5, "DH-DSS-AES256-GCM-SHA384"}, + {0x00A3, "DHE-DSS-AES256-GCM-SHA384"}, + {0x00A1, "DH-RSA-AES256-GCM-SHA384"}, + {0x009F, "DHE-RSA-AES256-GCM-SHA384"}, + {0x006B, "DHE-RSA-AES256-SHA256"}, + {0x006A, "DHE-DSS-AES256-SHA256"}, + {0x0069, "DH-RSA-AES256-SHA256"}, + {0x0068, "DH-DSS-AES256-SHA256"}, + {0x0039, "DHE-RSA-AES256-SHA"}, + {0x0038, "DHE-DSS-AES256-SHA"}, + {0x0037, "DH-RSA-AES256-SHA"}, + {0x0036, "DH-DSS-AES256-SHA"}, + {0x0088, "DHE-RSA-CAMELLIA256-SHA"}, + {0x0087, "DHE-DSS-CAMELLIA256-SHA"}, + {0x0086, "DH-RSA-CAMELLIA256-SHA"}, + {0x0085, "DH-DSS-CAMELLIA256-SHA"}, + {0xC019, "AECDH-AES256-SHA"}, + {0x00A7, "ADH-AES256-GCM-SHA384"}, + {0x006D, "ADH-AES256-SHA256"}, + {0x003A, "ADH-AES256-SHA"}, + {0x0089, "ADH-CAMELLIA256-SHA"}, + {0xC032, "ECDH-RSA-AES256-GCM-SHA384"}, + {0xC02E, "ECDH-ECDSA-AES256-GCM-SHA384"}, + {0xC02A, "ECDH-RSA-AES256-SHA384"}, + {0xC026, "ECDH-ECDSA-AES256-SHA384"}, + {0xC00F, "ECDH-RSA-AES256-SHA"}, + {0xC005, "ECDH-ECDSA-AES256-SHA"}, + {0x009D, "AES256-GCM-SHA384"}, + {0x003D, "AES256-SHA256"}, + {0x0035, "AES256-SHA"}, + {0x0084, "CAMELLIA256-SHA"}, + {0x008D, "PSK-AES256-CBC-SHA"}, + {0xC02F, "ECDHE-RSA-AES128-GCM-SHA256"}, + {0xC02B, "ECDHE-ECDSA-AES128-GCM-SHA256"}, + {0xC027, "ECDHE-RSA-AES128-SHA256"}, + {0xC023, "ECDHE-ECDSA-AES128-SHA256"}, + {0xC013, "ECDHE-RSA-AES128-SHA"}, + {0xC009, "ECDHE-ECDSA-AES128-SHA"}, + {0x00A4, "DH-DSS-AES128-GCM-SHA256"}, + {0x00A2, "DHE-DSS-AES128-GCM-SHA256"}, + {0x00A0, "DH-RSA-AES128-GCM-SHA256"}, + {0x009E, "DHE-RSA-AES128-GCM-SHA256"}, + {0x0067, "DHE-RSA-AES128-SHA256"}, + {0x0040, "DHE-DSS-AES128-SHA256"}, + {0x003F, "DH-RSA-AES128-SHA256"}, + {0x003E, "DH-DSS-AES128-SHA256"}, + {0x0033, "DHE-RSA-AES128-SHA"}, + {0x0032, "DHE-DSS-AES128-SHA"}, + {0x0031, "DH-RSA-AES128-SHA"}, + {0x0030, "DH-DSS-AES128-SHA"}, + {0x009A, "DHE-RSA-SEED-SHA"}, + {0x0099, "DHE-DSS-SEED-SHA"}, + {0x0098, "DH-RSA-SEED-SHA"}, + {0x0097, "DH-DSS-SEED-SHA"}, + {0x0045, "DHE-RSA-CAMELLIA128-SHA"}, + {0x0044, "DHE-DSS-CAMELLIA128-SHA"}, + {0x0043, "DH-RSA-CAMELLIA128-SHA"}, + {0x0042, "DH-DSS-CAMELLIA128-SHA"}, + {0xC018, "AECDH-AES128-SHA"}, + {0x00A6, "ADH-AES128-GCM-SHA256"}, + {0x006C, "ADH-AES128-SHA256"}, + {0x0034, "ADH-AES128-SHA"}, + {0x009B, "ADH-SEED-SHA"}, + {0x0046, "ADH-CAMELLIA128-SHA"}, + {0xC031, "ECDH-RSA-AES128-GCM-SHA256"}, + {0xC02D, "ECDH-ECDSA-AES128-GCM-SHA256"}, + {0xC029, "ECDH-RSA-AES128-SHA256"}, + {0xC025, "ECDH-ECDSA-AES128-SHA256"}, + {0xC00E, "ECDH-RSA-AES128-SHA"}, + {0xC004, "ECDH-ECDSA-AES128-SHA"}, + {0x009C, "AES128-GCM-SHA256"}, + {0x003C, "AES128-SHA256"}, + {0x002F, "AES128-SHA"}, + {0x0096, "SEED-SHA"}, + {0x0041, "CAMELLIA128-SHA"}, + {0x008C, "PSK-AES128-CBC-SHA"}, + {0xC012, "ECDHE-RSA-DES-CBC3-SHA"}, + {0xC008, "ECDHE-ECDSA-DES-CBC3-SHA"}, + {0x0016, "EDH-RSA-DES-CBC3-SHA"}, + {0x0013, "EDH-DSS-DES-CBC3-SHA"}, + {0x0010, "DH-RSA-DES-CBC3-SHA"}, + {0x000D, "DH-DSS-DES-CBC3-SHA"}, + {0xC017, "AECDH-DES-CBC3-SHA"}, + {0x001B, "ADH-DES-CBC3-SHA"}, + {0xC00D, "ECDH-RSA-DES-CBC3-SHA"}, + {0xC003, "ECDH-ECDSA-DES-CBC3-SHA"}, + {0x000A, "DES-CBC3-SHA"}, + {0x0007, "IDEA-CBC-SHA"}, + {0x008B, "PSK-3DES-EDE-CBC-SHA"}, + {0x0021, "KRB5-IDEA-CBC-SHA"}, + {0x001F, "KRB5-DES-CBC3-SHA"}, + {0x0025, "KRB5-IDEA-CBC-MD5"}, + {0x0023, "KRB5-DES-CBC3-MD5"}, + {0xC011, "ECDHE-RSA-RC4-SHA"}, + {0xC007, "ECDHE-ECDSA-RC4-SHA"}, + {0xC016, "AECDH-RC4-SHA"}, + {0x0018, "ADH-RC4-MD5"}, + {0xC00C, "ECDH-RSA-RC4-SHA"}, + {0xC002, "ECDH-ECDSA-RC4-SHA"}, + {0x0005, "RC4-SHA"}, + {0x0004, "RC4-MD5"}, + {0x008A, "PSK-RC4-SHA"}, + {0x0020, "KRB5-RC4-SHA"}, + {0x0024, "KRB5-RC4-MD5"}, + {0xC010, "ECDHE-RSA-NULL-SHA"}, + {0xC006, "ECDHE-ECDSA-NULL-SHA"}, + {0xC015, "AECDH-NULL-SHA"}, + {0xC00B, "ECDH-RSA-NULL-SHA"}, + {0xC001, "ECDH-ECDSA-NULL-SHA"}, + {0x003B, "NULL-SHA256"}, + {0x0002, "NULL-SHA"}, + {0x0001, "NULL-MD5"} +}; - //===== +struct cipher_suite cipher_suite_list_tls13[] = +{ + {0x1301, "TLS_AES_128_GCM_SHA256"}, + {0x1302, "TLS_AES_256_GCM_SHA384"}, + {0x1303, "TLS_CHACHA20_POLY1305_SHA256"}, + {0x1304, "TLS_AES_128_CCM_SHA256"}, + {0x1305, "TLS_AES_128_CCM_8_SHA256"} +}; - *clienthello = NULL; +enum parse_chello_result +{ + PARSE_CHELLO_INVALID_FORMAT = 0, + PARSE_CHELLO_NOT_ENOUGH_BUFF, + PARSE_CHELLO_SUCCESS +}; - DBG_printf("parsing buffer of sz %zd\n", sz); - do - { - if (*clienthello) - { - /* - * Rewind after skipping an invalid ClientHello by - * restarting the search one byte after the beginning - * of the last candidate - */ - p = (*clienthello) + 1; - n = sz - (p - buf); - if (sn) - { - free(sn); - sn = NULL; - } - } +struct ssl_version +{ + int major; + int minor; +}; - if (search) - { - /* Search for a potential ClientHello */ - while ((n > 0) && (*p != 0x16) && (*p != 0x80)) - { - p++; - n--; - } - if (n <= 0) - { - /* Search completed without a match; reset - * clienthello to NULL to indicate to the - * caller that this buffer does not need to be - * retried */ - DBG_printf("===> No match:" - " rv 1, *clienthello NULL\n"); - *clienthello = NULL; - return 1; - } - } - *clienthello = p; - DBG_printf("candidate at offset %td\n", p - buf); +struct ssl_chello +{ + struct ssl_version record_layer_version; + struct ssl_version chello_version; + char* sni; + char* alpn; + char* cipher_suites; + char* cipher_suites_tls13; +}; - DBG_printf("byte 0: %02x\n", *p); - /* +0 0x80 +2 0x01 SSLv2 short header, clientHello; - * +0 0x16 +1 0x03 SSLv3/TLSv1.x handshake, clientHello */ - if (*p == 0x80) - { - /* SSLv2 handled here */ - p++; - n--; - if (n < 10) - { /* length + 9 */ - DBG_printf("===> [SSLv2] Truncated:" - " rv 1, *clienthello set\n"); - return 1; - } - - DBG_printf("length: %02x\n", p[0]); - if (n - 1 < p[0]) - { - DBG_printf("===> [SSLv2] Truncated:" - " rv 1, *clienthello set\n"); - return 1; - } - p++; - n--; - - DBG_printf("msgtype: %02x\n", p[0]); - if (*p != 0x01) - continue; - p++; - n--; - - DBG_printf("version: %02x %02x\n", p[0], p[1]); - /* byte order is actually swapped for SSLv2 */ - if (!( -#ifdef HAVE_SSLV2 - (p[0] == 0x00 && p[1] == 0x02) || -#endif /* HAVE_SSLV2 */ - (p[0] == 0x03 && p[1] <= 0x03))) - continue; - p += 2; - n -= 2; - - DBG_printf("cipher-spec-len: %02x %02x\n", p[0], p[1]); - ssize_t cipherspec_len = p[0] << 8 | p[1]; - p += 2; - n -= 2; - - DBG_printf("session-id-len: %02x %02x\n", p[0], p[1]); - ssize_t sessionid_len = p[0] << 8 | p[1]; - p += 2; - n -= 2; - - DBG_printf("challenge-len: %02x %02x\n", p[0], p[1]); - ssize_t challenge_len = p[0] << 8 | p[1]; - p += 2; - n -= 2; - if (challenge_len < 16 || challenge_len > 32) - continue; - - if (n < cipherspec_len - + sessionid_len - + challenge_len) - { - DBG_printf("===> [SSLv2] Truncated:" - " rv 1, *clienthello set\n"); - return 1; - } - - p += cipherspec_len + sessionid_len + challenge_len; - n -= cipherspec_len + sessionid_len + challenge_len; - goto done_parsing; - } - else if (*p != 0x16) - { - /* this can only happen if search is 0 */ - DBG_printf("===> No match: rv 1, *clienthello NULL\n"); - *clienthello = NULL; - return 1; - } - p++; - n--; - - if (n < 2) - { - DBG_printf("===> Truncated: rv 1, *clienthello set\n"); - return 1; - } - DBG_printf("version: %02x %02x\n", p[0], p[1]); - /* This supports up to TLS 1.2 (0x03 0x03) and will need to be - * updated for TLS 1.3 once that is standardized and still - * compatible with this parser; remember to also update the - * inner version check below */ - if (p[0] != 0x03 || p[1] > 0x03) - continue; - p += 2; - n -= 2; - - if (n < 2) - { - DBG_printf("===> Truncated: rv 1, *clienthello set\n"); - return 1; - } - DBG_printf("length: %02x %02x\n", p[0], p[1]); - - recordlen = p[1] + (p[0] << 8); - DBG_printf("recordlen=%zd\n", recordlen); - p += 2; - n -= 2; - if (recordlen < 36) /* arbitrary size too small for a c-h */ - continue; - if (n < recordlen) - { - DBG_printf("n < recordlen: n=%zd\n", n); - DBG_printf("===> Truncated: rv 1, *clienthello set\n"); - return 1; - } - - /* from here we give up on a candidate if there is not enough - * data available in the buffer, because we already checked the - * availability of the whole record. */ - - if (n < 1) - continue; - DBG_printf("message type: %i\n", *p); - if (*p != 0x01) /* message type: ClientHello */ - continue; - p++; - n--; - - if (n < 3) - continue; - DBG_printf("message len: %02x %02x %02x\n", p[0], p[1], p[2]); - msglen = p[2] + (p[1] << 8) + (p[0] << 16); - DBG_printf("msglen=%zd\n", msglen); - p += 3; - n -= 3; - if (msglen < 32) /* arbitrary size too small for a c-h */ - continue; - if (msglen != recordlen - 4) - { - DBG_printf("msglen != recordlen - 4\n"); - continue; - } - if (n < msglen) - continue; - n = msglen; /* only parse first message */ - - if (n < 2) - continue; - DBG_printf("clienthello version %02x %02x\n", p[0], p[1]); - /* inner version check, see outer one above */ - if (p[0] != 0x03 || p[1] > 0x03) - continue; - p += 2; - n -= 2; - - if (n < 32) - continue; - DBG_printf("clienthello random %02x %02x %02x %02x ...\n", - p[0], p[1], p[2], p[3]); - DBG_printf("compare localtime: %08x\n", - (unsigned int) time(NULL)); - p += 32; - n -= 32; - - if (n < 1) - continue; - DBG_printf("clienthello sidlen %02x\n", *p); - sidlen = *p; /* session id length, 0..32 */ - p += 1; - n -= 1; - if (n < sidlen) - continue; - p += sidlen; - n -= sidlen; - - if (n < 2) - continue; - DBG_printf("clienthello cipher suites length %02x %02x\n", - p[0], p[1]); - - suiteslen = p[1] + (p[0] << 8); - p += 2; - n -= 2; - if (n < suiteslen) - continue; - p += suiteslen; - n -= suiteslen; - - if (n < 1) - continue; - DBG_printf("clienthello compress methods length %02x\n", *p); - - compslen = *p; - p++; - n--; - if (n < compslen) - continue; - p += compslen; - n -= compslen; - - /* begin of extensions */ - - if (n == 0) - { - /* valid ClientHello without extensions */ - DBG_printf("===> Match: rv 0, *clienthello set\n"); - if (servername) - *servername = NULL; - return 0; - } - if (n < 2) - continue; - DBG_printf("tlsexts length %02x %02x\n", p[0], p[1]); - tlsextslen = p[1] + (p[0] << 8); - DBG_printf("tlsextslen %zd\n", tlsextslen); - p += 2; - n -= 2; - if (n < tlsextslen) - continue; - n = tlsextslen; /* only parse exts, ignore trailing bits */ - - while (n > 0) - { - if (n < 4) - goto continue_search; - DBG_printf("tlsext type %02x %02x len %02x %02x\n", - p[0], p[1], p[2], p[3]); - unsigned short exttype = p[1] + (p[0] << 8); - ssize_t extlen = p[3] + (p[2] << 8); - p += 4; - n -= 4; - if (n < extlen) - goto continue_search; - switch (exttype) - { - case 0: - { - ssize_t extn = extlen; - const unsigned char * extp = p; - - if (extn < 2) - goto continue_search; - DBG_printf("list length %02x %02x\n", - extp[0], extp[1]); - ssize_t namelistlen = extp[1] + (extp[0] << 8); - DBG_printf("namelistlen = %zd\n", namelistlen); - extp += 2; - extn -= 2; - - if (namelistlen != extn) - goto continue_search; - - while (extn > 0) - { - if (extn < 3) - goto continue_search; - DBG_printf("ServerName type %02x" - " len %02x %02x\n", - extp[0], extp[1], extp[2]); - unsigned char sntype = extp[0]; - ssize_t snlen = extp[2] + (extp[1] << 8); - extp += 3; - extn -= 3; - if (snlen > extn) - goto continue_search; - if (snlen > TLSEXT_MAXLEN_host_name) - goto continue_search; - /* - * We copy the first name only. - * RFC 6066: "The ServerNameList MUST - * NOT contain more than one name of - * the same name_type." - */ - if (servername && - sntype == 0 && sn == NULL) - { - sn = (char *)malloc(snlen + 1); - memcpy(sn, extp, snlen); - sn[snlen] = '\0'; - /* deliberately not checking - * for malformed hostnames - * containing invalid chars */ - } - extp += snlen; - extn -= snlen; - } - break; - } - default: DBG_printf("skipped\n"); - break; - } - p += extlen; - n -= extlen; - } /* while have more extensions */ - -done_parsing:; -#ifdef DEBUG_CLIENTHELLO_PARSER - if (n > 0) { - DBG_printf("unparsed next bytes %02x %02x %02x %02x\n", - p[0], p[1], p[2], p[3]); - } -#endif /* DEBUG_CLIENTHELLO_PARSER */ - DBG_printf("%zd bytes unparsed\n", n); - - /* Valid ClientHello with or without server name */ - DBG_printf("===> Match: rv 0, *clienthello set\n"); - if (servername) - *servername = sn; - return 0; -continue_search:; - } while (search && n > 0); - - /* No valid ClientHello messages found, not even a truncated one */ - DBG_printf("===> No match: rv 1, *clienthello NULL\n"); - *clienthello = NULL; - if (sn) - { - free(sn); - sn = NULL; - } - return 1; +void ssl_chello_free(struct ssl_chello* chello) +{ + free(chello->sni); + chello->sni = NULL; + free(chello->alpn); + chello->alpn = NULL; + free(chello->cipher_suites); + chello->cipher_suites = NULL; + free(chello->cipher_suites_tls13); + chello->cipher_suites_tls13 = NULL; + free(chello); } -/* vim: set noet ft=c: */ +struct chello* ssl_chello_parse(const char* buff, size_t buff_len, enum parse_chello_result* result) +{ + if(buff == NULL) + { + result = PARSE_CHELLO_INVALID_FORMAT; + return NULL; + } + if(buff_len < 1) + { + result = PARSE_CHELLO_NOT_ENOUGH_BUFF; + return NULL; + } + if(buff[0] != 0x80 && buff[0] != 0x16) + { + result = PARSE_CHELLO_INVALID_FORMAT; + return NULL; + } + /* SSL 2.0 compatible Client Hello + * High bit of first byte (length) and content type is Client Hello + * See RFC5246 Appendix E.2 + * if it is SSL 2.0, only parse version + */ + if(buff[0] == 0x80) + { + struct chello* _chello = (struct chello*)ALLOC(struct chello, 1); + _chello->record_layer_version.major = 0x02; + if(buff_len < 2) + { + *result = PARSE_CHELLO_NOT_ENOUGH_BUFF; + return _chello; + } + size_t len = (size_t)buff[1]; + if (buff_len < len + 2) + { + *result = PARSE_CHELLO_NOT_ENOUGH_BUFF; + return _chello; + } + buff_len = len + 2; + size_t pos = 2; + /* Handshark Message Type: Client Hello */ + if (pos + 1 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + if (buff[pos] != 0x01) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + pos += 1; + /* Version */ + if(pos + 2 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + _chello->chello_version.major = buff[pos]; + _chello->chello_version.minor = buff[pos + 1]; + *result = PARSE_CHELLO_SUCCESS; + return _chello; + } + else + { + if (buff_len < 5) + { + *result = PARSE_CHELLO_NOT_ENOUGH_BUFF; + return NULL; + } + if(buff[1] != 3 || buff[2] > 4 || buff[2] < 0) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return NULL; + } + struct chello* _chello = (struct chello*)ALLOC(struct chello, 1); + _chello->record_layer_version.major = buff[1]; + _chello->record_layer_version.minor = buff[2]; + _chello->chello_version.major = -1; + _chello->chello_version.minor = -1; + _chello->sni = NULL; + _chello->alpn = NULL; + _chello->cipher_suites = NULL; + _chello->cipher_suites_tls13 = NULL; + /* TLS record length */ + size_t len = ((size_t)buf[3] << 8) + (size_t)buf[4] + 5; + if (buff_len < len) + { + *result = PARSE_CHELLO_NOT_ENOUGH_BUFF; + return _chello; + } + buff_len = len; + size_t pos = 5; + if (pos + 1 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + if (buff[pos] != 0x01) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + pos += 4; + if(pos + 2 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + _chello->chello_version.major = buff[pos]; + _chello->chello_version.minor = buff[pos+1]; + pos += 34; + /* Session ID */ + if (pos + 1 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + len = (size_t)buff[pos]; + pos += 1 + len; + /* Cipher Suites */ + if (pos + 2 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + len = ((size_t)buff[pos] << 8) + (size_t)buff[pos + 1]; + pos += 2; + if(pos + len > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + int n = sizeof(cipher_suite_list) / sizeof(struct cipher_suite); + _chello->cipher_suites = parse_cipher_suites(cipher_suite_list, n, buff + pos, len, result); + if(*result != PARSE_CHELLO_SUCCESS) + { + return _chello; + } + n = sizeof(cipher_suite_list_tls13) / sizeof(struct cipher_suite); + _chello->cipher_suites_tls13 = parse_cipher_suites_tls13(cipher_suite_list_tls13, n, buff + pos, len, result); + if(*result != PARSE_CHELLO_SUCCESS) + { + return _chello; + } + pos += len; + /* Compression Methods */ + if (pos >= buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + len = (size_t)buff[pos]; + pos += 1 + len; + /* ssl 3.0, no extensions */ + if(_chello->record_layer_version.major == 3 && _chello->record_layer_version.minor == 0) + { + if(pos == buff_len) + { + *result = PARSE_CHELLO_SUCCESS; + return _chello; + } + else + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + } + /* Extensions */ + if (pos + 2 > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + len = ((size_t)buff[pos] << 8) + (size_t)buff[pos + 1]; + pos += 2; + if (pos + len > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return _chello; + } + enum parse_chello_result rtn = parse_extensions(buff + pos, len, _chello); + *result = rtn; + return _chello; + } +} + +static enum parse_chello_result parse_extensions(const char* buff, size_t buff_len, struct chello* chello) { + size_t pos = 0; + /* Parse each 4 bytes for the extension header */ + while (pos + 4 <= buff_len) + { + size_t len = ((size_t)buff[pos + 2] << 8) + (size_t)buff[pos + 3]; + /* Check if it's a server name extension */ + if (buff[pos] == 0x00 && buff[pos + 1] == 0x00) + { + if (pos + 4 + len > buff_len) + { + return PARSE_CHELLO_INVALID_FORMAT; + } + pos + = 4; + enum parse_chello_result result = PARSE_CHELLO_SUCCESS; + chello->sni = parse_server_name_extension(data + pos, len, &result); + if(result != PARSE_CHELLO_SUCCESS) + { + return result; + } + } + /* Check if it's a alpn extension */ + if (buff[pos] == 0x00 && buff[pos + 1] == 0x10) + { + if (pos + 4 + len > buff_len) + { + return PARSE_CHELLO_INVALID_FORMAT; + } + pos + = 4; + enum parse_chello_result result = PARSE_CHELLO_SUCCESS; + chello->alpn = parse_alpn_extension(data + pos, len, &result); + if(result != PARSE_CHELLO_SUCCESS) + { + return result; + } + } + pos += len; + } + /* Check we ended where we expected to */ + if (pos != data_len) + { + return PARSE_CHELLO_INVALID_FORMAT; + } + return PARSE_CHELLO_SUCCESS; +} + +static const char* parse_cipher_suites(struct cipher_suite* _cipher_suite_list, int n, const char* buff, size_t buff_len, enum parse_chello_result* result) +{ + char* cipher_suites_str = malloc(TFE_STRING_MAX); + cipher_suites_str[0] = "\0"; + size_t pos = 0; + while(pos < buff_len) + { + int i = 0; + for(i = 0;i < n; i++) + { + int val = buff[pos] << 8 + buff[pos + 1]; + if(_cipher_suite_list[i].value == val) + { + strncat(cipher_suites_str, _cipher_suite_list[i].name, TFE_STRING_MAX); + } + } + pos += 2; + } + if(pos != buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return NULL; + } + *result = PARSE_CHELLO_SUCCESS; + return cipher_suites_str; +} + + +static const char* parse_alpn_extension(const char* buff, size_t buff_len, enum parse_chello_result* result) +{ + size_t len = ((size_t)buff[pos] << 8) + (size_t)buff[pos + 1]; + if(2 + len != buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return NULL; + } + char* alpn = malloc(len + 1); + strncpy(alpn, buff + 2, len); + alpn[len] = '\0'; + *result = PARSE_CHELLO_SUCCESS; + return alpn; +} + +static const char* parse_server_name_extension(const char* buff, size_t buff_len, enum parse_chello_result* result) +{ + size_t pos = 2; /* skip server name list length */ + size_t len; + const char* sni = NULL; + while (pos + 3 < buff_len) + { + len = ((size_t)buff[pos + 1] << 8) + (size_t)buff[pos + 2]; + if (pos + 3 + len > buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + return NULL; + } + switch (buff[pos]) + { + case 0x00: /* host_name */ + sni = malloc(len + 1); + strncpy(sni, (const char *)(buff + pos + 3), len); + sni[len] = '\0'; + *result = PARSE_CHELLO_SUCCESS; + default: + sni = NULL; + } + pos += 3 + len; + } + if (pos != buff_len) + { + *result = PARSE_CHELLO_INVALID_FORMAT; + } + return sni; +}