mirror of
https://github.com/moparisthebest/sslh
synced 2024-12-21 23:08:58 -05:00
Add ALPN protocol based probe
This commit is contained in:
parent
8758a298ba
commit
8af039d3eb
@ -2,7 +2,7 @@ vNEXT:
|
|||||||
Added USELIBPCRE to make use of regex engine
|
Added USELIBPCRE to make use of regex engine
|
||||||
optional.
|
optional.
|
||||||
|
|
||||||
Added support for RFC4366 SNI
|
Added support for RFC4366 SNI and RFC7301 ALPN
|
||||||
(Travis Burtrum)
|
(Travis Burtrum)
|
||||||
|
|
||||||
Changed connection log to include the name of the probe that
|
Changed connection log to include the name of the probe that
|
||||||
@ -10,8 +10,8 @@ vNEXT:
|
|||||||
|
|
||||||
Changed configuration file format: 'probe' field is
|
Changed configuration file format: 'probe' field is
|
||||||
no longer required, 'name' field can now contain
|
no longer required, 'name' field can now contain
|
||||||
'sni' or 'regex', with corresponding options (see
|
'tls' or 'regex', with corresponding options (see
|
||||||
example.org)
|
example.cfg)
|
||||||
Added 'log_level' option to each protocol, which
|
Added 'log_level' option to each protocol, which
|
||||||
allows to turn off generation of log at each
|
allows to turn off generation of log at each
|
||||||
connection.
|
connection.
|
||||||
|
28
example.cfg
28
example.cfg
@ -24,14 +24,19 @@ listen:
|
|||||||
#
|
#
|
||||||
# Each protocol entry consists of:
|
# Each protocol entry consists of:
|
||||||
# name: name of the probe. These are listed on the command
|
# name: name of the probe. These are listed on the command
|
||||||
# line (ssh -?), plus 'regex', 'sni' and 'timeout'.
|
# line (ssh -?), plus 'regex' and 'timeout'.
|
||||||
|
|
||||||
# service: (optional) libwrap service name (see hosts_access(5))
|
# service: (optional) libwrap service name (see hosts_access(5))
|
||||||
# host, port: where to connect when this probe succeeds
|
# host, port: where to connect when this probe succeeds
|
||||||
#
|
#
|
||||||
# Probe-specific options:
|
# Probe-specific options:
|
||||||
# sni:
|
# tls:
|
||||||
# sni_hotnames: list of FQDN for that target
|
# sni_hostnames: list of FQDN for that target
|
||||||
|
# alpn_protocols: list of ALPN protocols for that target, see:
|
||||||
|
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
||||||
|
#
|
||||||
|
# if both sni_hostnames AND alpn_protocols are specified, both must match
|
||||||
|
# if neither are set, it is just checked whether this is the TLS protocol or not
|
||||||
# regex:
|
# regex:
|
||||||
# regex_patterns: list of patterns to match for
|
# regex_patterns: list of patterns to match for
|
||||||
# that target.
|
# that target.
|
||||||
@ -39,15 +44,26 @@ listen:
|
|||||||
# sslh will try each probe in order they are declared, and
|
# sslh will try each probe in order they are declared, and
|
||||||
# connect to the first that matches.
|
# connect to the first that matches.
|
||||||
#
|
#
|
||||||
# You can specify several of 'regex' and 'sni'.
|
# You can specify several of 'regex' and 'tls'.
|
||||||
|
|
||||||
protocols:
|
protocols:
|
||||||
(
|
(
|
||||||
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
|
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
|
||||||
{ name: "http"; host: "localhost"; port: "80"; },
|
{ name: "http"; host: "localhost"; port: "80"; },
|
||||||
|
|
||||||
{ name: "sni"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; },
|
# match BOTH ALPN/SNI
|
||||||
{ name: "sni"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;},
|
{ name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0;},
|
||||||
|
|
||||||
|
# just match ALPN
|
||||||
|
{ name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; },
|
||||||
|
{ name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0;},
|
||||||
|
|
||||||
|
# just match SNI
|
||||||
|
{ name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; },
|
||||||
|
{ name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0;},
|
||||||
|
|
||||||
|
# catch anything else TLS
|
||||||
|
{ name: "tls"; host: "localhost"; port: "443"; },
|
||||||
|
|
||||||
# OpenVPN
|
# OpenVPN
|
||||||
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
|
||||||
|
29
probe.c
29
probe.c
@ -217,32 +217,17 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
|
|||||||
return PROBE_NEXT;
|
return PROBE_NEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int is_sni_protocol(const char *p, int len, struct proto *proto)
|
static int is_sni_alpn_protocol(const char *p, int len, struct proto *proto)
|
||||||
{
|
{
|
||||||
int valid_tls;
|
int valid_tls;
|
||||||
char *hostname;
|
|
||||||
char **sni_hostname;
|
|
||||||
|
|
||||||
valid_tls = parse_tls_header(p, len, &hostname);
|
valid_tls = parse_tls_header(proto->data, p, len);
|
||||||
|
|
||||||
if(valid_tls < 0)
|
if(valid_tls < 0)
|
||||||
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
|
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
|
||||||
|
|
||||||
if (verbose) fprintf(stderr, "sni hostname: %s\n", hostname);
|
/* There *was* a valid match */
|
||||||
|
return PROBE_MATCH;
|
||||||
/* Assume does not match */
|
|
||||||
valid_tls = PROBE_NEXT;
|
|
||||||
|
|
||||||
for (sni_hostname = proto->data; *sni_hostname; sni_hostname++) {
|
|
||||||
fprintf(stderr, "matching [%s] with [%s]\n", hostname, *sni_hostname);
|
|
||||||
if(!strcmp(hostname, *sni_hostname)) {
|
|
||||||
valid_tls = PROBE_MATCH;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(hostname);
|
|
||||||
return valid_tls;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int is_tls_protocol(const char *p, int len, struct proto *proto)
|
static int is_tls_protocol(const char *p, int len, struct proto *proto)
|
||||||
@ -363,9 +348,9 @@ T_PROBE* get_probe(const char* description) {
|
|||||||
if (!strcmp(description, "regex"))
|
if (!strcmp(description, "regex"))
|
||||||
return regex_probe;
|
return regex_probe;
|
||||||
|
|
||||||
/* Special case of "sni" probe for same reason as above*/
|
/* Special case of "sni/alpn" probe for same reason as above*/
|
||||||
if (!strcmp(description, "sni"))
|
if (!strcmp(description, "sni_alpn"))
|
||||||
return is_sni_protocol;
|
return is_sni_alpn_protocol;
|
||||||
|
|
||||||
/* Special case of "timeout" is allowed as a probe name in the
|
/* Special case of "timeout" is allowed as a probe name in the
|
||||||
* configuration file even though it's not really a probe */
|
* configuration file even though it's not really a probe */
|
||||||
|
3
probe.h
3
probe.h
@ -27,7 +27,8 @@ struct proto {
|
|||||||
/* function to probe that protocol; parameters are buffer and length
|
/* function to probe that protocol; parameters are buffer and length
|
||||||
* containing the data to probe, and a pointer to the protocol structure */
|
* containing the data to probe, and a pointer to the protocol structure */
|
||||||
T_PROBE* probe;
|
T_PROBE* probe;
|
||||||
void* data; /* opaque pointer ; used to pass list of regex to regex probe, or sni hostnames to sni probe */
|
/* opaque pointer ; used to pass list of regex to regex probe, or TLSProtocol struct to sni/alpn probe */
|
||||||
|
void* data;
|
||||||
struct proto *next; /* pointer to next protocol in list, NULL if last */
|
struct proto *next; /* pointer to next protocol in list, NULL if last */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
48
sslh-main.c
48
sslh-main.c
@ -213,34 +213,47 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef LIBCONFIG
|
#ifdef LIBCONFIG
|
||||||
static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames)
|
static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char* name, int alpn)
|
||||||
{
|
{
|
||||||
int num_probes, i, max_server_name_len, server_name_len;
|
int num_probes, i, max_server_name_len, server_name_len;
|
||||||
const char * sni_hostname;
|
const char * config_item;
|
||||||
char** sni_hostname_list;
|
char** sni_hostname_list;
|
||||||
|
|
||||||
num_probes = config_setting_length(sni_hostnames);
|
if(!config_items || !config_setting_is_array(config_items)) {
|
||||||
|
fprintf(stderr, "%s: no %s specified\n", p->description, name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
num_probes = config_setting_length(config_items);
|
||||||
if (!num_probes) {
|
if (!num_probes) {
|
||||||
fprintf(stderr, "%s: no sni_hostnames specified\n", p->description);
|
fprintf(stderr, "%s: no %s specified\n", p->description, name);
|
||||||
exit(1);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
max_server_name_len = 0;
|
max_server_name_len = 0;
|
||||||
for (i = 0; i < num_probes; i++) {
|
for (i = 0; i < num_probes; i++) {
|
||||||
server_name_len = strlen(config_setting_get_string_elem(sni_hostnames, i));
|
server_name_len = strlen(config_setting_get_string_elem(config_items, i));
|
||||||
if(server_name_len > max_server_name_len)
|
if(server_name_len > max_server_name_len)
|
||||||
max_server_name_len = server_name_len;
|
max_server_name_len = server_name_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
|
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
|
||||||
p->data = (void*)sni_hostname_list;
|
|
||||||
|
|
||||||
for (i = 0; i < num_probes; i++) {
|
for (i = 0; i < num_probes; i++) {
|
||||||
sni_hostname = config_setting_get_string_elem(sni_hostnames, i);
|
config_item = config_setting_get_string_elem(config_items, i);
|
||||||
sni_hostname_list[i] = malloc(max_server_name_len);
|
sni_hostname_list[i] = malloc(max_server_name_len);
|
||||||
strcpy (sni_hostname_list[i], sni_hostname);
|
strcpy (sni_hostname_list[i], config_item);
|
||||||
if(verbose) fprintf(stderr, "sni_hostnames[%d]: %s\n", i, sni_hostname_list[i]);
|
if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, config_setting_t* alpn_protocols)
|
||||||
|
{
|
||||||
|
p->data = (void*)new_tls_data();
|
||||||
|
p->probe = get_probe("sni_alpn");
|
||||||
|
setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0);
|
||||||
|
setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -250,7 +263,7 @@ static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames
|
|||||||
#ifdef LIBCONFIG
|
#ifdef LIBCONFIG
|
||||||
static int config_protocols(config_t *config, struct proto **prots)
|
static int config_protocols(config_t *config, struct proto **prots)
|
||||||
{
|
{
|
||||||
config_setting_t *setting, *prot, *patterns, *sni_hostnames;
|
config_setting_t *setting, *prot, *patterns, *sni_hostnames, *alpn_protocols;
|
||||||
const char *hostname, *port, *name;
|
const char *hostname, *port, *name;
|
||||||
int i, num_prots;
|
int i, num_prots;
|
||||||
struct proto *p, *prev = NULL;
|
struct proto *p, *prev = NULL;
|
||||||
@ -279,7 +292,7 @@ static int config_protocols(config_t *config, struct proto **prots)
|
|||||||
resolve_split_name(&(p->saddr), hostname, port);
|
resolve_split_name(&(p->saddr), hostname, port);
|
||||||
|
|
||||||
p->probe = get_probe(name);
|
p->probe = get_probe(name);
|
||||||
if (!p->probe) {
|
if (!p->probe || !strcmp(name, "sni_alpn")) {
|
||||||
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
|
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -292,13 +305,16 @@ static int config_protocols(config_t *config, struct proto **prots)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Probe-specific options: SNI hostnames */
|
/* Probe-specific options: SNI/ALPN */
|
||||||
if (!strcmp(name, "sni")) {
|
if (!strcmp(name, "tls")) {
|
||||||
sni_hostnames = config_setting_get_member(prot, "sni_hostnames");
|
sni_hostnames = config_setting_get_member(prot, "sni_hostnames");
|
||||||
if (sni_hostnames && config_setting_is_array(sni_hostnames)) {
|
alpn_protocols = config_setting_get_member(prot, "alpn_protocols");
|
||||||
setup_sni_hostnames(p, sni_hostnames);
|
|
||||||
|
if((sni_hostnames && config_setting_is_array(sni_hostnames)) || (alpn_protocols && config_setting_is_array(alpn_protocols))) {
|
||||||
|
setup_sni_alpn(p, sni_hostnames, alpn_protocols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
199
tls.c
199
tls.c
@ -30,8 +30,6 @@
|
|||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h> /* malloc() */
|
#include <stdlib.h> /* malloc() */
|
||||||
#include <string.h> /* strncpy() */
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
|
|
||||||
#define TLS_HEADER_LEN 5
|
#define TLS_HEADER_LEN 5
|
||||||
@ -42,19 +40,20 @@
|
|||||||
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int parse_extensions(const char *, size_t, char **);
|
|
||||||
static int parse_server_name_extension(const char *, size_t, char **);
|
|
||||||
|
|
||||||
const char tls_alert[] = {
|
struct TLSProtocol {
|
||||||
0x15, /* TLS Alert */
|
int use_alpn;
|
||||||
0x03, 0x01, /* TLS version */
|
char** sni_hostname_list;
|
||||||
0x00, 0x02, /* Payload length */
|
char** alpn_protocol_list;
|
||||||
0x02, 0x28, /* Fatal, handshake failure */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Parse a TLS packet for the Server Name Indication extension in the client
|
static int parse_extensions(const struct TLSProtocol *, const char *, size_t);
|
||||||
* hello handshake, returning the first servername found (pointer to static
|
static int parse_server_name_extension(const struct TLSProtocol *, const char *, size_t);
|
||||||
* array)
|
static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t);
|
||||||
|
static int has_match(char**, const char*, size_t);
|
||||||
|
|
||||||
|
/* Parse a TLS packet for the Server Name Indication and ALPN extension in the client
|
||||||
|
* hello handshake, returning a status code
|
||||||
*
|
*
|
||||||
* Returns:
|
* Returns:
|
||||||
* >=0 - length of the hostname and updates *hostname
|
* >=0 - length of the hostname and updates *hostname
|
||||||
@ -62,35 +61,20 @@ const char tls_alert[] = {
|
|||||||
* -1 - Incomplete request
|
* -1 - Incomplete request
|
||||||
* -2 - No Host header included in this request
|
* -2 - No Host header included in this request
|
||||||
* -3 - Invalid hostname pointer
|
* -3 - Invalid hostname pointer
|
||||||
* -4 - malloc failure
|
|
||||||
* < -4 - Invalid TLS client hello
|
* < -4 - Invalid TLS client hello
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
parse_tls_header(const char *data, size_t data_len, char **hostname) {
|
parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
|
||||||
char tls_content_type;
|
char tls_content_type;
|
||||||
char tls_version_major;
|
char tls_version_major;
|
||||||
char tls_version_minor;
|
char tls_version_minor;
|
||||||
size_t pos = TLS_HEADER_LEN;
|
size_t pos = TLS_HEADER_LEN;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
if (hostname == NULL)
|
|
||||||
return -3;
|
|
||||||
|
|
||||||
/* Check that our TCP payload is at least large enough for a TLS header */
|
/* Check that our TCP payload is at least large enough for a TLS header */
|
||||||
if (data_len < TLS_HEADER_LEN)
|
if (data_len < TLS_HEADER_LEN)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* SSL 2.0 compatible Client Hello
|
|
||||||
*
|
|
||||||
* High bit of first byte (length) and content type is Client Hello
|
|
||||||
*
|
|
||||||
* See RFC5246 Appendix E.2
|
|
||||||
*/
|
|
||||||
if (data[0] & 0x80 && data[2] == 1) {
|
|
||||||
if (verbose) fprintf(stderr, "Received SSL 2.0 Client Hello which can not support SNI.\n");
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tls_content_type = data[0];
|
tls_content_type = data[0];
|
||||||
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
|
||||||
if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
|
if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
|
||||||
@ -100,7 +84,7 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
|
|||||||
tls_version_major = data[1];
|
tls_version_major = data[1];
|
||||||
tls_version_minor = data[2];
|
tls_version_minor = data[2];
|
||||||
if (tls_version_major < 3) {
|
if (tls_version_major < 3) {
|
||||||
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which which can not support SNI.\n",
|
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
|
||||||
tls_version_major, tls_version_minor);
|
tls_version_major, tls_version_minor);
|
||||||
|
|
||||||
return -2;
|
return -2;
|
||||||
@ -108,7 +92,7 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
|
|||||||
|
|
||||||
/* TLS record length */
|
/* TLS record length */
|
||||||
len = ((unsigned char)data[3] << 8) +
|
len = ((unsigned char)data[3] << 8) +
|
||||||
(unsigned char)data[4] + TLS_HEADER_LEN;
|
(unsigned char)data[4] + TLS_HEADER_LEN;
|
||||||
data_len = MIN(data_len, len);
|
data_len = MIN(data_len, len);
|
||||||
|
|
||||||
/* Check we received entire TLS record length */
|
/* Check we received entire TLS record length */
|
||||||
@ -167,30 +151,75 @@ parse_tls_header(const char *data, size_t data_len, char **hostname) {
|
|||||||
|
|
||||||
if (pos + len > data_len)
|
if (pos + len > data_len)
|
||||||
return -5;
|
return -5;
|
||||||
return parse_extensions(data + pos, len, hostname);
|
return parse_extensions(tls_data, data + pos, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
parse_extensions(const char *data, size_t data_len, char **hostname) {
|
parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
int last_matched = 0;
|
||||||
|
|
||||||
|
if (tls_data == NULL)
|
||||||
|
return -3;
|
||||||
|
|
||||||
/* Parse each 4 bytes for the extension header */
|
/* Parse each 4 bytes for the extension header */
|
||||||
while (pos + 4 <= data_len) {
|
while (pos + 4 <= data_len) {
|
||||||
/* Extension Length */
|
/* Extension Length */
|
||||||
len = ((unsigned char)data[pos + 2] << 8) +
|
len = ((unsigned char) data[pos + 2] << 8) +
|
||||||
(unsigned char)data[pos + 3];
|
(unsigned char) data[pos + 3];
|
||||||
|
|
||||||
|
if (pos + 4 + len > data_len)
|
||||||
|
return -5;
|
||||||
|
|
||||||
|
size_t extension_type = ((unsigned char) data[pos] << 8) +
|
||||||
|
(unsigned char) data[pos + 1];
|
||||||
|
|
||||||
|
|
||||||
/* Check if it's a server name extension */
|
/* Check if it's a server name extension */
|
||||||
if (data[pos] == 0x00 && data[pos + 1] == 0x00) {
|
/* There can be only one extension of each type, so we break
|
||||||
/* There can be only one extension of each type, so we break
|
our state and move pos to beginning of the extension here */
|
||||||
our state and move p to beinnging of the extension here */
|
if (tls_data->use_alpn == 2) {
|
||||||
if (pos + 4 + len > data_len)
|
/* we want BOTH alpn and sni to match */
|
||||||
return -5;
|
if (extension_type == 0x00) { /* Server Name */
|
||||||
return parse_server_name_extension(data + pos + 4, len, hostname);
|
if (parse_server_name_extension(tls_data, data + pos + 4, len)) {
|
||||||
|
/* SNI matched */
|
||||||
|
if(last_matched) {
|
||||||
|
/* this is only true if ALPN matched, so return true */
|
||||||
|
return last_matched;
|
||||||
|
} else {
|
||||||
|
/* otherwise store that SNI matched */
|
||||||
|
last_matched = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// both can't match
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
} else if (extension_type == 0x10) { /* ALPN */
|
||||||
|
if (parse_alpn_extension(tls_data, data + pos + 4, len)) {
|
||||||
|
/* ALPN matched */
|
||||||
|
if(last_matched) {
|
||||||
|
/* this is only true if SNI matched, so return true */
|
||||||
|
return last_matched;
|
||||||
|
} else {
|
||||||
|
/* otherwise store that ALPN matched */
|
||||||
|
last_matched = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// both can't match
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (extension_type == 0x00 && tls_data->use_alpn == 0) { /* Server Name */
|
||||||
|
return parse_server_name_extension(tls_data, data + pos + 4, len);
|
||||||
|
} else if (extension_type == 0x10 && tls_data->use_alpn == 1) { /* ALPN */
|
||||||
|
return parse_alpn_extension(tls_data, data + pos + 4, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += 4 + len; /* Advance to the next extension header */
|
pos += 4 + len; /* Advance to the next extension header */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check we ended where we expected to */
|
/* Check we ended where we expected to */
|
||||||
if (pos != data_len)
|
if (pos != data_len)
|
||||||
return -5;
|
return -5;
|
||||||
@ -198,32 +227,25 @@ parse_extensions(const char *data, size_t data_len, char **hostname) {
|
|||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
parse_server_name_extension(const char *data, size_t data_len,
|
parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
|
||||||
char **hostname) {
|
|
||||||
size_t pos = 2; /* skip server name list length */
|
size_t pos = 2; /* skip server name list length */
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
while (pos + 3 < data_len) {
|
while (pos + 3 < data_len) {
|
||||||
len = ((unsigned char)data[pos + 1] << 8) +
|
len = ((unsigned char)data[pos + 1] << 8) +
|
||||||
(unsigned char)data[pos + 2];
|
(unsigned char)data[pos + 2];
|
||||||
|
|
||||||
if (pos + 3 + len > data_len)
|
if (pos + 3 + len > data_len)
|
||||||
return -5;
|
return -5;
|
||||||
|
|
||||||
switch (data[pos]) { /* name type */
|
switch (data[pos]) { /* name type */
|
||||||
case 0x00: /* host_name */
|
case 0x00: /* host_name */
|
||||||
*hostname = malloc(len + 1);
|
if(has_match(tls_data->sni_hostname_list, data + pos + 3, len)) {
|
||||||
if (*hostname == NULL) {
|
return len;
|
||||||
if (verbose) fprintf(stderr, "malloc() failure\n");
|
} else {
|
||||||
return -4;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
strncpy(*hostname, data + pos + 3, len);
|
|
||||||
|
|
||||||
(*hostname)[len] = '\0';
|
|
||||||
|
|
||||||
return len;
|
|
||||||
default:
|
default:
|
||||||
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
|
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
|
||||||
data[pos]);
|
data[pos]);
|
||||||
@ -236,3 +258,70 @@ parse_server_name_extension(const char *data, size_t data_len,
|
|||||||
|
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
|
||||||
|
size_t pos = 2;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
while (pos + 1 < data_len) {
|
||||||
|
len = (unsigned char)data[pos];
|
||||||
|
|
||||||
|
if (pos + 1 + len > data_len)
|
||||||
|
return -5;
|
||||||
|
|
||||||
|
if (len > 0 && has_match(tls_data->alpn_protocol_list, data + pos + 1, len)) {
|
||||||
|
return len;
|
||||||
|
} else if (len > 0) {
|
||||||
|
if (verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1);
|
||||||
|
}
|
||||||
|
pos += 1 + len;
|
||||||
|
}
|
||||||
|
/* Check we ended where we expected to */
|
||||||
|
if (pos != data_len)
|
||||||
|
return -5;
|
||||||
|
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
has_match(char** list, const char* name, size_t name_len) {
|
||||||
|
char **item;
|
||||||
|
|
||||||
|
for (item = list; *item; item++) {
|
||||||
|
if (verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
|
||||||
|
if(!strncmp(*item, name, name_len)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TLSProtocol *
|
||||||
|
new_tls_data() {
|
||||||
|
struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol));
|
||||||
|
if (tls_data != NULL) {
|
||||||
|
tls_data->use_alpn = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tls_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TLSProtocol *
|
||||||
|
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, char** list) {
|
||||||
|
if (alpn) {
|
||||||
|
tls_data->alpn_protocol_list = list;
|
||||||
|
if(tls_data->use_alpn == 0)
|
||||||
|
tls_data->use_alpn = 2;
|
||||||
|
else
|
||||||
|
tls_data->use_alpn = 1;
|
||||||
|
} else {
|
||||||
|
tls_data->sni_hostname_list = list;
|
||||||
|
if(tls_data->use_alpn == 1)
|
||||||
|
tls_data->use_alpn = 2;
|
||||||
|
else
|
||||||
|
tls_data->use_alpn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tls_data;
|
||||||
|
}
|
||||||
|
7
tls.h
7
tls.h
@ -28,6 +28,11 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
int parse_tls_header(const char *data, size_t data_len, char **hostname);
|
struct TLSProtocol;
|
||||||
|
|
||||||
|
int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len);
|
||||||
|
|
||||||
|
struct TLSProtocol *new_tls_data();
|
||||||
|
struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, char**);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user