nghttpx: Add --psk-secret option to enable PSK in frontend connection
This commit is contained in:
parent
1a07fb000b
commit
83c759572c
|
@ -153,6 +153,7 @@ OPTIONS = [
|
||||||
"dns-lookup-timeout",
|
"dns-lookup-timeout",
|
||||||
"dns-max-try",
|
"dns-max-try",
|
||||||
"frontend-keep-alive-timeout",
|
"frontend-keep-alive-timeout",
|
||||||
|
"psk-secrets",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGVARS = [
|
LOGVARS = [
|
||||||
|
|
|
@ -181,6 +181,9 @@ int main(int argc, char *argv[]) {
|
||||||
!CU_add_test(pSuite, "util_random_alpha_digit",
|
!CU_add_test(pSuite, "util_random_alpha_digit",
|
||||||
shrpx::test_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_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, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
||||||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
||||||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
||||||
|
|
14
src/shrpx.cc
14
src/shrpx.cc
|
@ -2103,6 +2103,15 @@ SSL/TLS:
|
||||||
argument <CERT>, or certificate option in configuration
|
argument <CERT>, or certificate option in configuration
|
||||||
file. For additional certificates, use --subcert
|
file. For additional certificates, use --subcert
|
||||||
option. This option requires OpenSSL >= 1.0.2.
|
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.
|
||||||
|
|
||||||
HTTP/2 and SPDY:
|
HTTP/2 and SPDY:
|
||||||
-c, --frontend-http2-max-concurrent-streams=<N>
|
-c, --frontend-http2-max-concurrent-streams=<N>
|
||||||
|
@ -3078,6 +3087,7 @@ int main(int argc, char **argv) {
|
||||||
{SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
|
{SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
|
||||||
{SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument,
|
{SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument,
|
||||||
&flag, 146},
|
&flag, 146},
|
||||||
|
{SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -3768,6 +3778,10 @@ int main(int argc, char **argv) {
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT,
|
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT,
|
||||||
StringRef{optarg});
|
StringRef{optarg});
|
||||||
break;
|
break;
|
||||||
|
case 147:
|
||||||
|
// --psk-secrets
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1199,6 +1199,70 @@ int read_tls_sct_from_dir(std::vector<uint8_t> &dst, const StringRef &opt,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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
|
||||||
|
|
||||||
// generated by gennghttpxfun.py
|
// generated by gennghttpxfun.py
|
||||||
int option_lookup_token(const char *name, size_t namelen) {
|
int option_lookup_token(const char *name, size_t namelen) {
|
||||||
switch (namelen) {
|
switch (namelen) {
|
||||||
|
@ -1366,6 +1430,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
if (util::strieq_l("ecdh-curve", name, 10)) {
|
if (util::strieq_l("ecdh-curve", name, 10)) {
|
||||||
return SHRPX_OPTID_ECDH_CURVES;
|
return SHRPX_OPTID_ECDH_CURVES;
|
||||||
}
|
}
|
||||||
|
if (util::strieq_l("psk-secret", name, 10)) {
|
||||||
|
return SHRPX_OPTID_PSK_SECRETS;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
if (util::strieq_l("write-burs", name, 10)) {
|
if (util::strieq_l("write-burs", name, 10)) {
|
||||||
|
@ -3138,6 +3205,8 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT:
|
case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT:
|
||||||
return parse_duration(&config->conn.upstream.timeout.idle_read, opt,
|
return parse_duration(&config->conn.upstream.timeout.idle_read, opt,
|
||||||
optarg);
|
optarg);
|
||||||
|
case SHRPX_OPTID_PSK_SECRETS:
|
||||||
|
return parse_psk_secrets(config, optarg);
|
||||||
case SHRPX_OPTID_CONF:
|
case SHRPX_OPTID_CONF:
|
||||||
LOG(WARN) << "conf: ignored";
|
LOG(WARN) << "conf: ignored";
|
||||||
|
|
||||||
|
|
|
@ -319,6 +319,7 @@ constexpr auto SHRPX_OPT_DNS_LOOKUP_TIMEOUT =
|
||||||
constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try");
|
constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try");
|
||||||
constexpr auto SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT =
|
constexpr auto SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT =
|
||||||
StringRef::from_lit("frontend-keep-alive-timeout");
|
StringRef::from_lit("frontend-keep-alive-timeout");
|
||||||
|
constexpr auto SHRPX_OPT_PSK_SECRETS = StringRef::from_lit("psk-secrets");
|
||||||
|
|
||||||
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -549,6 +550,9 @@ struct TLSConfig {
|
||||||
StringRef cert_file;
|
StringRef cert_file;
|
||||||
} client;
|
} client;
|
||||||
|
|
||||||
|
// 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
|
// The list of additional TLS certificate pair
|
||||||
std::vector<TLSCertificate> subcerts;
|
std::vector<TLSCertificate> subcerts;
|
||||||
std::vector<unsigned char> alpn_prefs;
|
std::vector<unsigned char> alpn_prefs;
|
||||||
|
@ -975,6 +979,7 @@ enum {
|
||||||
SHRPX_OPTID_PID_FILE,
|
SHRPX_OPTID_PID_FILE,
|
||||||
SHRPX_OPTID_PRIVATE_KEY_FILE,
|
SHRPX_OPTID_PRIVATE_KEY_FILE,
|
||||||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||||
|
SHRPX_OPTID_PSK_SECRETS,
|
||||||
SHRPX_OPTID_READ_BURST,
|
SHRPX_OPTID_READ_BURST,
|
||||||
SHRPX_OPTID_READ_RATE,
|
SHRPX_OPTID_READ_RATE,
|
||||||
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
|
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
|
||||||
|
|
|
@ -525,6 +525,30 @@ int sct_parse_cb(SSL *ssl, unsigned int ext_type, const unsigned char *in,
|
||||||
} // namespace
|
} // namespace
|
||||||
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#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
|
||||||
|
|
||||||
struct TLSProtocol {
|
struct TLSProtocol {
|
||||||
StringRef name;
|
StringRef name;
|
||||||
long int mask;
|
long int mask;
|
||||||
|
@ -734,6 +758,8 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
|
||||||
}
|
}
|
||||||
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#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();
|
auto tls_ctx_data = new TLSContextData();
|
||||||
tls_ctx_data->cert_file = cert_file;
|
tls_ctx_data->cert_file = cert_file;
|
||||||
tls_ctx_data->sct_data = sct_data;
|
tls_ctx_data->sct_data = sct_data;
|
||||||
|
|
24
src/util.cc
24
src/util.cc
|
@ -1465,6 +1465,30 @@ int sha256(uint8_t *res, const StringRef &s) {
|
||||||
return 0;
|
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 util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // 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');
|
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_unreserved_chars(const char c);
|
||||||
|
|
||||||
bool in_rfc3986_sub_delims(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);
|
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,
|
// Returns given time |t| from epoch in HTTP Date format (e.g., Mon,
|
||||||
// 10 Oct 2016 10:25:58 GMT).
|
// 10 Oct 2016 10:25:58 GMT).
|
||||||
std::string http_date(time_t t);
|
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("")));
|
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
|
} // namespace shrpx
|
||||||
|
|
|
@ -64,6 +64,8 @@ void test_util_make_hostport(void);
|
||||||
void test_util_strifind(void);
|
void test_util_strifind(void);
|
||||||
void test_util_random_alpha_digit(void);
|
void test_util_random_alpha_digit(void);
|
||||||
void test_util_format_hex(void);
|
void test_util_format_hex(void);
|
||||||
|
void test_util_is_hex_string(void);
|
||||||
|
void test_util_decode_hex(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue