Merge branch 'nghttpx-psk'
This commit is contained in:
commit
55bf6cdb15
|
@ -153,6 +153,8 @@ OPTIONS = [
|
|||
"dns-lookup-timeout",
|
||||
"dns-max-try",
|
||||
"frontend-keep-alive-timeout",
|
||||
"psk-secrets",
|
||||
"client-psk-secrets",
|
||||
]
|
||||
|
||||
LOGVARS = [
|
||||
|
|
|
@ -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) ||
|
||||
|
|
26
src/shrpx.cc
26
src/shrpx.cc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
24
src/util.cc
24
src/util.cc
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue