From 3f7bc30f65357b8dde47ec40f05013edaf82c8ca Mon Sep 17 00:00:00 2001 From: Nick Peng Date: Thu, 8 Aug 2019 01:20:21 +0800 Subject: [PATCH] Support verify TLS hostname --- ReadMe.md | 4 +- ReadMe_en.md | 4 +- etc/smartdns/smartdns.conf | 5 ++ .../luci/files/luci/i18n/smartdns.zh-cn.po | 6 ++ .../luci/model/cbi/smartdns/upstream.lua | 8 +++ package/openwrt/files/etc/init.d/smartdns | 5 ++ src/dns_client.c | 71 +++++++++++++++++++ src/dns_client.h | 2 + src/dns_conf.c | 6 ++ src/dns_conf.h | 1 + src/smartdns.c | 2 + 11 files changed, 110 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 7b0a1fd..6cd3067 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -562,8 +562,8 @@ https://github.com/pymumu/smartdns/releases |conf-file|附加配置文件|无|文件路径|conf-file /etc/smartdns/smartdns.more.conf |server|上游UDP DNS|无|可重复
`[ip][:port]`:服务器IP,端口可选。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-whitelist-ip]`:whitelist-ip参数指定仅接受whitelist-ip中配置IP范围。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server 8.8.8.8:53 -blacklist-ip -group g1 |server-tcp|上游TCP DNS|无|可重复
`[ip][:port]`:服务器IP,端口可选。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-whitelist-ip]`:whitelist-ip参数指定仅接受whitelist-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值
`[-host-name]`:TLS SNI名称。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-whitelist-ip]`:whitelist-ip参数指定仅接受whitelist-ip中配置IP范围。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-tls 8.8.8.8:853 -|server-https|上游HTTPS DNS|无|可重复
`https://[host][:port]/path`:服务器IP,端口可选。
`[-spki-pin [sha256-pin]]`: TLS合法性校验SPKI值,base64编码的sha256 SPKI pin值
`[-host-name]`:TLS SNI名称
`[-http-host]`:http协议头主机名。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-whitelist-ip]`:whitelist-ip参数指定仅接受whitelist-ip中配置IP范围。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-https https://cloudflare-dns.com/dns-query +|server-tls|上游TLS DNS|无|可重复
`[ip][:port]`:服务器IP,端口可选。
`[-spki-pin [sha256-pin]]`: TLS合法性校验SPKI值,base64编码的sha256 SPKI pin值
`[-host-name]`:TLS SNI名称。
`[-tls-host-check]`: TLS证书主机名校验。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-whitelist-ip]`:whitelist-ip参数指定仅接受whitelist-ip中配置IP范围。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-tls 8.8.8.8:853 +|server-https|上游HTTPS DNS|无|可重复
`https://[host][:port]/path`:服务器IP,端口可选。
`[-spki-pin [sha256-pin]]`: TLS合法性校验SPKI值,base64编码的sha256 SPKI pin值
`[-host-name]`:TLS SNI名称
`[-http-host]`:http协议头主机名。
`[-tls-host-check]`: TLS证书主机名校验。
`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。
`[-whitelist-ip]`:whitelist-ip参数指定仅接受whitelist-ip中配置IP范围。
`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。
`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-https https://cloudflare-dns.com/dns-query |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/ReadMe_en.md b/ReadMe_en.md index 082dd25..ac29598 100755 --- a/ReadMe_en.md +++ b/ReadMe_en.md @@ -557,8 +557,8 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use |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".
`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted.
`[-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 |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".
`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted.
`[-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
`[-host-name]`:TLS Server name.
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted.
`[-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-https|Upstream HTTPS DNS server|None|Repeatable
`https://[host][:port]/path`: Server IP, port optional.
`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash
`[-host-name]`:TLS Server name
`[-http-host]`:http header host.
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted.
`[-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-https https://cloudflare-dns.com/dns-query +|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
`[-host-name]`:TLS Server name.
`[-tls-host-check]`: TLS cert hostname to verify.
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted.
`[-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-https|Upstream HTTPS DNS server|None|Repeatable
`https://[host][:port]/path`: Server IP, port optional.
`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash
`[-host-name]`:TLS Server name
`[-http-host]`:http header host.
`[-tls-host-check]`: TLS cert hostname to verify.
`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip".
`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted.
`[-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-https https://cloudflare-dns.com/dns-query |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/etc/smartdns/smartdns.conf b/etc/smartdns/smartdns.conf index dd495ff..e4afc35 100644 --- a/etc/smartdns/smartdns.conf +++ b/etc/smartdns/smartdns.conf @@ -118,6 +118,8 @@ log-level info # remote tls dns server list # server-tls [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group] # -spki-pin: TLS spki pin to verify. +# -tls-host-check: cert hostname to verify. +# -hostname: TLS sni hostname. # Get SPKI 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 @@ -127,6 +129,9 @@ log-level info # remote https dns server list # server-https https://[host]:[port]/path [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group] # -spki-pin: TLS spki pin to verify. +# -tls-host-check: cert hostname to verify. +# -hostname: TLS sni hostname. +# -http-host: http host. # default port is 443 # server-https https://cloudflare-dns.com/dns-query diff --git a/package/luci/files/luci/i18n/smartdns.zh-cn.po b/package/luci/files/luci/i18n/smartdns.zh-cn.po index a990038..1d03550 100644 --- a/package/luci/files/luci/i18n/smartdns.zh-cn.po +++ b/package/luci/files/luci/i18n/smartdns.zh-cn.po @@ -139,6 +139,12 @@ msgstr "协议类型" msgid "Domain Address" msgstr "域名地址" +msgid "TLS Hostname Verify" +msgstr "校验TLS主机名" + +msgid "Set TLS hostname to verify" +msgstr "设置校验TLS主机名" + msgid "TLS SNI name" msgstr "TLS SNI名称" diff --git a/package/luci/files/luci/model/cbi/smartdns/upstream.lua b/package/luci/files/luci/model/cbi/smartdns/upstream.lua index 0a05fd4..36bad7e 100644 --- a/package/luci/files/luci/model/cbi/smartdns/upstream.lua +++ b/package/luci/files/luci/model/cbi/smartdns/upstream.lua @@ -39,6 +39,14 @@ o:value("https", translate("https")) o.default = "udp" o.rempty = false +---- TLS host check +o = s:option(Value, "tls_host_check", translate("TLS Hostname Verify"), translate("Set TLS hostname to verify")) +o.default = "" +o.datatype = "string" +o.rempty = true +o:depends("type", "tls") +o:depends("type", "https") + ---- SNI host name o = s:option(Value, "host_name", translate("TLS SNI name"), translate("Sets the server name indication")) o.default = "" diff --git a/package/openwrt/files/etc/init.d/smartdns b/package/openwrt/files/etc/init.d/smartdns index 4d29c98..865bf83 100644 --- a/package/openwrt/files/etc/init.d/smartdns +++ b/package/openwrt/files/etc/init.d/smartdns @@ -160,6 +160,7 @@ load_server() config_get "port" "$section" "port" "" config_get "type" "$section" "type" "udp" config_get "ip" "$section" "ip" "" + config_get "tls_host_check" "$section" "tls_host_check" "" config_get "host_name" "$section" "host_name" "" config_get "http_host" "$section" "http_host" "" config_get "server_group" "$section" "server_group" "" @@ -191,6 +192,10 @@ load_server() fi fi + if [ ! -z "$tls_host_check" ]; then + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -tls-host-check $tls_host_check" + fi + if [ ! -z "$host_name" ]; then ADDITIONAL_ARGS="$ADDITIONAL_ARGS -host-name $host_name" fi diff --git a/src/dns_client.c b/src/dns_client.c index b856824..bd9fbdd 100644 --- a/src/dns_client.c +++ b/src/dns_client.c @@ -29,6 +29,7 @@ #include "util.h" #include #include +#include #include #include #include @@ -623,6 +624,38 @@ int dns_client_spki_decode(const char *spki, unsigned char *spki_data_out) return spki_data_len; } +static char *_dns_client_server_get_tls_host_check(struct dns_server_info *server_info) +{ + char *tls_host_check = NULL; + + switch (server_info->type) { + case DNS_SERVER_UDP: { + } break; + case DNS_SERVER_HTTPS: { + struct client_dns_server_flag_https *flag_https = &server_info->flags.https; + tls_host_check = flag_https->tls_host_check; + } break; + case DNS_SERVER_TLS: { + struct client_dns_server_flag_tls *flag_tls = &server_info->flags.tls; + tls_host_check = flag_tls->tls_host_check; + } break; + break; + case DNS_SERVER_TCP: + break; + default: + return NULL; + break; + } + + if (tls_host_check) { + if (tls_host_check[0] == '\0') { + return NULL; + } + } + + return tls_host_check; +} + static char *_dns_client_server_get_spki(struct dns_server_info *server_info, int *spki_len) { *spki_len = 0; @@ -1874,6 +1907,34 @@ static inline int _dns_client_to_hex(int c) } } +static int _dns_client_tls_matchName(const char *host, const char *pattern, int size) +{ + int match = -1; + int i = 0, j = 0; + + while (i < size && host[j] != '\0') { + if (toupper(pattern[i]) == toupper(host[j])) { + i++; + j++; + continue; + } + if (pattern[i] == '*') { + while (host[j] != '.' && host[j] != '\0') { + j++; + } + i++; + continue; + } + break; + } + + if (i == size && host[j] == '\0') { + match = 0; + } + + return match; +} + static int _dns_client_tls_verify(struct dns_server_info *server_info) { X509 *cert = NULL; @@ -1886,6 +1947,7 @@ static int _dns_client_tls_verify(struct dns_server_info *server_info) unsigned char *key_sha256 = NULL; char *spki = NULL; int spki_len = 0; + char *tls_host_check = NULL; cert = SSL_get_peer_certificate(server_info->ssl); if (cert == NULL) { @@ -1896,6 +1958,15 @@ 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); + /* check tls host */ + tls_host_check = _dns_client_server_get_tls_host_check(server_info); + if (tls_host_check) { + if (_dns_client_tls_matchName(peer_CN, tls_host_check, strnlen(tls_host_check, DNS_MAX_CNAME_LEN)) != 0) { + tlog(TLOG_INFO, "server %s CN is invalid, peer CN: %s, expect CN: %s", server_info->ip, peer_CN, tls_host_check); + goto errout; + } + } + /* get spki pin */ key_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); if (key_len <= 0) { diff --git a/src/dns_client.h b/src/dns_client.h index 8986595..3a7f12e 100644 --- a/src/dns_client.h +++ b/src/dns_client.h @@ -45,6 +45,7 @@ struct client_dns_server_flag_tls { char spki[DNS_SERVER_SPKI_LEN]; int spi_len; char hostname[DNS_MAX_CNAME_LEN]; + char tls_host_check[DNS_MAX_CNAME_LEN]; }; struct client_dns_server_flag_https { @@ -53,6 +54,7 @@ struct client_dns_server_flag_https { char hostname[DNS_MAX_CNAME_LEN]; char httphost[DNS_MAX_CNAME_LEN]; char path[DNS_MAX_CNAME_LEN]; + char tls_host_check[DNS_MAX_CNAME_LEN]; }; struct client_dns_server_flags { diff --git a/src/dns_conf.c b/src/dns_conf.c index e00132c..5272240 100644 --- a/src/dns_conf.c +++ b/src/dns_conf.c @@ -181,6 +181,7 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de {"spki-pin", required_argument, NULL, 'p'}, /* check SPKI pin */ {"host-name", required_argument, NULL, 'h'}, /* host name */ {"http-host", required_argument, NULL, 'H'}, /* http host */ + {"tls-host-check", required_argument, NULL, 'V' }, /* check tls hostname */ {"group", required_argument, NULL, 'g'}, /* add to group */ {"exclude-default-group", no_argument, NULL, 'E'}, /* ecluse this from default group */ {NULL, no_argument, NULL, 0} @@ -201,6 +202,7 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de server->path[0] = '\0'; server->hostname[0] = '\0'; server->httphost[0] = '\0'; + server->tls_host_check[0] = '\0'; ip = argv[1]; @@ -269,6 +271,10 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de safe_strncpy(server->spki, optarg, DNS_MAX_SPKI_LEN); break; } + case 'V': { + safe_strncpy(server->tls_host_check, optarg, DNS_MAX_CNAME_LEN); + break; + } default: break; } diff --git a/src/dns_conf.h b/src/dns_conf.h index efed709..e91da9a 100644 --- a/src/dns_conf.h +++ b/src/dns_conf.h @@ -122,6 +122,7 @@ struct dns_servers { char spki[DNS_MAX_SPKI_LEN]; char hostname[DNS_MAX_CNAME_LEN]; char httphost[DNS_MAX_CNAME_LEN]; + char tls_host_check[DNS_MAX_CNAME_LEN]; char path[DNS_MAX_URL_LEN]; }; diff --git a/src/smartdns.c b/src/smartdns.c index f7bf4a1..f411047 100644 --- a/src/smartdns.c +++ b/src/smartdns.c @@ -157,11 +157,13 @@ static int _smartdns_add_servers(void) safe_strncpy(flag_http->hostname, dns_conf_servers[i].hostname, sizeof(flag_http->hostname)); safe_strncpy(flag_http->path, dns_conf_servers[i].path, sizeof(flag_http->path)); safe_strncpy(flag_http->httphost, dns_conf_servers[i].httphost, sizeof(flag_http->httphost)); + safe_strncpy(flag_http->tls_host_check, dns_conf_servers[i].tls_host_check, sizeof(flag_http->tls_host_check)); } break; case DNS_SERVER_TLS: { struct client_dns_server_flag_tls *flag_tls = &flags.tls; flag_tls->spi_len = dns_client_spki_decode(dns_conf_servers[i].spki, (unsigned char *)flag_tls->spki); safe_strncpy(flag_tls->hostname, dns_conf_servers[i].hostname, sizeof(flag_tls->hostname)); + safe_strncpy(flag_tls->tls_host_check, dns_conf_servers[i].tls_host_check, sizeof(flag_tls->tls_host_check)); } break; break; case DNS_SERVER_TCP: