diff --git a/doc/ssl/SSL_CTX_set_alpn_select_cb.pod b/doc/ssl/SSL_CTX_set_alpn_select_cb.pod index 4859b3c15f..59acbad545 100644 --- a/doc/ssl/SSL_CTX_set_alpn_select_cb.pod +++ b/doc/ssl/SSL_CTX_set_alpn_select_cb.pod @@ -44,7 +44,8 @@ the application callback. B is the application defined callback. The B, B parameters are a vector in protocol-list format. The value of the B, B vector should be set to the value of a single protocol selected from the B, -B vector. The B parameter is the pointer set via +B vector. The B buffer may point directly into B, or to a +buffer that outlives the handshake. The B parameter is the pointer set via SSL_CTX_set_alpn_select_cb(). SSL_select_next_proto() is a helper function used to select protocols. It diff --git a/ssl/ssl_stat.c b/ssl/ssl_stat.c index 1928bd2505..230eadf776 100644 --- a/ssl/ssl_stat.c +++ b/ssl/ssl_stat.c @@ -335,6 +335,8 @@ const char *SSL_alert_desc_string_long(int value) return "bad certificate hash value"; case TLS1_AD_UNKNOWN_PSK_IDENTITY: return "unknown PSK identity"; + case TLS1_AD_NO_APPLICATION_PROTOCOL: + return "no application protocol"; default: return "unknown"; } diff --git a/test/README.ssltest.md b/test/README.ssltest.md index ea90efcfdc..9d828b5146 100644 --- a/test/README.ssltest.md +++ b/test/README.ssltest.md @@ -84,6 +84,11 @@ The test section supports the following options: - No - a session ticket is not expected - Broken - a special test case where the session ticket callback does not initialize crypto +* ServerNPNProtocols, Server2NPNProtocols, ClientNPNProtocols, ExpectedNPNProtocol, + ServerALPNProtocols, Server2ALPNProtocols, ClientALPNProtocols, ExpectedALPNProtocol - + NPN and ALPN settings. Server and client protocols can be specified as a comma-separated list, + and a callback with the recommended behaviour will be installed automatically. + ## Configuring the client and server The client and server configurations can be any valid `SSL_CTX` diff --git a/test/handshake_helper.c b/test/handshake_helper.c index 8a8dab02bb..77852ad586 100644 --- a/test/handshake_helper.c +++ b/test/handshake_helper.c @@ -15,6 +15,23 @@ #include "handshake_helper.h" +HANDSHAKE_RESULT *HANDSHAKE_RESULT_new() +{ + HANDSHAKE_RESULT *ret; + ret = OPENSSL_zalloc(sizeof(*ret)); + OPENSSL_assert(ret != NULL); + return ret; +} + +void HANDSHAKE_RESULT_free(HANDSHAKE_RESULT *result) +{ + OPENSSL_free(result->client_npn_negotiated); + OPENSSL_free(result->server_npn_negotiated); + OPENSSL_free(result->client_alpn_negotiated); + OPENSSL_free(result->server_alpn_negotiated); + OPENSSL_free(result); +} + /* * Since there appears to be no way to extract the sent/received alert * from the SSL object directly, we use the info callback and stash @@ -27,6 +44,22 @@ typedef struct handshake_ex_data { ssl_servername_t servername; } HANDSHAKE_EX_DATA; +typedef struct ctx_data { + unsigned char *npn_protocols; + size_t npn_protocols_len; + unsigned char *alpn_protocols; + size_t alpn_protocols_len; +} CTX_DATA; + +/* |ctx_data| itself is stack-allocated. */ +static void ctx_data_free_data(CTX_DATA *ctx_data) +{ + OPENSSL_free(ctx_data->npn_protocols); + ctx_data->npn_protocols = NULL; + OPENSSL_free(ctx_data->alpn_protocols); + ctx_data->alpn_protocols = NULL; +} + static int ex_data_idx; static void info_cb(const SSL *s, int where, int ret) @@ -42,8 +75,7 @@ static void info_cb(const SSL *s, int where, int ret) } } -/* - * Select the appropriate server CTX. +/* Select the appropriate server CTX. * Returns SSL_TLSEXT_ERR_OK if a match was found. * If |ignore| is 1, returns SSL_TLSEXT_ERR_NOACK on mismatch. * Otherwise, returns SSL_TLSEXT_ERR_ALERT_FATAL on mismatch. @@ -115,13 +147,13 @@ static int verify_accept_cb(X509_STORE_CTX *ctx, void *arg) { return 1; } -static int broken_session_ticket_cb(SSL* s, unsigned char* key_name, unsigned char *iv, +static int broken_session_ticket_cb(SSL *s, unsigned char *key_name, unsigned char *iv, EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) { return 0; } -static int do_not_call_session_ticket_cb(SSL* s, unsigned char* key_name, +static int do_not_call_session_ticket_cb(SSL *s, unsigned char *key_name, unsigned char *iv, EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) @@ -132,13 +164,114 @@ static int do_not_call_session_ticket_cb(SSL* s, unsigned char* key_name, return 0; } +/* Parse the comma-separated list into TLS format. */ +static void parse_protos(const char *protos, unsigned char **out, size_t *outlen) +{ + size_t len, i, prefix; + + len = strlen(protos); + + /* Should never have reuse. */ + OPENSSL_assert(*out == NULL); + + /* Test values are small, so we omit length limit checks. */ + *out = OPENSSL_malloc(len + 1); + OPENSSL_assert(*out != NULL); + *outlen = len + 1; + + /* + * foo => '3', 'f', 'o', 'o' + * foo,bar => '3', 'f', 'o', 'o', '3', 'b', 'a', 'r' + */ + memcpy(*out + 1, protos, len); + + prefix = 0; + i = prefix + 1; + while (i <= len) { + if ((*out)[i] == ',') { + OPENSSL_assert(i - 1 - prefix > 0); + (*out)[prefix] = i - 1 - prefix; + prefix = i; + } + i++; + } + OPENSSL_assert(len - prefix > 0); + (*out)[prefix] = len - prefix; +} + +/* + * The client SHOULD select the first protocol advertised by the server that it + * also supports. In the event that the client doesn't support any of server's + * protocols, or the server doesn't advertise any, it SHOULD select the first + * protocol that it supports. + */ +static int client_npn_cb(SSL *s, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg) +{ + CTX_DATA *ctx_data = (CTX_DATA*)(arg); + int ret; + + ret = SSL_select_next_proto(out, outlen, in, inlen, + ctx_data->npn_protocols, + ctx_data->npn_protocols_len); + /* Accept both OPENSSL_NPN_NEGOTIATED and OPENSSL_NPN_NO_OVERLAP. */ + OPENSSL_assert(ret == OPENSSL_NPN_NEGOTIATED + || ret == OPENSSL_NPN_NO_OVERLAP); + return SSL_TLSEXT_ERR_OK; +} + +static int server_npn_cb(SSL *s, const unsigned char **data, + unsigned int *len, void *arg) +{ + CTX_DATA *ctx_data = (CTX_DATA*)(arg); + *data = ctx_data->npn_protocols; + *len = ctx_data->npn_protocols_len; + return SSL_TLSEXT_ERR_OK; +} + +/* + * The server SHOULD select the most highly preferred protocol that it supports + * and that is also advertised by the client. In the event that the server + * supports no protocols that the client advertises, then the server SHALL + * respond with a fatal "no_application_protocol" alert. + */ +static int server_alpn_cb(SSL *s, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) +{ + CTX_DATA *ctx_data = (CTX_DATA*)(arg); + int ret; + + /* SSL_select_next_proto isn't const-correct... */ + unsigned char *tmp_out; + + /* + * The result points either to |in| or to |ctx_data->alpn_protocols|. + * The callback is allowed to point to |in| or to a long-lived buffer, + * so we can return directly without storing a copy. + */ + ret = SSL_select_next_proto(&tmp_out, outlen, + ctx_data->alpn_protocols, + ctx_data->alpn_protocols_len, in, inlen); + + *out = tmp_out; + /* Unlike NPN, we don't tolerate a mismatch. */ + return ret == OPENSSL_NPN_NEGOTIATED ? SSL_TLSEXT_ERR_OK + : SSL_TLSEXT_ERR_NOACK; +} + + /* * Configure callbacks and other properties that can't be set directly * in the server/client CONF. */ static void configure_handshake_ctx(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, SSL_CTX *client_ctx, - const SSL_TEST_CTX *test_ctx) + const SSL_TEST_CTX *test_ctx, + CTX_DATA *server_ctx_data, + CTX_DATA *server2_ctx_data, + CTX_DATA *client_ctx_data) { switch (test_ctx->client_verify_callback) { case SSL_TEST_VERIFY_ACCEPT_ALL: @@ -179,12 +312,55 @@ static void configure_handshake_ctx(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, if (test_ctx->session_ticket_expected == SSL_TEST_SESSION_TICKET_BROKEN) { SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, broken_session_ticket_cb); } + + if (test_ctx->server_npn_protocols != NULL) { + parse_protos(test_ctx->server_npn_protocols, + &server_ctx_data->npn_protocols, + &server_ctx_data->npn_protocols_len); + SSL_CTX_set_next_protos_advertised_cb(server_ctx, server_npn_cb, + server_ctx_data); + } + if (test_ctx->server2_npn_protocols != NULL) { + parse_protos(test_ctx->server2_npn_protocols, + &server2_ctx_data->npn_protocols, + &server2_ctx_data->npn_protocols_len); + OPENSSL_assert(server2_ctx != NULL); + SSL_CTX_set_next_protos_advertised_cb(server2_ctx, server_npn_cb, + server2_ctx_data); + } + if (test_ctx->client_npn_protocols != NULL) { + parse_protos(test_ctx->client_npn_protocols, + &client_ctx_data->npn_protocols, + &client_ctx_data->npn_protocols_len); + SSL_CTX_set_next_proto_select_cb(client_ctx, client_npn_cb, + client_ctx_data); + } + if (test_ctx->server_alpn_protocols != NULL) { + parse_protos(test_ctx->server_alpn_protocols, + &server_ctx_data->alpn_protocols, + &server_ctx_data->alpn_protocols_len); + SSL_CTX_set_alpn_select_cb(server_ctx, server_alpn_cb, server_ctx_data); + } + if (test_ctx->server2_alpn_protocols != NULL) { + OPENSSL_assert(server2_ctx != NULL); + parse_protos(test_ctx->server2_alpn_protocols, + &server2_ctx_data->alpn_protocols, + &server2_ctx_data->alpn_protocols_len); + SSL_CTX_set_alpn_select_cb(server2_ctx, server_alpn_cb, server2_ctx_data); + } + if (test_ctx->client_alpn_protocols != NULL) { + unsigned char *alpn_protos = NULL; + size_t alpn_protos_len; + parse_protos(test_ctx->client_alpn_protocols, + &alpn_protos, &alpn_protos_len); + /* Reversed return value convention... */ + OPENSSL_assert(SSL_CTX_set_alpn_protos(client_ctx, alpn_protos, + alpn_protos_len) == 0); + OPENSSL_free(alpn_protos); + } } -/* - * Configure callbacks and other properties that can't be set directly - * in the server/client CONF. - */ +/* Configure per-SSL callbacks and other properties. */ static void configure_handshake_ssl(SSL *server, SSL *client, const SSL_TEST_CTX *test_ctx) { @@ -293,21 +469,45 @@ static handshake_status_t handshake_status(peer_status_t last_status, return INTERNAL_ERROR; } -HANDSHAKE_RESULT do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, - SSL_CTX *client_ctx, const SSL_TEST_CTX *test_ctx) +/* Convert unsigned char buf's that shouldn't contain any NUL-bytes to char. */ +static char *dup_str(const unsigned char *in, size_t len) +{ + char *ret; + + if(len == 0) + return NULL; + + /* Assert that the string does not contain NUL-bytes. */ + OPENSSL_assert(OPENSSL_strnlen((const char*)(in), len) == len); + ret = OPENSSL_strndup((const char*)(in), len); + OPENSSL_assert(ret != NULL); + return ret; +} + +HANDSHAKE_RESULT *do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, + SSL_CTX *client_ctx, const SSL_TEST_CTX *test_ctx) { SSL *server, *client; BIO *client_to_server, *server_to_client; HANDSHAKE_EX_DATA server_ex_data, client_ex_data; - HANDSHAKE_RESULT ret; + CTX_DATA client_ctx_data, server_ctx_data, server2_ctx_data; + HANDSHAKE_RESULT *ret = HANDSHAKE_RESULT_new(); int client_turn = 1; peer_status_t client_status = PEER_RETRY, server_status = PEER_RETRY; handshake_status_t status = HANDSHAKE_RETRY; unsigned char* tick = NULL; - size_t len = 0; + size_t tick_len = 0; SSL_SESSION* sess = NULL; + const unsigned char *proto = NULL; + /* API dictates unsigned int rather than size_t. */ + unsigned int proto_len = 0; - configure_handshake_ctx(server_ctx, server2_ctx, client_ctx, test_ctx); + memset(&server_ctx_data, 0, sizeof(server_ctx_data)); + memset(&server2_ctx_data, 0, sizeof(server2_ctx_data)); + memset(&client_ctx_data, 0, sizeof(client_ctx_data)); + + configure_handshake_ctx(server_ctx, server2_ctx, client_ctx, test_ctx, + &server_ctx_data, &server2_ctx_data, &client_ctx_data); server = SSL_new(server_ctx); client = SSL_new(client_ctx); @@ -317,8 +517,8 @@ HANDSHAKE_RESULT do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, memset(&server_ex_data, 0, sizeof(server_ex_data)); memset(&client_ex_data, 0, sizeof(client_ex_data)); - memset(&ret, 0, sizeof(ret)); - ret.result = SSL_TEST_INTERNAL_ERROR; + + ret->result = SSL_TEST_INTERNAL_ERROR; client_to_server = BIO_new(BIO_s_mem()); server_to_client = BIO_new(BIO_s_mem()); @@ -370,16 +570,16 @@ HANDSHAKE_RESULT do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, switch (status) { case HANDSHAKE_SUCCESS: - ret.result = SSL_TEST_SUCCESS; + ret->result = SSL_TEST_SUCCESS; goto err; case CLIENT_ERROR: - ret.result = SSL_TEST_CLIENT_FAIL; + ret->result = SSL_TEST_CLIENT_FAIL; goto err; case SERVER_ERROR: - ret.result = SSL_TEST_SERVER_FAIL; + ret->result = SSL_TEST_SERVER_FAIL; goto err; case INTERNAL_ERROR: - ret.result = SSL_TEST_INTERNAL_ERROR; + ret->result = SSL_TEST_INTERNAL_ERROR; goto err; case HANDSHAKE_RETRY: /* Continue. */ @@ -388,21 +588,36 @@ HANDSHAKE_RESULT do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, } } err: - ret.server_alert_sent = server_ex_data.alert_sent; - ret.server_alert_received = client_ex_data.alert_received; - ret.client_alert_sent = client_ex_data.alert_sent; - ret.client_alert_received = server_ex_data.alert_received; - ret.server_protocol = SSL_version(server); - ret.client_protocol = SSL_version(client); - ret.servername = server_ex_data.servername; + ret->server_alert_sent = server_ex_data.alert_sent; + ret->server_alert_received = client_ex_data.alert_received; + ret->client_alert_sent = client_ex_data.alert_sent; + ret->client_alert_received = server_ex_data.alert_received; + ret->server_protocol = SSL_version(server); + ret->client_protocol = SSL_version(client); + ret->servername = server_ex_data.servername; if ((sess = SSL_get0_session(client)) != NULL) - SSL_SESSION_get0_ticket(sess, &tick, &len); - if (tick == NULL || len == 0) - ret.session_ticket = SSL_TEST_SESSION_TICKET_NO; + SSL_SESSION_get0_ticket(sess, &tick, &tick_len); + if (tick == NULL || tick_len == 0) + ret->session_ticket = SSL_TEST_SESSION_TICKET_NO; else - ret.session_ticket = SSL_TEST_SESSION_TICKET_YES; - ret.session_ticket_do_not_call = server_ex_data.session_ticket_do_not_call; + ret->session_ticket = SSL_TEST_SESSION_TICKET_YES; + ret->session_ticket_do_not_call = server_ex_data.session_ticket_do_not_call; + SSL_get0_next_proto_negotiated(client, &proto, &proto_len); + ret->client_npn_negotiated = dup_str(proto, proto_len); + + SSL_get0_next_proto_negotiated(server, &proto, &proto_len); + ret->server_npn_negotiated = dup_str(proto, proto_len); + + SSL_get0_alpn_selected(client, &proto, &proto_len); + ret->client_alpn_negotiated = dup_str(proto, proto_len); + + SSL_get0_alpn_selected(server, &proto, &proto_len); + ret->server_alpn_negotiated = dup_str(proto, proto_len); + + ctx_data_free_data(&server_ctx_data); + ctx_data_free_data(&server2_ctx_data); + ctx_data_free_data(&client_ctx_data); SSL_free(server); SSL_free(client); return ret; diff --git a/test/handshake_helper.h b/test/handshake_helper.h index 4a51ad4a6e..56c0aac28a 100644 --- a/test/handshake_helper.h +++ b/test/handshake_helper.h @@ -32,10 +32,17 @@ typedef struct handshake_result { ssl_session_ticket_t session_ticket; /* Was this called on the second context? */ int session_ticket_do_not_call; + char *client_npn_negotiated; + char *server_npn_negotiated; + char *client_alpn_negotiated; + char *server_alpn_negotiated; } HANDSHAKE_RESULT; +HANDSHAKE_RESULT *HANDSHAKE_RESULT_new(void); +void HANDSHAKE_RESULT_free(HANDSHAKE_RESULT *result); + /* Do a handshake and report some information about the result. */ -HANDSHAKE_RESULT do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, - SSL_CTX *client_ctx, const SSL_TEST_CTX *test_ctx); +HANDSHAKE_RESULT *do_handshake(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, + SSL_CTX *client_ctx, const SSL_TEST_CTX *test_ctx); #endif /* HEADER_HANDSHAKE_HELPER_H */ diff --git a/test/recipes/80-test_ssl_new.t b/test/recipes/80-test_ssl_new.t index 258164fcfa..56afb6463c 100644 --- a/test/recipes/80-test_ssl_new.t +++ b/test/recipes/80-test_ssl_new.t @@ -36,6 +36,7 @@ my $is_default_dtls = (!disabled("dtls1") && !disabled("dtls1_2")); my $no_tls = alldisabled(available_protocols("tls")); my $no_dtls = alldisabled(available_protocols("dtls")); +my $no_npn = disabled("nextprotoneg"); my %conf_dependent_tests = ( "02-protocol-version.conf" => !$is_default_tls, @@ -46,6 +47,7 @@ my %conf_dependent_tests = ( # Default is $no_tls but some tests have different skip conditions. my %skip = ( "05-dtls-protocol-version.conf" => $no_dtls, + "08-npn.conf" => $no_tls || $no_npn, ); foreach my $conf (@conf_files) { @@ -58,7 +60,7 @@ foreach my $conf (@conf_files) { # We hard-code the number of tests to double-check that the globbing above # finds all files as expected. -plan tests => 7; # = scalar @conf_srcs +plan tests => 9; # = scalar @conf_srcs sub test_conf { plan tests => 3; diff --git a/test/recipes/80-test_ssl_old.t b/test/recipes/80-test_ssl_old.t index becfbae890..5228112e16 100644 --- a/test/recipes/80-test_ssl_old.t +++ b/test/recipes/80-test_ssl_old.t @@ -79,7 +79,7 @@ my $client_sess="client.ss"; # new format in ssl_test.c and add recipes to 80-test_ssl_new.t instead. plan tests => 1 # For testss - + 12 # For the first testssl + + 11 # For the first testssl ; subtest 'test_ss' => sub { @@ -529,19 +529,14 @@ sub testssl { subtest 'Next Protocol Negotiation Tests' => sub { ###################################################################### - plan tests => 7; + plan tests => 2; SKIP: { - skip "TLSv1.0 is not supported by this OpenSSL build", 7 + skip "TLSv1.0 is not supported by this OpenSSL build", 2 if $no_tls1; - skip "Next Protocol Negotiation is not supported by this OpenSSL build", 7 + skip "Next Protocol Negotiation is not supported by this OpenSSL build", 2 if disabled("nextprotoneg"); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_client"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_server"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_server_reject"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_client", "-npn_server_reject"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_client", "-npn_server"]))); ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_client", "-npn_server", "-num", "2"]))); ok(run(test([@ssltest, "-bio_pair", "-tls1", "-npn_client", "-npn_server", "-num", "2", "-reuse"]))); } @@ -579,47 +574,6 @@ sub testssl { } }; - subtest 'ALPN tests' => sub { - ###################################################################### - - plan tests => 13; - - SKIP: { - skip "TLSv1.0 is not supported by this OpenSSL build", 13 - if $no_tls1; - - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_server", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "foo", "-alpn_server", "foo", "-alpn_expected", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "foo,bar", "-alpn_server", "foo", "-alpn_expected", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "bar,foo", "-alpn_server", "foo", "-alpn_expected", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "bar,foo", "-alpn_server", "foo,bar", "-alpn_expected", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "bar,foo", "-alpn_server", "bar,foo", "-alpn_expected", "bar"]))); - ok(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "foo,bar", "-alpn_server", "bar,foo", "-alpn_expected", "bar"]))); - - is(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "foo", "-alpn_server", "bar"])), 0, - "Testing ALPN with protocol mismatch, expecting failure"); - is(run(test([@ssltest, "-bio_pair", "-tls1", "-alpn_client", "baz", "-alpn_server", "bar,foo"])), 0, - "Testing ALPN with protocol mismatch, expecting failure"); - - # ALPN + SNI - ok(run(test([@ssltest, "-bio_pair", - "-alpn_client", "foo,bar", "-sn_client", "alice", - "-alpn_server1", "foo,123", "-sn_server1", "alice", - "-alpn_server2", "bar,456", "-sn_server2", "bob", - "-alpn_expected", "foo"]))); - ok(run(test([@ssltest, "-bio_pair", - "-alpn_client", "foo,bar", "-sn_client", "bob", - "-alpn_server1", "foo,123", "-sn_server1", "alice", - "-alpn_server2", "bar,456", "-sn_server2", "bob", - "-alpn_expected", "bar"]))); - ok(run(test([@ssltest, "-bio_pair", - "-alpn_client", "foo,bar", "-sn_client", "bob", - "-alpn_server2", "bar,456", "-sn_server2", "bob", - "-alpn_expected", "bar"]))); - } - }; - subtest 'SRP tests' => sub { plan tests => 4; diff --git a/test/ssl-tests/08-npn.conf b/test/ssl-tests/08-npn.conf new file mode 100644 index 0000000000..a76aa21c1e --- /dev/null +++ b/test/ssl-tests/08-npn.conf @@ -0,0 +1,362 @@ +# Generated with generate_ssl_tests.pl + +num_tests = 12 + +test-0 = 0-npn-simple +test-1 = 1-npn-client-finds-match +test-2 = 2-npn-client-honours-server-pref +test-3 = 3-npn-client-first-pref-on-mismatch +test-4 = 4-npn-no-server-support +test-5 = 5-npn-no-client-support +test-6 = 6-npn-with-sni-no-context-switch +test-7 = 7-npn-with-sni-context-switch +test-8 = 8-npn-selected-sni-server-supports-npn +test-9 = 9-npn-selected-sni-server-does-not-support-npn +test-10 = 10-alpn-preferred-over-npn +test-11 = 11-sni-npn-preferred-over-alpn +# =========================================================== + +[0-npn-simple] +ssl_conf = 0-npn-simple-ssl + +[0-npn-simple-ssl] +server = 0-npn-simple-server +client = 0-npn-simple-client + +[0-npn-simple-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[0-npn-simple-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-0] +ClientNPNProtocols = foo +ExpectedNPNProtocol = foo +ServerNPNProtocols = foo + + +# =========================================================== + +[1-npn-client-finds-match] +ssl_conf = 1-npn-client-finds-match-ssl + +[1-npn-client-finds-match-ssl] +server = 1-npn-client-finds-match-server +client = 1-npn-client-finds-match-client + +[1-npn-client-finds-match-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[1-npn-client-finds-match-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-1] +ClientNPNProtocols = foo,bar +ExpectedNPNProtocol = bar +ServerNPNProtocols = baz,bar + + +# =========================================================== + +[2-npn-client-honours-server-pref] +ssl_conf = 2-npn-client-honours-server-pref-ssl + +[2-npn-client-honours-server-pref-ssl] +server = 2-npn-client-honours-server-pref-server +client = 2-npn-client-honours-server-pref-client + +[2-npn-client-honours-server-pref-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[2-npn-client-honours-server-pref-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-2] +ClientNPNProtocols = foo,bar +ExpectedNPNProtocol = bar +ServerNPNProtocols = bar,foo + + +# =========================================================== + +[3-npn-client-first-pref-on-mismatch] +ssl_conf = 3-npn-client-first-pref-on-mismatch-ssl + +[3-npn-client-first-pref-on-mismatch-ssl] +server = 3-npn-client-first-pref-on-mismatch-server +client = 3-npn-client-first-pref-on-mismatch-client + +[3-npn-client-first-pref-on-mismatch-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[3-npn-client-first-pref-on-mismatch-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-3] +ClientNPNProtocols = foo,bar +ExpectedNPNProtocol = foo +ServerNPNProtocols = baz + + +# =========================================================== + +[4-npn-no-server-support] +ssl_conf = 4-npn-no-server-support-ssl + +[4-npn-no-server-support-ssl] +server = 4-npn-no-server-support-server +client = 4-npn-no-server-support-client + +[4-npn-no-server-support-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[4-npn-no-server-support-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-4] +ClientNPNProtocols = foo + + +# =========================================================== + +[5-npn-no-client-support] +ssl_conf = 5-npn-no-client-support-ssl + +[5-npn-no-client-support-ssl] +server = 5-npn-no-client-support-server +client = 5-npn-no-client-support-client + +[5-npn-no-client-support-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[5-npn-no-client-support-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-5] +ServerNPNProtocols = foo + + +# =========================================================== + +[6-npn-with-sni-no-context-switch] +ssl_conf = 6-npn-with-sni-no-context-switch-ssl + +[6-npn-with-sni-no-context-switch-ssl] +server = 6-npn-with-sni-no-context-switch-server +server2 = 6-npn-with-sni-no-context-switch-server2 +client = 6-npn-with-sni-no-context-switch-client + +[6-npn-with-sni-no-context-switch-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[6-npn-with-sni-no-context-switch-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[6-npn-with-sni-no-context-switch-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-6] +ClientNPNProtocols = foo,bar +ExpectedNPNProtocol = foo +ExpectedServerName = server1 +Server2NPNProtocols = bar +ServerNPNProtocols = foo +ServerName = server1 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[7-npn-with-sni-context-switch] +ssl_conf = 7-npn-with-sni-context-switch-ssl + +[7-npn-with-sni-context-switch-ssl] +server = 7-npn-with-sni-context-switch-server +server2 = 7-npn-with-sni-context-switch-server2 +client = 7-npn-with-sni-context-switch-client + +[7-npn-with-sni-context-switch-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[7-npn-with-sni-context-switch-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[7-npn-with-sni-context-switch-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-7] +ClientNPNProtocols = foo,bar +ExpectedNPNProtocol = bar +ExpectedServerName = server2 +Server2NPNProtocols = bar +ServerNPNProtocols = foo +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[8-npn-selected-sni-server-supports-npn] +ssl_conf = 8-npn-selected-sni-server-supports-npn-ssl + +[8-npn-selected-sni-server-supports-npn-ssl] +server = 8-npn-selected-sni-server-supports-npn-server +server2 = 8-npn-selected-sni-server-supports-npn-server2 +client = 8-npn-selected-sni-server-supports-npn-client + +[8-npn-selected-sni-server-supports-npn-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[8-npn-selected-sni-server-supports-npn-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[8-npn-selected-sni-server-supports-npn-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-8] +ClientNPNProtocols = foo,bar +ExpectedNPNProtocol = bar +ExpectedServerName = server2 +Server2NPNProtocols = bar +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[9-npn-selected-sni-server-does-not-support-npn] +ssl_conf = 9-npn-selected-sni-server-does-not-support-npn-ssl + +[9-npn-selected-sni-server-does-not-support-npn-ssl] +server = 9-npn-selected-sni-server-does-not-support-npn-server +server2 = 9-npn-selected-sni-server-does-not-support-npn-server2 +client = 9-npn-selected-sni-server-does-not-support-npn-client + +[9-npn-selected-sni-server-does-not-support-npn-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[9-npn-selected-sni-server-does-not-support-npn-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[9-npn-selected-sni-server-does-not-support-npn-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-9] +ClientNPNProtocols = foo,bar +ExpectedServerName = server2 +ServerNPNProtocols = foo +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[10-alpn-preferred-over-npn] +ssl_conf = 10-alpn-preferred-over-npn-ssl + +[10-alpn-preferred-over-npn-ssl] +server = 10-alpn-preferred-over-npn-server +client = 10-alpn-preferred-over-npn-client + +[10-alpn-preferred-over-npn-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[10-alpn-preferred-over-npn-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-10] +ClientALPNProtocols = foo +ClientNPNProtocols = bar +ExpectedALPNProtocol = foo +ServerALPNProtocols = foo +ServerNPNProtocols = bar + + +# =========================================================== + +[11-sni-npn-preferred-over-alpn] +ssl_conf = 11-sni-npn-preferred-over-alpn-ssl + +[11-sni-npn-preferred-over-alpn-ssl] +server = 11-sni-npn-preferred-over-alpn-server +server2 = 11-sni-npn-preferred-over-alpn-server2 +client = 11-sni-npn-preferred-over-alpn-client + +[11-sni-npn-preferred-over-alpn-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[11-sni-npn-preferred-over-alpn-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[11-sni-npn-preferred-over-alpn-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-11] +ClientALPNProtocols = foo +ClientNPNProtocols = bar +ExpectedNPNProtocol = bar +ExpectedServerName = server2 +Server2NPNProtocols = bar +ServerALPNProtocols = foo +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + diff --git a/test/ssl-tests/08-npn.conf.in b/test/ssl-tests/08-npn.conf.in new file mode 100644 index 0000000000..9b0dcba999 --- /dev/null +++ b/test/ssl-tests/08-npn.conf.in @@ -0,0 +1,165 @@ +# -*- mode: perl; -*- +# Copyright 2016-2016 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the OpenSSL license (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + + +## Test version negotiation + +use strict; +use warnings; + +package ssltests; + + +our @tests = ( + { + name => "npn-simple", + server => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo", + "ServerNPNProtocols" => "foo", + "ExpectedNPNProtocol" => "foo", + }, + }, + { + name => "npn-client-finds-match", + server => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "ServerNPNProtocols" => "baz,bar", + "ExpectedNPNProtocol" => "bar", + }, + }, + { + name => "npn-client-honours-server-pref", + server => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "ServerNPNProtocols" => "bar,foo", + "ExpectedNPNProtocol" => "bar", + }, + }, + { + name => "npn-client-first-pref-on-mismatch", + server => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "ServerNPNProtocols" => "baz", + "ExpectedNPNProtocol" => "foo", + }, + }, + { + name => "npn-no-server-support", + server => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo", + "ExpectedNPNProtocol" => undef, + }, + }, + { + name => "npn-no-client-support", + server => { }, + client => { }, + test => { + "ServerNPNProtocols" => "foo", + "ExpectedNPNProtocol" => undef, + }, + }, + { + name => "npn-with-sni-no-context-switch", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "ServerNPNProtocols" => "foo", + "Server2NPNProtocols" => "bar", + "ServerName" => "server1", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server1", + "ExpectedNPNProtocol" => "foo", + }, + }, + { + name => "npn-with-sni-context-switch", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "ServerNPNProtocols" => "foo", + "Server2NPNProtocols" => "bar", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedNPNProtocol" => "bar", + }, + }, + { + name => "npn-selected-sni-server-supports-npn", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "Server2NPNProtocols" => "bar", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedNPNProtocol" => "bar", + }, + }, + { + name => "npn-selected-sni-server-does-not-support-npn", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientNPNProtocols" => "foo,bar", + "ServerNPNProtocols" => "foo", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedNPNProtocol" => undef, + }, + }, + { + name => "alpn-preferred-over-npn", + server => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo", + "ClientNPNProtocols" => "bar", + "ServerALPNProtocols" => "foo", + "ServerNPNProtocols" => "bar", + "ExpectedALPNProtocol" => "foo", + "ExpectedNPNProtocol" => undef, + }, + }, + { + name => "sni-npn-preferred-over-alpn", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo", + "ClientNPNProtocols" => "bar", + "ServerALPNProtocols" => "foo", + "Server2NPNProtocols" => "bar", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedALPNProtocol" => undef, + "ExpectedNPNProtocol" => "bar", + }, + }, +); diff --git a/test/ssl-tests/09-alpn.conf b/test/ssl-tests/09-alpn.conf new file mode 100644 index 0000000000..73fee872c2 --- /dev/null +++ b/test/ssl-tests/09-alpn.conf @@ -0,0 +1,298 @@ +# Generated with generate_ssl_tests.pl + +num_tests = 10 + +test-0 = 0-alpn-simple +test-1 = 1-alpn-client-finds-match +test-2 = 2-alpn-client-honours-server-pref +test-3 = 3-alpn-alert-on-mismatch +test-4 = 4-alpn-no-server-support +test-5 = 5-alpn-no-client-support +test-6 = 6-alpn-with-sni-no-context-switch +test-7 = 7-alpn-with-sni-context-switch +test-8 = 8-alpn-selected-sni-server-supports-alpn +test-9 = 9-alpn-selected-sni-server-does-not-support-alpn +# =========================================================== + +[0-alpn-simple] +ssl_conf = 0-alpn-simple-ssl + +[0-alpn-simple-ssl] +server = 0-alpn-simple-server +client = 0-alpn-simple-client + +[0-alpn-simple-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[0-alpn-simple-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-0] +ClientALPNProtocols = foo +ExpectedALPNProtocol = foo +ServerALPNProtocols = foo + + +# =========================================================== + +[1-alpn-client-finds-match] +ssl_conf = 1-alpn-client-finds-match-ssl + +[1-alpn-client-finds-match-ssl] +server = 1-alpn-client-finds-match-server +client = 1-alpn-client-finds-match-client + +[1-alpn-client-finds-match-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[1-alpn-client-finds-match-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-1] +ClientALPNProtocols = foo,bar +ExpectedALPNProtocol = bar +ServerALPNProtocols = baz,bar + + +# =========================================================== + +[2-alpn-client-honours-server-pref] +ssl_conf = 2-alpn-client-honours-server-pref-ssl + +[2-alpn-client-honours-server-pref-ssl] +server = 2-alpn-client-honours-server-pref-server +client = 2-alpn-client-honours-server-pref-client + +[2-alpn-client-honours-server-pref-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[2-alpn-client-honours-server-pref-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-2] +ClientALPNProtocols = foo,bar +ExpectedALPNProtocol = bar +ServerALPNProtocols = bar,foo + + +# =========================================================== + +[3-alpn-alert-on-mismatch] +ssl_conf = 3-alpn-alert-on-mismatch-ssl + +[3-alpn-alert-on-mismatch-ssl] +server = 3-alpn-alert-on-mismatch-server +client = 3-alpn-alert-on-mismatch-client + +[3-alpn-alert-on-mismatch-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[3-alpn-alert-on-mismatch-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-3] +ClientALPNProtocols = foo,bar +ExpectedResult = ServerFail +ServerALPNProtocols = baz +ServerAlert = NoApplicationProtocol + + +# =========================================================== + +[4-alpn-no-server-support] +ssl_conf = 4-alpn-no-server-support-ssl + +[4-alpn-no-server-support-ssl] +server = 4-alpn-no-server-support-server +client = 4-alpn-no-server-support-client + +[4-alpn-no-server-support-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[4-alpn-no-server-support-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-4] +ClientALPNProtocols = foo + + +# =========================================================== + +[5-alpn-no-client-support] +ssl_conf = 5-alpn-no-client-support-ssl + +[5-alpn-no-client-support-ssl] +server = 5-alpn-no-client-support-server +client = 5-alpn-no-client-support-client + +[5-alpn-no-client-support-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[5-alpn-no-client-support-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-5] +ServerALPNProtocols = foo + + +# =========================================================== + +[6-alpn-with-sni-no-context-switch] +ssl_conf = 6-alpn-with-sni-no-context-switch-ssl + +[6-alpn-with-sni-no-context-switch-ssl] +server = 6-alpn-with-sni-no-context-switch-server +server2 = 6-alpn-with-sni-no-context-switch-server2 +client = 6-alpn-with-sni-no-context-switch-client + +[6-alpn-with-sni-no-context-switch-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[6-alpn-with-sni-no-context-switch-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[6-alpn-with-sni-no-context-switch-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-6] +ClientALPNProtocols = foo,bar +ExpectedALPNProtocol = foo +ExpectedServerName = server1 +Server2ALPNProtocols = bar +ServerALPNProtocols = foo +ServerName = server1 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[7-alpn-with-sni-context-switch] +ssl_conf = 7-alpn-with-sni-context-switch-ssl + +[7-alpn-with-sni-context-switch-ssl] +server = 7-alpn-with-sni-context-switch-server +server2 = 7-alpn-with-sni-context-switch-server2 +client = 7-alpn-with-sni-context-switch-client + +[7-alpn-with-sni-context-switch-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[7-alpn-with-sni-context-switch-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[7-alpn-with-sni-context-switch-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-7] +ClientALPNProtocols = foo,bar +ExpectedALPNProtocol = bar +ExpectedServerName = server2 +Server2ALPNProtocols = bar +ServerALPNProtocols = foo +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[8-alpn-selected-sni-server-supports-alpn] +ssl_conf = 8-alpn-selected-sni-server-supports-alpn-ssl + +[8-alpn-selected-sni-server-supports-alpn-ssl] +server = 8-alpn-selected-sni-server-supports-alpn-server +server2 = 8-alpn-selected-sni-server-supports-alpn-server2 +client = 8-alpn-selected-sni-server-supports-alpn-client + +[8-alpn-selected-sni-server-supports-alpn-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[8-alpn-selected-sni-server-supports-alpn-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[8-alpn-selected-sni-server-supports-alpn-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-8] +ClientALPNProtocols = foo,bar +ExpectedALPNProtocol = bar +ExpectedServerName = server2 +Server2ALPNProtocols = bar +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + +# =========================================================== + +[9-alpn-selected-sni-server-does-not-support-alpn] +ssl_conf = 9-alpn-selected-sni-server-does-not-support-alpn-ssl + +[9-alpn-selected-sni-server-does-not-support-alpn-ssl] +server = 9-alpn-selected-sni-server-does-not-support-alpn-server +server2 = 9-alpn-selected-sni-server-does-not-support-alpn-server2 +client = 9-alpn-selected-sni-server-does-not-support-alpn-client + +[9-alpn-selected-sni-server-does-not-support-alpn-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[9-alpn-selected-sni-server-does-not-support-alpn-server2] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[9-alpn-selected-sni-server-does-not-support-alpn-client] +CipherString = DEFAULT +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-9] +ClientALPNProtocols = foo,bar +ExpectedServerName = server2 +ServerALPNProtocols = foo +ServerName = server2 +ServerNameCallback = IgnoreMismatch + + diff --git a/test/ssl-tests/09-alpn.conf.in b/test/ssl-tests/09-alpn.conf.in new file mode 100644 index 0000000000..2a7b1f97c2 --- /dev/null +++ b/test/ssl-tests/09-alpn.conf.in @@ -0,0 +1,136 @@ +# -*- mode: perl; -*- +# Copyright 2016-2016 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the OpenSSL license (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + + +## Test version negotiation + +use strict; +use warnings; + +package ssltests; + + +our @tests = ( + { + name => "alpn-simple", + server => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo", + "ServerALPNProtocols" => "foo", + "ExpectedALPNProtocol" => "foo", + }, + }, + { + name => "alpn-client-finds-match", + server => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "ServerALPNProtocols" => "baz,bar", + "ExpectedALPNProtocol" => "bar", + }, + }, + { + name => "alpn-client-honours-server-pref", + server => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "ServerALPNProtocols" => "bar,foo", + "ExpectedALPNProtocol" => "bar", + }, + }, + { + name => "alpn-alert-on-mismatch", + server => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "ServerALPNProtocols" => "baz", + "ExpectedResult" => "ServerFail", + "ServerAlert" => "NoApplicationProtocol", + }, + }, + { + name => "alpn-no-server-support", + server => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo", + "ExpectedALPNProtocol" => undef, + }, + }, + { + name => "alpn-no-client-support", + server => { }, + client => { }, + test => { + "ServerALPNProtocols" => "foo", + "ExpectedALPNProtocol" => undef, + }, + }, + { + name => "alpn-with-sni-no-context-switch", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "ServerALPNProtocols" => "foo", + "Server2ALPNProtocols" => "bar", + "ServerName" => "server1", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server1", + "ExpectedALPNProtocol" => "foo", + }, + }, + { + name => "alpn-with-sni-context-switch", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "ServerALPNProtocols" => "foo", + "Server2ALPNProtocols" => "bar", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedALPNProtocol" => "bar", + }, + }, + { + name => "alpn-selected-sni-server-supports-alpn", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "Server2ALPNProtocols" => "bar", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedALPNProtocol" => "bar", + }, + }, + { + name => "alpn-selected-sni-server-does-not-support-alpn", + server => { }, + server2 => { }, + client => { }, + test => { + "ClientALPNProtocols" => "foo,bar", + "ServerALPNProtocols" => "foo", + "ServerName" => "server2", + "ServerNameCallback" => "IgnoreMismatch", + "ExpectedServerName" => "server2", + "ExpectedALPNProtocol" => undef, + }, + }, +); diff --git a/test/ssl_test.c b/test/ssl_test.c index 060f73eeba..5a3aaa8d08 100644 --- a/test/ssl_test.c +++ b/test/ssl_test.c @@ -40,23 +40,23 @@ static const char *print_alert(int alert) return alert ? SSL_alert_desc_string_long(alert) : "no alert"; } -static int check_result(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) +static int check_result(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) { - if (result.result != test_ctx->expected_result) { + if (result->result != test_ctx->expected_result) { fprintf(stderr, "ExpectedResult mismatch: expected %s, got %s.\n", ssl_test_result_name(test_ctx->expected_result), - ssl_test_result_name(result.result)); + ssl_test_result_name(result->result)); return 0; } return 1; } -static int check_alerts(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) +static int check_alerts(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) { - if (result.client_alert_sent != result.client_alert_received) { + if (result->client_alert_sent != result->client_alert_received) { fprintf(stderr, "Client sent alert %s but server received %s\n.", - print_alert(result.client_alert_sent), - print_alert(result.client_alert_received)); + print_alert(result->client_alert_sent), + print_alert(result->client_alert_received)); /* * We can't bail here because the peer doesn't always get far enough * to process a received alert. Specifically, in protocol version @@ -71,10 +71,10 @@ static int check_alerts(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) /* return 0; */ } - if (result.server_alert_sent != result.server_alert_received) { + if (result->server_alert_sent != result->server_alert_received) { fprintf(stderr, "Server sent alert %s but client received %s\n.", - print_alert(result.server_alert_sent), - print_alert(result.server_alert_received)); + print_alert(result->server_alert_sent), + print_alert(result->server_alert_received)); /* return 0; */ } @@ -85,86 +85,112 @@ static int check_alerts(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) * (s->s3->send_alert[0] << 8) | s->s3->send_alert[1] * where the low byte is the alert code and the high byte is other stuff. */ - && (result.client_alert_sent & 0xff) != test_ctx->client_alert) { + && (result->client_alert_sent & 0xff) != test_ctx->client_alert) { fprintf(stderr, "ClientAlert mismatch: expected %s, got %s.\n", print_alert(test_ctx->client_alert), - print_alert(result.client_alert_sent)); + print_alert(result->client_alert_sent)); return 0; } if (test_ctx->server_alert - && (result.server_alert_sent & 0xff) != test_ctx->server_alert) { + && (result->server_alert_sent & 0xff) != test_ctx->server_alert) { fprintf(stderr, "ServerAlert mismatch: expected %s, got %s.\n", print_alert(test_ctx->server_alert), - print_alert(result.server_alert_sent)); + print_alert(result->server_alert_sent)); return 0; } return 1; } -static int check_protocol(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) +static int check_protocol(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) { - if (result.client_protocol != result.server_protocol) { + if (result->client_protocol != result->server_protocol) { fprintf(stderr, "Client has protocol %s but server has %s\n.", - ssl_protocol_name(result.client_protocol), - ssl_protocol_name(result.server_protocol)); + ssl_protocol_name(result->client_protocol), + ssl_protocol_name(result->server_protocol)); return 0; } if (test_ctx->protocol) { - if (result.client_protocol != test_ctx->protocol) { + if (result->client_protocol != test_ctx->protocol) { fprintf(stderr, "Protocol mismatch: expected %s, got %s.\n", ssl_protocol_name(test_ctx->protocol), - ssl_protocol_name(result.client_protocol)); + ssl_protocol_name(result->client_protocol)); return 0; } } return 1; } -static int check_servername(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) +static int check_servername(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) { - if (result.servername != test_ctx->expected_servername) { + if (result->servername != test_ctx->expected_servername) { fprintf(stderr, "Client ServerName mismatch, expected %s, got %s\n.", ssl_servername_name(test_ctx->expected_servername), - ssl_servername_name(result.servername)); + ssl_servername_name(result->servername)); return 0; } return 1; } -static int check_session_ticket(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) +static int check_session_ticket(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) { if (test_ctx->session_ticket_expected == SSL_TEST_SESSION_TICKET_IGNORE) return 1; if (test_ctx->session_ticket_expected == SSL_TEST_SESSION_TICKET_BROKEN && - result.session_ticket == SSL_TEST_SESSION_TICKET_NO) + result->session_ticket == SSL_TEST_SESSION_TICKET_NO) return 1; - if (result.session_ticket != test_ctx->session_ticket_expected) { + if (result->session_ticket != test_ctx->session_ticket_expected) { fprintf(stderr, "Client SessionTicketExpected mismatch, expected %s, got %s\n.", ssl_session_ticket_name(test_ctx->session_ticket_expected), - ssl_session_ticket_name(result.session_ticket)); + ssl_session_ticket_name(result->session_ticket)); return 0; } return 1; } +static int check_npn(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) +{ + int ret = 1; + ret &= strings_equal("NPN Negotiated (client vs server)", + result->client_npn_negotiated, + result->server_npn_negotiated); + ret &= strings_equal("ExpectedNPNProtocol", + test_ctx->expected_npn_protocol, + result->client_npn_negotiated); + return ret; +} + +static int check_alpn(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) +{ + int ret = 1; + ret &= strings_equal("ALPN Negotiated (client vs server)", + result->client_alpn_negotiated, + result->server_alpn_negotiated); + ret &= strings_equal("ExpectedALPNProtocol", + test_ctx->expected_alpn_protocol, + result->client_alpn_negotiated); + return ret; +} + /* * This could be further simplified by constructing an expected * HANDSHAKE_RESULT, and implementing comparison methods for * its fields. */ -static int check_test(HANDSHAKE_RESULT result, SSL_TEST_CTX *test_ctx) +static int check_test(HANDSHAKE_RESULT *result, SSL_TEST_CTX *test_ctx) { int ret = 1; ret &= check_result(result, test_ctx); ret &= check_alerts(result, test_ctx); - if (result.result == SSL_TEST_SUCCESS) { + if (result->result == SSL_TEST_SUCCESS) { ret &= check_protocol(result, test_ctx); ret &= check_servername(result, test_ctx); ret &= check_session_ticket(result, test_ctx); - ret &= (result.session_ticket_do_not_call == 0); + ret &= (result->session_ticket_do_not_call == 0); + ret &= check_npn(result, test_ctx); + ret &= check_alpn(result, test_ctx); } return ret; } @@ -174,7 +200,7 @@ static int execute_test(SSL_TEST_FIXTURE fixture) int ret = 0; SSL_CTX *server_ctx = NULL, *server2_ctx = NULL, *client_ctx = NULL; SSL_TEST_CTX *test_ctx = NULL; - HANDSHAKE_RESULT result; + HANDSHAKE_RESULT *result = NULL; test_ctx = SSL_TEST_CTX_create(conf, fixture.test_app); if (test_ctx == NULL) @@ -223,6 +249,7 @@ err: SSL_TEST_CTX_free(test_ctx); if (ret != 1) ERR_print_errors_fp(stderr); + HANDSHAKE_RESULT_free(result); return ret; } diff --git a/test/ssl_test_ctx.c b/test/ssl_test_ctx.c index 4d038d2c23..090e1a330e 100644 --- a/test/ssl_test_ctx.c +++ b/test/ssl_test_ctx.c @@ -83,7 +83,8 @@ static const test_enum ssl_alerts[] = { {"UnknownCA", SSL_AD_UNKNOWN_CA}, {"HandshakeFailure", SSL_AD_HANDSHAKE_FAILURE}, {"UnrecognizedName", SSL_AD_UNRECOGNIZED_NAME}, - {"BadCertificate", SSL_AD_BAD_CERTIFICATE} + {"BadCertificate", SSL_AD_BAD_CERTIFICATE}, + {"NoApplicationProtocol", SSL_AD_NO_APPLICATION_PROTOCOL}, }; __owur static int parse_alert(int *alert, const char *value) @@ -281,6 +282,28 @@ const char *ssl_test_method_name(ssl_test_method_t method) return enum_name(ssl_test_methods, OSSL_NELEM(ssl_test_methods), method); } +#define IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(field) \ + static int parse_##field(SSL_TEST_CTX *test_ctx, const char *value) \ + { \ + OPENSSL_free(test_ctx->field); \ + test_ctx->field = OPENSSL_strdup(value); \ + OPENSSL_assert(test_ctx->field != NULL); \ + return 1; \ + } + +/************************************/ +/* NPN and ALPN options */ +/************************************/ + +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(client_npn_protocols) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(server_npn_protocols) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(server2_npn_protocols) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(expected_npn_protocol) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(client_alpn_protocols) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(server_alpn_protocols) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(server2_alpn_protocols) +IMPLEMENT_SSL_TEST_CTX_STRING_OPTION(expected_alpn_protocol) + /*************************************************************/ /* Known test options and their corresponding parse methods. */ /*************************************************************/ @@ -301,9 +324,16 @@ static const ssl_test_ctx_option ssl_test_ctx_options[] = { { "ServerNameCallback", &parse_servername_callback }, { "SessionTicketExpected", &parse_session_ticket }, { "Method", &parse_test_method }, + { "ClientNPNProtocols", &parse_client_npn_protocols }, + { "ServerNPNProtocols", &parse_server_npn_protocols }, + { "Server2NPNProtocols", &parse_server2_npn_protocols }, + { "ExpectedNPNProtocol", &parse_expected_npn_protocol }, + { "ClientALPNProtocols", &parse_client_alpn_protocols }, + { "ServerALPNProtocols", &parse_server_alpn_protocols }, + { "Server2ALPNProtocols", &parse_server2_alpn_protocols }, + { "ExpectedALPNProtocol", &parse_expected_alpn_protocol }, }; - /* * Since these methods are used to create tests, we use OPENSSL_assert liberally * for malloc failures and other internal errors. @@ -318,6 +348,15 @@ SSL_TEST_CTX *SSL_TEST_CTX_new() void SSL_TEST_CTX_free(SSL_TEST_CTX *ctx) { + + OPENSSL_free(ctx->client_npn_protocols); + OPENSSL_free(ctx->server_npn_protocols); + OPENSSL_free(ctx->server2_npn_protocols); + OPENSSL_free(ctx->client_alpn_protocols); + OPENSSL_free(ctx->server_alpn_protocols); + OPENSSL_free(ctx->server2_alpn_protocols); + OPENSSL_free(ctx->expected_npn_protocol); + OPENSSL_free(ctx->expected_alpn_protocol); OPENSSL_free(ctx); } diff --git a/test/ssl_test_ctx.h b/test/ssl_test_ctx.h index c551a9baa7..a96245eec7 100644 --- a/test/ssl_test_ctx.h +++ b/test/ssl_test_ctx.h @@ -84,6 +84,18 @@ typedef struct ssl_test_ctx { ssl_session_ticket_t session_ticket_expected; /* Whether the server/client CTX should use DTLS or TLS. */ ssl_test_method_t method; + /* + * NPN and ALPN protocols supported by the client, server, and second + * (SNI) server. A comma-separated list. + */ + char *client_npn_protocols; + char *server_npn_protocols; + char *server2_npn_protocols; + char *expected_npn_protocol; + char *client_alpn_protocols; + char *server_alpn_protocols; + char *server2_alpn_protocols; + char *expected_alpn_protocol; } SSL_TEST_CTX; const char *ssl_test_result_name(ssl_test_result_t result); diff --git a/test/ssl_test_ctx_test.c b/test/ssl_test_ctx_test.c index 3818ba5de8..b3e2e705a3 100644 --- a/test/ssl_test_ctx_test.c +++ b/test/ssl_test_ctx_test.c @@ -13,6 +13,7 @@ */ #include +#include #include "e_os.h" #include "ssl_test_ctx.h" @@ -88,7 +89,32 @@ static int SSL_TEST_CTX_equal(SSL_TEST_CTX *ctx, SSL_TEST_CTX *ctx2) ssl_session_ticket_name(ctx2->session_ticket_expected)); return 0; } + if (!strings_equal("ClientNPNProtocols", ctx->client_npn_protocols, + ctx2->client_npn_protocols)) + return 0; + if (!strings_equal("ServerNPNProtocols", ctx->server_npn_protocols, + ctx2->server_npn_protocols)) + return 0; + if (!strings_equal("Server2NPNProtocols", ctx->server_npn_protocols, + ctx2->server_npn_protocols)) + return 0; + if (!strings_equal("ExpectedNPNProtocol", ctx->expected_npn_protocol, + ctx2->expected_npn_protocol)) + return 0; + if (!strings_equal("ClientALPNProtocols", ctx->client_alpn_protocols, + ctx2->client_alpn_protocols)) + return 0; + + if (!strings_equal("ServerALPNProtocols", ctx->server_alpn_protocols, + ctx2->server_alpn_protocols)) + return 0; + if (!strings_equal("Server2ALPNProtocols", ctx->server_alpn_protocols, + ctx2->server_alpn_protocols)) + return 0; + if (!strings_equal("ExpectedALPNProtocol", ctx->expected_alpn_protocol, + ctx2->expected_alpn_protocol)) + return 0; return 1; } @@ -172,6 +198,10 @@ static int test_good_configuration() SSL_TEST_SERVERNAME_IGNORE_MISMATCH; fixture.expected_ctx->session_ticket_expected = SSL_TEST_SESSION_TICKET_YES; fixture.expected_ctx->method = SSL_TEST_METHOD_DTLS; + fixture.expected_ctx->client_npn_protocols = OPENSSL_strdup("foo,bar"); + fixture.expected_ctx->server2_alpn_protocols = OPENSSL_strdup("baz"); + OPENSSL_assert(fixture.expected_ctx->client_npn_protocols != NULL); + OPENSSL_assert(fixture.expected_ctx->server2_alpn_protocols != NULL); EXECUTE_SSL_TEST_CTX_TEST(); } diff --git a/test/ssl_test_ctx_test.conf b/test/ssl_test_ctx_test.conf index 2fa54b58d9..17925b5c7a 100644 --- a/test/ssl_test_ctx_test.conf +++ b/test/ssl_test_ctx_test.conf @@ -10,6 +10,8 @@ ExpectedServerName = server2 ServerNameCallback = IgnoreMismatch SessionTicketExpected = Yes Method = DTLS +ClientNPNProtocols = foo,bar +Server2ALPNProtocols = baz [ssltest_unknown_option] UnknownOption = Foo diff --git a/test/testutil.c b/test/testutil.c index 9a67630fd3..a16ef0fa07 100644 --- a/test/testutil.c +++ b/test/testutil.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "e_os.h" /* @@ -89,3 +90,20 @@ int run_tests(const char *test_prog_name) printf(" All tests passed.\n"); return EXIT_SUCCESS; } + +static const char *print_string_maybe_null(const char *s) +{ + return s == NULL ? "(NULL)" : s; +} + +int strings_equal(const char *desc, const char *s1, const char *s2) +{ + if (s1 == NULL && s2 == NULL) + return 1; + if (s1 == NULL || s2 == NULL || strcmp(s1, s2) != 0) { + fprintf(stderr, "%s mismatch: %s vs %s\n", desc, print_string_maybe_null(s1), + print_string_maybe_null(s2)); + return 0; + } + return 1; +} diff --git a/test/testutil.h b/test/testutil.h index 0170a226f7..0ff2a82f72 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -84,4 +84,13 @@ void add_test(const char *test_case_name, int (*test_fn) ()); void add_all_tests(const char *test_case_name, int (*test_fn)(int idx), int num); int run_tests(const char *test_prog_name); +/* + * Test assumption verification helpers. + */ + +/* + * Returns 1 if |s1| and |s2| are both NULL or equal. + * Otherwise, returns 0 and pretty-prints diagnostics using |desc|. + */ +int strings_equal(const char *desc, const char *s1, const char *s2); #endif /* HEADER_TESTUTIL_H */