Add ALPN extension to CCF servers, advertising HTTP/1.1 (#3643)

This commit is contained in:
Eddy Ashton 2022-03-11 11:02:16 +00:00 коммит произвёл GitHub
Родитель a1d1520b58
Коммит 952df50c75
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 90 добавлений и 39 удалений

Просмотреть файл

@ -661,12 +661,16 @@ if(BUILD_TESTS)
endif()
if(TLS_TEST)
add_custom_target(
testssl ALL
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/testssl/testssl.sh
COMMAND
test -d testssl || git clone https://github.com/drwetter/testssl.sh
rm -rf ${CMAKE_CURRENT_BINARY_DIR}/testssl && git clone --depth 1
https://github.com/drwetter/testssl.sh
${CMAKE_CURRENT_BINARY_DIR}/testssl
)
add_custom_target(
testssl ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/testssl/testssl.sh
)
endif()
add_e2e_test(

Просмотреть файл

@ -6,6 +6,33 @@
namespace tls
{
struct AlpnProtocols
{
const unsigned char* data;
unsigned int size;
};
static int alpn_select_cb(
SSL* ssl,
const unsigned char** out,
unsigned char* outlen,
const unsigned char* in,
unsigned int inlen,
void* arg)
{
auto protos = (AlpnProtocols*)arg;
if (
SSL_select_next_proto(
(unsigned char**)out, outlen, protos->data, protos->size, in, inlen) !=
OPENSSL_NPN_NEGOTIATED)
{
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
class Server : public Context
{
private:
@ -15,6 +42,13 @@ namespace tls
Server(std::shared_ptr<Cert> cert_) : Context(false), cert(cert_)
{
cert->use(ssl, cfg);
// Configure protocols negotiated by ALPN
static unsigned char alpn_protos_data[] = {
8, 'h', 't', 't', 'p', '/', '1', '.', '1'};
static AlpnProtocols alpn_protos{
alpn_protos_data, sizeof(alpn_protos_data)};
SSL_CTX_set_alpn_select_cb(cfg, alpn_select_cb, &alpn_protos);
}
};
}

Просмотреть файл

@ -206,51 +206,64 @@ def test_protocols(network, args):
url = f"{primary_root}/node/state"
ca_path = os.path.join(network.common_dir, "service_cert.pem")
common_options = [url, "-sS", "--cacert", ca_path]
common_options = [
url,
"-sS",
"--cacert",
ca_path,
"-w",
"\\n%{http_code}\\n%{http_version}",
]
# Check that websocket upgrade request is ignored
def parse_result_out(r):
assert r.returncode == 0, r.returncode
body = r.stdout.decode()
return body.rsplit("\n", 2)
# Call without any extra args to get golden response
res = infra.proc.ccall(
"curl",
"--no-buffer",
"-H",
"Connection: Upgrade",
"-H",
"Upgrade: websocket",
"-w",
"\n%{http_code}",
*common_options,
)
assert res.returncode == 0, res.returncode
body = res.stdout.decode()
status_code = body.splitlines()[-1]
assert status_code == "200", body
expected_response_body = body[: body.rfind("\n")]
expected_response_body, status_code, http_version = parse_result_out(res)
assert status_code == "200", status_code
assert http_version == "1.1", http_version
# Test additional HTTP versions with curl
for (protocol, expected_errors) in (
("", None),
("--http1.0", None),
("--http1.1", None),
("--http2", None), # Upgrade request is ignored
("--http2-prior-knowledge", ["Error in the HTTP2 framing layer"]),
(
"--http3",
[
# Test additional protocols with curl
for protocol, expected_result in {
# HTTP/1.x requests succeed, as HTTP/1.1
"--http1.0": {"http_status": "200", "http_version": "1.1"},
"--http1.1": {"http_status": "200", "http_version": "1.1"},
# WebSockets upgrade request is ignored
"websockets": {"extra_args": [], "http_status": "200", "http_version": "1.1"},
# TLS handshake negotiates HTTP/1.1
"--http2": {"http_status": "200", "http_version": "1.1"},
"--http2-prior-knowledge": {"http_status": "200", "http_version": "1.1"},
# HTTP3 is not supported by curl _or_ CCF
"--http3": {
"errors": [
"the installed libcurl version doesn't support this",
"option --http3: is unknown",
],
),
):
res = infra.proc.ccall("curl", protocol, *common_options)
if expected_errors is None:
assert res.returncode == 0, res.returncode
out = res.stdout.decode()
]
},
}.items():
cmd = ["curl", *common_options]
if "extra_args" in expected_result:
cmd.extend(expected_result["extra_args"])
else:
cmd.append(protocol)
res = infra.proc.ccall(*cmd)
if "errors" not in expected_result:
response_body, status_code, http_version = parse_result_out(res)
assert (
out == expected_response_body
), f"{out}\n !=\n{expected_response_body}"
response_body == expected_response_body
), f"{response_body}\n !=\n{expected_response_body}"
assert status_code == "200", status_code
assert http_version == "1.1", http_version
else:
assert res.returncode != 0, res.returncode
err = res.stderr.decode()
expected_errors = expected_result["errors"]
assert any(expected_error in err for expected_error in expected_errors), err
# Valid transactions are still accepted
@ -1456,9 +1469,9 @@ def run_parsing_errors(args):
) as network:
network.start_and_open(args)
network.ignore_error_pattern_on_shutdown("Error parsing HTTP request")
network = test_illegal(network, args)
network = test_protocols(network, args)
network.ignore_error_pattern_on_shutdown("Error parsing HTTP request")
if __name__ == "__main__":

Просмотреть файл

@ -8,7 +8,7 @@
"TLS1_2","","OK","offered","",""
"TLS1_3","","OK","offered with final","",""
"NPN","","INFO","not offered","",""
"ALPN","","INFO","not offered","",""
"ALPN","","INFO","http/1.1","",""
"cipherlist_NULL","","OK","not offered","","CWE-327"
"cipherlist_aNULL","","OK","not offered","","CWE-327"
"cipherlist_EXPORT","","OK","not offered","","CWE-327"
@ -29,7 +29,7 @@
"FS","","OK","offered","",""
"FS_ciphers","","INFO","TLS_AES_256_GCM_SHA384 ECDHE-ECDSA-AES256-GCM-SHA384 TLS_AES_128_GCM_SHA256 ECDHE-ECDSA-AES128-GCM-SHA256","",""
"FS_ECDHE_curves","","OK","secp384r1 secp521r1","",""
"TLS_extensions","","INFO","'renegotiation info/#65281' 'EC point formats/#11' 'session ticket/#35' 'supported versions/#43' 'key share/#51' 'supported_groups/#10' 'max fragment length/#1' 'extended master secret/#23'","",""
"TLS_extensions","","INFO","'renegotiation info/#65281' 'EC point formats/#11' 'session ticket/#35' 'supported versions/#43' 'key share/#51' 'supported_groups/#10' 'max fragment length/#1' 'application layer protocol negotiation/#16' 'extended master secret/#23'","",""
"TLS_session_ticket","","INFO","valid for 7200 seconds only (<daily)","",""
"SSL_sessionID_support","","INFO","yes","",""
"sessionresumption_ticket","","INFO","not supported","",""

Не удается отобразить этот файл, потому что он имеет неправильное количество полей в строке 2.