From 79a24f5dd972da0c712a4df9e623d47f1d64b63a Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 8 Jan 2017 00:24:33 +0900 Subject: [PATCH] nghttpx: Add --client-psk-secret option to enable PSK in backend --- gennghttpxfun.py | 1 + src/shrpx.cc | 12 ++++++++ src/shrpx_config.cc | 67 +++++++++++++++++++++++++++++++++++++++++++++ src/shrpx_config.h | 10 +++++++ src/shrpx_ssl.cc | 41 +++++++++++++++++++++++++-- 5 files changed, 129 insertions(+), 2 deletions(-) diff --git a/gennghttpxfun.py b/gennghttpxfun.py index ad5057e7..4d4fb7e4 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -154,6 +154,7 @@ OPTIONS = [ "dns-max-try", "frontend-keep-alive-timeout", "psk-secrets", + "client-psk-secrets", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index 922fa600..c31ae514 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -2112,6 +2112,13 @@ SSL/TLS: are skipped. The default enabled cipher list might not contain any PSK cipher suite. In that case, desired PSK cipher suites must be enabled using --ciphers option. + --client-psk-secrets= + Read PSK identity and secrets from . This is used + for backend connection. The each line of input file is + formatted as :, where + is PSK identity, and is secret in hex. An + empty line, and line which starts with '#' are skipped. + The first identity and secret pair encountered is used. HTTP/2 and SPDY: -c, --frontend-http2-max-concurrent-streams= @@ -3088,6 +3095,7 @@ int main(int argc, char **argv) { {SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, &flag, 146}, {SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147}, + {SHRPX_OPT_CLIENT_PSK_SECRETS.c_str(), required_argument, &flag, 148}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -3782,6 +3790,10 @@ int main(int argc, char **argv) { // --psk-secrets cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg}); break; + case 148: + // --client-psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PSK_SECRETS, StringRef{optarg}); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index b59facd7..21f8f1a4 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -1263,6 +1263,66 @@ int parse_psk_secrets(Config *config, const StringRef &path) { } } // namespace +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.client_psk. This function returns +// 0 if it succeeds, or -1. +int parse_client_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": could not open file " + << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": could not fine separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + tlsconf.client_psk.identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + tlsconf.client_psk.secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + return 0; + } + + return 0; +} +} // namespace + // generated by gennghttpxfun.py int option_lookup_token(const char *name, size_t namelen) { switch (namelen) { @@ -1620,6 +1680,11 @@ int option_lookup_token(const char *name, size_t namelen) { return SHRPX_OPTID_ADD_REQUEST_HEADER; } break; + case 's': + if (util::strieq_l("client-psk-secret", name, 17)) { + return SHRPX_OPTID_CLIENT_PSK_SECRETS; + } + break; case 't': if (util::strieq_l("dns-lookup-timeou", name, 17)) { return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT; @@ -3207,6 +3272,8 @@ int parse_config(Config *config, int optid, const StringRef &opt, optarg); case SHRPX_OPTID_PSK_SECRETS: return parse_psk_secrets(config, optarg); + case SHRPX_OPTID_CLIENT_PSK_SECRETS: + return parse_client_psk_secrets(config, optarg); case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index a9b8ca11..c033b639 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -320,6 +320,8 @@ constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try"); constexpr auto SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT = StringRef::from_lit("frontend-keep-alive-timeout"); constexpr auto SHRPX_OPT_PSK_SECRETS = StringRef::from_lit("psk-secrets"); +constexpr auto SHRPX_OPT_CLIENT_PSK_SECRETS = + StringRef::from_lit("client-psk-secrets"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -550,6 +552,13 @@ struct TLSConfig { StringRef cert_file; } client; + // Client PSK configuration + struct { + // identity must be NULL terminated string. + StringRef identity; + StringRef secret; + } client_psk; + // PSK secrets. The key is identity, and the associated value is // its secret. std::map psk_secrets; @@ -918,6 +927,7 @@ enum { SHRPX_OPTID_CLIENT_CERT_FILE, SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE, SHRPX_OPTID_CLIENT_PROXY, + SHRPX_OPTID_CLIENT_PSK_SECRETS, SHRPX_OPTID_CONF, SHRPX_OPTID_DAEMON, SHRPX_OPTID_DH_PARAM_FILE, diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index cdd9c852..9912ac5c 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -549,6 +549,39 @@ unsigned int psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk, } } // namespace +namespace { +unsigned int psk_client_cb(SSL *ssl, const char *hint, char *identity_out, + unsigned int max_identity_len, unsigned char *psk, + unsigned int max_psk_len) { + auto config = get_config(); + auto &tlsconf = config->tls; + + auto &identity = tlsconf.client_psk.identity; + auto &secret = tlsconf.client_psk.secret; + + if (identity.empty()) { + return 0; + } + + if (identity.size() + 1 > max_identity_len) { + LOG(ERROR) << "The size of PSK identity is " << identity.size() + << ", but the acceptable maximum size is " << max_identity_len; + return 0; + } + + if (secret.size() > max_psk_len) { + LOG(ERROR) << "The size of PSK secret is " << secret.size() + << ", but the acceptable maximum size is " << max_psk_len; + return 0; + } + + *std::copy(std::begin(identity), std::end(identity), identity_out) = '\0'; + std::copy(std::begin(secret), std::end(secret), psk); + + return (unsigned int)secret.size(); +} +} // namespace + struct TLSProtocol { StringRef name; long int mask; @@ -899,6 +932,8 @@ SSL_CTX *create_ssl_client_context( #endif // HAVE_NEVERBLEED } + SSL_CTX_set_psk_client_callback(ssl_ctx, psk_client_cb); + // NPN selection callback. This is required to set SSL_CTX because // OpenSSL does not offer SSL_set_next_proto_select_cb. SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_cb, nullptr); @@ -1181,8 +1216,10 @@ int verify_hostname(X509 *cert, const StringRef &hostname, int check_cert(SSL *ssl, const Address *addr, const StringRef &host) { auto cert = SSL_get_peer_certificate(ssl); if (!cert) { - LOG(ERROR) << "No certificate found"; - return -1; + // By the protocol definition, TLS server always sends certificate + // if it has. If certificate cannot be retrieved, authentication + // without certificate is used, such as PSK. + return 0; } auto cert_deleter = defer(X509_free, cert); auto verify_res = SSL_get_verify_result(ssl);