Add more regress coverage for SSL_select_next_proto()
authortb <tb@openbsd.org>
Fri, 28 Jun 2024 14:50:37 +0000 (14:50 +0000)
committertb <tb@openbsd.org>
Fri, 28 Jun 2024 14:50:37 +0000 (14:50 +0000)
regress/lib/libssl/unit/ssl_set_alpn_protos.c

index 87dd4d9..6f3fcfb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ssl_set_alpn_protos.c,v 1.2 2022/07/21 03:59:04 tb Exp $ */
+/*     $OpenBSD: ssl_set_alpn_protos.c,v 1.3 2024/06/28 14:50:37 tb Exp $ */
 /*
  * Copyright (c) 2022 Theo Buehler <tb@openbsd.org>
  *
 
 #include <openssl/ssl.h>
 
+static void
+hexdump(const unsigned char *buf, size_t len)
+{
+       size_t i;
+
+       if (buf == NULL) {
+               fprintf(stderr, "(null), len %zu\n", len);
+               return;
+       }
+       for (i = 1; i <= len; i++)
+               fprintf(stderr, " 0x%02hhx,%s", buf[i - 1], i % 8 ? "" : "\n");
+       if (len % 8)
+               fprintf(stderr, "\n");
+}
+
 struct alpn_test {
        const char *description;
        const uint8_t protocols[24];
@@ -186,6 +201,279 @@ test_ssl_set_alpn_protos_edge_cases(void)
        return failed;
 }
 
+static const struct select_next_proto_test {
+       const unsigned char *server_list;
+       size_t server_list_len;
+       const unsigned char *client_list;
+       size_t client_list_len;
+       int want_ret;
+       const unsigned char *want_out;
+       unsigned char want_out_len; /* yes, unsigned char */
+} select_next_proto_tests[] = {
+       {
+               .server_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .server_list_len = 6,
+               .client_list = "\x01" "a",
+               .client_list_len = 2,
+               .want_ret = OPENSSL_NPN_NEGOTIATED,
+               .want_out = "a",
+               .want_out_len = 1,
+       },
+       {
+               .server_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .server_list_len = 6,
+               .client_list = "\x02" "aa" "\x01" "b" "\x01" "c",
+               .client_list_len = 7,
+               .want_ret = OPENSSL_NPN_NEGOTIATED,
+               .want_out = "b",
+               .want_out_len = 1,
+       },
+       {
+               /* Use server preference. */
+               .server_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .server_list_len = 6,
+               .client_list = "\x01" "c" "\x01" "b" "\x01" "a",
+               .client_list_len = 6,
+               .want_ret = OPENSSL_NPN_NEGOTIATED,
+               .want_out = "a",
+               .want_out_len = 1,
+       },
+       {
+               /* Again server preference wins. */
+               .server_list = "\x01" "a" "\x03" "bbb" "\x02" "cc",
+               .server_list_len = 9,
+               .client_list = "\x01" "z" "\x02" "cc" "\x03" "bbb",
+               .client_list_len = 9,
+               .want_ret = OPENSSL_NPN_NEGOTIATED,
+               .want_out = "bbb",
+               .want_out_len = 3,
+       },
+       {
+               /* No overlap fails with first client protocol. */
+               .server_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .server_list_len = 6,
+               .client_list = "\x01" "z" "\x01" "y",
+               .client_list_len = 4,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+               .want_out = "z",
+               .want_out_len = 1,
+       },
+       {
+               /*
+                * No server protocols is a misconfiguration, but should fail
+                * cleanly.
+                */
+               .server_list = "",
+               .server_list_len = 0,
+               .client_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .client_list_len = 6,
+               .want_out = "a",
+               .want_out_len = 1,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * NULL server protocols is a programming error that fails
+                * cleanly.
+                */
+               .server_list = NULL,
+               .server_list_len = 0,
+               .client_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .client_list_len = 6,
+               .want_out = "a",
+               .want_out_len = 1,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * Malformed server protocols is a misconfiguration, but it
+                * should fail cleanly.
+                */
+               .server_list = "\x00",
+               .server_list_len = 1,
+               .client_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .client_list_len = 6,
+               .want_out = "a",
+               .want_out_len = 1,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * Malformed server protocols is a misconfiguration, but it
+                * should fail cleanly.
+                */
+               .server_list = "\x01" "a" "\x03" "bb",
+               .server_list_len = 5,
+               .client_list = "\x01" "a" "\x01" "b" "\x01" "c",
+               .client_list_len = 6,
+               .want_out = "a",
+               .want_out_len = 1,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * Empty client protocols is not reachable from the ALPN
+                * callback. It fails cleanly with NULL protocol and 0 length.
+                */
+               .server_list = "\x01" "a",
+               .server_list_len = 2,
+               .client_list = "",
+               .client_list_len = 0,
+               .want_out = NULL,
+               .want_out_len = 0,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * NULL client protocols is not reachable from the ALPN
+                * callback. It fails cleanly with NULL protocol and 0 length.
+                */
+               .server_list = "\x01" "a",
+               .server_list_len = 2,
+               .client_list = NULL,
+               .client_list_len = 0,
+               .want_out = NULL,
+               .want_out_len = 0,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * Malformed client list fails cleanly with NULL protocol and
+                * 0 length.
+                */
+               .server_list = "\x01" "a",
+               .server_list_len = 2,
+               .client_list = "\x01" "a" "\x02" "bb" "\x03" "cc" "\x04" "ddd",
+               .client_list_len = 12,
+               .want_out = NULL,
+               .want_out_len = 0,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /*
+                * Malformed client list fails cleanly with NULL protocol and
+                * 0 length.
+                */
+               .server_list = "\x01" "a",
+               .server_list_len = 2,
+               .client_list = "\x01" "a" "\x02" "bb" "\x00" "\x03" "ddd",
+               .client_list_len = 10,
+               .want_out = NULL,
+               .want_out_len = 0,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+
+       /*
+        * Some non-toy examples.
+        */
+
+       {
+               .server_list = "\x08" "http/1.1" "\x06" "spdy/1",
+               .server_list_len = 16,
+               .client_list = "\x08" "http/2.0" "\x08" "http/1.1",
+               .client_list_len = 18,
+               .want_out = "http/1.1",
+               .want_out_len = 8,
+               .want_ret = OPENSSL_NPN_NEGOTIATED,
+       },
+       {
+               .server_list = "\x08" "http/2.0" "\x06" "spdy/1",
+               .server_list_len = 16,
+               .client_list = "\x08" "http/1.0" "\x08" "http/1.1",
+               .client_list_len = 18,
+               .want_out = "http/1.0",
+               .want_out_len = 8,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               .server_list = "\x08" "http/1.1" "\x08" "http/1.0",
+               .server_list_len = 18,
+               .client_list = "\x08" "http/1.0" "\x08" "http/1.1",
+               .client_list_len = 18,
+               .want_out = "http/1.1",
+               .want_out_len = 8,
+               .want_ret = OPENSSL_NPN_NEGOTIATED,
+       },
+       {
+               /* Server malformed. */
+               .server_list = "\x08" "http/1.1" "\x07" "http/1.0",
+               .server_list_len = 18,
+               .client_list = "\x08" "http/1.0" "\x08" "http/1.1",
+               .client_list_len = 18,
+               .want_out = "http/1.0",
+               .want_out_len = 8,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /* Server malformed. */
+               .server_list = "\x07" "http/1.1" "\x08" "http/1.0",
+               .server_list_len = 18,
+               .client_list = "\x08" "http/1.0" "\x08" "http/1.1",
+               .client_list_len = 18,
+               .want_out = "http/1.0",
+               .want_out_len = 8,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+       {
+               /* Client has trailing bytes. */
+               .server_list = "\x08" "http/1.1" "\x08" "http/1.0",
+               .server_list_len = 18,
+               .client_list = "\x08" "http/1.0" "\x07" "http/1.1",
+               .client_list_len = 18,
+               .want_out = NULL,
+               .want_out_len = 0,
+               .want_ret = OPENSSL_NPN_NO_OVERLAP,
+       },
+};
+
+#define N_SELECT_NEXT_PROTO_TESTS \
+    (sizeof(select_next_proto_tests) / sizeof(select_next_proto_tests[0]))
+
+static int
+select_next_proto_testcase(const struct select_next_proto_test *test)
+{
+       unsigned char *out;
+       unsigned char out_len;
+       int ret;
+       int failed = 0;
+
+       ret = SSL_select_next_proto(&out, &out_len, test->server_list,
+           test->server_list_len, test->client_list, test->client_list_len);
+
+       if (ret != test->want_ret || out_len != test->want_out_len ||
+           (out == NULL && test->want_out != NULL) ||
+           (out != NULL && test->want_out == NULL) ||
+           (out != NULL && test->want_out != NULL &&
+            memcmp(out, test->want_out, out_len) != 0)) {
+               fprintf(stderr, "FAIL: ret: %u (want %u), out_len: %u (want %u)\n",
+                   ret, test->want_ret, out_len, test->want_out_len);
+               fprintf(stderr, "\ngot:\n");
+               hexdump(out, out_len);
+               fprintf(stderr, "\nwant:\n");
+               hexdump(test->want_out, test->want_out_len);
+               fprintf(stderr, "\nserver:\n");
+               hexdump(test->server_list, test->server_list_len);
+               fprintf(stderr, "\nclient:\n");
+               hexdump(test->client_list, test->client_list_len);
+               fprintf(stderr, "\n");
+               failed = 1;
+       }
+
+       return failed;
+}
+
+static int
+test_ssl_select_next_proto(void)
+{
+       size_t i;
+       int failed = 0;
+
+       for (i = 0; i < N_SELECT_NEXT_PROTO_TESTS; i++)
+               failed |= select_next_proto_testcase(&select_next_proto_tests[i]);
+
+       return failed;
+}
+
 int
 main(void)
 {
@@ -197,6 +485,8 @@ main(void)
 
        failed |= test_ssl_set_alpn_protos_edge_cases();
 
+       failed |= test_ssl_select_next_proto();
+
        if (!failed)
                printf("PASS %s\n", __FILE__);