Merge branch 'nghttpx-psk'

This commit is contained in:
Tatsuhiro Tsujikawa 2017-01-08 21:10:07 +09:00
commit 55bf6cdb15
10 changed files with 297 additions and 2 deletions

View File

@ -153,6 +153,8 @@ OPTIONS = [
"dns-lookup-timeout",
"dns-max-try",
"frontend-keep-alive-timeout",
"psk-secrets",
"client-psk-secrets",
]
LOGVARS = [

View File

@ -181,6 +181,9 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "util_random_alpha_digit",
shrpx::test_util_random_alpha_digit) ||
!CU_add_test(pSuite, "util_format_hex", shrpx::test_util_format_hex) ||
!CU_add_test(pSuite, "util_is_hex_string",
shrpx::test_util_is_hex_string) ||
!CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) ||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||

View File

@ -2103,6 +2103,22 @@ SSL/TLS:
argument <CERT>, or certificate option in configuration
file. For additional certificates, use --subcert
option. This option requires OpenSSL >= 1.0.2.
--psk-secrets=<PATH>
Read list of PSK identity and secrets from <PATH>. This
is used for frontend connection. The each line of input
file is formatted as <identity>:<hex-secret>, where
<identity> is PSK identity, and <hex-secret> is secret
in hex. An empty line, and line which starts with '#'
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=<PATH>
Read PSK identity and secrets from <PATH>. This is used
for backend connection. The each line of input file is
formatted as <identity>:<hex-secret>, where <identity>
is PSK identity, and <hex-secret> 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=<N>
@ -3078,6 +3094,8 @@ int main(int argc, char **argv) {
{SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
{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;
@ -3768,6 +3786,14 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT,
StringRef{optarg});
break;
case 147:
// --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;
}

View File

@ -1199,6 +1199,130 @@ int read_tls_sct_from_dir(std::vector<uint8_t> &dst, const StringRef &opt,
}
} // namespace
namespace {
// Reads PSK secrets from path, and parses each line. The result is
// directly stored into config->tls.psk_secrets. This function
// returns 0 if it succeeds, or -1.
int parse_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_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_PSK_SECRETS
<< ": could not fine separator at line " << lineno;
return -1;
}
if (sep_it == std::begin(line)) {
LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty identity at line "
<< lineno;
return -1;
}
if (sep_it + 1 == std::end(line)) {
LOG(ERROR) << SHRPX_OPT_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_PSK_SECRETS
<< ": secret must be hex string at line " << lineno;
return -1;
}
auto identity =
make_string_ref(config->balloc, StringRef{std::begin(line), sep_it});
auto secret =
util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)});
auto rv = tlsconf.psk_secrets.emplace(identity, secret);
if (!rv.second) {
LOG(ERROR) << SHRPX_OPT_PSK_SECRETS
<< ": identity has already been registered at line " << lineno;
return -1;
}
}
return 0;
}
} // 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) {
@ -1366,6 +1490,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("ecdh-curve", name, 10)) {
return SHRPX_OPTID_ECDH_CURVES;
}
if (util::strieq_l("psk-secret", name, 10)) {
return SHRPX_OPTID_PSK_SECRETS;
}
break;
case 't':
if (util::strieq_l("write-burs", name, 10)) {
@ -1553,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;
@ -3138,6 +3270,10 @@ int parse_config(Config *config, int optid, const StringRef &opt,
case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT:
return parse_duration(&config->conn.upstream.timeout.idle_read, 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";

View File

@ -319,6 +319,9 @@ constexpr auto SHRPX_OPT_DNS_LOOKUP_TIMEOUT =
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;
@ -549,6 +552,16 @@ 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<StringRef, StringRef> psk_secrets;
// The list of additional TLS certificate pair
std::vector<TLSCertificate> subcerts;
std::vector<unsigned char> alpn_prefs;
@ -914,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,
@ -975,6 +989,7 @@ enum {
SHRPX_OPTID_PID_FILE,
SHRPX_OPTID_PRIVATE_KEY_FILE,
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
SHRPX_OPTID_PSK_SECRETS,
SHRPX_OPTID_READ_BURST,
SHRPX_OPTID_READ_RATE,
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,

View File

@ -525,6 +525,63 @@ int sct_parse_cb(SSL *ssl, unsigned int ext_type, const unsigned char *in,
} // namespace
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L
namespace {
unsigned int psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk,
unsigned int max_psk_len) {
auto config = get_config();
auto &tlsconf = config->tls;
auto it = tlsconf.psk_secrets.find(StringRef{identity});
if (it == std::end(tlsconf.psk_secrets)) {
return 0;
}
auto &secret = (*it).second;
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(secret), std::end(secret), psk);
return static_cast<unsigned int>(secret.size());
}
} // 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;
@ -734,6 +791,8 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
}
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
auto tls_ctx_data = new TLSContextData();
tls_ctx_data->cert_file = cert_file;
tls_ctx_data->sct_data = sct_data;
@ -873,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);
@ -1155,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);

View File

@ -1465,6 +1465,30 @@ int sha256(uint8_t *res, const StringRef &s) {
return 0;
}
bool is_hex_string(const StringRef &s) {
if (s.size() % 2) {
return false;
}
for (auto c : s) {
if (!is_hex_digit(c)) {
return false;
}
}
return true;
}
StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) {
auto iov = make_byte_ref(balloc, s.size() + 1);
auto p = iov.base;
for (auto it = std::begin(s); it != std::end(s); it += 2) {
*p++ = (hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1));
}
*p = '\0';
return StringRef{iov.base, p};
}
} // namespace util
} // namespace nghttp2

View File

@ -82,6 +82,9 @@ inline bool is_hex_digit(const char c) {
return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
}
// Returns true if |s| is hex string.
bool is_hex_string(const StringRef &s);
bool in_rfc3986_unreserved_chars(const char c);
bool in_rfc3986_sub_delims(const char c);
@ -147,6 +150,11 @@ template <size_t N> std::string format_hex(const std::array<uint8_t, N> &s) {
StringRef format_hex(BlockAllocator &balloc, const StringRef &s);
// decode_hex decodes hex string |s|, returns the decoded byte string.
// This function assumes |s| is hex string, that is is_hex_string(s)
// == true.
StringRef decode_hex(BlockAllocator &balloc, const StringRef &s);
// Returns given time |t| from epoch in HTTP Date format (e.g., Mon,
// 10 Oct 2016 10:25:58 GMT).
std::string http_date(time_t t);

View File

@ -593,4 +593,20 @@ void test_util_format_hex(void) {
CU_ASSERT("" == util::format_hex(balloc, StringRef::from_lit("")));
}
void test_util_is_hex_string(void) {
CU_ASSERT(util::is_hex_string(StringRef{}));
CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789abcdef")));
CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789ABCDEF")));
CU_ASSERT(!util::is_hex_string(StringRef::from_lit("000")));
CU_ASSERT(!util::is_hex_string(StringRef::from_lit("XX")));
}
void test_util_decode_hex(void) {
BlockAllocator balloc(4096, 4096);
CU_ASSERT("\x0f\xf0" ==
util::decode_hex(balloc, StringRef::from_lit("0ff0")));
CU_ASSERT("" == util::decode_hex(balloc, StringRef{}));
}
} // namespace shrpx

View File

@ -64,6 +64,8 @@ void test_util_make_hostport(void);
void test_util_strifind(void);
void test_util_random_alpha_digit(void);
void test_util_format_hex(void);
void test_util_is_hex_string(void);
void test_util_decode_hex(void);
} // namespace shrpx