refactor ssl_client_hello_parse
This commit is contained in:
@@ -1644,444 +1644,485 @@ int ssl_is_ocspreq(const unsigned char * buf, size_t sz)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Ugly hack to manually parse a clientHello message from a memory buffer.
|
struct cipher_suite
|
||||||
* This is needed in order to be able to support SNI and STARTTLS.
|
{
|
||||||
*
|
int value;
|
||||||
* The OpenSSL SNI API only allows to read the indicated server name at the
|
char* name;
|
||||||
* 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
|
struct cipher_suite cipher_suite_list[] =
|
||||||
* 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.
|
{0xC030, "ECDHE-RSA-AES256-GCM-SHA384"},
|
||||||
*
|
{0xC02C, "ECDHE-ECDSA-AES256-GCM-SHA384"},
|
||||||
* For STARTTLS support in autossl mode, we need to peek into the buffer of
|
{0xC028, "ECDHE-RSA-AES256-SHA384"},
|
||||||
* received octets and decide whether we have something that resembles a
|
{0xC024, "ECDHE-ECDSA-AES256-SHA384"},
|
||||||
* (possibly incomplete) ClientHello message, so we can upgrade the connection
|
{0xC014, "ECDHE-RSA-AES256-SHA"},
|
||||||
* to SSL automatically.
|
{0xC00A, "ECDHE-ECDSA-AES256-SHA"},
|
||||||
*
|
{0x00A5, "DH-DSS-AES256-GCM-SHA384"},
|
||||||
* This function takes a buffer containing (part of) a ClientHello message as
|
{0x00A3, "DHE-DSS-AES256-GCM-SHA384"},
|
||||||
* seen on the network as input.
|
{0x00A1, "DH-RSA-AES256-GCM-SHA384"},
|
||||||
*
|
{0x009F, "DHE-RSA-AES256-GCM-SHA384"},
|
||||||
* Returns:
|
{0x006B, "DHE-RSA-AES256-SHA256"},
|
||||||
* 1 if buf does not contain a complete ClientHello message;
|
{0x006A, "DHE-DSS-AES256-SHA256"},
|
||||||
* *clienthello may point to the start of a truncated ClientHello message,
|
{0x0069, "DH-RSA-AES256-SHA256"},
|
||||||
* indicating that the caller should retry later with more bytes available
|
{0x0068, "DH-DSS-AES256-SHA256"},
|
||||||
* 0 if buf contains a complete ClientHello message;
|
{0x0039, "DHE-RSA-AES256-SHA"},
|
||||||
* *clienthello will point to the start of the complete ClientHello message
|
{0x0038, "DHE-DSS-AES256-SHA"},
|
||||||
*
|
{0x0037, "DH-RSA-AES256-SHA"},
|
||||||
* If a servername pointer was supplied by the caller, and a server name
|
{0x0036, "DH-DSS-AES256-SHA"},
|
||||||
* extension was found and parsed, the server name is returned in *servername
|
{0x0088, "DHE-RSA-CAMELLIA256-SHA"},
|
||||||
* as a newly allocated string that must be freed by the caller. This may
|
{0x0087, "DHE-DSS-CAMELLIA256-SHA"},
|
||||||
* only occur for a return value of 0.
|
{0x0086, "DH-RSA-CAMELLIA256-SHA"},
|
||||||
*
|
{0x0085, "DH-DSS-CAMELLIA256-SHA"},
|
||||||
* If search is non-zero, then the buffer will be searched for a ClientHello
|
{0xC019, "AECDH-AES256-SHA"},
|
||||||
* message beginning at offsets >= 0, whereas if search is zero, only
|
{0x00A7, "ADH-AES256-GCM-SHA384"},
|
||||||
* ClientHello messages starting at offset 0 will be considered.
|
{0x006D, "ADH-AES256-SHA256"},
|
||||||
*
|
{0x003A, "ADH-AES256-SHA"},
|
||||||
* This code currently supports SSL 2.0, SSL 3.0 and TLS 1.0-1.2.
|
{0x0089, "ADH-CAMELLIA256-SHA"},
|
||||||
*
|
{0xC032, "ECDH-RSA-AES256-GCM-SHA384"},
|
||||||
* References:
|
{0xC02E, "ECDH-ECDSA-AES256-GCM-SHA384"},
|
||||||
* draft-hickman-netscape-ssl-00: The SSL Protocol
|
{0xC02A, "ECDH-RSA-AES256-SHA384"},
|
||||||
* RFC 6101: The Secure Sockets Layer (SSL) Protocol Version 3.0
|
{0xC026, "ECDH-ECDSA-AES256-SHA384"},
|
||||||
* RFC 2246: The TLS Protocol Version 1.0
|
{0xC00F, "ECDH-RSA-AES256-SHA"},
|
||||||
* RFC 3546: Transport Layer Security (TLS) Extensions
|
{0xC005, "ECDH-ECDSA-AES256-SHA"},
|
||||||
* RFC 4346: The Transport Layer Security (TLS) Protocol Version 1.1
|
{0x009D, "AES256-GCM-SHA384"},
|
||||||
* RFC 4366: Transport Layer Security (TLS) Extensions
|
{0x003D, "AES256-SHA256"},
|
||||||
* RFC 5246: The Transport Layer Security (TLS) Protocol Version 1.2
|
{0x0035, "AES256-SHA"},
|
||||||
* RFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions
|
{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"}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum parse_chello_result
|
||||||
|
{
|
||||||
|
PARSE_CHELLO_INVALID_FORMAT = 0,
|
||||||
|
PARSE_CHELLO_NOT_ENOUGH_BUFF,
|
||||||
|
PARSE_CHELLO_SUCCESS
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct ssl_version
|
||||||
|
{
|
||||||
|
int major;
|
||||||
|
int minor;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
int
|
if(buff[0] == 0x80)
|
||||||
ssl_tls_clienthello_parse(
|
|
||||||
const unsigned char * buf, ssize_t sz, int search,
|
|
||||||
const unsigned char ** clienthello, char ** servername)
|
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_CLIENTHELLO_PARSER
|
struct chello* _chello = (struct chello*)ALLOC(struct chello, 1);
|
||||||
#define DBG_printf(...) log_dbg_printf("ClientHello parser: " __VA_ARGS__)
|
_chello->record_layer_version.major = 0x02;
|
||||||
#else /* !DEBUG_CLIENTHELLO_PARSER */
|
if(buff_len < 2)
|
||||||
#define DBG_printf(...)
|
|
||||||
#endif /* !DEBUG_CLIENTHELLO_PARSER */
|
|
||||||
const unsigned char * p = buf;
|
|
||||||
ssize_t n = sz;
|
|
||||||
char * sn = NULL;
|
|
||||||
|
|
||||||
ssize_t tlsextslen;
|
|
||||||
ssize_t sidlen;
|
|
||||||
ssize_t suiteslen;
|
|
||||||
ssize_t compslen;
|
|
||||||
ssize_t msglen;
|
|
||||||
ssize_t recordlen;
|
|
||||||
|
|
||||||
//=====
|
|
||||||
|
|
||||||
*clienthello = NULL;
|
|
||||||
|
|
||||||
DBG_printf("parsing buffer of sz %zd\n", sz);
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
if (*clienthello)
|
*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;
|
||||||
* Rewind after skipping an invalid ClientHello by
|
return _chello;
|
||||||
* restarting the search one byte after the beginning
|
}
|
||||||
* of the last candidate
|
buff_len = len + 2;
|
||||||
*/
|
size_t pos = 2;
|
||||||
p = (*clienthello) + 1;
|
/* Handshark Message Type: Client Hello */
|
||||||
n = sz - (p - buf);
|
if (pos + 1 > buff_len)
|
||||||
if (sn)
|
|
||||||
{
|
{
|
||||||
free(sn);
|
*result = PARSE_CHELLO_INVALID_FORMAT;
|
||||||
sn = NULL;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search)
|
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)
|
||||||
{
|
{
|
||||||
/* Search for a potential ClientHello */
|
size_t len = ((size_t)buff[pos + 2] << 8) + (size_t)buff[pos + 3];
|
||||||
while ((n > 0) && (*p != 0x16) && (*p != 0x80))
|
/* Check if it's a server name extension */
|
||||||
|
if (buff[pos] == 0x00 && buff[pos + 1] == 0x00)
|
||||||
{
|
{
|
||||||
p++;
|
if (pos + 4 + len > buff_len)
|
||||||
n--;
|
|
||||||
}
|
|
||||||
if (n <= 0)
|
|
||||||
{
|
{
|
||||||
/* Search completed without a match; reset
|
return PARSE_CHELLO_INVALID_FORMAT;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
}
|
pos + = 4;
|
||||||
*clienthello = p;
|
enum parse_chello_result result = PARSE_CHELLO_SUCCESS;
|
||||||
DBG_printf("candidate at offset %td\n", p - buf);
|
chello->sni = parse_server_name_extension(data + pos, len, &result);
|
||||||
|
if(result != PARSE_CHELLO_SUCCESS)
|
||||||
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 */
|
return result;
|
||||||
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]);
|
/* Check if it's a alpn extension */
|
||||||
if (n - 1 < p[0])
|
if (buff[pos] == 0x00 && buff[pos + 1] == 0x10)
|
||||||
{
|
{
|
||||||
DBG_printf("===> [SSLv2] Truncated:"
|
if (pos + 4 + len > buff_len)
|
||||||
" 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:"
|
return PARSE_CHELLO_INVALID_FORMAT;
|
||||||
" rv 1, *clienthello set\n");
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
pos + = 4;
|
||||||
p += cipherspec_len + sessionid_len + challenge_len;
|
enum parse_chello_result result = PARSE_CHELLO_SUCCESS;
|
||||||
n -= cipherspec_len + sessionid_len + challenge_len;
|
chello->alpn = parse_alpn_extension(data + pos, len, &result);
|
||||||
goto done_parsing;
|
if(result != PARSE_CHELLO_SUCCESS)
|
||||||
}
|
|
||||||
else if (*p != 0x16)
|
|
||||||
{
|
{
|
||||||
/* this can only happen if search is 0 */
|
return result;
|
||||||
DBG_printf("===> No match: rv 1, *clienthello NULL\n");
|
|
||||||
*clienthello = NULL;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
p++;
|
}
|
||||||
n--;
|
pos += len;
|
||||||
|
}
|
||||||
if (n < 2)
|
/* Check we ended where we expected to */
|
||||||
|
if (pos != data_len)
|
||||||
{
|
{
|
||||||
DBG_printf("===> Truncated: rv 1, *clienthello set\n");
|
return PARSE_CHELLO_INVALID_FORMAT;
|
||||||
return 1;
|
}
|
||||||
|
return PARSE_CHELLO_SUCCESS;
|
||||||
}
|
}
|
||||||
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)
|
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)
|
||||||
{
|
{
|
||||||
DBG_printf("===> Truncated: rv 1, *clienthello set\n");
|
char* cipher_suites_str = malloc(TFE_STRING_MAX);
|
||||||
return 1;
|
cipher_suites_str[0] = "\0";
|
||||||
}
|
size_t pos = 0;
|
||||||
DBG_printf("length: %02x %02x\n", p[0], p[1]);
|
while(pos < buff_len)
|
||||||
|
|
||||||
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);
|
int i = 0;
|
||||||
DBG_printf("===> Truncated: rv 1, *clienthello set\n");
|
for(i = 0;i < n; i++)
|
||||||
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");
|
int val = buff[pos] << 8 + buff[pos + 1];
|
||||||
continue;
|
if(_cipher_suite_list[i].value == val)
|
||||||
}
|
|
||||||
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 */
|
strncat(cipher_suites_str, _cipher_suite_list[i].name, TFE_STRING_MAX);
|
||||||
DBG_printf("===> Match: rv 0, *clienthello set\n");
|
|
||||||
if (servername)
|
|
||||||
*servername = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (n < 2)
|
}
|
||||||
continue;
|
pos += 2;
|
||||||
DBG_printf("tlsexts length %02x %02x\n", p[0], p[1]);
|
}
|
||||||
tlsextslen = p[1] + (p[0] << 8);
|
if(pos != buff_len)
|
||||||
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)
|
*result = PARSE_CHELLO_INVALID_FORMAT;
|
||||||
goto continue_search;
|
return NULL;
|
||||||
DBG_printf("tlsext type %02x %02x len %02x %02x\n",
|
}
|
||||||
p[0], p[1], p[2], p[3]);
|
*result = PARSE_CHELLO_SUCCESS;
|
||||||
unsigned short exttype = p[1] + (p[0] << 8);
|
return cipher_suites_str;
|
||||||
ssize_t extlen = p[3] + (p[2] << 8);
|
}
|
||||||
p += 4;
|
|
||||||
n -= 4;
|
|
||||||
if (n < extlen)
|
static const char* parse_alpn_extension(const char* buff, size_t buff_len, enum parse_chello_result* result)
|
||||||
goto continue_search;
|
|
||||||
switch (exttype)
|
|
||||||
{
|
{
|
||||||
case 0:
|
size_t len = ((size_t)buff[pos] << 8) + (size_t)buff[pos + 1];
|
||||||
|
if(2 + len != buff_len)
|
||||||
{
|
{
|
||||||
ssize_t extn = extlen;
|
*result = PARSE_CHELLO_INVALID_FORMAT;
|
||||||
const unsigned char * extp = p;
|
return NULL;
|
||||||
|
}
|
||||||
|
char* alpn = malloc(len + 1);
|
||||||
|
strncpy(alpn, buff + 2, len);
|
||||||
|
alpn[len] = '\0';
|
||||||
|
*result = PARSE_CHELLO_SUCCESS;
|
||||||
|
return alpn;
|
||||||
|
}
|
||||||
|
|
||||||
if (extn < 2)
|
static const char* parse_server_name_extension(const char* buff, size_t buff_len, enum parse_chello_result* result)
|
||||||
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)
|
size_t pos = 2; /* skip server name list length */
|
||||||
goto continue_search;
|
size_t len;
|
||||||
DBG_printf("ServerName type %02x"
|
const char* sni = NULL;
|
||||||
" len %02x %02x\n",
|
while (pos + 3 < buff_len)
|
||||||
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);
|
len = ((size_t)buff[pos + 1] << 8) + (size_t)buff[pos + 2];
|
||||||
memcpy(sn, extp, snlen);
|
if (pos + 3 + len > buff_len)
|
||||||
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);
|
*result = PARSE_CHELLO_INVALID_FORMAT;
|
||||||
sn = NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return 1;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vim: set noet ft=c: */
|
|
||||||
|
|||||||
Reference in New Issue
Block a user