From 85b0eed3a2f94d7fc310d5318fece669806e1f89 Mon Sep 17 00:00:00 2001 From: Nick Peng Date: Fri, 22 Feb 2019 00:40:30 +0800 Subject: [PATCH] Support TLS SPKI verify --- ReadMe.md | 6 +-- ReadMe_zh-CN.md | 6 +-- etc/smartdns/smartdns.conf | 7 ++- src/dns_client.c | 102 ++++++++++++++++++++++++++++++------- src/dns_client.h | 2 +- src/dns_conf.c | 19 ++++++- src/dns_conf.h | 2 + src/smartdns.c | 2 +- src/util.c | 38 ++++++++++++++ src/util.h | 4 ++ 10 files changed, 159 insertions(+), 29 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 44bb1f7..61e5a41 100755 --- a/ReadMe.md +++ b/ReadMe.md @@ -446,9 +446,9 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use |audit-size|audit log size|128K|number+K,M,G|audit-size 128K |audit-num|archived audit log number|2|Integer|audit-num 2 |conf-file|additional conf file|None|File path|conf-file /etc/smartdns/smartdns.more.conf -|server|Upstream UDP DNS server|None|Repeatable
[ip][:port]: Server IP, port optional.
[-blacklist-ip]: The `-blacklist-ip` parameter is to filtering IPs which is configured by `blacklist-ip`.
[-check-edns]: edns filter.
[-group [group] ...]: The group to which the DNS server belongs, such as office, foreign, use with nameserver.
[-exclude-default-group]: Exclude DNS servers from the default group| server 8.8.8.8:53 -blacklist-ip -check-edns -|server-tcp|Upstream TCP DNS server|None|Repeatable
[ip][:port]: Server IP, port optional.
[-blacklist-ip]: The `-blacklist-ip` parameter is to filtering IPs which is configured by `blacklist-ip`.
[-group [group] ...]: The group to which the DNS server belongs, such as office, foreign, use with nameserver.
[-exclude-default-group]: Exclude DNS servers from the default group| server-tcp 8.8.8.8:53 -|server-tls|Upstream TLS DNS server|None|Repeatable
[ip][:port]: Server IP, port optional.
[-blacklist-ip]: The `-blacklist-ip` parameter is to filtering IPs which is configured by `blacklist-ip`.
[-group [group] ...]: The group to which the DNS server belongs, such as office, foreign, use with nameserver.
[-exclude-default-group]: Exclude DNS servers from the default group| server-tls 8.8.8.8:853 +|server|Upstream UDP DNS server|None|Repeatable
`[ip][:port]`: Server IP, port optional.
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-check-edns]`: edns filter.
`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver.
`[-exclude-default-group]`: Exclude DNS servers from the default group| server 8.8.8.8:53 -blacklist-ip -check-edns +|server-tcp|Upstream TCP DNS server|None|Repeatable
`[ip][:port]`: Server IP, port optional.
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver.
`[-exclude-default-group]`: Exclude DNS servers from the default group| server-tcp 8.8.8.8:53 +|server-tls|Upstream TLS DNS server|None|Repeatable
`[ip][:port]`: Server IP, port optional.
`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver.
`[-exclude-default-group]`: Exclude DNS servers from the default group| server-tls 8.8.8.8:853 |address|Domain IP address|None|address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6], `-` for ignore, `#` for return SOA, `4` for IPV4, `6` for IPV6| address /www.example.com/1.2.3.4 |nameserver|To query domain with specific server group|None|nameserver /domain/[group\|-], `group` is the group name, `-` means ignore this rule, use the `-group` parameter in the related server|nameserver /www.example.com/office |ipset|Domain IPSet|None|ipset /domain/[ipset\|-], `-` for ignore|ipset /www.example.com/pass diff --git a/ReadMe_zh-CN.md b/ReadMe_zh-CN.md index c690228..458b946 100644 --- a/ReadMe_zh-CN.md +++ b/ReadMe_zh-CN.md @@ -447,9 +447,9 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms |audit-size|审计大小|128K|数字+K,M,G|audit-size 128K |audit-num|审计归档个数|2|数字|audit-num 2 |conf-file|附加配置文件|无|文件路径|conf-file /etc/smartdns/smartdns.more.conf -|server|上游UDP DNS|无|可重复
[ip][:port]:服务器IP,端口可选。
[-blacklist-ip]:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
[-check-edns]:edns过滤。
[-group [group] ...]:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
[-exclude-default-group]:将DNS服务器从默认组中排除| server 8.8.8.8:53 -blacklist-ip -check-edns -group g1 -|server-tcp|上游TCP DNS|无|可重复
[ip][:port]:服务器IP,端口可选。
[-blacklist-ip]:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
[-group [group] ...]:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
[-exclude-default-group]:将DNS服务器从默认组中排除| server-tcp 8.8.8.8:53 -|server-tls|上游TLS DNS|无|可重复
[ip][:port]:服务器IP,端口可选。
[-blacklist-ip]:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
[-group [group] ...]:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
[-exclude-default-group]:将DNS服务器从默认组中排除| server-tls 8.8.8.8:853 +|server|上游UDP DNS|无|可重复
`[ip][:port]`:服务器IP,端口可选。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-check-edns]`:edns过滤。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server 8.8.8.8:53 -blacklist-ip -check-edns -group g1 +|server-tcp|上游TCP DNS|无|可重复
`[ip][:port]`:服务器IP,端口可选。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-tcp 8.8.8.8:53 +|server-tls|上游TLS DNS|无|可重复
`[ip][:port]`:服务器IP,端口可选。
`[-spki-pin [sha256-pin]]`: TLS合法性校验SPKI值,base64编码的sha256 SPKI pin值
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-tls 8.8.8.8:853 |address|指定域名IP地址|无|address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6]
`-`表示忽略
`#`表示返回SOA
`4`表示IPV4
`6`表示IPV6| address /www.example.com/1.2.3.4 |nameserver|指定域名使用server组解析|无|nameserver /domain/[group\|-], `group`为组名,`-`表示忽略此规则,配套server中的`-group`参数使用| nameserver /www.example.com/office |ipset|域名IPSET|None|ipset /domain/[ipset\|-], `-`表示忽略|ipset /www.example.com/pass diff --git a/etc/smartdns/smartdns.conf b/etc/smartdns/smartdns.conf index bc2a1cf..9b3e29c 100644 --- a/etc/smartdns/smartdns.conf +++ b/etc/smartdns/smartdns.conf @@ -93,7 +93,10 @@ log-level info # server-tcp 8.8.8.8 # remote tls dns server list -# server-tls [IP]:[PORT] [-blacklist-ip] [-group [group] ...] [-exclude-default-group] +# server-tls [IP]:[PORT] [-blacklist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group] +# -spki-pin: TLS spki pin to verify. +# Get SKPI with this command: +# echo | openssl s_client -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 # default port is 853 # server-tls 8.8.8.8 # server-tls 1.0.0.1 @@ -115,4 +118,4 @@ log-level info # specific ipset to domain # ipset /domain/[ipset|-] # ipset /www.example.com/block, set ipset with ipset name of block -# ipset /www.example.com/-, ignore this domain \ No newline at end of file +# ipset /www.example.com/-, ignore this domain diff --git a/src/dns_client.c b/src/dns_client.c index 502a6f1..13bb404 100644 --- a/src/dns_client.c +++ b/src/dns_client.c @@ -93,6 +93,9 @@ struct dns_server_info { /* server type */ dns_server_type_t type; + unsigned char *spki; + int spki_len; + /* client socket */ int fd; int ttl; @@ -535,9 +538,25 @@ void _dns_client_group_remove_all(void) } /* add dns server information */ -int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl) +int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl, char *spki) { struct dns_server_info *server_info = NULL; + unsigned char *spki_data = NULL; + int spki_data_len = 0; + + /* read SPKI */ + if (spki && (strlen(spki) < DNS_MAX_SPKI_LEN)) { + spki_data = malloc(DNS_MAX_SPKI_LEN); + if (spki_data) { + memset(spki_data, 0, DNS_MAX_SPKI_LEN); + spki_data_len = SSL_base64_decode(spki, spki_data); + if (spki_data_len != SHA256_DIGEST_LENGTH) { + free(spki_data); + spki_data = NULL; + spki_data_len = 0; + } + } + } if (_dns_client_server_exist(gai, server_type) == 0) { return 0; @@ -562,6 +581,8 @@ int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_server_typ server_info->result_flag = result_flag; server_info->ttl = ttl; server_info->ttl_range = 0; + server_info->spki = spki_data; + server_info->spki_len = spki_data_len; if ((server_flag & SERVER_FLAG_EXCLUDE_DEFAULT) == 0) { if (_dns_client_add_to_group(DNS_SERVER_GROUP_DEFAULT, server_info) != 0) { @@ -605,6 +626,10 @@ int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_server_typ atomic_inc(&client.dns_server_num); return 0; errout: + if (spki_data) { + free(spki_data); + } + if (server_info) { if (server_info->ssl_ctx) { SSL_CTX_free(server_info->ssl_ctx); @@ -673,6 +698,10 @@ void _dns_client_server_remove_all(void) { list_del(&server_info->list); _dns_client_server_close(server_info); + if (server_info->spki) { + free(server_info->spki); + server_info->spki = NULL; + } free(server_info); } pthread_mutex_unlock(&client.server_list_lock); @@ -707,7 +736,7 @@ int _dns_client_server_remove(char *server_ip, struct addrinfo *gai, dns_server_ return -1; } -int _dns_client_server_operate(char *server_ip, int port, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl, int operate) +int _dns_client_server_operate(char *server_ip, int port, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl, char *spki, int operate) { char port_s[8]; int sock_type; @@ -741,7 +770,7 @@ int _dns_client_server_operate(char *server_ip, int port, dns_server_type_t serv } if (operate == 0) { - ret = _dns_client_server_add(server_ip, gai, server_type, server_flag, result_flag, ttl); + ret = _dns_client_server_add(server_ip, gai, server_type, server_flag, result_flag, ttl, spki); if (ret != 0) { goto errout; } @@ -760,14 +789,14 @@ errout: return -1; } -int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, unsigned server_flag, unsigned int result_flag, int ttl) +int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, unsigned server_flag, unsigned int result_flag, int ttl, char *spki) { - return _dns_client_server_operate(server_ip, port, server_type, server_flag, result_flag, ttl, 0); + return _dns_client_server_operate(server_ip, port, server_type, server_flag, result_flag, ttl, spki, 0); } int dns_client_remove_server(char *server_ip, int port, dns_server_type_t server_type) { - return _dns_client_server_operate(server_ip, port, server_type, 0, 0, 0, 1); + return _dns_client_server_operate(server_ip, port, server_type, 0, 0, 0, NULL, 1); } int dns_server_num(void) @@ -1653,11 +1682,12 @@ static int _dns_client_tls_verify(struct dns_server_info *server_info) { X509 *cert = NULL; char peer_CN[256]; - const EVP_MD *digest; - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int n; char cert_fingerprint[256]; int i = 0; + int key_len = 0; + unsigned char *key_data = NULL; + unsigned char *key_data_tmp = NULL; + unsigned char *key_sha256 = NULL; cert = SSL_get_peer_certificate(server_info->ssl); if (cert == NULL) { @@ -1666,28 +1696,66 @@ static int _dns_client_tls_verify(struct dns_server_info *server_info) } X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, peer_CN, 256); - tlog(TLOG_DEBUG, "peer CN: %s", peer_CN); - digest = EVP_get_digestbyname("sha256"); - X509_digest(cert, digest, md, &n); + /* get spki pin */ + key_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); + if (key_len <= 0 ) { + tlog(TLOG_ERROR, "get x509 public key failed."); + goto errout; + } + + key_data = OPENSSL_malloc(key_len); + key_data_tmp = key_data; + if (key_data == NULL) { + tlog(TLOG_ERROR, "malloc memory failed."); + goto errout; + } + + i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &key_data_tmp); + + key_sha256 = SSL_SHA256(key_data, key_len, 0); + if (key_sha256 == NULL) { + tlog(TLOG_ERROR, "get sha256 failed."); + goto errout; + } char *ptr = cert_fingerprint; - for (i = 0; i < 32; i++) { - *ptr = _dns_client_to_hex(md[i] >> 4 & 0xF); + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + *ptr = _dns_client_to_hex(key_sha256[i] >> 4 & 0xF); ptr++; - *ptr = _dns_client_to_hex(md[i] & 0xF); + *ptr = _dns_client_to_hex(key_sha256[i] & 0xF); ptr++; *ptr = ':'; ptr++; } ptr--; *ptr = 0; - tlog(TLOG_DEBUG, "cert fingerprint(%s): %s", "sha256", cert_fingerprint); + tlog(TLOG_DEBUG, "cert SPKI pin(%s): %s", "sha256", cert_fingerprint); + if (server_info->spki) { + if (memcmp(server_info->spki, key_sha256, server_info->spki_len) != 0) { + tlog(TLOG_INFO, "server %s cert spki is invalid", server_info->ip); + goto errout; + } else { + tlog(TLOG_DEBUG, "server %s cert spki verify succeed", server_info->ip); + } + } + + OPENSSL_free(key_data); X509_free(cert); - return 0; + +errout: + if (key_data) { + OPENSSL_free(key_data); + } + + if (cert) { + X509_free(cert); + } + + return -1; } static int _dns_client_process_tls(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) diff --git a/src/dns_client.h b/src/dns_client.h index 774cea4..3f65090 100644 --- a/src/dns_client.h +++ b/src/dns_client.h @@ -36,7 +36,7 @@ int dns_client_query(char *domain, int qtype, dns_client_callback callback, void void dns_client_exit(void); /* add remote dns server */ -int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl); +int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl, char *spki); /* remove remote dns server */ int dns_client_remove_server(char *server_ip, int port, dns_server_type_t server_type); diff --git a/src/dns_conf.c b/src/dns_conf.c index d3ae495..134f822 100644 --- a/src/dns_conf.c +++ b/src/dns_conf.c @@ -142,11 +142,14 @@ int config_server(int argc, char *argv[], dns_server_type_t type, int default_po int opt = 0; unsigned int result_flag = 0; unsigned int server_flag = 0; + unsigned char *spki = NULL; + int ttl = 0; /* clang-format off */ static struct option long_options[] = { {"blacklist-ip", 0, 0, 'b'}, {"check-edns", 0, 0, 'e'}, + {"spki-pin", required_argument, 0, 'p'}, {"check-ttl", required_argument, 0, 't'}, {"group", required_argument, 0, 'g'}, {"exclude-default-group", 0, 0, 'E'}, @@ -164,6 +167,7 @@ int config_server(int argc, char *argv[], dns_server_type_t type, int default_po } server = &dns_conf_servers[index]; + server->spki[0] = '\0'; ip = argv[1]; /* parse ip, port from ip */ @@ -200,7 +204,7 @@ int config_server(int argc, char *argv[], dns_server_type_t type, int default_po ttl = atoi(optarg); if (ttl < -255 || ttl > 255) { tlog(TLOG_ERROR, "ttl value is invalid."); - return -1; + goto errout; } result_flag |= DNSSERVER_FLAG_CHECK_TTL; break; @@ -212,10 +216,14 @@ int config_server(int argc, char *argv[], dns_server_type_t type, int default_po case 'g': { if (dns_conf_get_group_set(optarg, server) != 0) { tlog(TLOG_ERROR, "add group failed."); - return -1; + goto errout; } break; } + case 'p': { + strncpy(server->spki, optarg, DNS_MAX_SPKI_LEN); + break; + } default: break; } @@ -230,6 +238,13 @@ int config_server(int argc, char *argv[], dns_server_type_t type, int default_po tlog(TLOG_DEBUG, "add server %s, flag: %X, ttl: %d", ip, result_flag, ttl); return 0; + +errout: + if (spki) { + free(spki); + } + + return -1; } int config_domain_iter_cb(void *data, const unsigned char *key, uint32_t key_len, void *value) diff --git a/src/dns_conf.h b/src/dns_conf.h index 588ffaa..4d17e85 100644 --- a/src/dns_conf.h +++ b/src/dns_conf.h @@ -15,6 +15,7 @@ #define DNS_GROUP_NAME_LEN 32 #define DNS_NAX_GROUP_NUMBER 16 #define DNS_MAX_IPLEN 64 +#define DNS_MAX_SPKI_LEN 64 #define DNS_MAX_PATH 1024 #define DEFAULT_DNS_PORT 53 #define DEFAULT_DNS_TLS_PORT 853 @@ -91,6 +92,7 @@ struct dns_servers { unsigned int server_flag; int ttl; dns_server_type_t type; + char spki[DNS_MAX_SPKI_LEN]; }; /* ip address lists of domain */ diff --git a/src/smartdns.c b/src/smartdns.c index 3366cba..ba9d94c 100644 --- a/src/smartdns.c +++ b/src/smartdns.c @@ -130,7 +130,7 @@ int smartdns_add_servers(void) for (i = 0; i < dns_conf_server_num; i++) { ret = dns_client_add_server(dns_conf_servers[i].server, dns_conf_servers[i].port, dns_conf_servers[i].type, dns_conf_servers[i].server_flag, dns_conf_servers[i].result_flag, - dns_conf_servers[i].ttl); + dns_conf_servers[i].ttl, dns_conf_servers[i].spki); if (ret != 0) { tlog(TLOG_ERROR, "add server failed, %s:%d", dns_conf_servers[i].server, dns_conf_servers[i].port); return -1; diff --git a/src/util.c b/src/util.c index a9183a5..b4def9b 100644 --- a/src/util.c +++ b/src/util.c @@ -353,6 +353,44 @@ int ipset_del(const char *ipsetname, const unsigned char addr[], int addr_len) return _ipset_operate(ipsetname, addr, addr_len, 0, IPSET_DEL); } +unsigned char *SSL_SHA256(const unsigned char *d, size_t n, unsigned char *md) +{ + SHA256_CTX c; + static unsigned char m[SHA256_DIGEST_LENGTH]; + + if (md == NULL) + md = m; + SHA256_Init(&c); + SHA256_Update(&c, d, n); + SHA256_Final(md, &c); + OPENSSL_cleanse(&c, sizeof(c)); + return (md); +} + +int SSL_base64_decode(const char *in, unsigned char *out) +{ + size_t inlen = strlen(in); + int outlen; + + if (inlen == 0) { + return 0; + } + + outlen = EVP_DecodeBlock(out, (unsigned char *)in, inlen); + if (outlen < 0) { + goto errout; + } + + /* Subtract padding bytes from |outlen| */ + while (in[--inlen] == '=') { + --outlen; + } + + return outlen; +errout: + return -1; +} + #define THREAD_STACK_SIZE (16*1024) static pthread_mutex_t *lock_cs; static long *lock_count; diff --git a/src/util.h b/src/util.h index 9d1bee7..76d5893 100644 --- a/src/util.h +++ b/src/util.h @@ -30,4 +30,8 @@ void SSL_CRYPTO_thread_setup(void); void SSL_CRYPTO_thread_cleanup(void); +unsigned char *SSL_SHA256(const unsigned char *d, size_t n, unsigned char *md); + +int SSL_base64_decode(const char *in, unsigned char *out); + #endif \ No newline at end of file