#include "sni.hpp"
typedef struct {
sockaddr_t client;
sockaddr_t server;
} sni_cache_key_t;
typedef struct {
char host[MAX_SNI_LEN+1];
} sni_cache_val_t;
CONF_DEF(config) {
ConfigKey sni_cache;
};
typedef HashCache<sni_cache_key_t, sni_cache_val_t> SniCache;
CONF_INIT(config) {
CONF_KEY_INST(sni_cache, true, SniCache, size_t);
}
HOOK(CACHE_CLEAN, HOOK_PRIO_MID) {
if (config) {
SniCache* cache = config->sni_cache.ctx_as<SniCache>();
if (cache) {
cache->invalidate();
}
}
}
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
static INLINE bool buf_match(const char*& b, size_t& bl, const char* s, size_t sl) {
if (bl >= sl && memcmp(b, s, sl) == 0) {
b += sl;
bl -= sl;
return true;
}
return false;
}
static INLINE bool buf_size(const char*& b, size_t& bl, size_t& s, size_t sl) {
assert(sl <= sizeof(size_t));
s = 0;
if (sl > bl) return false;
while (sl--) {
s = s << 8;
((unsigned char*)&s)[0] = ((const unsigned char*)b)[0];
++b;
--bl;
}
return true;
}
TEST(buf_size) {
const char *bar = "\x01\x02\x03\x04", *p;
size_t l, rv;
p = bar; l = strlen(p);
if (!buf_size(p, l, rv, 1) || rv != 0x01) return false;
p = bar; l = strlen(p);
if (!buf_size(p, l, rv, 2) || rv != 0x0102) return false;
p = bar; l = strlen(p);
if (!buf_size(p, l, rv, 3) || rv != 0x010203) return false;
p = bar; l = strlen(p);
if (!buf_size(p, l, rv, 4) || rv != 0x01020304) return false;
return true;
}
static INLINE bool buf_inc(const char*& b, size_t& bl, size_t sl) {
if (sl > bl) return false;
b += sl;
bl -= sl;
return true;
}
static INLINE bool buf_arr(const char*& b, size_t& bl, size_t sl) {
if (sl > bl) return false;
size_t al;
unless (buf_size(b, bl, al, sl)) {
return false;
}
unless (buf_inc(b, bl, al)) {
b -= sl;
bl += sl;
return false;
}
return true;
}
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
static char* sni(const char* b, size_t l) {
// Handshake + Version
if (!buf_match(b, l, "\x16\x03\x00", 3) && !buf_match(b, l, "\x16\x03\x01", 3) && !buf_match(b, l, "\x16\x03\x02", 3) && !buf_match(b, l, "\x16\x03\x03", 3)) return NULL;
// Len
if (!buf_inc(b, l, 2)) return NULL;
// Client Hello
if (!buf_match(b, l, "\x01", 1)) return NULL;
// Len
if (!buf_inc(b, l, 3)) return NULL;
// Version
if (!buf_match(b, l, "\x03\x00", 2) && !buf_match(b, l, "\x03\x01", 2) && !buf_match(b, l, "\x03\x02", 2) && !buf_match(b, l, "\x03\x03", 2)) return NULL;
// Rand
if (!buf_inc(b, l, 32)) return NULL;
// SID
if (!buf_arr(b, l, 1)) return NULL;
// Cipher Suites
if (!buf_arr(b, l, 2)) return NULL;
// Compression Methods
if (!buf_arr(b, l, 1)) return NULL;
// Extensions Length
size_t tmp;
if (!buf_size(b, l, tmp, 2)) return NULL;
l = MIN(l, tmp);
// Extension Types (SNI: 0)
while (l) {
if (!buf_size(b, l, tmp, 2)) return NULL;
if (tmp != 0) {
if (!buf_arr(b, l, 2)) return NULL;
continue;
}
// list len (2) + item len (2)
if (!buf_inc(b, l, 4)) return NULL;
// item type (1)
if (!buf_size(b, l, tmp, 1)) return NULL;
if (tmp != 0) return NULL;
// SNI len (2)
if (!buf_size(b, l, tmp, 2)) return NULL;
if (tmp > l || tmp < 3 || tmp > MAX_SNI_LEN) return NULL;
// SNI validation
if (!validate_host(b, tmp)) {
return NULL;
}
// great success
return strndup(b, tmp);
}
return NULL;
}
TEST(client_helo_sni) {
char* host = sni("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03\x06\x90\x6a\x86\xfe\x34\xf5\x1c\x7c\xd5\x24\x67\x60\xfc\x1e\x20\xfd\x57\x21\xfa\x9e\x21\x36\x5f\x97\x95\xd7\x94\x21\x76\xcf\x14\x20\xc2\x00\xb0\xd2\xa1\x76\x80\x77\x63\x0b\x93\x20\x61\x78\x66\xd6\xe8\xb6\xaa\xcf\x56\xb2\x7d\x40\xbb\x5c\x77\xda\x6b\x50\xea\xc3\x00\x2c\xc0\x2b\xc0\x2c\xc0\x2f\xc0\x30\x00\x9e\x00\x9f\xc0\x09\xc0\x0a\xc0\x13\xc0\x14\x00\x33\x00\x39\x00\x32\x00\x38\xc0\x07\xc0\x11\x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x05\x00\xff\x01\x00\x01\x87\x00\x00\x00\x28\x00\x26\x00\x00\x23\x72\x33\x2d\x2d\x2d\x73\x6e\x2d\x32\x35\x61\x75\x78\x61\x2d\x31\x71\x68\x65\x2e\x67\x6f\x6f\x67\x6c\x65\x76\x69\x64\x65\x6f\x2e\x63\x6f\x6d\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x34\x00\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10\x00\x11\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x15\x00\xf3", 274);
if (!host || strcmp(host, "r3---sn-25auxa-1qhe.googlevideo.com") != 0) return false;
free(host);
return true;
}
static bool sni(const char* buf, size_t len, const sockaddr_t& client, const sockaddr_t& server, char*& host) {
assert(!host);
bool handshake_success = false;
if (buf && len) {
#if (PARSE_CLIENT_HELO)
host = sni(buf, len);
handshake_success = (host != NULL);
#else
handshake_success = ssl_sni(buf, len, host);
#endif
}
// TODO: session tickets?
// TODO: caching sni by src/dst ok?
if (!config || !config->sni_cache.ctx_as<void*>()) {
return handshake_success;
}
assert(!client.addr4.sin_port);
assert(server.addr4.sin_port);
bool hit;
sni_cache_val_t* node = config->sni_cache.ctx_as<SniCache>()->get((sni_cache_key_t){client, server}, hit);
if (host) {
assert(strlen(host) < sizeof(node->host));
strcpy(node->host, host);
} else if (hit) {
if (*node->host) {
log(debug, "SNI cache hit");
host = strdup(node->host); // TODO: slab_pool?
}
} else {
node->host[0] = '\0';
}
return handshake_success;
}
bool sni(int fd, const sockaddr_t& client, const sockaddr_t& server, buf_t*& buf, char*& host) {
buf = buf_pool.pop();
char* b;
size_t l;
buf->get_space(b, l);
ssize_t len = read(fd, b, l); // TODO: don't have to do this if cached?
log(io, "SNI read(%d): %zd", fd, len);
if (len > 0) {
buf->add_data((size_t)len);
} else {
buf_pool.push(buf);
buf = NULL;
}
return sni(b, MAX(len,0), client, server, host);
}
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
char* cn_cert(const X509* x) {
const char* rv = strstr(x->name, "/CN=");
if (!rv) return NULL;
rv += 4;
while (*rv == '.' || *rv == '*') rv++;
size_t l = strchrnul(rv, '/') - rv;
if (validate_host(rv, l)) {
return strndup(rv, l);
} else {
return NULL;
}
}
static char* cn_cert(const char* b, size_t l) {
char* rv = NULL;
X509* x = d2i_X509(NULL, (const unsigned char**)&b, l);
if (x) {
rv = cn_cert(x);
X509_free(x);
}
return rv;
}
static char* cn_ssl(const char* b, size_t l) { // NOPE if too short/not definitive yet
if (!buf_inc(b, l, 2)) return NULL; // skip length
if (!buf_match(b, l,
"\x04" // server helo
"\x00" // no session id
"\x01" // X509
"\x00\x02" // handshake version
,1+1+1+2)) return NULL;
size_t len;
if (!buf_size(b, l, len, 2)) return NULL; // cert len
if (!buf_inc(b, l, 4)) return NULL; // some extension lenghts
if (l < len) return (char*)NOPE; // pretty sure it will come
return cn_cert(b, len);
}
static char* cn_tls(const char* b, size_t l) { // NOPE if too short/not definitive yet
while (true) {
bool is_hs = false;
if (buf_match(b, l, "\x16", 1)) {
is_hs = true; // handshake
} else if (buf_match(b, l, "\x14", 1)) {
return NULL; // cipher spec -> encrypted handshake
} else if (!buf_inc(b, l, 1)) {
return NULL; //XXX: cert handshake msg might still come in a separate read
}
size_t version;
if (!buf_size(b, l, version, 2)) return NULL; // we require this to be there for the basic version check
if (version != 0x300 && version != 0x301 && version != 0x302 && version != 0x303) return NULL;
size_t hs_len;
if (!buf_size(b, l, hs_len, 2)) return NULL;
if (!is_hs) {
if (!buf_inc(b, l, hs_len)) return NULL;
} else {
while (true) {
if (buf_match(b, l, "\x0b", 1)) { // found certificate, return NOPE upon errors, as we're pretty sure it will come
if (!buf_inc(b, l, 6)) return (char*)NOPE; // not interested in cert list len
size_t len;
if (!buf_size(b, l, len, 3)) return (char*)NOPE;
if (l < len) return (char*)NOPE;
return cn_cert(b, len);
} else if (buf_match(b, l, "\x16", 1)) { // most probably new handshake from start
b--;
l++;
break;
} else if (buf_inc(b, l, 1)) { // ignore type
if (!buf_arr(b, l, 3)) return NULL; // assume 3 bytes length for non-cert type
} else {
return NULL;
}
}
}
}
}
TEST(server_helo_cn) {
char* x = cn_tls(
"\x16\x03\x01\x00\x35\x02\x00\x00\x31\x03\x01\xdc\x2b\xb8\x20\x73\x6d\xfa\x10\xb5\x84\xa6\x5c\x6f\xfa\x16\x2c\xbd\xee\xfe\x5b\xad\x16\xf4\x95\xe3\x13\x0f\x3b\x0a\x16\xa1\xd4\x00\xc0\x14\x00\x00\x09\x00\x23\x00\x00\xff\x01\x00\x01\x00"
"\x16\x03\x01\x10\xd4\x0b\x00\x10\xd0\x00\x10\xcd\x00\x05\x40\x30\x82\x05\x3c\x30\x82\x04\x24\xa0\x03\x02\x01\x02\x02\x10\x52\x43\x4d\x90\x5e\x3f\x4f\x0b\x80\x37\x45\x77\xa9\x8f\x70\xd8\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x81\x90\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x47\x42\x31\x1b\x30\x19\x06\x03\x55\x04\x08\x13\x12\x47\x72\x65\x61\x74\x65\x72\x20\x4d\x61\x6e\x63\x68\x65\x73\x74\x65\x72\x31\x10\x30\x0e\x06\x03\x55\x04\x07\x13\x07\x53\x61\x6c\x66\x6f\x72\x64\x31\x1a\x30\x18\x06\x03\x55\x04\x0a\x13\x11\x43\x4f\x4d\x4f\x44\x4f\x20\x43\x41\x20\x4c\x69\x6d\x69\x74\x65\x64\x31\x36\x30\x34\x06\x03\x55\x04\x03\x13\x2d\x43\x4f\x4d\x4f\x44\x4f\x20\x52\x53\x41\x20\x44\x6f\x6d\x61\x69\x6e\x20\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x20\x53\x65\x63\x75\x72\x65\x20\x53\x65\x72\x76\x65\x72\x20\x43\x41\x30\x1e\x17\x0d\x31\x35\x31\x30\x30\x31\x30\x30\x30\x30\x30\x30\x5a\x17\x0d\x31\x37\x31\x32\x32\x39\x32\x33\x35\x39\x35\x39\x5a\x30\x4c\x31\x21\x30\x1f\x06\x03\x55\x04\x0b\x13\x18\x44\x6f\x6d\x61\x69\x6e\x20\x43\x6f\x6e\x74\x72\x6f\x6c\x20\x56\x61\x6c\x69\x64\x61\x74\x65\x64\x31\x14\x30\x12\x06\x03\x55\x04\x0b\x13\x0b\x50\x6f\x73\x69\x74\x69\x76\x65\x53\x53\x4c\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x13\x08\x68\x65\x69\x73\x65\x2e\x64\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xb8\xe3\x3a\xd9\xdb\x88\xc6\xde\x9d\x7e\xbf\xcc\x5a\x53\xfc\x45\x01\x9a\x29\x19\xaf\xf3\xc1\x4b\xad\x20\x1b\xc3\xb3\xa1\x26\x61\x51\xf6\x6c\xec\x54\x9d\xf8\x8e\xd0\xc4\x9f\x07\x33\x18\x68\x12\x9f\x8a\xbd\xc8\xf7\xf7\xce\xd9\x0e\x87\x8c\xec\xeb\x22\x06\x51\x29\xd6\x84\xd5\x2c\x7b\x52\xed\x9f\xd3\x9d\x5f\x25\x38\xdf\x23\x2c\x46\x13\x1c\xa8\x35\xf2\xb2\xf3\x4b\xd8\x1b\x5b\x89\x31\x3c\xb9\x2d\xe7\xb7\x34\x06\xeb\x5a\xe4\xd3\xe5\xd8\xe5\xa2\xc1\x9f\xe2\xef\x15\x2a\xa8\xd5\x54\xce\x68\xbe\xd6\xe1\x3c\x2d\x19\xc8\x8b\xa0\x4e\x65\x41\x68\x46\x1a\x24\x7d\xb6\x9c\x01\x22\x79\x14\x31\x10\xac\xa8\x88\x88\xea\x62\x7a\x9f\xc7\x7d\x74\x93\x9c\x17\x58\x6e\x96\x6f\xbb\x3d\x50\x8c\xeb\x15\x95\xa1\x14\x2f\xfc\x8e\xfb\xf8\x0b\x5a\xa2\x53\xe0\x08\x17\x80\x16\xfd\xa1\x3f\xc0\xc9\x36\xd1\xd2\x8b\x46\xc7\xf5\x98\xbd\xf9\x99\xb6\x17\xd9\xb3\xf2\xec\xa5\x49\xf1\x31\xd3\x02\x34\x00\x0d\xff\xd3\x67\xc1\xf0\x21\x05\x74\xd6\x54\x38\x49\xb7\x7a\x3a\x11\x47\x40\x59\x6b\xc1\x03\x9e\x67\x32\x29\xcb\xd1\xcc\xa3\x9a\xb8\x93\xaa\x6c\x2b\x17\xa1\x02\x03\x01\x00\x01\xa3\x82\x01\xd3\x30\x82\x01\xcf\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x90\xaf\x6a\x3a\x94\x5a\x0b\xd8\x90\xea\x12\x56\x73\xdf\x43\xb4\x3a\x28\xda\xe7\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\xda\xfb\x9f\xef\x75\x37\xed\x4b\xed\x75\x50\x87\x07\xd5\x6a\x38\xca\xd8\x28\x20\x30\x0e\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x0c\x06\x03\x55\x1d\x13\x01\x01\xff\x04\x02\x30\x00\x30\x1d\x06\x03\x55\x1d\x25\x04\x16\x30\x14\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x01\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x30\x4f\x06\x03\x55\x1d\x20\x04\x48\x30\x46\x30\x3a\x06\x0b\x2b\x06\x01\x04\x01\xb2\x31\x01\x02\x02\x07\x30\x2b\x30\x29\x06\x08\x2b\x06\x01\x05\x05\x07\x02\x01\x16\x1d\x68\x74\x74\x70\x73\x3a\x2f\x2f\x73\x65\x63\x75\x72\x65\x2e\x63\x6f\x6d\x6f\x64\x6f\x2e\x63\x6f\x6d\x2f\x43\x50\x53\x30\x08\x06\x06\x67\x81\x0c\x01\x02\x01\x30\x54\x06\x03\x55\x1d\x1f\x04\x4d\x30\x4b\x30\x49\xa0\x47\xa0\x45\x86\x43\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\x6c\x2e\x63\x6f\x6d\x6f\x64\x6f\x63\x61\x2e\x63\x6f\x6d\x2f\x43\x4f\x4d\x4f\x44\x4f\x52\x53\x41\x44\x6f\x6d\x61\x69\x6e\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x53\x65\x63\x75\x72\x65\x53\x65\x72\x76\x65\x72\x43\x41\x2e\x63\x72\x6c\x30\x81\x85\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x79\x30\x77\x30\x4f\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x43\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\x74\x2e\x63\x6f\x6d\x6f\x64\x6f\x63\x61\x2e\x63\x6f\x6d\x2f\x43\x4f\x4d\x4f\x44\x4f\x52\x53\x41\x44\x6f\x6d\x61\x69\x6e\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x53\x65\x63\x75\x72\x65\x53\x65\x72\x76\x65\x72\x43\x41\x2e\x63\x72\x74\x30\x24\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x01\x86\x18\x68\x74\x74\x70\x3a\x2f\x2f\x6f\x63\x73\x70\x2e\x63\x6f\x6d\x6f\x64\x6f\x63\x61\x2e\x63\x6f\x6d\x30\x21\x06\x03\x55\x1d\x11\x04\x1a\x30\x18\x82\x08\x68\x65\x69\x73\x65\x2e\x64\x65\x82\x0c\x77\x77\x77\x2e\x68\x65\x69\x73\x65\x2e\x64\x65\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x7f\xf6\xe8\xaa\x1b\x75\x96\xee\xd6\xd9\xe9\xac\x0c\x46\xee\xea\x93\x20\x54\xfd\xa0\xb1\x0d\x60\xc4\x92\xfa\x81\x2d\x61\xd7\xb5\x8e\x10\x0e\x4a\xe3\xe7\x6f\x24\xab\x37\xab\xc8\x17\x3c\xb0\xac\x3c\x45\x50\xc8\x9b\xac\x78\x70\x8c\xec\x59\x0d\x55\x83\xa1\xef\x2f\x46\x15\x96\x96\x05\xae\x5f\x25\x15\x81\xfc\xb7\x1c\xde\xda\x0d\x17\x3d\xe0\x07\x02\xa3\x8d\x19\xa2\xce\x9a\x54\x43\x6a\xc5\x43\xf8\x7c\x67\x5b\x65\xa3\x8a\xfd\x50\xcb\x73\x4b\x37\x76\xec\xab\x99\x8b\x03\x8f\x3b\xb6\x13\xa4\xb6\x01\x6c\xde\xb8\x2a\xf2\x2d\xd2\x89\xf4\x7d\x4f\xda\x0b\x9c\x40\x49\xf3\xc6\xe8\x4e\x5b\xe2\xa8\x25\x09\xd0\x50\x3f\xa1\x23\x1b\x58\xa7\x86\x40\x69\xd2\x8f\x3a\x22\x62\xbd\xda\x0b\x23\xb9\x2c\xa0\xfa\x65\x73\x01\x9c\x33\xf5\x56\x8f\x16\xd0\x25\x1f\x96\xc8\xeb\xc5\x66\xc9\x5c\xdd\x0b\xe3\x29\xa5\xc6\x35\x89\xcb\xfb\x4f\xef\x87\x6e\xe0\x57\x13\x01\x13\x93\x09\xe4\x9c\xe1\xd5\x3b\x7f\x1d\x53\xa3\xc7\x27\xab\xb9\x44\xf5\x3a\x4c\x54\x4d\xbc\x21\x8b\xd1\xb3\x76\x7e\xa3\x11\x1d\x86\x1c\x66\x44\xf1\xff\x68\xb1\x0c\x1a\x4c\x2b\xc2\x03\xa2\x00\x06\x0c\x30\x82\x06\x08\x30\x82\x03\xf0\xa0\x03\x02\x01\x02\x02\x10\x2b\x2e\x6e\xea\xd9\x75\x36\x6c\x14\x8a\x6e\xdb\xa3\x7c\x8c\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0c\x05\x00\x30\x81\x85\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x47\x42\x31\x1b\x30\x19\x06\x03\x55\x04\x08\x13\x12\x47\x72\x65\x61\x74",
58+1440
);
if (!x || x == NOPE) {
return false;
} else if (strcmp(x, "heise.de") != 0) {
free(x);
return false;
} else {
free(x);
return true;
}
}
tristate_t cn_cert(const char* b, size_t l, const sockaddr_t& client, const sockaddr_t& server, char*& rv) {
assert(!rv);
rv = cn_tls(b, l) ?: cn_ssl(b, l);
if (rv == NOPE) {
rv = NULL;
return TRI_NONE;
}
if (!config || !config->sni_cache.ctx_as<void*>()) {
return rv? TRI_TRUE: TRI_FALSE;
}
assert(!client.addr4.sin_port);
assert(server.addr4.sin_port);
bool hit;
sni_cache_val_t* node = config->sni_cache.ctx_as<SniCache>()->get((sni_cache_key_t){client, server}, hit);
if (rv) {
assert(strlen(rv) < sizeof(node->host));
strcpy(node->host, rv);
return TRI_TRUE;
} else if (hit) {
if (*node->host) {
log(debug, "CN cache hit");
rv = strdup(node->host);
return TRI_TRUE;
} else {
return TRI_FALSE;
}
} else {
node->host[0] = '\0';
return TRI_FALSE;
}
}