зеркало из https://github.com/microsoft/CCF.git
Add ALPN extension to CCF servers, advertising HTTP/1.1 (#3643)
This commit is contained in:
Родитель
a1d1520b58
Коммит
952df50c75
|
@ -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.
|
Загрузка…
Ссылка в новой задаче