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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user