From cffaab34940dff27cf1ed44f3f580b66e564cf83 Mon Sep 17 00:00:00 2001 From: Justin W Smith <103147162+justsmth@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:58:54 -0500 Subject: [PATCH 1/8] Add Clang 19 to CI (#1998) ### Description of changes: * Add clang-19 to our CI. * Switch the pedantic test from clang-18 to clang-19. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --- .github/workflows/actions-ci.yml | 43 +++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/.github/workflows/actions-ci.yml b/.github/workflows/actions-ci.yml index 311d54b17a..bdaba2cf42 100644 --- a/.github/workflows/actions-ci.yml +++ b/.github/workflows/actions-ci.yml @@ -281,18 +281,23 @@ jobs: - name: Build SSL run: cmake --build ./build --target ssl - clang-18-pedantic: + clang-19-pedantic: if: github.repository_owner == 'aws' needs: [ sanity-test-run ] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 + - name: Install clang-19 + run: | + wget https://apt.llvm.org/llvm.sh + chmod u+x llvm.sh + sudo ./llvm.sh 19 - name: Setup CMake uses: threeal/cmake-action@v1.3.0 with: generator: Ninja - c-compiler: clang-18 - cxx-compiler: clang++-18 + c-compiler: clang-19 + cxx-compiler: clang++-19 options: CMAKE_BUILD_TYPE=Release CMAKE_C_FLAGS=-pedantic CMAKE_CXX_FLAGS=-pedantic - name: Build Crypto run: cmake --build ./build --target crypto @@ -392,6 +397,38 @@ jobs: - name: Run tests run: cmake --build ./build --target run_tests + clang-19-sanity: + if: github.repository_owner == 'aws' + needs: [ sanity-test-run ] + strategy: + fail-fast: false + matrix: + fips: + - "0" + - "1" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '>=1.18' + - name: Install clang-19 + run: | + wget https://apt.llvm.org/llvm.sh + chmod u+x llvm.sh + sudo ./llvm.sh 19 + - name: Setup CMake + uses: threeal/cmake-action@v1.3.0 + with: + generator: Ninja + c-compiler: clang-19 + cxx-compiler: clang++-19 + options: FIPS=${{ matrix.fips }} CMAKE_BUILD_TYPE=Release + - name: Build Project + run: cmake --build ./build --target all + - name: Run tests + run: cmake --build ./build --target run_tests + OpenBSD-x86-64: needs: [sanity-test-run] runs-on: ubuntu-latest From 1dc8f2a62f2a66118c9ee42f594ed2606df195d6 Mon Sep 17 00:00:00 2001 From: Shubham Mittal <107728331+smittals2@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:50:19 -0800 Subject: [PATCH 2/8] Adding the OpenSSL s_client tool (#1959) ### Issues: `CryptoAlg-2679` ### Description of changes: Adding the s_client tool to our openssl CLI executable. It mostly uses the same code from our existing bssl CLI s_client tool. There are various changes to functions to help distinguish which CLI variant is being called. The output of bssl's CLI for s_client is also very different from OpenSSL 1.1.1. There are a lot of utility printing functions added and changes in logic to suppress output when using the openssl CLI. ### Call-outs: The verification output still doesn't match OpenSSL 1.1.1 fully. There is some missing data like peer CAs, peer tmp key, public key bits, and sig_algs. ### Testing: There are no OpenSSL comparison tests since our output does not match OpenSSL yet. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --- crypto/ocsp/ocsp_integration_test.cc | 2 +- tool-openssl/CMakeLists.txt | 6 + tool-openssl/internal.h | 1 + tool-openssl/s_client.cc | 46 ++++++ tool-openssl/s_client_test.cc | 35 +++++ tool-openssl/tool.cc | 7 +- tool/client.cc | 206 +++++++++++++++++++++++++-- tool/internal.h | 6 + tool/transport_common.cc | 38 ++--- tool/transport_common.h | 4 +- 10 files changed, 313 insertions(+), 38 deletions(-) create mode 100644 tool-openssl/s_client.cc create mode 100644 tool-openssl/s_client_test.cc diff --git a/crypto/ocsp/ocsp_integration_test.cc b/crypto/ocsp/ocsp_integration_test.cc index f1c3d69fc0..d48c5fe0e2 100644 --- a/crypto/ocsp/ocsp_integration_test.cc +++ b/crypto/ocsp/ocsp_integration_test.cc @@ -204,7 +204,7 @@ TEST_P(OCSPIntegrationTest, AmazonTrustServices) { // certificate chain the endpoint uses. int sock = -1; ASSERT_TRUE(InitSocketLibrary()); - ASSERT_TRUE(Connect(&sock, t.url_host)); + ASSERT_TRUE(Connect(&sock, t.url_host, false)); ASSERT_TRUE(SSL_library_init()); bssl::UniquePtr ssl_ctx(SSL_CTX_new(TLS_method())); ASSERT_TRUE(ssl_ctx); diff --git a/tool-openssl/CMakeLists.txt b/tool-openssl/CMakeLists.txt index d9a7c6a72c..c31fbc4de1 100644 --- a/tool-openssl/CMakeLists.txt +++ b/tool-openssl/CMakeLists.txt @@ -4,6 +4,8 @@ add_executable( ../tool/args.cc ../tool/file.cc ../tool/fd.cc + ../tool/client.cc + ../tool/transport_common.cc dgst.cc rsa.cc @@ -11,6 +13,7 @@ add_executable( x509.cc crl.cc version.cc + s_client.cc ) target_include_directories(openssl PUBLIC ${PROJECT_SOURCE_DIR}/include) @@ -53,6 +56,8 @@ if(BUILD_TESTING) ../tool/file.cc ../tool/fd.cc ../crypto/test/test_util.cc + ../tool/client.cc + ../tool/transport_common.cc dgst.cc dgst_test.cc @@ -60,6 +65,7 @@ if(BUILD_TESTING) rsa_test.cc x509.cc x509_test.cc + s_client.cc crl.cc crl_test.cc ) diff --git a/tool-openssl/internal.h b/tool-openssl/internal.h index 3364f4639f..c640bb4539 100644 --- a/tool-openssl/internal.h +++ b/tool-openssl/internal.h @@ -34,5 +34,6 @@ bool rsaTool(const args_list_t &args); bool X509Tool(const args_list_t &args); bool CRLTool(const args_list_t &args); bool VersionTool(const args_list_t &args); +bool SClientTool(const args_list_t &args); #endif //INTERNAL_H diff --git a/tool-openssl/s_client.cc b/tool-openssl/s_client.cc new file mode 100644 index 0000000000..eb15c3d959 --- /dev/null +++ b/tool-openssl/s_client.cc @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include +#include +#include "internal.h" +#include "../tool/internal.h" + +static const argument_t kArguments[] = { + { "-help", kBooleanArgument, "Display option summary" }, + { "-connect", kRequiredArgument, + "The hostname and port of the server to connect to, e.g. foo.com:443" }, + { "-CAfile", kOptionalArgument, + "A file containing trusted certificates to use during server authentication " + "and to use when attempting to build the client certificate chain. " }, + { "-CApath", kOptionalArgument, + "The directory to use for server certificate verification. " }, + { "-showcerts", kBooleanArgument, + "Displays the server certificate list as sent by the server: it only " + "consists of certificates the server has sent (in the order the server " + "has sent them). It is not a verified chain. " }, + { "-verify", kOptionalArgument, + "The verify depth to use. This specifies the maximum length of the server " + "certificate chain and turns on server certificate verification. " + "Currently the verify operation continues after errors so all the problems " + "with a certificate chain can be seen. As a side effect the connection will " + "never fail due to a server certificate verify failure." }, + { "", kOptionalArgument, "" }, +}; + +bool SClientTool(const args_list_t &args) { + std::map args_map; + + if (!ParseKeyValueArguments(&args_map, args, kArguments)) { + PrintUsage(kArguments); + return false; + } + + if(args_map.count("help")) { + fprintf(stderr, "Usage: s_client [options] [host:port]\n"); + PrintUsage(kArguments); + return false; + } + + return DoClient(args_map, true); +} diff --git a/tool-openssl/s_client_test.cc b/tool-openssl/s_client_test.cc new file mode 100644 index 0000000000..205509e6ad --- /dev/null +++ b/tool-openssl/s_client_test.cc @@ -0,0 +1,35 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include +#include "internal.h" +#include + +// Test -connect +TEST(SClientTest, SClientConnect) { + args_list_t args = {"-connect", "amazon.com:443"}; + bool result = SClientTool(args); + ASSERT_TRUE(result); +} + +// Test without connect but with help +TEST(SClientTest, SClientNoConnect) { + args_list_t args = {}; + bool result = SClientTool(args); + ASSERT_FALSE(result); +} + +// Test -help +TEST(SClientTest, SClientHelp) { + args_list_t args = {"-help"}; + bool result = SClientTool(args); + ASSERT_FALSE(result); +} + +// Test -connect, -verify, -showcerts +TEST(SClientTest, SClientConnectVerifyShowcerts) { + args_list_t args = {"-connect", "amazon.com:443", "-verify", "99"}; + bool result = SClientTool(args); + ASSERT_TRUE(result); +} + diff --git a/tool-openssl/tool.cc b/tool-openssl/tool.cc index ae42679eca..09c61b6f2c 100644 --- a/tool-openssl/tool.cc +++ b/tool-openssl/tool.cc @@ -15,13 +15,14 @@ #include "./internal.h" -static const std::array kTools = {{ +static const std::array kTools = {{ + {"crl", CRLTool}, {"dgst", dgstTool}, {"md5", md5Tool}, {"rsa", rsaTool}, + {"s_client", SClientTool}, + {"version", VersionTool}, {"x509", X509Tool}, - {"crl", CRLTool}, - {"version", VersionTool} }}; static void usage(const std::string &name) { diff --git a/tool/client.cc b/tool/client.cc index b3efe63b3e..57f2b24237 100644 --- a/tool/client.cc +++ b/tool/client.cc @@ -245,19 +245,92 @@ static bool WaitForSession(SSL *ssl, int sock) { return true; } +static void print_verify_details(SSL *s) { + long verify_err = SSL_get_verify_result(s); + + if (verify_err == X509_V_OK) { + fprintf(stdout, "Verification: OK\n"); + } else { + const char *reason = X509_verify_cert_error_string(verify_err); + fprintf(stdout, "Verification error: %s\n", reason); + } +} + +static void PrintOpenSSLConnectionInfo(SSL *ssl, bool show_certs) { + STACK_OF(X509) *sk; + + sk = SSL_get_peer_cert_chain(ssl); + if (sk != NULL) { + fprintf(stdout, "---\nCertificate chain\n"); + for (size_t i = 0; i < sk_X509_num(sk); i++) { + fprintf(stdout, "%2zu s:", i); + if (X509_NAME_print_ex_fp(stdout, X509_get_subject_name(sk_X509_value(sk, i)), + 0, XN_FLAG_ONELINE) < 0) { + fprintf(stderr, "Error: Printing subject name failed"); + } + fprintf(stdout, "\n i:"); + if (X509_NAME_print_ex_fp(stdout, X509_get_issuer_name(sk_X509_value(sk, i)), + 0, XN_FLAG_ONELINE) < 0) { + fprintf(stderr, "Error: Printing issuer name failed"); + } + fprintf(stdout, "\n"); + if (show_certs) { + PEM_write_X509(stdout, sk_X509_value(sk, i)); + } + } + } + + fprintf(stdout, "---\n"); + bssl::UniquePtr peer(SSL_get_peer_certificate(ssl)); + if (peer) { + fprintf(stdout, "Server certificate\n"); + PEM_write_X509(stdout, peer.get()); + fprintf(stdout, "subject="); + if (X509_NAME_print_ex_fp(stdout, X509_get_subject_name(peer.get()), + 0, XN_FLAG_ONELINE) < 0) { + fprintf(stderr, "Error: Printing subject name failed"); + } + fprintf(stdout, "\n\nissuer="); + if (X509_NAME_print_ex_fp(stdout, X509_get_issuer_name(peer.get()), + 0, XN_FLAG_ONELINE) < 0) { + fprintf(stderr, "Error: Printing issuer name failed"); + } + fprintf(stdout, "\n\n---\n"); + } else { + fprintf(stdout, "no peer certificate available\n"); + } + + // TODO (aws-lc): we are missing some functions needed to print the following data + // print_ca_names(bio, s); + // ssl_print_sigalgs(bio, s); + // ssl_print_tmp_key(bio, s); + + fprintf(stdout, + "---\nSSL handshake has read %d bytes " + "and written %d bytes\n", + (int)BIO_number_read(SSL_get_rbio(ssl)), + (int)BIO_number_written(SSL_get_wbio(ssl))); + print_verify_details(ssl); +} + static bool DoConnection(SSL_CTX *ctx, std::map args_map, - bool (*cb)(SSL *ssl, int sock)) { + bool (*cb)(SSL *ssl, int sock), bool is_openssl_s_client) { int sock = -1; if (args_map.count("-http-tunnel") != 0) { - if (!Connect(&sock, args_map["-http-tunnel"]) || + if (!Connect(&sock, args_map["-http-tunnel"], is_openssl_s_client) || !DoHTTPTunnel(sock, args_map["-connect"])) { return false; } - } else if (!Connect(&sock, args_map["-connect"])) { + } else if (!Connect(&sock, args_map["-connect"], is_openssl_s_client)) { return false; } + // print for openssl tool + if (is_openssl_s_client) { + fprintf(stdout, "CONNECTED(%08d)\n", sock); + } + if (args_map.count("-starttls") != 0) { const std::string& starttls = args_map["-starttls"]; if (starttls == "smtp") { @@ -357,9 +430,14 @@ static bool DoConnection(SSL_CTX *ctx, } } - fprintf(stderr, "Connected.\n"); - bssl::UniquePtr bio_stderr(BIO_new_fp(stderr, BIO_NOCLOSE)); - PrintConnectionInfo(bio_stderr.get(), ssl.get()); + // print for bssl + if (!is_openssl_s_client) { + fprintf(stderr, "Connected.\n"); + bssl::UniquePtr bio_stderr(BIO_new_fp(stderr, BIO_NOCLOSE)); + PrintConnectionInfo(bio_stderr.get(), ssl.get()); + } else { // print for openssl + PrintOpenSSLConnectionInfo(ssl.get(), args_map.count("-showcerts")); + } return cb(ssl.get(), sock); } @@ -378,11 +456,67 @@ static void InfoCallback(const SSL *ssl, int type, int value) { } } -bool Client(const std::vector &args) { - if (!InitSocketLibrary()) { - return false; +static int verify_cb(int ok, X509_STORE_CTX *ctx) +{ + X509 *err_cert = X509_STORE_CTX_get_current_cert(ctx); + int err = X509_STORE_CTX_get_error(ctx); + int depth = X509_STORE_CTX_get_error_depth(ctx); + bssl::UniquePtr bio_err(BIO_new_fp(stderr, BIO_CLOSE)); + + BIO_printf(bio_err.get(), "depth=%d ", depth); + if (err_cert != NULL) { + X509_NAME_print_ex(bio_err.get(), + X509_get_subject_name(err_cert), + 0, XN_FLAG_ONELINE); + BIO_puts(bio_err.get(), "\n"); + } else { + BIO_puts(bio_err.get(), "\n"); + } + + if (!ok) { + BIO_printf(bio_err.get(), "verify error:num=%d:%s\n", err, + X509_verify_cert_error_string(err)); + ok = 1; + } + + switch (err) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + if (err_cert != NULL) { + BIO_puts(bio_err.get(), "issuer= "); + X509_NAME_print_ex(bio_err.get(), X509_get_issuer_name(err_cert), + 0, XN_FLAG_ONELINE); + BIO_puts(bio_err.get(), "\n"); + } + ok = 1; + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + if (err_cert != NULL) { + BIO_printf(bio_err.get(), "notBefore="); + ASN1_TIME_print(bio_err.get(), X509_get0_notBefore(err_cert)); + BIO_printf(bio_err.get(), "\n"); + } + ok = 1; + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + if (err_cert != NULL) { + BIO_printf(bio_err.get(), "notAfter="); + ASN1_TIME_print(bio_err.get(), X509_get0_notAfter(err_cert)); + BIO_printf(bio_err.get(), "\n"); + } + ok = 1; + break; + case X509_V_ERR_NO_EXPLICIT_POLICY: + ok = 1; + break; } + BIO_printf(bio_err.get(), "verify return:%d\n", ok); + return ok; +} + +bool Client(const std::vector &args) { std::map args_map; if (!ParseKeyValueArguments(&args_map, args, kArguments)) { @@ -390,6 +524,14 @@ bool Client(const std::vector &args) { return false; } + return DoClient(args_map, false); +} + +bool DoClient(std::map args_map, bool is_openssl_s_client) { + if (!InitSocketLibrary()) { + return false; + } + bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); const char *keylog_file = getenv("SSLKEYLOGFILE"); @@ -540,24 +682,58 @@ bool Client(const std::vector &args) { SSL_CTX_set_permute_extensions(ctx.get(), 1); } + std::string certPathFlag; + int verify = SSL_VERIFY_NONE; if (args_map.count("-root-certs") != 0) { + certPathFlag = "-root-certs"; + verify = SSL_VERIFY_PEER; + } + // For the OpenSSL tool, simply specifying -CAfile does not imply verification + if (args_map.count("-CAfile") != 0) { + certPathFlag = "-CAfile"; + } + if (!certPathFlag.empty()) { if (!SSL_CTX_load_verify_locations( - ctx.get(), args_map["-root-certs"].c_str(), nullptr)) { + ctx.get(), args_map[certPathFlag].c_str(), nullptr)) { fprintf(stderr, "Failed to load root certificates.\n"); ERR_print_errors_fp(stderr); return false; } - SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); } + certPathFlag = ""; if (args_map.count("-root-cert-dir") != 0) { + certPathFlag = "-root-cert-dir"; + verify = SSL_VERIFY_PEER; + } + // For the OpenSSL tool, simply specifying -CApath does not imply verification + if (args_map.count("-CApath") != 0) { + certPathFlag = "-CApath"; + } + + if (!certPathFlag.empty()) { if (!SSL_CTX_load_verify_locations( - ctx.get(), nullptr, args_map["-root-cert-dir"].c_str())) { + ctx.get(), nullptr, args_map[certPathFlag].c_str())) { fprintf(stderr, "Failed to load root certificates.\n"); ERR_print_errors_fp(stderr); return false; } - SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); + } + + if (args_map.count("-verify") != 0) { + unsigned int depth; + if (!GetUnsigned(&depth, "-verify", 0, args_map)) { + fprintf(stderr, "s_client: Can't parse \"%s\" as a number\n", args_map.find("-verify")->second.c_str()); + return false; + } + fprintf(stdout, "verify depth is %d\n", (int)depth); + verify = SSL_VERIFY_PEER; + } + + if (is_openssl_s_client) { // openssl tool + SSL_CTX_set_verify(ctx.get(), verify, verify_cb); + } else { + SSL_CTX_set_verify(ctx.get(), verify, nullptr); } if (args_map.count("-early-data") != 0) { @@ -575,10 +751,10 @@ bool Client(const std::vector &args) { return false; } - if (!DoConnection(ctx.get(), args_map, &WaitForSession)) { + if (!DoConnection(ctx.get(), args_map, &WaitForSession, is_openssl_s_client)) { return false; } } - return DoConnection(ctx.get(), args_map, &TransferData); + return DoConnection(ctx.get(), args_map, &TransferData, is_openssl_s_client); } diff --git a/tool/internal.h b/tool/internal.h index 18414b65aa..ddf919642b 100644 --- a/tool/internal.h +++ b/tool/internal.h @@ -135,6 +135,12 @@ bool GetBoolArgument(bool *out, const std::string &arg_name, const args_map_t &a bool ReadAll(std::vector *out, FILE *in); bool WriteToFile(const std::string &path, const uint8_t *in, size_t in_len); +// DoClient is a common function used to support the s_client option in both +// bssl and openssl tools. It takes an additional parameter |tool| to indicate +// which tool's s_client is being invoked. A value of true indicates openssl +// and false indicates the internal bssl tool. +bool DoClient(std::map args_map, bool is_openssl_s_client); + bool Ciphers(const std::vector &args); bool Client(const std::vector &args); bool DoPKCS12(const std::vector &args); diff --git a/tool/transport_common.cc b/tool/transport_common.cc index cd43f9906b..80c8d91030 100644 --- a/tool/transport_common.cc +++ b/tool/transport_common.cc @@ -137,7 +137,7 @@ static void PrintSocketError(const char *function) { // Connect sets |*out_sock| to be a socket connected to the destination given // in |hostname_and_port|, which should be of the form "www.example.com:123". // It returns true on success and false otherwise. -bool Connect(int *out_sock, const std::string &hostname_and_port) { +bool Connect(int *out_sock, const std::string &hostname_and_port, bool quiet) { std::string hostname, port; SplitHostPort(&hostname, &port, hostname_and_port); @@ -153,7 +153,7 @@ bool Connect(int *out_sock, const std::string &hostname_and_port) { hint.ai_socktype = SOCK_STREAM; int ret = getaddrinfo(hostname.c_str(), port.c_str(), &hint, &result); - if (ret != 0) { + if (ret != 0 && !quiet) { #if defined(OPENSSL_WINDOWS) const char *error = gai_strerrorA(ret); #else @@ -173,22 +173,24 @@ bool Connect(int *out_sock, const std::string &hostname_and_port) { goto out; } - switch (result->ai_family) { - case AF_INET: { - struct sockaddr_in *sin = - reinterpret_cast(result->ai_addr); - fprintf(stderr, "Connecting to %s:%d\n", - inet_ntop(result->ai_family, &sin->sin_addr, buf, sizeof(buf)), - ntohs(sin->sin_port)); - break; - } - case AF_INET6: { - struct sockaddr_in6 *sin6 = - reinterpret_cast(result->ai_addr); - fprintf(stderr, "Connecting to [%s]:%d\n", - inet_ntop(result->ai_family, &sin6->sin6_addr, buf, sizeof(buf)), - ntohs(sin6->sin6_port)); - break; + if(!quiet) { + switch (result->ai_family) { + case AF_INET: { + struct sockaddr_in *sin = + reinterpret_cast(result->ai_addr); + fprintf(stderr, "Connecting to %s:%d\n", + inet_ntop(result->ai_family, &sin->sin_addr, buf, sizeof(buf)), + ntohs(sin->sin_port)); + break; + } + case AF_INET6: { + struct sockaddr_in6 *sin6 = + reinterpret_cast(result->ai_addr); + fprintf(stderr, "Connecting to [%s]:%d\n", + inet_ntop(result->ai_family, &sin6->sin6_addr, buf, sizeof(buf)), + ntohs(sin6->sin6_port)); + break; + } } } diff --git a/tool/transport_common.h b/tool/transport_common.h index 7d45d1c707..c23e856e50 100644 --- a/tool/transport_common.h +++ b/tool/transport_common.h @@ -25,8 +25,10 @@ bool InitSocketLibrary(); // Connect sets |*out_sock| to be a socket connected to the destination given // in |hostname_and_port|, which should be of the form "www.example.com:123". +// |quiet| when set to true suppresses any output to stdout or stderr from this +// function. // It returns true on success and false otherwise. -bool Connect(int *out_sock, const std::string &hostname_and_port); +bool Connect(int *out_sock, const std::string &hostname_and_port, bool quiet); class Listener { public: From 7c04616e5a8346c32eea3bbd69fc856a921d54b6 Mon Sep 17 00:00:00 2001 From: dkostic <25055813+dkostic@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:54:00 -0800 Subject: [PATCH 3/8] [EC] Unify scalar_mul_public for ec_nistp curves (#2004) Added unified scalar_mul_public implemented in ec_nistp. This is a refactor of the algorithm in p384.c and p521.c that makes it generic. The implementations in p384.c, p521.c, as well as in p256.c, are substituted with this new unified implementation. --- crypto/fipsmodule/ec/ec_nistp.c | 137 ++++++++++++++++++++++++++- crypto/fipsmodule/ec/ec_nistp.h | 10 ++ crypto/fipsmodule/ec/internal.h | 3 +- crypto/fipsmodule/ec/p256.c | 2 +- crypto/fipsmodule/ec/p384.c | 162 +------------------------------ crypto/fipsmodule/ec/p521.c | 163 +------------------------------- crypto/fipsmodule/ec/wnaf.c | 12 +-- 7 files changed, 163 insertions(+), 326 deletions(-) diff --git a/crypto/fipsmodule/ec/ec_nistp.c b/crypto/fipsmodule/ec/ec_nistp.c index f3025ee3bd..e2415fd0c5 100644 --- a/crypto/fipsmodule/ec/ec_nistp.c +++ b/crypto/fipsmodule/ec/ec_nistp.c @@ -19,7 +19,7 @@ // | 2. | x | x | x* | // | 3. | x | x | | // | 4. | x | x | x* | -// | 5. | | | | +// | 5. | x | x | | // * For P-256, only the Fiat-crypto implementation in p256.c is replaced. #include "ec_nistp.h" @@ -317,9 +317,13 @@ static void scalar_rwnaf(int16_t *out, size_t window_size, #define SCALAR_MUL_TABLE_MAX_NUM_FELEM_LIMBS \ (SCALAR_MUL_TABLE_NUM_POINTS * 3 * FELEM_MAX_NUM_OF_LIMBS) +// The maximum number of bits for a scalar. +#define SCALAR_MUL_MAX_SCALAR_BITS (521) + // Maximum number of windows (digits) for a scalar encoding which is // determined by the maximum scalar bit size -- 521 bits in our case. -#define SCALAR_MUL_MAX_NUM_WINDOWS DIV_AND_CEIL(521, SCALAR_MUL_WINDOW_SIZE) +#define SCALAR_MUL_MAX_NUM_WINDOWS \ + DIV_AND_CEIL(SCALAR_MUL_MAX_SCALAR_BITS, SCALAR_MUL_WINDOW_SIZE) // Generate table of multiples of the input point P = (x_in, y_in, z_in): // table <-- [2i + 1]P for i in [0, SCALAR_MUL_TABLE_NUM_POINTS - 1]. @@ -636,3 +640,132 @@ void ec_nistp_scalar_mul_base(const ec_nistp_meth *ctx, cmovznz(y_out, ctx->felem_num_limbs, t, y_tmp, y_res); cmovznz(z_out, ctx->felem_num_limbs, t, z_tmp, z_res); } + +// Computes [g_scalar]G + [p_scalar]P, where G is the base point of the curve +// curve, and P is the given point (x_p, y_p, z_p). +// +// Both scalar products are computed by the same "textbook" wNAF method, +// with w = 5 for g_scalar and w = 5 for p_scalar. +// For the base point G product we use the first sub-table of the precomputed +// table, while for P we generate the table on-the-fly. The tables hold the +// first 16 odd multiples of G or P: +// g_pre_comp = {[1]G, [3]G, ..., [31]G}, +// p_pre_comp = {[1]P, [3]P, ..., [31]P}. +// Computing the negation of a point P = (x, y) is relatively easy: +// -P = (x, -y). +// So we may assume that we also have the negatives of the points in the tables. +// +// The scalars are recoded by the textbook wNAF method to digits, where a digit +// is either a zero or an odd integer in [-31, 31]. The method guarantees that +// each non-zero digit is followed by at least four zeroes. +// +// The result [g_scalar]G + [p_scalar]P is computed by the following algorithm: +// 1. Initialize the accumulator with the point-at-infinity. +// 2. For i starting from 521 down to 0: +// 3. Double the accumulator (doubling can be skipped while the +// accumulator is equal to the point-at-infinity). +// 4. Read from |p_pre_comp| the point corresponding to the i-th digit of +// p_scalar, negate it if the digit is negative, and add it to the +// accumulator. +// 5. Read from |g_pre_comp| the point corresponding to the i-th digit of +// g_scalar, negate it if the digit is negative, and add it to the +// accumulator. +// Note: this function is NOT constant-time. +void ec_nistp_scalar_mul_public(const ec_nistp_meth *ctx, + ec_nistp_felem_limb *x_out, + ec_nistp_felem_limb *y_out, + ec_nistp_felem_limb *z_out, + const EC_SCALAR *g_scalar, + const ec_nistp_felem_limb *x_p, + const ec_nistp_felem_limb *y_p, + const ec_nistp_felem_limb *z_p, + const EC_SCALAR *p_scalar) { + + const size_t felem_num_bytes = ctx->felem_num_limbs * sizeof(ec_nistp_felem_limb); + + // Table of multiples of P. + ec_nistp_felem_limb p_table[SCALAR_MUL_TABLE_MAX_NUM_FELEM_LIMBS]; + generate_table(ctx, p_table, x_p, y_p, z_p); + const size_t p_point_num_limbs = 3 * ctx->felem_num_limbs; // Projective. + + // Table of multiples of G. + const ec_nistp_felem_limb *g_table = ctx->scalar_mul_base_table; + const size_t g_point_num_limbs = 2 * ctx->felem_num_limbs; // Affine. + + // Recode the scalars. + int8_t p_wnaf[SCALAR_MUL_MAX_SCALAR_BITS + 1] = {0}; + int8_t g_wnaf[SCALAR_MUL_MAX_SCALAR_BITS + 1] = {0}; + ec_compute_wNAF(p_wnaf, p_scalar, ctx->felem_num_bits, SCALAR_MUL_WINDOW_SIZE); + ec_compute_wNAF(g_wnaf, g_scalar, ctx->felem_num_bits, SCALAR_MUL_WINDOW_SIZE); + + // In the beginning res is set to point-at-infinity, so we set the flag. + int16_t res_is_inf = 1; + int16_t d, is_neg, idx; + ec_nistp_felem ftmp; + + for (int i = ctx->felem_num_bits; i >= 0; i--) { + + // If |res| is point-at-infinity there is no point in doubling so skip it. + if (!res_is_inf) { + ctx->point_dbl(x_out, y_out, z_out, x_out, y_out, z_out); + } + + // Process the p_scalar digit. + d = p_wnaf[i]; + if (d != 0) { + is_neg = d < 0 ? 1 : 0; + idx = (is_neg) ? (-d - 1) >> 1 : (d - 1) >> 1; + + if (res_is_inf) { + // If |res| is point-at-infinity there is no need to add the new point, + // we can simply copy it. + const size_t table_idx = idx * p_point_num_limbs; + OPENSSL_memcpy(x_out, &p_table[table_idx], felem_num_bytes); + OPENSSL_memcpy(y_out, &p_table[table_idx + ctx->felem_num_limbs], felem_num_bytes); + OPENSSL_memcpy(z_out, &p_table[table_idx + ctx->felem_num_limbs * 2], felem_num_bytes); + res_is_inf = 0; + } else { + // Otherwise, add to the accumulator either the point at position idx + // in the table or its negation. + const ec_nistp_felem_limb *y_tmp; + y_tmp = &p_table[idx * p_point_num_limbs + ctx->felem_num_limbs]; + if (is_neg) { + ctx->felem_neg(ftmp, y_tmp); + y_tmp = ftmp; + } + ctx->point_add(x_out, y_out, z_out, x_out, y_out, z_out, 0, + &p_table[idx * p_point_num_limbs], + y_tmp, + &p_table[idx * p_point_num_limbs + ctx->felem_num_limbs * 2]); + } + } + + /* // Process the g_scalar digit. */ + d = g_wnaf[i]; + if (d != 0) { + is_neg = d < 0 ? 1 : 0; + idx = (is_neg) ? (-d - 1) >> 1 : (d - 1) >> 1; + + if (res_is_inf) { + // If |res| is point-at-infinity there is no need to add the new point, + // we can simply copy it. + const size_t table_idx = idx * g_point_num_limbs; + OPENSSL_memcpy(x_out, &g_table[table_idx], felem_num_bytes); + OPENSSL_memcpy(y_out, &g_table[table_idx + ctx->felem_num_limbs], felem_num_bytes); + OPENSSL_memcpy(z_out, ctx->felem_one, felem_num_bytes); + res_is_inf = 0; + } else { + // Otherwise, add to the accumulator either the point at position idx + // in the table or its negation. + const ec_nistp_felem_limb *y_tmp ; + y_tmp = &g_table[idx * g_point_num_limbs + ctx->felem_num_limbs]; + if (is_neg) { + ctx->felem_neg(ftmp, &g_table[idx * g_point_num_limbs + ctx->felem_num_limbs]); + y_tmp = ftmp; + } + ctx->point_add(x_out, y_out, z_out, x_out, y_out, z_out, 1, + &g_table[idx * g_point_num_limbs], y_tmp, ctx->felem_one); + } + } + } +} diff --git a/crypto/fipsmodule/ec/ec_nistp.h b/crypto/fipsmodule/ec/ec_nistp.h index 13e143a88c..107087dafa 100644 --- a/crypto/fipsmodule/ec/ec_nistp.h +++ b/crypto/fipsmodule/ec/ec_nistp.h @@ -114,5 +114,15 @@ void ec_nistp_scalar_mul_base(const ec_nistp_meth *ctx, ec_nistp_felem_limb *y_out, ec_nistp_felem_limb *z_out, const EC_SCALAR *scalar); + +void ec_nistp_scalar_mul_public(const ec_nistp_meth *ctx, + ec_nistp_felem_limb *x_out, + ec_nistp_felem_limb *y_out, + ec_nistp_felem_limb *z_out, + const EC_SCALAR *g_scalar, + const ec_nistp_felem_limb *x_p, + const ec_nistp_felem_limb *y_p, + const ec_nistp_felem_limb *z_p, + const EC_SCALAR *p_scalar); #endif // EC_NISTP_H diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h index bc2d5897f1..8621b4dc39 100644 --- a/crypto/fipsmodule/ec/internal.h +++ b/crypto/fipsmodule/ec/internal.h @@ -697,8 +697,7 @@ void ec_GFp_mont_felem_exp(const EC_GROUP *group, EC_FELEM *out, // where at most one of any w+1 consecutive digits is non-zero // with the exception that the most significant digit may be only // w-1 zeros away from that next non-zero digit. -void ec_compute_wNAF(const EC_GROUP *group, int8_t *out, - const EC_SCALAR *scalar, size_t bits, int w); +void ec_compute_wNAF(int8_t *out, const EC_SCALAR *scalar, size_t bits, int w); int ec_GFp_mont_mul_public_batch(const EC_GROUP *group, EC_JACOBIAN *r, const EC_SCALAR *g_scalar, diff --git a/crypto/fipsmodule/ec/p256.c b/crypto/fipsmodule/ec/p256.c index f8789ced96..4d4b877099 100644 --- a/crypto/fipsmodule/ec/p256.c +++ b/crypto/fipsmodule/ec/p256.c @@ -380,7 +380,7 @@ static void ec_GFp_nistp256_point_mul_public(const EC_GROUP *group, // Set up the coefficients for |p_scalar|. int8_t p_wNAF[257]; - ec_compute_wNAF(group, p_wNAF, p_scalar, 256, P256_WSIZE_PUBLIC); + ec_compute_wNAF(p_wNAF, p_scalar, 256, P256_WSIZE_PUBLIC); // Set |ret| to the point at infinity. int skip = 1; // Save some point operations. diff --git a/crypto/fipsmodule/ec/p384.c b/crypto/fipsmodule/ec/p384.c index e3e8063ab3..91ef79725e 100644 --- a/crypto/fipsmodule/ec/p384.c +++ b/crypto/fipsmodule/ec/p384.c @@ -84,13 +84,6 @@ static p384_limb_t p384_felem_nz(const p384_limb_t in1[P384_NLIMBS]) { #endif // EC_NISTP_USE_S2N_BIGNUM -static void p384_felem_copy(p384_limb_t out[P384_NLIMBS], - const p384_limb_t in1[P384_NLIMBS]) { - for (size_t i = 0; i < P384_NLIMBS; i++) { - out[i] = in1[i]; - } -} - static void p384_from_generic(p384_felem out, const EC_FELEM *in) { #ifdef OPENSSL_BIG_ENDIAN uint8_t tmp[P384_EC_FELEM_BYTES]; @@ -470,26 +463,6 @@ static int ec_GFp_nistp384_cmp_x_coordinate(const EC_GROUP *group, // // For detailed analysis of different window sizes see the bottom of this file. -// Constants for scalar encoding in the scalar multiplication functions. -#define P384_MUL_WSIZE (5) // window size w -// Assert the window size is 5 because the pre-computed table in |p384_table.h| -// is generated for window size 5. -OPENSSL_STATIC_ASSERT(P384_MUL_WSIZE == 5, - p384_scalar_mul_window_size_is_not_equal_to_five) - -#define P384_MUL_TWO_TO_WSIZE (1 << P384_MUL_WSIZE) - -// Number of |P384_MUL_WSIZE|-bit windows in a 384-bit value -#define P384_MUL_NWINDOWS ((384 + P384_MUL_WSIZE - 1)/P384_MUL_WSIZE) - -// For the public point in |ec_GFp_nistp384_point_mul_public| function -// we use window size w = 5. -#define P384_MUL_PUB_WSIZE (5) - -// We keep only odd multiples in tables, hence the table size is (2^w)/2 -#define P384_MUL_TABLE_SIZE (P384_MUL_TWO_TO_WSIZE >> 1) -#define P384_MUL_PUB_TABLE_SIZE (1 << (P384_MUL_PUB_WSIZE - 1)) - // Multiplication of an arbitrary point by a scalar, r = [scalar]P. static void ec_GFp_nistp384_point_mul(const EC_GROUP *group, EC_JACOBIAN *r, const EC_JACOBIAN *p, @@ -523,36 +496,6 @@ static void ec_GFp_nistp384_point_mul_base(const EC_GROUP *group, // Computes [g_scalar]G + [p_scalar]P, where G is the base point of the P-384 // curve, and P is the given point |p|. -// -// Both scalar products are computed by the same "textbook" wNAF method, -// with w = 5 for g_scalar and w = 5 for p_scalar. -// For the base point G product we use the first sub-table of the precomputed -// table |p384_g_pre_comp| from |p384_table.h| file, while for P we generate -// |p_pre_comp| table on-the-fly. The tables hold the first 16 odd multiples -// of G or P: -// g_pre_comp = {[1]G, [3]G, ..., [31]G}, -// p_pre_comp = {[1]P, [3]P, ..., [31]P}. -// Computing the negation of a point P = (x, y) is relatively easy: -// -P = (x, -y). -// So we may assume that we also have the negatives of the points in the tables. -// -// The 384-bit scalars are recoded by the textbook wNAF method to 385 digits, -// where a digit is either a zero or an odd integer in [-31, 31]. The method -// guarantees that each non-zero digit is followed by at least four -// zeroes. -// -// The result [g_scalar]G + [p_scalar]P is computed by the following algorithm: -// 1. Initialize the accumulator with the point-at-infinity. -// 2. For i starting from 384 down to 0: -// 3. Double the accumulator (doubling can be skipped while the -// accumulator is equal to the point-at-infinity). -// 4. Read from |p_pre_comp| the point corresponding to the i-th digit of -// p_scalar, negate it if the digit is negative, and add it to the -// accumulator. -// 5. Read from |g_pre_comp| the point corresponding to the i-th digit of -// g_scalar, negate it if the digit is negative, and add it to the -// accumulator. -// // Note: this function is NOT constant-time. static void ec_GFp_nistp384_point_mul_public(const EC_GROUP *group, EC_JACOBIAN *r, @@ -560,109 +503,14 @@ static void ec_GFp_nistp384_point_mul_public(const EC_GROUP *group, const EC_JACOBIAN *p, const EC_SCALAR *p_scalar) { - p384_felem res[3] = {{0}, {0}, {0}}, two_p[3] = {{0}, {0}, {0}}, ftmp; - - // Table of multiples of P: [2i + 1]P for i in [0, 15]. - p384_felem p_pre_comp[P384_MUL_PUB_TABLE_SIZE][3]; - - // Set the first point in the table to P. - p384_from_generic(p_pre_comp[0][0], &p->X); - p384_from_generic(p_pre_comp[0][1], &p->Y); - p384_from_generic(p_pre_comp[0][2], &p->Z); - - // Compute two_p = [2]P. - p384_point_double(two_p[0], two_p[1], two_p[2], - p_pre_comp[0][0], p_pre_comp[0][1], p_pre_comp[0][2]); - - // Generate the remaining 15 multiples of P. - for (size_t i = 1; i < P384_MUL_PUB_TABLE_SIZE; i++) { - p384_point_add(p_pre_comp[i][0], p_pre_comp[i][1], p_pre_comp[i][2], - two_p[0], two_p[1], two_p[2], 0 /* both Jacobian */, - p_pre_comp[i - 1][0], - p_pre_comp[i - 1][1], - p_pre_comp[i - 1][2]); - } - - // Recode the scalars. - int8_t p_wnaf[385] = {0}, g_wnaf[385] = {0}; - ec_compute_wNAF(group, p_wnaf, p_scalar, 384, P384_MUL_PUB_WSIZE); - ec_compute_wNAF(group, g_wnaf, g_scalar, 384, P384_MUL_WSIZE); - - // In the beginning res is set to point-at-infinity, so we set the flag. - int16_t res_is_inf = 1; - int16_t d, is_neg, idx; - - for (int i = 384; i >= 0; i--) { - - // If |res| is point-at-infinity there is no point in doubling so skip it. - if (!res_is_inf) { - p384_point_double(res[0], res[1], res[2], res[0], res[1], res[2]); - } + p384_felem res[3] = {{0}, {0}, {0}}, tmp[3] = {{0}, {0}, {0}}; - // Process the p_scalar digit. - d = p_wnaf[i]; - if (d != 0) { - is_neg = d < 0 ? 1 : 0; - idx = (is_neg) ? (-d - 1) >> 1 : (d - 1) >> 1; - - if (res_is_inf) { - // If |res| is point-at-infinity there is no need to add the new point, - // we can simply copy it. - p384_felem_copy(res[0], p_pre_comp[idx][0]); - p384_felem_copy(res[1], p_pre_comp[idx][1]); - p384_felem_copy(res[2], p_pre_comp[idx][2]); - res_is_inf = 0; - } else { - // Otherwise, add to the accumulator either the point at position idx - // in the table or its negation. - if (is_neg) { - p384_felem_opp(ftmp, p_pre_comp[idx][1]); - } else { - p384_felem_copy(ftmp, p_pre_comp[idx][1]); - } - p384_point_add(res[0], res[1], res[2], - res[0], res[1], res[2], - 0 /* both Jacobian */, - p_pre_comp[idx][0], ftmp, p_pre_comp[idx][2]); - } - } + p384_from_generic(tmp[0], &p->X); + p384_from_generic(tmp[1], &p->Y); + p384_from_generic(tmp[2], &p->Z); - // Process the g_scalar digit. - d = g_wnaf[i]; - if (d != 0) { - is_neg = d < 0 ? 1 : 0; - idx = (is_neg) ? (-d - 1) >> 1 : (d - 1) >> 1; - - if (res_is_inf) { - // If |res| is point-at-infinity there is no need to add the new point, - // we can simply copy it. - p384_felem_copy(res[0], p384_g_pre_comp[0][idx][0]); - p384_felem_copy(res[1], p384_g_pre_comp[0][idx][1]); - p384_felem_copy(res[2], p384_felem_one); - res_is_inf = 0; - } else { - // Otherwise, add to the accumulator either the point at position idx - // in the table or its negation. - if (is_neg) { - p384_felem_opp(ftmp, p384_g_pre_comp[0][idx][1]); - } else { - p384_felem_copy(ftmp, p384_g_pre_comp[0][idx][1]); - } - // Add the point to the accumulator |res|. - // Note that the points in the pre-computed table are given with affine - // coordinates. The point addition function computes a sum of two points, - // either both given in projective, or one in projective and one in - // affine coordinates. The |mixed| flag indicates the latter option, - // in which case we set the third coordinate of the second point to one. - p384_point_add(res[0], res[1], res[2], - res[0], res[1], res[2], - 1 /* mixed */, - p384_g_pre_comp[0][idx][0], ftmp, p384_felem_one); - } - } - } + ec_nistp_scalar_mul_public(p384_methods(), res[0], res[1], res[2], g_scalar, tmp[0], tmp[1], tmp[2], p_scalar); - // Copy the result to the output. p384_to_generic(&r->X, res[0]); p384_to_generic(&r->Y, res[1]); p384_to_generic(&r->Z, res[2]); diff --git a/crypto/fipsmodule/ec/p521.c b/crypto/fipsmodule/ec/p521.c index c35cde1d96..c8d8116ec2 100644 --- a/crypto/fipsmodule/ec/p521.c +++ b/crypto/fipsmodule/ec/p521.c @@ -147,13 +147,6 @@ static p521_limb_t p521_felem_nz(const p521_limb_t in1[P521_NLIMBS]) { #endif } -static void p521_felem_copy(p521_limb_t out[P521_NLIMBS], - const p521_limb_t in1[P521_NLIMBS]) { - for (size_t i = 0; i < P521_NLIMBS; i++) { - out[i] = in1[i]; - } -} - // NOTE: the input and output are in little-endian representation. static void p521_from_generic(p521_felem out, const EC_FELEM *in) { #ifdef OPENSSL_BIG_ENDIAN @@ -407,28 +400,6 @@ static void ec_GFp_nistp521_dbl(const EC_GROUP *group, EC_JACOBIAN *r, // The precomputed table of base point multiples is generated by the code in // |make_tables.go| script. -// Constants for scalar encoding in the scalar multiplication functions. -#define P521_MUL_WSIZE (5) // window size w -// Assert the window size is 5 because the pre-computed table in |p521_table.h| -// is generated for window size 5. -OPENSSL_STATIC_ASSERT(P521_MUL_WSIZE == 5, - p521_scalar_mul_window_size_is_not_equal_to_five) - -#define P521_MUL_TWO_TO_WSIZE (1 << P521_MUL_WSIZE) - -// Number of |P521_MUL_WSIZE|-bit windows in a 521-bit value -#define P521_MUL_NWINDOWS ((521 + P521_MUL_WSIZE - 1)/P521_MUL_WSIZE) - -// For the public point in |ec_GFp_nistp521_point_mul_public| function -// we use window size equal to 5. -#define P521_MUL_PUB_WSIZE (5) - -// We keep only odd multiples in tables, hence the table size is (2^w)/2 -#define P521_MUL_TABLE_SIZE (P521_MUL_TWO_TO_WSIZE >> 1) -#define P521_MUL_PUB_TABLE_SIZE (1 << (P521_MUL_PUB_WSIZE - 1)) - -// p521_select_point_affine selects the |idx|-th affine point from -// the given precomputed table and copies it to |out| in constant-time. // Multiplication of an arbitrary point by a scalar, r = [scalar]P. static void ec_GFp_nistp521_point_mul(const EC_GROUP *group, EC_JACOBIAN *r, const EC_JACOBIAN *p, @@ -462,36 +433,6 @@ static void ec_GFp_nistp521_point_mul_base(const EC_GROUP *group, // Computes [g_scalar]G + [p_scalar]P, where G is the base point of the P-521 // curve, and P is the given point |p|. -// -// Both scalar products are computed by the same "textbook" wNAF method, -// with w = 5 for g_scalar and w = 5 for p_scalar. -// For the base point G product we use the first sub-table of the precomputed -// table |p521_g_pre_comp| from |p521_table.h| file, while for P we generate -// |p_pre_comp| table on-the-fly. The tables hold the first 16 odd multiples -// of G or P: -// g_pre_comp = {[1]G, [3]G, ..., [31]G}, -// p_pre_comp = {[1]P, [3]P, ..., [31]P}. -// Computing the negation of a point P = (x, y) is relatively easy: -// -P = (x, -y). -// So we may assume that we also have the negatives of the points in the tables. -// -// The 521-bit scalars are recoded by the textbook wNAF method to 522 digits, -// where a digit is either a zero or an odd integer in [-31, 31]. The method -// guarantees that each non-zero digit is followed by at least four -// zeroes. -// -// The result [g_scalar]G + [p_scalar]P is computed by the following algorithm: -// 1. Initialize the accumulator with the point-at-infinity. -// 2. For i starting from 521 down to 0: -// 3. Double the accumulator (doubling can be skipped while the -// accumulator is equal to the point-at-infinity). -// 4. Read from |p_pre_comp| the point corresponding to the i-th digit of -// p_scalar, negate it if the digit is negative, and add it to the -// accumulator. -// 5. Read from |g_pre_comp| the point corresponding to the i-th digit of -// g_scalar, negate it if the digit is negative, and add it to the -// accumulator. -// // Note: this function is NOT constant-time. static void ec_GFp_nistp521_point_mul_public(const EC_GROUP *group, EC_JACOBIAN *r, @@ -499,107 +440,13 @@ static void ec_GFp_nistp521_point_mul_public(const EC_GROUP *group, const EC_JACOBIAN *p, const EC_SCALAR *p_scalar) { - p521_felem res[3] = {{0}, {0}, {0}}, two_p[3] = {{0}, {0}, {0}}, ftmp; - - // Table of multiples of P: [2i + 1]P for i in [0, 15]. - p521_felem p_pre_comp[P521_MUL_PUB_TABLE_SIZE][3]; - - // Set the first point in the table to P. - p521_from_generic(p_pre_comp[0][0], &p->X); - p521_from_generic(p_pre_comp[0][1], &p->Y); - p521_from_generic(p_pre_comp[0][2], &p->Z); - - // Compute two_p = [2]P. - p521_point_double(two_p[0], two_p[1], two_p[2], - p_pre_comp[0][0], p_pre_comp[0][1], p_pre_comp[0][2]); - - // Generate the remaining 15 multiples of P. - for (size_t i = 1; i < P521_MUL_PUB_TABLE_SIZE; i++) { - p521_point_add(p_pre_comp[i][0], p_pre_comp[i][1], p_pre_comp[i][2], - two_p[0], two_p[1], two_p[2], 0 /* both Jacobian */, - p_pre_comp[i - 1][0], - p_pre_comp[i - 1][1], - p_pre_comp[i - 1][2]); - } - - // Recode the scalars. - int8_t p_wnaf[522] = {0}, g_wnaf[522] = {0}; - ec_compute_wNAF(group, p_wnaf, p_scalar, 521, P521_MUL_PUB_WSIZE); - ec_compute_wNAF(group, g_wnaf, g_scalar, 521, P521_MUL_WSIZE); - - // In the beginning res is set to point-at-infinity, so we set the flag. - int16_t res_is_inf = 1; - int16_t d, is_neg, idx; - - for (int i = 521; i >= 0; i--) { - - // If |res| is point-at-infinity there is no point in doubling so skip it. - if (!res_is_inf) { - p521_point_double(res[0], res[1], res[2], res[0], res[1], res[2]); - } + p521_felem res[3] = {{0}, {0}, {0}}, tmp[3] = {{0}, {0}, {0}}; - // Process the p_scalar digit. - d = p_wnaf[i]; - if (d != 0) { - is_neg = d < 0 ? 1 : 0; - idx = (is_neg) ? (-d - 1) >> 1 : (d - 1) >> 1; - - if (res_is_inf) { - // If |res| is point-at-infinity there is no need to add the new point, - // we can simply copy it. - p521_felem_copy(res[0], p_pre_comp[idx][0]); - p521_felem_copy(res[1], p_pre_comp[idx][1]); - p521_felem_copy(res[2], p_pre_comp[idx][2]); - res_is_inf = 0; - } else { - // Otherwise, add to the accumulator either the point at position idx - // in the table or its negation. - if (is_neg) { - p521_felem_opp(ftmp, p_pre_comp[idx][1]); - } else { - p521_felem_copy(ftmp, p_pre_comp[idx][1]); - } - p521_point_add(res[0], res[1], res[2], - res[0], res[1], res[2], - 0 /* both Jacobian */, - p_pre_comp[idx][0], ftmp, p_pre_comp[idx][2]); - } - } + p521_from_generic(tmp[0], &p->X); + p521_from_generic(tmp[1], &p->Y); + p521_from_generic(tmp[2], &p->Z); - // Process the g_scalar digit. - d = g_wnaf[i]; - if (d != 0) { - is_neg = d < 0 ? 1 : 0; - idx = (is_neg) ? (-d - 1) >> 1 : (d - 1) >> 1; - - if (res_is_inf) { - // If |res| is point-at-infinity there is no need to add the new point, - // we can simply copy it. - p521_felem_copy(res[0], p521_g_pre_comp[0][idx][0]); - p521_felem_copy(res[1], p521_g_pre_comp[0][idx][1]); - p521_felem_copy(res[2], p521_felem_one); - res_is_inf = 0; - } else { - // Otherwise, add to the accumulator either the point at position idx - // in the table or its negation. - if (is_neg) { - p521_felem_opp(ftmp, p521_g_pre_comp[0][idx][1]); - } else { - p521_felem_copy(ftmp, p521_g_pre_comp[0][idx][1]); - } - // Add the point to the accumulator |res|. - // Note that the points in the pre-computed table are given with affine - // coordinates. The point addition function computes a sum of two points, - // either both given in projective, or one in projective and one in - // affine coordinates. The |mixed| flag indicates the latter option, - // in which case we set the third coordinate of the second point to one. - p521_point_add(res[0], res[1], res[2], - res[0], res[1], res[2], - 1 /* mixed */, - p521_g_pre_comp[0][idx][0], ftmp, p521_felem_one); - } - } - } + ec_nistp_scalar_mul_public(p521_methods(), res[0], res[1], res[2], g_scalar, tmp[0], tmp[1], tmp[2], p_scalar); // Copy the result to the output. p521_to_generic(&r->X, res[0]); diff --git a/crypto/fipsmodule/ec/wnaf.c b/crypto/fipsmodule/ec/wnaf.c index 225cdfe1d7..da334fe657 100644 --- a/crypto/fipsmodule/ec/wnaf.c +++ b/crypto/fipsmodule/ec/wnaf.c @@ -85,8 +85,7 @@ // http://link.springer.com/chapter/10.1007%2F3-540-45537-X_13 // http://www.bmoeller.de/pdf/TI-01-08.multiexp.pdf -void ec_compute_wNAF(const EC_GROUP *group, int8_t *out, - const EC_SCALAR *scalar, size_t bits, int w) { +void ec_compute_wNAF(int8_t *out, const EC_SCALAR *scalar, size_t bits, int w) { // 'int8_t' can represent integers with absolute values less than 2^7. assert(0 < w && w <= 7); assert(bits != 0); @@ -138,8 +137,9 @@ void ec_compute_wNAF(const EC_GROUP *group, int8_t *out, // we shift and add at most one copy of |bit|, this will continue to hold // afterwards. window_val >>= 1; - window_val += bit * bn_is_bit_set_words(scalar->words, group->order.N.width, - j + w + 1); + const size_t bits_per_word = sizeof(scalar->words[0]) * 8; + const size_t num_words = (bits + bits_per_word - 1) / bits_per_word; + window_val += bit * bn_is_bit_set_words(scalar->words, num_words, j + w + 1); assert(window_val <= next_bit); } @@ -211,13 +211,13 @@ int ec_GFp_mont_mul_public_batch(const EC_GROUP *group, EC_JACOBIAN *r, assert(wNAF_len <= OPENSSL_ARRAY_SIZE(g_wNAF)); const EC_JACOBIAN *g = &group->generator.raw; if (g_scalar != NULL) { - ec_compute_wNAF(group, g_wNAF, g_scalar, bits, EC_WNAF_WINDOW_BITS); + ec_compute_wNAF(g_wNAF, g_scalar, bits, EC_WNAF_WINDOW_BITS); compute_precomp(group, g_precomp, g, EC_WNAF_TABLE_SIZE); } for (size_t i = 0; i < num; i++) { assert(wNAF_len <= OPENSSL_ARRAY_SIZE(wNAF[i])); - ec_compute_wNAF(group, wNAF[i], &scalars[i], bits, EC_WNAF_WINDOW_BITS); + ec_compute_wNAF(wNAF[i], &scalars[i], bits, EC_WNAF_WINDOW_BITS); compute_precomp(group, precomp[i], &points[i], EC_WNAF_TABLE_SIZE); } From f14cc9aafbda560db995dabb3f3b2472a095a0a1 Mon Sep 17 00:00:00 2001 From: Will Childs-Klein Date: Mon, 25 Nov 2024 15:45:09 -0500 Subject: [PATCH 4/8] Implement PKCS7_encrypt and PKC7_decrypt (#1996) This PR adds 2 new functions to encrypt/decrypt BIO contents into/out of "enveloped"-type PKCS7 objects. Like OpenSSL, this implementation of `PKCS7_decrypt` contains mitigations against the "Million Message Attack" (MMA) as prescribed in [RFC 3218](https://www.rfc-editor.org/rfc/rfc3218). A more detailed description is given in source comments. --- crypto/pkcs7/bio/bio_cipher_test.cc | 3 - crypto/pkcs7/bio/cipher.c | 5 +- crypto/pkcs7/internal.h | 6 + crypto/pkcs7/pkcs7.c | 396 +++++++++++++++++++++++++--- crypto/pkcs7/pkcs7_test.cc | 190 +++++++++++++ include/openssl/pkcs7.h | 21 ++ 6 files changed, 581 insertions(+), 40 deletions(-) diff --git a/crypto/pkcs7/bio/bio_cipher_test.cc b/crypto/pkcs7/bio/bio_cipher_test.cc index d111d27476..c05ffcf04c 100644 --- a/crypto/pkcs7/bio/bio_cipher_test.cc +++ b/crypto/pkcs7/bio/bio_cipher_test.cc @@ -13,9 +13,6 @@ // NOTE: need to keep this in sync with sizeof(ctx->buf) cipher.c #define ENC_BLOCK_SIZE 1024 * 4 -#define BIO_get_cipher_status(bio) \ - BIO_ctrl(bio, BIO_C_GET_CIPHER_STATUS, 0, NULL) - struct CipherParams { const char name[40]; const EVP_CIPHER *(*cipher)(void); diff --git a/crypto/pkcs7/bio/cipher.c b/crypto/pkcs7/bio/cipher.c index 7d822a492a..ee5c95e7eb 100644 --- a/crypto/pkcs7/bio/cipher.c +++ b/crypto/pkcs7/bio/cipher.c @@ -193,7 +193,6 @@ static int enc_write(BIO *b, const char *in, int inl) { static long enc_ctrl(BIO *b, int cmd, long num, void *ptr) { GUARD_PTR(b); long ret = 1; - BIO_ENC_CTX *ctx = BIO_get_data(b); EVP_CIPHER_CTX **cipher_ctx; BIO *next = BIO_next(b); @@ -326,3 +325,7 @@ const BIO_METHOD *BIO_f_cipher(void) { return &methods_enc; } int BIO_get_cipher_ctx(BIO *b, EVP_CIPHER_CTX **ctx) { return BIO_ctrl(b, BIO_C_GET_CIPHER_CTX, 0, ctx); } + +int BIO_get_cipher_status(BIO *b) { + return BIO_ctrl(b, BIO_C_GET_CIPHER_STATUS, 0, NULL); +} diff --git a/crypto/pkcs7/internal.h b/crypto/pkcs7/internal.h index a59f01f3c2..a3b5bc433e 100644 --- a/crypto/pkcs7/internal.h +++ b/crypto/pkcs7/internal.h @@ -223,6 +223,12 @@ OPENSSL_EXPORT int BIO_set_cipher(BIO *b, const EVP_CIPHER *cipher, const unsigned char *key, const unsigned char *iv, int enc); +// BIO_get_cipher_status returns 1 if the cipher is in a healthy state or 0 +// otherwise. Unhealthy state could indicate decryption failure or other +// abnormalities. Data read from an unhealthy cipher should not be considered +// authentic. +OPENSSL_EXPORT int BIO_get_cipher_status(BIO *b); + #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/pkcs7/pkcs7.c b/crypto/pkcs7/pkcs7.c index 984b6b56e9..4ff90e8280 100644 --- a/crypto/pkcs7/pkcs7.c +++ b/crypto/pkcs7/pkcs7.c @@ -662,8 +662,7 @@ static int pkcs7_encode_rinfo(PKCS7_RECIP_INFO *ri, unsigned char *key, int ret = 0; size_t eklen; - pkey = X509_get0_pubkey(ri->cert); - if (pkey == NULL) { + if ((pkey = X509_get0_pubkey(ri->cert)) == NULL) { goto err; } @@ -816,13 +815,9 @@ BIO *PKCS7_dataInit(PKCS7 *p7, BIO *bio) { goto err; } BIO_set_mem_eof_return(bio, /*eof_value*/ 0); - // clang-format off -OPENSSL_BEGIN_ALLOW_DEPRECATED - // clang-format on + OPENSSL_BEGIN_ALLOW_DEPRECATED if (!PKCS7_is_detached(p7) && content && content->length > 0) { - // clang-format off -OPENSSL_END_ALLOW_DEPRECATED - // clang-format on + OPENSSL_END_ALLOW_DEPRECATED // |bio |needs a copy of |os->data| instead of a pointer because the data // will be used after |os |has been freed if (BIO_write(bio, content->data, content->length) != content->length) { @@ -843,13 +838,9 @@ OPENSSL_END_ALLOW_DEPRECATED return NULL; } -// clang-format off OPENSSL_BEGIN_ALLOW_DEPRECATED -// clang-format on int PKCS7_is_detached(PKCS7 *p7) { - // clang-format off -OPENSSL_END_ALLOW_DEPRECATED - // clang-format on + OPENSSL_END_ALLOW_DEPRECATED GUARD_PTR(p7); if (PKCS7_type_is_signed(p7)) { return (p7->d.sign == NULL || p7->d.sign->contents->d.ptr == NULL); @@ -858,7 +849,7 @@ OPENSSL_END_ALLOW_DEPRECATED } -static BIO *PKCS7_find_digest(EVP_MD_CTX **pmd, BIO *bio, int nid) { +static BIO *pkcs7_find_digest(EVP_MD_CTX **pmd, BIO *bio, int nid) { GUARD_PTR(pmd); while (bio != NULL) { bio = BIO_find_type(bio, BIO_TYPE_MD); @@ -970,17 +961,9 @@ int PKCS7_dataFinal(PKCS7 *p7, BIO *bio) { si_sk = p7->d.sign->signer_info; // clang-format off OPENSSL_BEGIN_ALLOW_DEPRECATED - // clang-format on content = PKCS7_get_octet_string(p7->d.sign->contents); - // clang-format off -OPENSSL_END_ALLOW_DEPRECATED - // clang-format on // If detached data then the content is excluded - // clang-format off -OPENSSL_BEGIN_ALLOW_DEPRECATED - // clang-format on if (PKCS7_type_is_data(p7->d.sign->contents) && PKCS7_is_detached(p7)) { - // clang-format off OPENSSL_END_ALLOW_DEPRECATED // clang-format on ASN1_OCTET_STRING_free(content); @@ -992,13 +975,9 @@ OPENSSL_END_ALLOW_DEPRECATED case NID_pkcs7_digest: content = PKCS7_get_octet_string(p7->d.digest->contents); // If detached data, then the content is excluded - // clang-format off -OPENSSL_BEGIN_ALLOW_DEPRECATED - // clang-format on + OPENSSL_BEGIN_ALLOW_DEPRECATED if (PKCS7_type_is_data(p7->d.digest->contents) && PKCS7_is_detached(p7)) { - // clang-format off -OPENSSL_END_ALLOW_DEPRECATED - // clang-format on + OPENSSL_END_ALLOW_DEPRECATED ASN1_OCTET_STRING_free(content); content = NULL; p7->d.digest->contents->d.data = NULL; @@ -1017,7 +996,7 @@ OPENSSL_END_ALLOW_DEPRECATED continue; } int sign_nid = OBJ_obj2nid(si->digest_alg->algorithm); - bio_tmp = PKCS7_find_digest(&md_ctx, bio_tmp, sign_nid); + bio_tmp = pkcs7_find_digest(&md_ctx, bio_tmp, sign_nid); if (bio_tmp == NULL) { goto err; } @@ -1046,20 +1025,16 @@ OPENSSL_END_ALLOW_DEPRECATED } else if (OBJ_obj2nid(p7->type) == NID_pkcs7_digest) { unsigned char md_data[EVP_MAX_MD_SIZE]; unsigned int md_len; - if (!PKCS7_find_digest(&md_ctx, bio, EVP_MD_nid(p7->d.digest->md)) || + if (!pkcs7_find_digest(&md_ctx, bio, EVP_MD_nid(p7->d.digest->md)) || !EVP_DigestFinal_ex(md_ctx, md_data, &md_len) || !ASN1_OCTET_STRING_set(p7->d.digest->digest, md_data, md_len)) { goto err; } } - // clang-format off -OPENSSL_BEGIN_ALLOW_DEPRECATED - // clang-format on + OPENSSL_BEGIN_ALLOW_DEPRECATED if (!PKCS7_is_detached(p7)) { - // clang-format off -OPENSSL_END_ALLOW_DEPRECATED - // clang-format on + OPENSSL_END_ALLOW_DEPRECATED if (content == NULL) { goto err; } @@ -1085,3 +1060,352 @@ OPENSSL_END_ALLOW_DEPRECATED EVP_MD_CTX_free(md_ctx_tmp); return ret; } + +// pkcs7_bio_copy_content copies the contents of |src| into |dst|. Only full +// copies are considered successful. It returns 1 on success and 0 on failure. +static int pkcs7_bio_copy_content(BIO *src, BIO *dst) { + uint8_t buf[1024]; + int bytes_processed, ret = 0; + while ((bytes_processed = BIO_read(src, buf, sizeof(buf))) > 0) { + if (!BIO_write_all(dst, buf, bytes_processed)) { + goto err; + } + } + if (bytes_processed < 0) { + goto err; + } + ret = 1; +err: + OPENSSL_cleanse(buf, sizeof(buf)); + return ret; +} + +// PKCS7_final copies the contents of |data| into |p7| before finalizing |p7|. +static int pkcs7_final(PKCS7 *p7, BIO *data) { + BIO *p7bio; + int ret = 0; + + OPENSSL_BEGIN_ALLOW_DEPRECATED + if ((p7bio = PKCS7_dataInit(p7, NULL)) == NULL) { + OPENSSL_END_ALLOW_DEPRECATED + OPENSSL_PUT_ERROR(PKCS7, ERR_R_PKCS7_LIB); + return 0; + } + + if (!pkcs7_bio_copy_content(data, p7bio)) { + goto err; + } + + BIO_flush(p7bio); + OPENSSL_BEGIN_ALLOW_DEPRECATED + if (!PKCS7_dataFinal(p7, p7bio)) { + OPENSSL_END_ALLOW_DEPRECATED + OPENSSL_PUT_ERROR(PKCS7, ERR_R_PKCS7_LIB); + goto err; + } + ret = 1; +err: + BIO_free_all(p7bio); + + return ret; +} + +PKCS7 *PKCS7_encrypt(STACK_OF(X509) *certs, BIO *in, const EVP_CIPHER *cipher, + int flags) { + GUARD_PTR(certs); + GUARD_PTR(in); + GUARD_PTR(cipher); + PKCS7 *p7; + X509 *x509; + + if ((p7 = PKCS7_new()) == NULL) { + OPENSSL_PUT_ERROR(PKCS7, ERR_R_PKCS7_LIB); + return NULL; + } + if (!PKCS7_set_type(p7, NID_pkcs7_enveloped)) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_WRONG_CONTENT_TYPE); + goto err; + } + if (!PKCS7_set_cipher(p7, cipher)) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_ERROR_SETTING_CIPHER); + goto err; + } + + for (size_t i = 0; i < sk_X509_num(certs); i++) { + x509 = sk_X509_value(certs, i); + OPENSSL_BEGIN_ALLOW_DEPRECATED + if (!PKCS7_add_recipient(p7, x509)) { + OPENSSL_END_ALLOW_DEPRECATED + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_ERROR_ADDING_RECIPIENT); + goto err; + } + } + + if (pkcs7_final(p7, in)) { + return p7; + } + +err: + PKCS7_free(p7); + return NULL; +} + +static int pkcs7_decrypt_rinfo(unsigned char **ek_out, PKCS7_RECIP_INFO *ri, + EVP_PKEY *pkey) { + GUARD_PTR(ri); + GUARD_PTR(ek_out); + unsigned char *ek = NULL; + int ret = 0; + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, /*engine*/ NULL); + if (ctx == NULL || !EVP_PKEY_decrypt_init(ctx)) { + goto err; + } + size_t len; + if (!EVP_PKEY_decrypt(ctx, NULL, &len, ri->enc_key->data, + ri->enc_key->length) || + (ek = OPENSSL_malloc(len)) == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_EVP_LIB); + goto err; + } + + int ok = + EVP_PKEY_decrypt(ctx, ek, &len, ri->enc_key->data, ri->enc_key->length); + // We return 0 any failure except for decryption failure. On decrypt failure, + // we still need to set |ek| to NULL to signal decryption failure to callers + // so they can use random bytes as content encryption key for MMA defense. + if (!ok) { + OPENSSL_free(ek); + ek = NULL; + } + + ret = 1; + *ek_out = ek; + +err: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +// pkcs7_cmp_ri is a comparison function, so it returns 0 if |ri| and |pcert| +// match and 1 if they do not. +static int pkcs7_cmp_ri(PKCS7_RECIP_INFO *ri, X509 *pcert) { + if (ri == NULL || ri->issuer_and_serial == NULL || pcert == NULL) { + return 1; + } + int ret = + X509_NAME_cmp(ri->issuer_and_serial->issuer, X509_get_issuer_name(pcert)); + if (ret) { + return ret; + } + return ASN1_INTEGER_cmp(X509_get0_serialNumber(pcert), + ri->issuer_and_serial->serial); +} + +static BIO *pkcs7_data_decode(PKCS7 *p7, EVP_PKEY *pkey, X509 *pcert) { + GUARD_PTR(p7); + GUARD_PTR(pkey); + BIO *out = NULL, *cipher_bio = NULL, *data_bio = NULL; + ASN1_OCTET_STRING *data_body = NULL; + const EVP_CIPHER *cipher = NULL; + X509_ALGOR *enc_alg = NULL; + STACK_OF(PKCS7_RECIP_INFO) *rsk = NULL; + PKCS7_RECIP_INFO *ri = NULL; + uint8_t *cek = NULL, *dummy_key = NULL; // cek means "content encryption key" + + if (p7->d.ptr == NULL) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_NO_CONTENT); + return NULL; + } + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_enveloped: + rsk = p7->d.enveloped->recipientinfo; + enc_alg = p7->d.enveloped->enc_data->algorithm; + // |data_body| is NULL if the optional EncryptedContent is missing. + data_body = p7->d.enveloped->enc_data->enc_data; + cipher = EVP_get_cipherbynid(OBJ_obj2nid(enc_alg->algorithm)); + if (cipher == NULL) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_UNSUPPORTED_CIPHER_TYPE); + goto err; + } + break; + default: + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_UNSUPPORTED_CONTENT_TYPE); + goto err; + } + + if ((cipher_bio = BIO_new(BIO_f_cipher())) == NULL) { + OPENSSL_PUT_ERROR(PKCS7, ERR_R_BIO_LIB); + goto err; + } + + // RFC 3218 provides an overview of, and mitigations for, the "Million Message + // Attack" (MMA) on RSA encryption with PKCS-1 padding. Section 2.3 describes + // implementor countermeasures. We implement the following countermeasures, as + // does OpenSSL. + // + // 1. Do not branch on |cek| decryption failure when checking recip infos + // 2. Clear error state after |cek| decrypt is attempted + // 3. If no cek was decrypted, use same-size random bytes + // to output gibberish "plaintext" + // 4. Always pay same allocation costs, regardless of |cek| decrypt result + + // If |pcert| was specified, find the matching recipient info + if (pcert) { + for (size_t ii = 0; ii < sk_PKCS7_RECIP_INFO_num(rsk); ii++) { + ri = sk_PKCS7_RECIP_INFO_value(rsk, ii); + // No decryption operation here, so we can return early without divulging + // information that could be used in MMA. + if (!pkcs7_cmp_ri(ri, pcert)) { + break; + } + ri = NULL; + } + if (ri == NULL) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_NO_RECIPIENT_MATCHES_CERTIFICATE); + goto err; + } + // |pkcs7_decrypt_rinfo| will only return false on critical failure, not + // on decryption failure. Decryption check happens below, after we populate + // |dummy_key| with random bytes. + if (!pkcs7_decrypt_rinfo(&cek, ri, pkey)) { + goto err; + } + } else { + // Attempt to decrypt every recipient info. Don't exit early as + // countermeasure for MMA. + for (size_t ii = 0; ii < sk_PKCS7_RECIP_INFO_num(rsk); ii++) { + ri = sk_PKCS7_RECIP_INFO_value(rsk, ii); + uint8_t *tmp_cek; + // |pkcs7_decrypt_rinfo| will only return false on critical failure, not + // on decryption failure. Check whether |tmp_cek| is present after the + // call to determine if decryption succeeded. + if (!pkcs7_decrypt_rinfo(&tmp_cek, ri, pkey)) { + goto err; + } + // OpenSSL sets encryption key to last successfully decrypted key. Copy + // that behavior, but free previously allocated key memory. + if (tmp_cek) { + OPENSSL_free(cek); + cek = tmp_cek; + } + } + } + // Clear any decryption errors to minimize behavioral difference under MMA + ERR_clear_error(); + + EVP_CIPHER_CTX *evp_ctx = NULL; + if (!BIO_get_cipher_ctx(cipher_bio, &evp_ctx) || + !EVP_CipherInit_ex(evp_ctx, cipher, NULL, NULL, NULL, 0)) { + goto err; + } + uint8_t iv[EVP_MAX_IV_LENGTH]; + OPENSSL_memcpy(iv, enc_alg->parameter->value.octet_string->data, + enc_alg->parameter->value.octet_string->length); + const int expected_iv_len = EVP_CIPHER_CTX_iv_length(evp_ctx); + if (enc_alg->parameter->value.octet_string->length != expected_iv_len) { + OPENSSL_PUT_ERROR(PKCS7, ERR_R_PKCS7_LIB); + goto err; + } + if (!EVP_CipherInit_ex(evp_ctx, NULL, NULL, NULL, iv, 0)) { + goto err; + } + // Get the key length from cipher context so we don't condition on |cek_len| + int len = EVP_CIPHER_CTX_key_length(evp_ctx); + if (!len) { + goto err; + } + // Always generate random bytes for the dummy key, regardless of |cek| decrypt + dummy_key = OPENSSL_malloc(len); + RAND_bytes(dummy_key, len); + // At this point, null |cek| indicates that no content encryption key was + // successfully decrypted. We don't want to return early due to MMA. So, swap + // in the dummy key and proceed. Content decryption result will be gibberish. + if (cek == NULL) { + cek = dummy_key; + dummy_key = NULL; + } + + if (!EVP_CipherInit_ex(evp_ctx, NULL, NULL, cek, NULL, 0)) { + goto err; + } + + OPENSSL_free(cek); + OPENSSL_free(dummy_key); + out = cipher_bio; + + if (data_body && data_body->length > 0) { + data_bio = BIO_new_mem_buf(data_body->data, data_body->length); + } else { + data_bio = BIO_new(BIO_s_mem()); + if (data_bio == NULL || !BIO_set_mem_eof_return(data_bio, 0)) { + goto err; + } + } + if (data_bio == NULL) { + goto err; + } + BIO_push(out, data_bio); + return out; + +err: + OPENSSL_free(cek); + OPENSSL_free(dummy_key); + BIO_free_all(out); + BIO_free_all(cipher_bio); + BIO_free_all(data_bio); + return NULL; +} + +PKCS7_RECIP_INFO *PKCS7_add_recipient(PKCS7 *p7, X509 *x509) { + GUARD_PTR(p7); + GUARD_PTR(x509); + PKCS7_RECIP_INFO *ri; + if ((ri = PKCS7_RECIP_INFO_new()) == NULL || + !PKCS7_RECIP_INFO_set(ri, x509) || !PKCS7_add_recipient_info(p7, ri)) { + PKCS7_RECIP_INFO_free(ri); + return NULL; + } + return ri; +} + +int PKCS7_decrypt(PKCS7 *p7, EVP_PKEY *pkey, X509 *cert, BIO *data, int flags) { + GUARD_PTR(p7); + GUARD_PTR(pkey); + GUARD_PTR(data); + BIO *bio = NULL; + int ret = 0; + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_enveloped: + case NID_pkcs7_signedAndEnveloped: + break; + default: + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_WRONG_CONTENT_TYPE); + goto err; + } + + if (cert && !X509_check_private_key(cert, pkey)) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE); + goto err; + } + + if ((bio = pkcs7_data_decode(p7, pkey, cert)) == NULL || + !pkcs7_bio_copy_content(bio, data)) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_DECRYPT_ERROR); + goto err; + } + + // Check whether content decryption was successful + if (1 != BIO_get_cipher_status(bio)) { + OPENSSL_PUT_ERROR(PKCS7, PKCS7_R_DECRYPT_ERROR); + goto err; + } + + ret = 1; + +err: + BIO_free_all(bio); + return ret; +} diff --git a/crypto/pkcs7/pkcs7_test.cc b/crypto/pkcs7/pkcs7_test.cc index a8199edc1b..b75408a1a3 100644 --- a/crypto/pkcs7/pkcs7_test.cc +++ b/crypto/pkcs7/pkcs7_test.cc @@ -1689,3 +1689,193 @@ TEST(PKCS7Test, DataInitFinal) { EXPECT_FALSE(bio); EXPECT_FALSE(PKCS7_dataFinal(p7.get(), bio.get())); } + +TEST(PKCS7Test, TestEnveloped) { + bssl::UniquePtr p7; + bssl::UniquePtr bio; + bssl::UniquePtr certs; + bssl::UniquePtr rsa_x509; + const size_t pt_len = 64; + // NOTE: we make |buf| larger than |pt_len| in case padding gets added. + // without the extra room, we sometimes overflow into the next variable on the + // stack. + uint8_t buf[pt_len + EVP_MAX_BLOCK_LENGTH], decrypted[pt_len]; + + OPENSSL_cleanse(buf, sizeof(buf)); + OPENSSL_memset(buf, 'A', pt_len); + + // parse a cert for use with recipient infos + bssl::UniquePtr rsa(RSA_new()); + ASSERT_TRUE(rsa); + ASSERT_TRUE(RSA_generate_key_fips(rsa.get(), 2048, nullptr)); + bssl::UniquePtr rsa_pkey(EVP_PKEY_new()); + ASSERT_TRUE(rsa_pkey); + ASSERT_TRUE(EVP_PKEY_set1_RSA(rsa_pkey.get(), rsa.get())); + certs.reset(sk_X509_new_null()); + bio.reset(BIO_new_mem_buf(kPEMCert, strlen(kPEMCert))); + ASSERT_TRUE(bio); + certs.reset(sk_X509_new_null()); + ASSERT_TRUE(certs); + ASSERT_TRUE(PKCS7_get_PEM_certificates(certs.get(), bio.get())); + ASSERT_EQ(1U, sk_X509_num(certs.get())); + rsa_x509.reset(sk_X509_value(certs.get(), 0)); + ASSERT_TRUE(X509_set_pubkey(rsa_x509.get(), rsa_pkey.get())); + X509_up_ref(rsa_x509.get()); + + // standard success case + bio.reset(BIO_new_mem_buf(buf, pt_len)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_cbc(), /*flags*/ 0)); + EXPECT_TRUE(p7); + EXPECT_TRUE(PKCS7_type_is_enveloped(p7.get())); + bio.reset(BIO_new(BIO_s_mem())); + EXPECT_TRUE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), rsa_x509.get(), bio.get(), + /*flags*/ 0)); + EXPECT_EQ(sizeof(decrypted), BIO_pending(bio.get())); + OPENSSL_cleanse(decrypted, sizeof(decrypted)); + ASSERT_EQ((int)sizeof(decrypted), + BIO_read(bio.get(), decrypted, sizeof(decrypted))); + EXPECT_EQ(Bytes(buf, pt_len), Bytes(decrypted, sizeof(decrypted))); + + // no certs provided for decryption + bio.reset(BIO_new_mem_buf(buf, pt_len)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_cbc(), /*flags*/ 0)); + EXPECT_TRUE(p7); + EXPECT_TRUE(PKCS7_type_is_enveloped(p7.get())); + bio.reset(BIO_new(BIO_s_mem())); + EXPECT_TRUE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), /*certs*/ nullptr, + bio.get(), + /*flags*/ 0)); + EXPECT_EQ(sizeof(decrypted), BIO_pending(bio.get())); + OPENSSL_cleanse(decrypted, sizeof(decrypted)); + ASSERT_EQ((int)sizeof(decrypted), + BIO_read(bio.get(), decrypted, sizeof(decrypted))); + EXPECT_EQ(Bytes(buf, pt_len), Bytes(decrypted, sizeof(decrypted))); + + // empty plaintext + bio.reset(BIO_new(BIO_s_mem())); + ASSERT_TRUE(BIO_set_mem_eof_return(bio.get(), 0)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_cbc(), /*flags*/ 0)); + EXPECT_TRUE(p7); + EXPECT_TRUE(PKCS7_type_is_enveloped(p7.get())); + bio.reset(BIO_new(BIO_s_mem())); + ASSERT_TRUE(BIO_set_mem_eof_return(bio.get(), 0)); + EXPECT_TRUE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), rsa_x509.get(), bio.get(), + /*flags*/ 0)); + EXPECT_EQ(0UL, BIO_pending(bio.get())); + EXPECT_EQ(0, BIO_read(bio.get(), decrypted, sizeof(decrypted))); + EXPECT_FALSE(BIO_should_retry(bio.get())); + + // unsupported content type, with and without content + p7.reset(PKCS7_new()); + ASSERT_TRUE(p7); + ASSERT_TRUE(PKCS7_set_type(p7.get(), NID_pkcs7_signed)); + EXPECT_FALSE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), nullptr, bio.get(), 0)); + ASSERT_TRUE(PKCS7_content_new(p7.get(), NID_pkcs7_data)); + EXPECT_FALSE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), nullptr, bio.get(), 0)); + + // test multiple recipients using the same recipient twice. elide |cert| to + // exercise iterative decryption attempt behavior with multiple (2) successful + // decryptions. + sk_X509_push(certs.get(), rsa_x509.get()); + bio.reset(BIO_new_mem_buf(buf, pt_len)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_cbc(), /*flags*/ 0)); + ASSERT_TRUE(p7); + bio.reset(BIO_new(BIO_s_mem())); + // set |rsa_pkey| back to original RSA key + ASSERT_TRUE(EVP_PKEY_set1_RSA(rsa_pkey.get(), rsa.get())); + EXPECT_TRUE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), /*cert*/ nullptr, + bio.get(), + /*flags*/ 0)); + ASSERT_TRUE(sk_X509_pop(certs.get())); + ASSERT_EQ(1LU, sk_X509_num(certs.get())); + + // test "MMA" decrypt with mismatched cert pub key/pkey private key and block + // cipher used for content encryption + bio.reset(BIO_new_mem_buf(buf, pt_len)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_cbc(), /*flags*/ 0)); + EXPECT_TRUE(p7); + EXPECT_TRUE(PKCS7_type_is_enveloped(p7.get())); + bio.reset(BIO_new(BIO_s_mem())); + // set new RSA key, cert pub key and PKEY private key now mismatch + rsa.reset(RSA_new()); + ASSERT_TRUE(RSA_generate_key_fips(rsa.get(), 2048, nullptr)); + ASSERT_TRUE(EVP_PKEY_set1_RSA(rsa_pkey.get(), rsa.get())); + // attempt decryption with the new, mismatched keypair. content key decryption + // should "succeed" and produce random, useless content decryption key. + // The content is "decrypted" with the useless key, so nonsense gets written + // to the output |bio|. The cipher ends up in an unhealthy state due to bad + // padding (what should be the final pad block is now just random bytes), so + // the overall |PKCS7_decrypt| operation fails. + int decrypt_ok = + PKCS7_decrypt(p7.get(), rsa_pkey.get(), /*certs*/ nullptr, bio.get(), + /*flags*/ 0); + EXPECT_LE(sizeof(decrypted), BIO_pending(bio.get())); + OPENSSL_cleanse(decrypted, sizeof(decrypted)); + // There's a fun edge case here for block ciphers using conventional PKCS#7 + // padding. In this padding scheme, the last byte of the padded plaintext + // determines how many bytes of padding have been appended and must be + // stripped, A random MMA-defense-garbled padded plaintext with last byte of + // 0x01 will trick the EVP API into thinking that byte is a valid padding + // byte, so it (and only it) will be stripped. This leaves the other + // block_size-1 bytes of the padding block in place, resulting in a larger + // "decrypted plaintext" than anticipated. However, this doesn't only apply to + // one byte of padding. With probability 16^-2, it applies to pad 0x02 0x02 + // and so on with increasingly small probabilities. So, we give slack up to + // 16^-4 which means this test will erroneously fail 0.001526% of the time in + // expectation. Ideally we'd find a way to access the padded plaintext and + // account for this deterministically by checking the random "padding" and + // adusting accordingly. + int max_decrypt = + sizeof(decrypted) + EVP_CIPHER_block_size(EVP_aes_128_cbc()); + int decrypted_len = BIO_read(bio.get(), decrypted, max_decrypt); + if (decrypted_len > (int)pt_len) { + EXPECT_LT(max_decrypt - 4, decrypted_len); + EXPECT_TRUE(decrypt_ok); + EXPECT_FALSE(ERR_GET_REASON(ERR_peek_error())); + } else { + EXPECT_EQ((int)sizeof(decrypted), decrypted_len); + EXPECT_FALSE(decrypt_ok); + EXPECT_EQ(CIPHER_R_BAD_DECRYPT, ERR_GET_REASON(ERR_peek_error())); + } + // Of course, plaintext shouldn't equal decrypted in any case here + EXPECT_NE(Bytes(buf, pt_len), Bytes(decrypted, sizeof(decrypted))); + + // test "MMA" decrypt as above, but with stream cipher. stream cipher has no + // padding, so content encryption should "succeed" but return nonsense because + // the content decryption key is just randomly generated bytes. + bio.reset(BIO_new_mem_buf(buf, pt_len)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_ctr(), /*flags*/ 0)); + EXPECT_TRUE(p7); + EXPECT_TRUE(PKCS7_type_is_enveloped(p7.get())); + bio.reset(BIO_new(BIO_s_mem())); + // content decryption "succeeds"... + EXPECT_TRUE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), /*certs*/ nullptr, + bio.get(), + /*flags*/ 0)); + EXPECT_EQ(sizeof(decrypted), BIO_pending(bio.get())); + OPENSSL_cleanse(decrypted, sizeof(decrypted)); + ASSERT_EQ((int)sizeof(decrypted), + BIO_read(bio.get(), decrypted, sizeof(decrypted))); + // ...but it produces pseudo-random nonsense + EXPECT_NE(Bytes(buf, pt_len), Bytes(decrypted, sizeof(decrypted))); + EXPECT_FALSE(ERR_GET_REASON(ERR_peek_error())); + + // mismatched cert + pkey on decrypt + bio.reset(BIO_new_mem_buf(buf, pt_len)); + p7.reset( + PKCS7_encrypt(certs.get(), bio.get(), EVP_aes_128_cbc(), /*flags*/ 0)); + bio.reset(BIO_new(BIO_s_mem())); + bssl::UniquePtr rsa2(RSA_new()); + ASSERT_TRUE(RSA_generate_key_fips(rsa2.get(), 2048, nullptr)); + ASSERT_TRUE(EVP_PKEY_set1_RSA(rsa_pkey.get(), rsa2.get())); + EXPECT_FALSE(PKCS7_decrypt(p7.get(), rsa_pkey.get(), rsa_x509.get(), + bio.get(), + /*flags*/ 0)); + EXPECT_EQ(X509_R_KEY_VALUES_MISMATCH, ERR_GET_REASON(ERR_peek_error())); +} diff --git a/include/openssl/pkcs7.h b/include/openssl/pkcs7.h index 4c0948a7cd..3562c22820 100644 --- a/include/openssl/pkcs7.h +++ b/include/openssl/pkcs7.h @@ -359,6 +359,26 @@ OPENSSL_EXPORT OPENSSL_DEPRECATED int PKCS7_set_digest(PKCS7 *p7, const EVP_MD * // |PKCS7_RECIP_INFO| or NULL if none are present. OPENSSL_EXPORT OPENSSL_DEPRECATED STACK_OF(PKCS7_RECIP_INFO) *PKCS7_get_recipient_info(PKCS7 *p7); +// PKCS7_add_recipient allocates a new |PCKS7_RECEPIENT_INFO|, adds |x509| to it +// and returns that |PCKS7_RECEPIENT_INFO|. +OPENSSL_EXPORT OPENSSL_DEPRECATED PKCS7_RECIP_INFO *PKCS7_add_recipient(PKCS7 *p7, X509 *x509); + +// PKCS7_encrypt encrypts the contents of |in| with |cipher| and adds |certs| as +// recipient infos and returns an encrypted |PKCS7| or NULL on failed +// encryption. |flags| is ignored. We only perform key encryption using RSA, so +// |certs| must use RSA public keys. +OPENSSL_EXPORT OPENSSL_DEPRECATED PKCS7 *PKCS7_encrypt(STACK_OF(X509) *certs, BIO *in, const EVP_CIPHER *cipher, int flags); + +// PKCS7_decrypt decrypts |p7| with |pkey| and writes the plaintext to |data|. +// If |cert| is present, it's public key is checked against |pkey| and |p7|'s +// recipient infos. 1 is returned on success and 0 on failure. |flags| is +// ignored. |pkey| must be an |EVP_PKEY_RSA|. +// +// NOTE: If |p7| was encrypted with a stream cipher, this operation may return 1 +// even on decryption failure. The reason for this is detailed in RFC 3218 and +// comments in the |PKCS7_decrypt| source. +OPENSSL_EXPORT OPENSSL_DEPRECATED int PKCS7_decrypt(PKCS7 *p7, EVP_PKEY *pkey, X509 *cert, BIO *data, int flags); + #if defined(__cplusplus) } // extern C @@ -405,5 +425,6 @@ BSSL_NAMESPACE_END #define PKCS7_R_PKCS7_ADD_SIGNER_ERROR 131 #define PKCS7_R_PKCS7_ADD_SIGNATURE_ERROR 132 #define PKCS7_R_NO_DEFAULT_DIGEST 133 +#define PKCS7_R_CERT_MUST_BE_RSA 134 #endif // OPENSSL_HEADER_PKCS7_H From 974ae0dff69dd76d33ab14735d46b38ca13fa6ab Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 21 Aug 2023 09:45:18 -0700 Subject: [PATCH 5/8] Generate certs on the fly in runner, pass trusted cert to shim Rather than using the pre-generated certificates, generate them on the fly. This allows TLS stacks for which certificate validation and verification are coupled to work as expected. Certificates and keys are written to temporary files which are then passed to the shim, and cleaned up on exit. This requires reworking how testCase passes certs/keys by adding a new field, sendCertificate, rather than manually setting the -cert-file and -key-file flags. Incidentally the rsaChainCertificate is removed, since it was essentially unused, and all tests that used it also work with rsaCertificate. Finally, include a single SAN ("test") in all certificates, which fixes some TLS stacks which require this to operate (such as rustls, which currently regenerates all the certificates currently in the tree to add a SAN). Additionally, add a new flag, -trust-cert, which tells the the shim which certificates it should trust. Shims for TLS stacks which can completely decouple validation and verification of X509 certificates (like BoringSSL) can ignore this flag, but for stacks where this functionality is somewhat more intertwined (like Go), this allows the shim to properly process the sent certificates. Change-Id: Ic5c63e18fb2b852cc693aacb3b06cfe7993bc90c Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/62565 Commit-Queue: David Benjamin Reviewed-by: David Benjamin (cherry picked from commit df3b58ea74c50ff785ab902be3b007ff008d3e3c) --- ssl/test/runner/cert.pem | 22 - ssl/test/runner/channel_id_key.pem | 10 +- ssl/test/runner/cipher_suites.go | 4 +- ssl/test/runner/common.go | 143 ++- ssl/test/runner/ecdsa_p224_cert.pem | 12 - ssl/test/runner/ecdsa_p256_cert.pem | 12 - ssl/test/runner/ecdsa_p384_cert.pem | 15 - ssl/test/runner/ecdsa_p521_cert.pem | 17 - ssl/test/runner/ed25519_cert.pem | 11 - ssl/test/runner/handshake_client.go | 14 +- ssl/test/runner/handshake_server.go | 6 +- ssl/test/runner/key_agreement.go | 22 +- ssl/test/runner/rsa_1024_cert.pem | 15 - ssl/test/runner/{key.pem => rsa_2048_key.pem} | 0 ssl/test/runner/rsa_chain_cert.pem | 35 - ssl/test/runner/rsa_chain_key.pem | 28 - ssl/test/runner/runner.go | 1119 +++++++---------- ssl/test/runner/tls.go | 6 +- ssl/test/test_config.cc | 1 + ssl/test/test_config.h | 1 + util/run_android_tests.go | 11 +- 21 files changed, 620 insertions(+), 884 deletions(-) delete mode 100644 ssl/test/runner/cert.pem delete mode 100644 ssl/test/runner/ecdsa_p224_cert.pem delete mode 100644 ssl/test/runner/ecdsa_p256_cert.pem delete mode 100644 ssl/test/runner/ecdsa_p384_cert.pem delete mode 100644 ssl/test/runner/ecdsa_p521_cert.pem delete mode 100644 ssl/test/runner/ed25519_cert.pem delete mode 100644 ssl/test/runner/rsa_1024_cert.pem rename ssl/test/runner/{key.pem => rsa_2048_key.pem} (100%) delete mode 100644 ssl/test/runner/rsa_chain_cert.pem delete mode 100644 ssl/test/runner/rsa_chain_key.pem diff --git a/ssl/test/runner/cert.pem b/ssl/test/runner/cert.pem deleted file mode 100644 index c360dc7317..0000000000 --- a/ssl/test/runner/cert.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDtTCCAp2gAwIBAgIJALW2IrlaBKUhMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTYwNzA5MDQzODA5WhcNMTYwODA4MDQzODA5WjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWssZBc -HprZ5BkWf6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXCzepB -rhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Aky+aN -lcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs2R65 -LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmET19W -JH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABo4GnMIGkMB0GA1UdDgQWBBT5m6Vv -zYjVYHG30iBE+j2XDhUE8jB1BgNVHSMEbjBsgBT5m6VvzYjVYHG30iBE+j2XDhUE -8qFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV -BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJALW2IrlaBKUhMAwGA1UdEwQF -MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD7Jg68SArYWlcoHfZAB90Pmyrt5H6D8 -LRi+W2Ri1fBNxREELnezWJ2scjl4UMcsKYp4Pi950gVN+62IgrImcCNvtb5I1Cfy -/MNNur9ffas6X334D0hYVIQTePyFk3umI+2mJQrtZZyMPIKSY/sYGQHhGGX6wGK+ -GO/og0PQk/Vu6D+GU2XRnDV0YZg1lsAsHd21XryK6fDmNkEMwbIWrts4xc7scRrG -HWy+iMf6/7p/Ak/SIicM4XSwmlQ8pPxAZPr+E2LoVd9pMpWUwpW2UbtO5wsGTrY5 -sO45tFNN/y+jtUheB1C2ijObG/tXELaiyCdM+S/waeuv0MXtI4xnn1A= ------END CERTIFICATE----- diff --git a/ssl/test/runner/channel_id_key.pem b/ssl/test/runner/channel_id_key.pem index 604752bcda..83eb53a459 100644 --- a/ssl/test/runner/channel_id_key.pem +++ b/ssl/test/runner/channel_id_key.pem @@ -1,5 +1,5 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIPwxu50c7LEhVNRYJFRWBUnoaz7JSos96T5hBp4rjyptoAoGCCqGSM49 -AwEHoUQDQgAEzFSVTE5guxJRQ0VbZ8dicPs5e/DT7xpW7Yc9hq0VOchv7cbXuI/T -CwadDjGWX/oaz0ftFqrVmfkwZu+C58ioWg== ------END EC PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg/DG7nRzssSFU1Fgk +VFYFSehrPslKiz3pPmEGniuPKm2hRANCAATMVJVMTmC7ElFDRVtnx2Jw+zl78NPv +Glbthz2GrRU5yG/txte4j9MLBp0OMZZf+hrPR+0WqtWZ+TBm74LnyKha +-----END PRIVATE KEY----- diff --git a/ssl/test/runner/cipher_suites.go b/ssl/test/runner/cipher_suites.go index 5db57498be..8bbde8e6e4 100644 --- a/ssl/test/runner/cipher_suites.go +++ b/ssl/test/runner/cipher_suites.go @@ -28,8 +28,8 @@ type keyAgreement interface { // In the case that the key agreement protocol doesn't use a // ServerKeyExchange message, generateServerKeyExchange can return nil, // nil. - generateServerKeyExchange(*Config, *Certificate, *clientHelloMsg, *serverHelloMsg, uint16) (*serverKeyExchangeMsg, error) - processClientKeyExchange(*Config, *Certificate, *clientKeyExchangeMsg, uint16) ([]byte, error) + generateServerKeyExchange(*Config, *CertificateChain, *clientHelloMsg, *serverHelloMsg, uint16) (*serverKeyExchangeMsg, error) + processClientKeyExchange(*Config, *CertificateChain, *clientKeyExchangeMsg, uint16) ([]byte, error) // On the client side, the next two methods are called in order. diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index 024c921095..cd10e14641 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -10,9 +10,12 @@ import ( "crypto/ecdsa" "crypto/rand" "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" "io" "math/big" + "os" "strings" "sync" "time" @@ -434,18 +437,18 @@ type Config struct { // If Time is nil, TLS uses time.Now. Time func() time.Time - // Certificates contains one or more certificate chains + // Chains contains one or more certificate chains // to present to the other side of the connection. // Server configurations must include at least one certificate. - Certificates []Certificate + Chains []CertificateChain - // NameToCertificate maps from a certificate name to an element of - // Certificates. Note that a certificate name can be of the form + // NameToChain maps from a certificate name to an element of + // Chains. Note that a certificate name can be of the form // '*.example.com' and so doesn't have to be a domain name as such. // See Config.BuildNameToCertificate - // The nil value causes the first element of Certificates to be used + // The nil value causes the first element of Chains to be used // for all connections. - NameToCertificate map[string]*Certificate + NameToChain map[string]*CertificateChain // RootCAs defines the set of root certificate authorities // that clients use when verifying server certificates. @@ -1842,7 +1845,7 @@ type ProtocolBugs struct { // RenegotiationCertificate, if not nil, is the certificate to use on // renegotiation handshakes. - RenegotiationCertificate *Certificate + RenegotiationCertificate *CertificateChain // ExpectNoCertificateAuthoritiesExtension, if true, causes the client to // reject CertificateRequest with the CertificateAuthorities extension. @@ -2126,12 +2129,12 @@ func (c *Config) supportedVersions(isDTLS, requireTLS13 bool) []uint16 { } // getCertificateForName returns the best certificate for the given name, -// defaulting to the first element of c.Certificates if there are no good +// defaulting to the first element of c.Chains if there are no good // options. -func (c *Config) getCertificateForName(name string) *Certificate { - if len(c.Certificates) == 1 || c.NameToCertificate == nil { +func (c *Config) getCertificateForName(name string) *CertificateChain { + if len(c.Chains) == 1 || c.NameToChain == nil { // There's only one choice, so no point doing any work. - return &c.Certificates[0] + return &c.Chains[0] } name = strings.ToLower(name) @@ -2139,7 +2142,7 @@ func (c *Config) getCertificateForName(name string) *Certificate { name = name[:len(name)-1] } - if cert, ok := c.NameToCertificate[name]; ok { + if cert, ok := c.NameToChain[name]; ok { return cert } @@ -2149,13 +2152,13 @@ func (c *Config) getCertificateForName(name string) *Certificate { for i := range labels { labels[i] = "*" candidate := strings.Join(labels, ".") - if cert, ok := c.NameToCertificate[candidate]; ok { + if cert, ok := c.NameToChain[candidate]; ok { return cert } } // If nothing matches, return the first certificate. - return &c.Certificates[0] + return &c.Chains[0] } func (c *Config) signSignatureAlgorithms() []signatureAlgorithm { @@ -2172,28 +2175,28 @@ func (c *Config) verifySignatureAlgorithms() []signatureAlgorithm { return supportedSignatureAlgorithms } -// BuildNameToCertificate parses c.Certificates and builds c.NameToCertificate +// BuildNameToCertificate parses c.Chains and builds c.NameToCertificate // from the CommonName and SubjectAlternateName fields of each of the leaf // certificates. func (c *Config) BuildNameToCertificate() { - c.NameToCertificate = make(map[string]*Certificate) - for i := range c.Certificates { - cert := &c.Certificates[i] + c.NameToChain = make(map[string]*CertificateChain) + for i := range c.Chains { + cert := &c.Chains[i] x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { continue } if len(x509Cert.Subject.CommonName) > 0 { - c.NameToCertificate[x509Cert.Subject.CommonName] = cert + c.NameToChain[x509Cert.Subject.CommonName] = cert } for _, san := range x509Cert.DNSNames { - c.NameToCertificate[san] = cert + c.NameToChain[san] = cert } } } -// A Certificate is a chain of one or more certificates, leaf first. -type Certificate struct { +// A CertificateChain is a chain of one or more certificates, leaf first. +type CertificateChain struct { Certificate [][]byte PrivateKey crypto.PrivateKey // supported types: *rsa.PrivateKey, *ecdsa.PrivateKey // OCSPStaple contains an optional OCSP response which will be served @@ -2208,6 +2211,15 @@ type Certificate struct { // processing for TLS clients doing client authentication. If nil, the // leaf certificate will be parsed as needed. Leaf *x509.Certificate + // ChainPath is the path to the temporary on disk copy of the certificate + // chain. + ChainPath string + // KeyPath is the path to the temporary on disk copy of the key. + KeyPath string + // RootPath is the path to the temporary on disk copy of the root of the + // certificate chain. If the chain only contains one certificate ChainPath + // and RootPath will be the same. + RootPath string } // A TLS record. @@ -2419,3 +2431,90 @@ func isAllZero(v []byte) bool { } return true } + +var baseCertTemplate = &x509.Certificate{ + SerialNumber: big.NewInt(57005), + Subject: pkix.Name{ + CommonName: "test cert", + Country: []string{"US"}, + Province: []string{"Some-State"}, + Organization: []string{"Internet Widgits Pty Ltd"}, + }, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + DNSNames: []string{"test"}, + IsCA: true, + BasicConstraintsValid: true, +} + +var tmpDir string + +func generateSingleCertChain(template *x509.Certificate, key crypto.Signer, ocspStaple, sctList []byte) CertificateChain { + cert := generateTestCert(template, nil, key, ocspStaple, sctList) + tmpCertPath, tmpKeyPath := writeTempCertFile([]*x509.Certificate{cert}), writeTempKeyFile(key) + return CertificateChain{ + Certificate: [][]byte{cert.Raw}, + PrivateKey: key, + OCSPStaple: ocspStaple, + SignedCertificateTimestampList: sctList, + Leaf: cert, + ChainPath: tmpCertPath, + KeyPath: tmpKeyPath, + RootPath: tmpCertPath, + } +} + +func writeTempCertFile(certs []*x509.Certificate) string { + f, err := os.CreateTemp(tmpDir, "test-cert") + if err != nil { + panic(fmt.Sprintf("failed to create temp file: %s", err)) + } + for _, cert := range certs { + if _, err := f.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})); err != nil { + panic(fmt.Sprintf("failed to write test certificate: %s", err)) + } + } + tmpCertPath := f.Name() + if err := f.Close(); err != nil { + panic(fmt.Sprintf("failed to close test certificate temp file: %s", err)) + } + return tmpCertPath +} + +func writeTempKeyFile(privKey crypto.Signer) string { + f, err := os.CreateTemp(tmpDir, "test-key") + if err != nil { + panic(fmt.Sprintf("failed to create temp file: %s", err)) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(privKey) + if err != nil { + panic(fmt.Sprintf("failed to marshal test key: %s", err)) + } + if _, err := f.Write(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})); err != nil { + panic(fmt.Sprintf("failed to write test key: %s", err)) + } + tmpKeyPath := f.Name() + if err := f.Close(); err != nil { + panic(fmt.Sprintf("failed to close test key temp file: %s", err)) + } + return tmpKeyPath +} + +func generateTestCert(template, issuer *x509.Certificate, key crypto.Signer, ocspStaple, sctList []byte) *x509.Certificate { + if template == nil { + template = baseCertTemplate + } + if issuer == nil { + issuer = template + } + der, err := x509.CreateCertificate(rand.Reader, template, issuer, key.Public(), key) + if err != nil { + panic(fmt.Sprintf("failed to create test certificate: %s", err)) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + panic(fmt.Sprintf("failed to parse test certificate: %s", err)) + } + + return cert +} diff --git a/ssl/test/runner/ecdsa_p224_cert.pem b/ssl/test/runner/ecdsa_p224_cert.pem deleted file mode 100644 index 29246a2eac..0000000000 --- a/ssl/test/runner/ecdsa_p224_cert.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBvjCCAWygAwIBAgIJAPlkrPTq4HgnMAoGCCqGSM49BAMCMEUxCzAJBgNVBAYT -AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn -aXRzIFB0eSBMdGQwHhcNMTcwMjI3MjAxOTIzWhcNMTkwMjI3MjAxOTIzWjBFMQsw -CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu -ZXQgV2lkZ2l0cyBQdHkgTHRkME4wEAYHKoZIzj0CAQYFK4EEACEDOgAE6dul6dL0 -+CyooFiKK4V+EYNYPbMZoLTxRcjRgrw3db6QzBAviDSxKADTVuyRmaVC74Mf6boB -HDmjUDBOMB0GA1UdDgQWBBSMtlkUJ7SCZ4zRqkjXMWvOebSgpTAfBgNVHSMEGDAW -gBSMtlkUJ7SCZ4zRqkjXMWvOebSgpTAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMC -A0AAMD0CHHolWPktSLbVMy9ukQUb2E7+Jb3hcNFqAXh47pYCHQC+jv2EE6oOEZ9F -tLkFLtap71+83P0NUEJX4Etu ------END CERTIFICATE----- diff --git a/ssl/test/runner/ecdsa_p256_cert.pem b/ssl/test/runner/ecdsa_p256_cert.pem deleted file mode 100644 index 50bcbf5bfd..0000000000 --- a/ssl/test/runner/ecdsa_p256_cert.pem +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBzzCCAXagAwIBAgIJANlMBNpJfb/rMAkGByqGSM49BAEwRTELMAkGA1UEBhMC -QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMyMzIxNTdaMEUxCzAJ -BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l -dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2ni -v2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYa -HPUdfvGULUvPciLBo1AwTjAdBgNVHQ4EFgQUq4TSrKuV8IJOFngHVVdf5CaNgtEw -HwYDVR0jBBgwFoAUq4TSrKuV8IJOFngHVVdf5CaNgtEwDAYDVR0TBAUwAwEB/zAJ -BgcqhkjOPQQBA0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13E -BwIgfB55FGohg/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ= ------END CERTIFICATE----- diff --git a/ssl/test/runner/ecdsa_p384_cert.pem b/ssl/test/runner/ecdsa_p384_cert.pem deleted file mode 100644 index 1fd3fec63d..0000000000 --- a/ssl/test/runner/ecdsa_p384_cert.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICZTCCAeugAwIBAgIJAN+/LubpDwxNMAkGByqGSM49BAEwRTELMAkGA1UEBhMC -QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDAeFw0xNjA3MDkwMDAxMzJaFw0xNjA4MDgwMDAxMzJaMEUxCzAJ -BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l -dCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQOdTJNqxiZ -+B68tCZV4GEJwDJ18jK9gFzvefcEAQluBijjrMjflL+RZAT64ExWzedRMp9PD9CW -Tz9hG/Kz4q/l952YsIhy7LTGXzwy7549WUOi+N3aW8psDjtwzWNZXqWjgacwgaQw -HQYDVR0OBBYEFKmYPjADcOlogOMU6D9wlftIWMj6MHUGA1UdIwRuMGyAFKmYPjAD -cOlogOMU6D9wlftIWMj6oUmkRzBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29t -ZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkggkA378u -5ukPDE0wDAYDVR0TBAUwAwEB/zAJBgcqhkjOPQQBA2kAMGYCMQDTfL0OkRGnS5Ze -tsxagAuZqM2Zyv5a2g7u6eFLCx2rpTuQndWOtEnmVo3wjTDtkDcCMQCg+05XSqEF -cqxdXMZJMhqj2jS+tWucdgDstp/1KzJkbsupSjBzIycjVBKLdRwtNg8= ------END CERTIFICATE----- diff --git a/ssl/test/runner/ecdsa_p521_cert.pem b/ssl/test/runner/ecdsa_p521_cert.pem deleted file mode 100644 index 8b9a1e8384..0000000000 --- a/ssl/test/runner/ecdsa_p521_cert.pem +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICrzCCAhGgAwIBAgIJAMZT7oSqTg/lMAkGByqGSM49BAEwRTELMAkGA1UEBhMC -QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDAeFw0xNjA3MDkwMDAwNTBaFw0xNjA4MDgwMDAwNTBaMEUxCzAJ -BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l -dCBXaWRnaXRzIFB0eSBMdGQwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAFD0TiZ -InCoCYDI66FvoaZ0tniUhaNk2YnjEPikfmYHDwstbqkTEqnGAq3pKF2y/MHTBXFd -E0KcFOPs5Ju8EgIcqwGvCkwHPmZtvvOrf+nJNHdm1gZfBUy09f3fVe/mHwfM2++H -dz54nHKxtQqvdafhmMV77R/rBuI24MLKbMihxRgQKaOBpzCBpDAdBgNVHQ4EFgQU -mLWm6ZtGfvih6iFx0+duYfHjGsYwdQYDVR0jBG4wbIAUmLWm6ZtGfvih6iFx0+du -YfHjGsahSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEw -HwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDGU+6Eqk4P5TAMBgNV -HRMEBTADAQH/MAkGByqGSM49BAEDgYwAMIGIAkIBL/oEXMMQH9fefVd8onvgQbWI -/CJXYE+kLO15gO8satasGutVpXtYGeP8gZeuHrq+jWxzj7dGM7YzkW0pAu+wOAkC -QgFJSvKFcmbreLIjuwYik5aXeaUnTCvTQumG07cF0hZRgCf/kTxxmu2ffRoGCDTz -XoPqlxwaO7K+gaAS//CvR0E3lw== ------END CERTIFICATE----- diff --git a/ssl/test/runner/ed25519_cert.pem b/ssl/test/runner/ed25519_cert.pem deleted file mode 100644 index 308c2c9d28..0000000000 --- a/ssl/test/runner/ed25519_cert.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBkTCCAUOgAwIBAgIJAJwooam0UCDmMAUGAytlcDBFMQswCQYDVQQGEwJBVTET -MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1owRTELMAkGA1UE -BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp -ZGdpdHMgUHR5IEx0ZDAqMAUGAytlcAMhANdamAGCsQq31Uv+08lkBzoO4XLz2qYj -Ja8CGmj3B1Eao1AwTjAdBgNVHQ4EFgQUoux7eV+fJK2v3ah6QPU/lj1/+7UwHwYD -VR0jBBgwFoAUoux7eV+fJK2v3ah6QPU/lj1/+7UwDAYDVR0TBAUwAwEB/zAFBgMr -ZXADQQBuCzqji8VP9xU8mHEMjXGChX7YP5J664UyVKHKH9Z1u4wEbB8dJ3ScaWSL -r+VHVKUhsrvcdCelnXRrrSD7xWAL ------END CERTIFICATE----- diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index cd23337e16..41840d7191 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go @@ -1183,7 +1183,7 @@ func (hs *clientHandshakeState) doTLS13Handshake(msg interface{}) error { return err } - var chainToSend *Certificate + var chainToSend *CertificateChain var certReq *certificateRequestMsg if c.didResume { // Copy over authentication from the session. @@ -1691,7 +1691,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } - var chainToSend *Certificate + var chainToSend *CertificateChain var certRequested bool certReq, ok := msg.(*certificateRequestMsg) if ok { @@ -1769,7 +1769,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { } // Determine the hash to sign. - privKey := c.config.Certificates[0].PrivateKey + privKey := c.config.Chains[0].PrivateKey if certVerify.hasSignatureAlgorithm { certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.vers, privKey, c.config, certReq.signatureAlgorithms) @@ -2403,18 +2403,18 @@ func (hs *clientHandshakeState) writeServerHash(msg []byte) { // selectClientCertificate selects a certificate for use with the given // certificate, or none if none match. It may return a particular certificate or // nil on success, or an error on internal error. -func selectClientCertificate(c *Conn, certReq *certificateRequestMsg) (*Certificate, error) { - if len(c.config.Certificates) == 0 { +func selectClientCertificate(c *Conn, certReq *certificateRequestMsg) (*CertificateChain, error) { + if len(c.config.Chains) == 0 { return nil, nil } // The test is assumed to have configured the certificate it meant to // send. - if len(c.config.Certificates) > 1 { + if len(c.config.Chains) > 1 { return nil, errors.New("tls: multiple certificates configured") } - return &c.config.Certificates[0], nil + return &c.config.Chains[0], nil } // clientSessionCacheKey returns a key used to cache sessionTickets that could diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 7c3b587ab3..3cae911706 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -36,7 +36,7 @@ type serverHandshakeState struct { finishedHash finishedHash masterSecret []byte certsFromClient [][]byte - cert *Certificate + cert *CertificateChain finishedBytes []byte echHPKEContext *hpke.Context echConfigID uint8 @@ -1579,11 +1579,11 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server if len(hs.clientHello.serverName) > 0 { c.serverName = hs.clientHello.serverName } - if len(config.Certificates) == 0 { + if len(config.Chains) == 0 { c.sendAlert(alertInternalError) return errors.New("tls: no certificates configured") } - hs.cert = &config.Certificates[0] + hs.cert = &config.Chains[0] if len(hs.clientHello.serverName) > 0 { hs.cert = config.getCertificateForName(hs.clientHello.serverName) } diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go index 1053e4f46e..94d2543057 100644 --- a/ssl/test/runner/key_agreement.go +++ b/ssl/test/runner/key_agreement.go @@ -38,7 +38,7 @@ type rsaKeyAgreement struct { exportKey *rsa.PrivateKey } -func (ka *rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { +func (ka *rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { // Save the client version for comparison later. ka.clientVersion = clientHello.vers @@ -95,7 +95,7 @@ func (ka *rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certi return skx, nil } -func (ka *rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { +func (ka *rsaKeyAgreement) processClientKeyExchange(config *Config, cert *CertificateChain, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { preMasterSecret := make([]byte, 48) _, err := io.ReadFull(config.rand(), preMasterSecret[2:]) if err != nil { @@ -362,7 +362,7 @@ func curveForCurveID(id CurveID, config *Config) (ecdhCurve, bool) { // keyAgreementAuthentication is a helper interface that specifies how // to authenticate the ServerKeyExchange parameters. type keyAgreementAuthentication interface { - signParameters(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error) + signParameters(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error) verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, params []byte, sig []byte) error } @@ -370,7 +370,7 @@ type keyAgreementAuthentication interface { // agreement parameters. type nilKeyAgreementAuthentication struct{} -func (ka *nilKeyAgreementAuthentication) signParameters(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error) { +func (ka *nilKeyAgreementAuthentication) signParameters(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error) { skx := new(serverKeyExchangeMsg) skx.key = params return skx, nil @@ -388,7 +388,7 @@ type signedKeyAgreement struct { peerSignatureAlgorithm signatureAlgorithm } -func (ka *signedKeyAgreement) signParameters(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error) { +func (ka *signedKeyAgreement) signParameters(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error) { // The message to be signed is prepended by the randoms. var msg []byte msg = append(msg, clientHello.random...) @@ -494,7 +494,7 @@ type ecdheKeyAgreement struct { peerKey []byte } -func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { +func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { var curveid CurveID preferredCurves := config.curvePreferences() @@ -545,7 +545,7 @@ NextCandidate: return ka.auth.signParameters(config, cert, clientHello, hello, serverECDHParams) } -func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { +func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *CertificateChain, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 { return nil, errClientKeyExchange } @@ -612,11 +612,11 @@ func (ka *ecdheKeyAgreement) peerSignatureAlgorithm() signatureAlgorithm { // exchange. type nilKeyAgreement struct{} -func (ka *nilKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { +func (ka *nilKeyAgreement) generateServerKeyExchange(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { return nil, nil } -func (ka *nilKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { +func (ka *nilKeyAgreement) processClientKeyExchange(config *Config, cert *CertificateChain, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { if len(ckx.ciphertext) != 0 { return nil, errClientKeyExchange } @@ -664,7 +664,7 @@ type pskKeyAgreement struct { identityHint string } -func (ka *pskKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { +func (ka *pskKeyAgreement) generateServerKeyExchange(config *Config, cert *CertificateChain, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) { // Assemble the identity hint. bytes := make([]byte, 2+len(config.PreSharedKeyIdentity)) bytes[0] = byte(len(config.PreSharedKeyIdentity) >> 8) @@ -691,7 +691,7 @@ func (ka *pskKeyAgreement) generateServerKeyExchange(config *Config, cert *Certi return skx, nil } -func (ka *pskKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { +func (ka *pskKeyAgreement) processClientKeyExchange(config *Config, cert *CertificateChain, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { // First, process the PSK identity. if len(ckx.ciphertext) < 2 { return nil, errClientKeyExchange diff --git a/ssl/test/runner/rsa_1024_cert.pem b/ssl/test/runner/rsa_1024_cert.pem deleted file mode 100644 index 4de4f49a34..0000000000 --- a/ssl/test/runner/rsa_1024_cert.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICWDCCAcGgAwIBAgIJAPuwTC6rEJsMMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTQwNDIzMjA1MDQwWhcNMTcwNDIyMjA1MDQwWjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQDYK8imMuRi/03z0K1Zi0WnvfFHvwlYeyK9Na6XJYaUoIDAtB92kWdGMdAQhLci -HnAjkXLI6W15OoV3gA/ElRZ1xUpxTMhjP6PyY5wqT5r6y8FxbiiFKKAnHmUcrgfV -W28tQ+0rkLGMryRtrukXOgXBv7gcrmU7G1jC2a7WqmeI8QIDAQABo1AwTjAdBgNV -HQ4EFgQUi3XVrMsIvg4fZbf6Vr5sp3Xaha8wHwYDVR0jBBgwFoAUi3XVrMsIvg4f -Zbf6Vr5sp3Xaha8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQA76Hht -ldY9avcTGSwbwoiuIqv0jTL1fHFnzy3RHMLDh+Lpvolc5DSrSJHCP5WuK0eeJXhr -T5oQpHL9z/cCDLAKCKRa4uV0fhEdOWBqyR9p8y5jJtye72t6CuFUV5iqcpF4BH4f -j2VNHwsSrJwkD4QUGlUtH7vwnQmyCFxZMmWAJg== ------END CERTIFICATE----- diff --git a/ssl/test/runner/key.pem b/ssl/test/runner/rsa_2048_key.pem similarity index 100% rename from ssl/test/runner/key.pem rename to ssl/test/runner/rsa_2048_key.pem diff --git a/ssl/test/runner/rsa_chain_cert.pem b/ssl/test/runner/rsa_chain_cert.pem deleted file mode 100644 index 8eb6e60eda..0000000000 --- a/ssl/test/runner/rsa_chain_cert.pem +++ /dev/null @@ -1,35 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC0jCCAbqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEQiBD -QTAeFw0xNjAyMjgyMDI3MDNaFw0yNjAyMjUyMDI3MDNaMBgxFjAUBgNVBAMMDUNs -aWVudCBDZXJ0IEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRvaz8 -CC/cshpCafJo4jLkHEoBqDLhdgFelJoAiQUyIqyWl2O7YHPnpJH+TgR7oelzNzt/ -kLRcH89M/TszB6zqyLTC4aqmvzKL0peD/jL2LWBucR0WXIvjA3zoRuF/x86+rYH3 -tHb+xs2PSs8EGL/Ev+ss+qTzTGEn26fuGNHkNw6tOwPpc+o8+wUtzf/kAthamo+c -IDs2rQ+lP7+aLZTLeU/q4gcLutlzcK5imex5xy2jPkweq48kijK0kIzl1cPlA5d1 -z7C8jU50Pj9X9sQDJTN32j7UYRisJeeYQF8GaaN8SbrDI6zHgKzrRLyxDt/KQa9V -iLeXANgZi+Xx9KgfAgMBAAGjLzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYI -KwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBFEVbmYl+2RtNw -rDftRDF1v2QUbcN2ouSnQDHxeDQdSgasLzT3ui8iYu0Rw2WWcZ0DV5e0ztGPhWq7 -AO0B120aFRMOY+4+bzu9Q2FFkQqc7/fKTvTDzIJI5wrMnFvUfzzvxh3OHWMYSs/w -giq33hTKeHEq6Jyk3btCny0Ycecyc3yGXH10sizUfiHlhviCkDuESk8mFDwDDzqW -ZF0IipzFbEDHoIxLlm3GQxpiLoEV4k8KYJp3R5KBLFyxM6UGPz8h72mIPCJp2RuK -MYgF91UDvVzvnYm6TfseM2+ewKirC00GOrZ7rEcFvtxnKSqYf4ckqfNdSU1Y+RRC -1ngWZ7Ih ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICwjCCAaqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJQyBS -b290IENBMB4XDTE2MDIyODIwMjcwM1oXDTI2MDIyNTIwMjcwM1owDzENMAsGA1UE -AwwEQiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALsSCYmDip2D -GkjFxw7ykz26JSjELkl6ArlYjFJ3aT/SCh8qbS4gln7RH8CPBd78oFdfhIKQrwtZ -3/q21ykD9BAS3qHe2YdcJfm8/kWAy5DvXk6NXU4qX334KofBAEpgdA/igEFq1P1l -HAuIfZCpMRfT+i5WohVsGi8f/NgpRvVaMONLNfgw57mz1lbtFeBEISmX0kbsuJxF -Qj/Bwhi5/0HAEXG8e7zN4cEx0yPRvmOATRdVb/8dW2pwOHRJq9R5M0NUkIsTSnL7 -6N/z8hRAHMsV3IudC5Yd7GXW1AGu9a+iKU+Q4xcZCoj0DC99tL4VKujrV1kAeqsM -cz5/dKzi6+cCAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwDQYJKoZIhvcNAQELBQADggEBAIIeZiEeNhWWQ8Y4D+AGDwqUUeG8NjCbKrXQ -BlHg5wZ8xftFaiP1Dp/UAezmx2LNazdmuwrYB8lm3FVTyaPDTKEGIPS4wJKHgqH1 -QPDhqNm85ey7TEtI9oYjsNim/Rb+iGkIAMXaxt58SzxbjvP0kMr1JfJIZbic9vye -NwIspMFIpP3FB8ywyu0T0hWtCQgL4J47nigCHpOu58deP88fS/Nyz/fyGVWOZ76b -WhWwgM3P3X95fQ3d7oFPR/bVh0YV+Cf861INwplokXgXQ3/TCQ+HNXeAMWn3JLWv -XFwk8owk9dq/kQGdndGgy3KTEW4ctPX5GNhf3LJ9Q7dLji4ReQ4= ------END CERTIFICATE----- diff --git a/ssl/test/runner/rsa_chain_key.pem b/ssl/test/runner/rsa_chain_key.pem deleted file mode 100644 index d94d704422..0000000000 --- a/ssl/test/runner/rsa_chain_key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRvaz8CC/cshpC -afJo4jLkHEoBqDLhdgFelJoAiQUyIqyWl2O7YHPnpJH+TgR7oelzNzt/kLRcH89M -/TszB6zqyLTC4aqmvzKL0peD/jL2LWBucR0WXIvjA3zoRuF/x86+rYH3tHb+xs2P -Ss8EGL/Ev+ss+qTzTGEn26fuGNHkNw6tOwPpc+o8+wUtzf/kAthamo+cIDs2rQ+l -P7+aLZTLeU/q4gcLutlzcK5imex5xy2jPkweq48kijK0kIzl1cPlA5d1z7C8jU50 -Pj9X9sQDJTN32j7UYRisJeeYQF8GaaN8SbrDI6zHgKzrRLyxDt/KQa9ViLeXANgZ -i+Xx9KgfAgMBAAECggEBAK0VjSJzkyPaamcyTVSWjo7GdaBGcK60lk657RjR+lK0 -YJ7pkej4oM2hdsVZFsP8Cs4E33nXLa/0pDsRov/qrp0WQm2skwqGMC1I/bZ0WRPk -wHaDrBBfESWnJDX/AGpVtlyOjPmgmK6J2usMPihQUDkKdAYrVWJePrMIxt1q6BMe -iczs3qriMmtY3bUc4UyUwJ5fhDLjshHvfuIpYQyI6EXZM6dZksn9LylXJnigY6QJ -HxOYO0BDwOsZ8yQ8J8afLk88i0GizEkgE1z3REtQUwgWfxr1WV/ud+T6/ZhSAgH9 -042mQvSFZnIUSEsmCvjhWuAunfxHKCTcAoYISWfzWpkCgYEA7gpf3HHU5Tn+CgUn -1X5uGpG3DmcMgfeGgs2r2f/IIg/5Ac1dfYILiybL1tN9zbyLCJfcbFpWBc9hJL6f -CPc5hUiwWFJqBJewxQkC1Ae/HakHbip+IZ+Jr0842O4BAArvixk4Lb7/N2Ct9sTE -NJO6RtK9lbEZ5uK61DglHy8CS2UCgYEA4ZC1o36kPAMQBggajgnucb2yuUEelk0f -AEr+GI32MGE+93xMr7rAhBoqLg4AITyIfEnOSQ5HwagnIHonBbv1LV/Gf9ursx8Z -YOGbvT8zzzC+SU1bkDzdjAYnFQVGIjMtKOBJ3K07++ypwX1fr4QsQ8uKL8WSOWwt -Z3Bym6XiZzMCgYADnhy+2OwHX85AkLt+PyGlPbmuelpyTzS4IDAQbBa6jcuW/2wA -UE2km75VUXmD+u2R/9zVuLm99NzhFhSMqlUxdV1YukfqMfP5yp1EY6m/5aW7QuIP -2MDa7TVL9rIFMiVZ09RKvbBbQxjhuzPQKL6X/PPspnhiTefQ+dl2k9xREQKBgHDS -fMfGNEeAEKezrfSVqxphE9/tXms3L+ZpnCaT+yu/uEr5dTIAawKoQ6i9f/sf1/Sy -xedsqR+IB+oKrzIDDWMgoJybN4pkZ8E5lzhVQIjFjKgFdWLzzqyW9z1gYfABQPlN -FiS20WX0vgP1vcKAjdNrHzc9zyHBpgQzDmAj3NZZAoGBAI8vKCKdH7w3aL5CNkZQ -2buIeWNA2HZazVwAGG5F2TU/LmXfRKnG6dX5bkU+AkBZh56jNZy//hfFSewJB4Kk -buB7ERSdaNbO21zXt9FEA3+z0RfMd/Zv2vlIWOSB5nzl/7UKti3sribK6s9ZVLfi -SxpiPQ8d/hmSGwn4ksrWUsJD ------END PRIVATE KEY----- diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 22674bd67c..17bc2d61cb 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -18,6 +18,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -117,110 +118,79 @@ var shimConfig ShimConfiguration = ShimConfiguration{ HalfRTTTickets: 2, } -type testCert int - -const ( - testCertRSA testCert = iota - testCertRSA1024 - testCertRSAChain - testCertECDSAP224 - testCertECDSAP256 - testCertECDSAP384 - testCertECDSAP521 - testCertEd25519 -) - const ( - rsaCertificateFile = "cert.pem" - rsa1024CertificateFile = "rsa_1024_cert.pem" - rsaChainCertificateFile = "rsa_chain_cert.pem" - ecdsaP224CertificateFile = "ecdsa_p224_cert.pem" - ecdsaP256CertificateFile = "ecdsa_p256_cert.pem" - ecdsaP384CertificateFile = "ecdsa_p384_cert.pem" - ecdsaP521CertificateFile = "ecdsa_p521_cert.pem" - ed25519CertificateFile = "ed25519_cert.pem" -) + rsa2048KeyFile = "rsa_2048_key.pem" + rsa1024KeyFile = "rsa_1024_key.pem" -const ( - rsaKeyFile = "key.pem" - rsa1024KeyFile = "rsa_1024_key.pem" - rsaChainKeyFile = "rsa_chain_key.pem" ecdsaP224KeyFile = "ecdsa_p224_key.pem" ecdsaP256KeyFile = "ecdsa_p256_key.pem" ecdsaP384KeyFile = "ecdsa_p384_key.pem" ecdsaP521KeyFile = "ecdsa_p521_key.pem" - ed25519KeyFile = "ed25519_key.pem" + + ed25519KeyFile = "ed25519_key.pem" + channelIDKeyFile = "channel_id_key.pem" ) var ( - rsaCertificate Certificate - rsa1024Certificate Certificate - rsaChainCertificate Certificate - ecdsaP224Certificate Certificate - ecdsaP256Certificate Certificate - ecdsaP384Certificate Certificate - ecdsaP521Certificate Certificate - ed25519Certificate Certificate - garbageCertificate Certificate + rsa1024Key rsa.PrivateKey + rsa2048Key rsa.PrivateKey + + ecdsaP224Key ecdsa.PrivateKey + ecdsaP256Key ecdsa.PrivateKey + ecdsaP384Key ecdsa.PrivateKey + ecdsaP521Key ecdsa.PrivateKey + + ed25519Key ed25519.PrivateKey + + channelIDKey ecdsa.PrivateKey ) -var testCerts = []struct { - id testCert - certFile, keyFile string - cert *Certificate -}{ - { - id: testCertRSA, - certFile: rsaCertificateFile, - keyFile: rsaKeyFile, - cert: &rsaCertificate, - }, - { - id: testCertRSA1024, - certFile: rsa1024CertificateFile, - keyFile: rsa1024KeyFile, - cert: &rsa1024Certificate, - }, - { - id: testCertRSAChain, - certFile: rsaChainCertificateFile, - keyFile: rsaChainKeyFile, - cert: &rsaChainCertificate, - }, - { - id: testCertECDSAP224, - certFile: ecdsaP224CertificateFile, - keyFile: ecdsaP224KeyFile, - cert: &ecdsaP224Certificate, - }, - { - id: testCertECDSAP256, - certFile: ecdsaP256CertificateFile, - keyFile: ecdsaP256KeyFile, - cert: &ecdsaP256Certificate, - }, - { - id: testCertECDSAP384, - certFile: ecdsaP384CertificateFile, - keyFile: ecdsaP384KeyFile, - cert: &ecdsaP384Certificate, - }, - { - id: testCertECDSAP521, - certFile: ecdsaP521CertificateFile, - keyFile: ecdsaP521KeyFile, - cert: &ecdsaP521Certificate, - }, - { - id: testCertEd25519, - certFile: ed25519CertificateFile, - keyFile: ed25519KeyFile, - cert: &ed25519Certificate, - }, +func initKeys() { + // Since key generation is not particularly cheap (especially RSA), and the + // runner is intended to run on systems which may be resource constrained, + // we load keys from disk instead of dynamically generating them. We treat + // key files the same as dynamically generated certificates, writing them + // out to temporary files before passing them to the shim. + + for _, k := range []struct { + path string + key *rsa.PrivateKey + }{ + {rsa1024KeyFile, &rsa1024Key}, + {rsa2048KeyFile, &rsa2048Key}, + } { + key, err := loadPEMKey(k.path) + if err != nil { + panic(fmt.Sprintf("failed to load RSA test key: %s", err)) + } + *k.key = *(key.(*rsa.PrivateKey)) + } + + for _, k := range []struct { + path string + key *ecdsa.PrivateKey + }{ + {ecdsaP224KeyFile, &ecdsaP224Key}, + {ecdsaP256KeyFile, &ecdsaP256Key}, + {ecdsaP384KeyFile, &ecdsaP384Key}, + {ecdsaP521KeyFile, &ecdsaP521Key}, + {channelIDKeyFile, &channelIDKey}, + } { + key, err := loadPEMKey(k.path) + if err != nil { + panic(fmt.Sprintf("failed to load ECDSA test key: %s", err)) + } + *k.key = *(key.(*ecdsa.PrivateKey)) + } + + k, err := loadPEMKey(ed25519KeyFile) + if err != nil { + panic(fmt.Sprintf("failed to load Ed25519 test key: %s", err)) + } + ed25519Key = k.(ed25519.PrivateKey) } -var channelIDKey *ecdsa.PrivateKey var channelIDBytes []byte var testOCSPResponse = []byte{1, 2, 3, 4} @@ -231,31 +201,35 @@ var testSCTList2 = []byte{0, 6, 0, 4, 1, 2, 3, 4} var testOCSPExtension = append([]byte{byte(extensionStatusRequest) >> 8, byte(extensionStatusRequest), 0, 8, statusTypeOCSP, 0, 0, 4}, testOCSPResponse...) var testSCTExtension = append([]byte{byte(extensionSignedCertificateTimestamp) >> 8, byte(extensionSignedCertificateTimestamp), 0, byte(len(testSCTList))}, testSCTList...) -func initCertificates() { - for i := range testCerts { - cert, err := LoadX509KeyPair(path.Join(*resourceDir, testCerts[i].certFile), path.Join(*resourceDir, testCerts[i].keyFile)) - if err != nil { - panic(err) - } - cert.OCSPStaple = testOCSPResponse - cert.SignedCertificateTimestampList = testSCTList - *testCerts[i].cert = cert - } +var ( + rsaCertificate CertificateChain + rsaChainCertificate CertificateChain + rsa1024Certificate CertificateChain + ecdsaP224Certificate CertificateChain + ecdsaP256Certificate CertificateChain + ecdsaP384Certificate CertificateChain + ecdsaP521Certificate CertificateChain + ed25519Certificate CertificateChain + garbageCertificate CertificateChain +) - channelIDPEMBlock, err := os.ReadFile(path.Join(*resourceDir, channelIDKeyFile)) - if err != nil { - panic(err) - } - channelIDDERBlock, _ := pem.Decode(channelIDPEMBlock) - if channelIDDERBlock.Type != "EC PRIVATE KEY" { - panic("bad key type") - } - channelIDKey, err = x509.ParseECPrivateKey(channelIDDERBlock.Bytes) - if err != nil { - panic(err) - } - if channelIDKey.Curve != elliptic.P256() { - panic("bad curve") +var testCerts []*CertificateChain + +func initCertificates() { + for _, def := range []struct { + key crypto.Signer + out *CertificateChain + }{ + {&rsa1024Key, &rsa1024Certificate}, + {&rsa2048Key, &rsaCertificate}, + {&ecdsaP224Key, &ecdsaP224Certificate}, + {&ecdsaP256Key, &ecdsaP256Certificate}, + {&ecdsaP384Key, &ecdsaP384Certificate}, + {&ecdsaP521Key, &ecdsaP521Certificate}, + {ed25519Key, &ed25519Certificate}, + } { + *def.out = generateSingleCertChain(nil, def.key, testOCSPResponse, testSCTList) + testCerts = append(testCerts, def.out) } channelIDBytes = make([]byte, 64) @@ -264,6 +238,31 @@ func initCertificates() { garbageCertificate.Certificate = [][]byte{[]byte("GARBAGE")} garbageCertificate.PrivateKey = rsaCertificate.PrivateKey + + // Build a basic three cert chain for testing chain specific things. + rootTmpl := *baseCertTemplate + rootTmpl.Subject.CommonName = "test root" + rootCert := generateTestCert(&rootTmpl, nil, &rsa2048Key, testOCSPResponse, testSCTList) + intermediateTmpl := *baseCertTemplate + intermediateTmpl.Subject.CommonName = "test inter" + intermediateCert := generateTestCert(&intermediateTmpl, rootCert, &rsa2048Key, testOCSPResponse, testSCTList) + leafTmpl := *baseCertTemplate + leafTmpl.IsCA, leafTmpl.BasicConstraintsValid = false, false + leafCert := generateTestCert(nil, intermediateCert, &rsa2048Key, testOCSPResponse, testSCTList) + + keyPath := writeTempKeyFile(&rsa2048Key) + rootCertPath, chainPath := writeTempCertFile([]*x509.Certificate{rootCert}), writeTempCertFile([]*x509.Certificate{leafCert, intermediateCert}) + + rsaChainCertificate = CertificateChain{ + Certificate: [][]byte{leafCert.Raw, intermediateCert.Raw}, + PrivateKey: &rsa2048Key, + OCSPStaple: testOCSPResponse, + SignedCertificateTimestampList: testSCTList, + Leaf: leafCert, + ChainPath: chainPath, + KeyPath: keyPath, + RootPath: rootCertPath, + } } func flagInts(flagName string, vals []int) []string { @@ -300,30 +299,28 @@ type delegatedCredentialConfig struct { algo signatureAlgorithm } -func loadRSAPrivateKey(filename string) (priv *rsa.PrivateKey, privPKCS8 []byte, err error) { +func loadPEMKey(filename string) (crypto.PrivateKey, error) { pemPath := path.Join(*resourceDir, filename) pemBytes, err := os.ReadFile(pemPath) if err != nil { - return nil, nil, err + return nil, err } block, _ := pem.Decode(pemBytes) if block == nil { - return nil, nil, fmt.Errorf("no PEM block found in %q", pemPath) + return nil, fmt.Errorf("no PEM block found in %q", pemPath) } - privPKCS8 = block.Bytes - parsed, err := x509.ParsePKCS8PrivateKey(privPKCS8) - if err != nil { - return nil, nil, fmt.Errorf("failed to parse PKCS#8 key from %q", pemPath) + if block.Type != "PRIVATE KEY" { + return nil, fmt.Errorf("unexpected PEM type in %q (expected \"PRIVATE KEY\"): %s", pemPath, block.Type) } - priv, ok := parsed.(*rsa.PrivateKey) - if !ok { - return nil, nil, fmt.Errorf("found %T in %q rather than an RSA private key", parsed, pemPath) + k, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse PKCS#8 key from %q: %s", pemPath, err) } - return priv, privPKCS8, nil + return k, nil } func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byte, parentPriv crypto.PrivateKey) (dc, privPKCS8 []uint8, err error) { @@ -336,14 +333,12 @@ func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byt switch dcAlgo { case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512: - // RSA keys are expensive to generate so load from disk instead. - var priv *rsa.PrivateKey - if priv, privPKCS8, err = loadRSAPrivateKey(rsaKeyFile); err != nil { + pub = &rsa2048Key.PublicKey + privPKCS8, err = x509.MarshalPKCS8PrivateKey(rsa2048Key) + if err != nil { return nil, nil, err } - pub = &priv.PublicKey - case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512: var curve elliptic.Curve switch dcAlgo { @@ -419,33 +414,6 @@ func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byt return dc, privPKCS8, nil } -func getRunnerCertificate(t testCert) Certificate { - for _, cert := range testCerts { - if cert.id == t { - return *cert.cert - } - } - panic("Unknown test certificate") -} - -func getShimCertificate(t testCert) string { - for _, cert := range testCerts { - if cert.id == t { - return cert.certFile - } - } - panic("Unknown test certificate") -} - -func getShimKey(t testCert) string { - for _, cert := range testCerts { - if cert.id == t { - return cert.keyFile - } - } - panic("Unknown test certificate") -} - // recordVersionToWire maps a record-layer protocol version to its wire // representation. func recordVersionToWire(vers uint16, protocol protocol) uint16 { @@ -569,7 +537,7 @@ type connectionExpectations struct { curveID CurveID // peerCertificate, if not nil, is the certificate chain the peer is // expected to send. - peerCertificate *Certificate + peerCertificate *CertificateChain // quicTransportParams contains the QUIC transport parameters that are to be // sent by the peer using codepoint 57. quicTransportParams []byte @@ -608,10 +576,6 @@ type testCase struct { messageLen int // messageCount is the number of test messages that will be sent. messageCount int - // certFile is the path to the certificate to use for the server. - certFile string - // keyFile is the path to the private key to use for the server. - keyFile string // resumeSession controls whether a second connection should be tested // which attempts to resume the first session. resumeSession bool @@ -733,6 +697,9 @@ type testCase struct { // skipVersionNameCheck, if true, will skip the consistency check between // test name and the versions. skipVersionNameCheck bool + // shimCertificate, if populated, is the certificate/chain which should be sent + // by the server/client (this populates the -cert-file and -key-file flags). + shimCertificate *CertificateChain } func (t *testCase) MarshalJSON() ([]byte, error) { @@ -805,17 +772,14 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr config.ServerSessionCache = NewLRUServerSessionCache(1) } } - if test.testType == clientTest { - if len(config.Certificates) == 0 { - config.Certificates = []Certificate{rsaCertificate} - } - } else { + if test.testType != clientTest { // Supply a ServerName to ensure a constant session cache key, // rather than falling back to net.Conn.RemoteAddr. if len(config.ServerName) == 0 { config.ServerName = "test" } } + if *fuzzer { config.Bugs.NullAllCiphers = true } @@ -1430,6 +1394,9 @@ func doExchanges(test *testCase, shim *shimProcess, resumeCount int, transcripts if test.resumeConfig != nil { resumeConfig = *test.resumeConfig resumeConfig.Rand = config.Rand + if resumeConfig.Chains == nil { + resumeConfig.Chains = config.Chains + } } else { resumeConfig = config } @@ -1503,20 +1470,14 @@ func runTest(dispatcher *shimDispatcher, statusChan chan statusMsg, test *testCa } if test.testType == serverTest { flags = append(flags, "-server") + } - flags = append(flags, "-key-file") - if test.keyFile == "" { - flags = append(flags, path.Join(*resourceDir, rsaKeyFile)) - } else { - flags = append(flags, path.Join(*resourceDir, test.keyFile)) - } - - flags = append(flags, "-cert-file") - if test.certFile == "" { - flags = append(flags, path.Join(*resourceDir, rsaCertificateFile)) - } else { - flags = append(flags, path.Join(*resourceDir, test.certFile)) - } + if test.shimCertificate != nil { + flags = append(flags, "-key-file", test.shimCertificate.KeyPath) + flags = append(flags, "-cert-file", test.shimCertificate.ChainPath) + } else if test.testType == serverTest { + flags = append(flags, "-key-file", rsaCertificate.KeyPath) + flags = append(flags, "-cert-file", rsaCertificate.ChainPath) } if test.protocol == dtls { @@ -1687,6 +1648,19 @@ func runTest(dispatcher *shimDispatcher, statusChan chan statusMsg, test *testCa flags = append(flags, "-write-settings", transcriptPrefix) } + if test.testType == clientTest { + if len(test.config.Chains) == 0 { + test.config.Chains = []CertificateChain{rsaCertificate} + } + + rootFiles := make([]string, 0, len(test.config.Chains)) + for _, c := range test.config.Chains { + rootFiles = append(rootFiles, c.RootPath) + } + + flags = append(flags, "-trust-cert", strings.Join(rootFiles, ",")) + } + flags = append(flags, test.flags...) var env []string @@ -2223,14 +2197,14 @@ read alert 1 0 testType: serverTest, name: "ServerSkipCertificateVerify", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaChainCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ SkipCertificateVerify: true, }, }, expectations: connectionExpectations{ - peerCertificate: &rsaChainCertificate, + peerCertificate: &rsaCertificate, }, flags: []string{ "-require-any-client-certificate", @@ -3753,14 +3727,14 @@ read alert 1 0 testCases = append(testCases, testCase{ name: "LargeMessage", config: Config{ - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, }, }) testCases = append(testCases, testCase{ protocol: dtls, name: "LargeMessage-DTLS", config: Config{ - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, }, }) @@ -3768,7 +3742,7 @@ read alert 1 0 testCases = append(testCases, testCase{ name: "LargeMessage-Reject", config: Config{ - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, }, flags: []string{"-max-cert-list", "16384"}, shouldFail: true, @@ -3778,7 +3752,7 @@ read alert 1 0 protocol: dtls, name: "LargeMessage-Reject-DTLS", config: Config{ - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, }, flags: []string{"-max-cert-list", "16384"}, shouldFail: true, @@ -3826,17 +3800,11 @@ func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol proto } prefix := protocol.String() + "-" - var cert Certificate - var certFile string - var keyFile string + var cert CertificateChain if hasComponent(suite.name, "ECDSA") { cert = ecdsaP256Certificate - certFile = ecdsaP256CertificateFile - keyFile = ecdsaP256KeyFile } else { cert = rsaCertificate - certFile = rsaCertificateFile - keyFile = rsaKeyFile } var flags []string @@ -3880,15 +3848,14 @@ func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol proto MinVersion: ver.version, MaxVersion: ver.version, CipherSuites: []uint16{suite.id}, - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, PreSharedKey: []byte(psk), PreSharedKeyIdentity: pskIdentity, Bugs: ProtocolBugs{ AdvertiseAllConfiguredCiphers: true, }, }, - certFile: certFile, - keyFile: keyFile, + shimCertificate: &cert, flags: flags, resumeSession: true, shouldFail: shouldFail, @@ -3904,7 +3871,7 @@ func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol proto MinVersion: ver.version, MaxVersion: ver.version, CipherSuites: serverCipherSuites, - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, PreSharedKey: []byte(psk), PreSharedKeyIdentity: pskIdentity, Bugs: ProtocolBugs{ @@ -3931,7 +3898,7 @@ func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol proto MinVersion: ver.version, MaxVersion: ver.version, CipherSuites: []uint16{suite.id}, - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, PreSharedKey: []byte(psk), PreSharedKeyIdentity: pskIdentity, }, @@ -3958,7 +3925,7 @@ func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol proto MinVersion: ver.version, MaxVersion: ver.version, CipherSuites: []uint16{suite.id}, - Certificates: []Certificate{cert}, + Chains: []CertificateChain{cert}, PreSharedKey: []byte(psk), PreSharedKeyIdentity: pskIdentity, }, @@ -4098,7 +4065,7 @@ func addCipherSuiteTests() { config: Config{ MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - Certificates: []Certificate{rsaCertificate}, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ SendCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, }, @@ -4111,7 +4078,7 @@ func addCipherSuiteTests() { config: Config{ MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - Certificates: []Certificate{ecdsaP256Certificate}, + Chains: []CertificateChain{ecdsaP256Certificate}, Bugs: ProtocolBugs{ SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, }, @@ -4124,7 +4091,7 @@ func addCipherSuiteTests() { config: Config{ MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - Certificates: []Certificate{ed25519Certificate}, + Chains: []CertificateChain{ed25519Certificate}, Bugs: ProtocolBugs{ SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, }, @@ -4142,12 +4109,9 @@ func addCipherSuiteTests() { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, - shouldFail: true, - expectedError: ":NO_SHARED_CIPHER:", + shimCertificate: &rsaCertificate, + shouldFail: true, + expectedError: ":NO_SHARED_CIPHER:", }) testCases = append(testCases, testCase{ testType: serverTest, @@ -4156,12 +4120,9 @@ func addCipherSuiteTests() { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, - shouldFail: true, - expectedError: ":NO_SHARED_CIPHER:", + shimCertificate: &ecdsaP256Certificate, + shouldFail: true, + expectedError: ":NO_SHARED_CIPHER:", }) testCases = append(testCases, testCase{ testType: serverTest, @@ -4170,12 +4131,9 @@ func addCipherSuiteTests() { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ed25519CertificateFile), - "-key-file", path.Join(*resourceDir, ed25519KeyFile), - }, - shouldFail: true, - expectedError: ":NO_SHARED_CIPHER:", + shimCertificate: &ed25519Certificate, + shouldFail: true, + expectedError: ":NO_SHARED_CIPHER:", }) // Test cipher suite negotiation works as expected. Configure a @@ -4299,7 +4257,7 @@ func addBadECDSASignatureTests() { config: Config{ MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - Certificates: []Certificate{ecdsaP256Certificate}, + Chains: []CertificateChain{ecdsaP256Certificate}, Bugs: ProtocolBugs{ BadECDSAR: badR, BadECDSAS: badS, @@ -4311,8 +4269,8 @@ func addBadECDSASignatureTests() { testCases = append(testCases, testCase{ name: fmt.Sprintf("BadECDSA-%d-%d-TLS13", badR, badS), config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{ecdsaP256Certificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{ecdsaP256Certificate}, Bugs: ProtocolBugs{ BadECDSAR: badR, BadECDSAS: badS, @@ -4419,7 +4377,7 @@ func addCBCSplittingTests() { func addClientAuthTests() { // Add a dummy cert pool to stress certificate authority parsing. certPool := x509.NewCertPool() - for _, cert := range []Certificate{rsaCertificate, rsa1024Certificate} { + for _, cert := range []CertificateChain{rsaCertificate, rsa1024Certificate} { cert, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { panic(err) @@ -4438,18 +4396,15 @@ func addClientAuthTests() { ClientAuth: RequireAnyClientCert, ClientCAs: certPool, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) testCases = append(testCases, testCase{ testType: serverTest, name: ver.name + "-Server-ClientAuth-RSA", config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{"-require-any-client-certificate"}, }) @@ -4457,9 +4412,9 @@ func addClientAuthTests() { testType: serverTest, name: ver.name + "-Server-ClientAuth-ECDSA", config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{ecdsaP256Certificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{ecdsaP256Certificate}, }, flags: []string{"-require-any-client-certificate"}, }) @@ -4472,10 +4427,7 @@ func addClientAuthTests() { ClientAuth: RequireAnyClientCert, ClientCAs: certPool, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, }) testCases = append(testCases, testCase{ @@ -4577,7 +4529,7 @@ func addClientAuthTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, }, expectations: connectionExpectations{ channelID: true, @@ -4592,9 +4544,9 @@ func addClientAuthTests() { testType: serverTest, name: ver.name + "-Server-CertReq-CA-List", config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ ExpectCertificateReqNames: caNames, }, @@ -4609,15 +4561,14 @@ func addClientAuthTests() { testType: clientTest, name: ver.name + "-Client-CertReq-CA-List", config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, - ClientAuth: RequireAnyClientCert, - ClientCAs: certPool, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, + ClientAuth: RequireAnyClientCert, + ClientCAs: certPool, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-expect-client-ca-list", encodeDERValues(caNames), }, }) @@ -4633,9 +4584,8 @@ func addClientAuthTests() { PreSharedKey: []byte("secret"), ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-psk", "secret", }, shouldFail: true, @@ -4650,9 +4600,8 @@ func addClientAuthTests() { PreSharedKey: []byte("secret"), ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-psk", "secret", }, shouldFail: true, @@ -4665,8 +4614,8 @@ func addClientAuthTests() { testType: serverTest, name: "Null-Client-CA-List", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ ExpectCertificateReqNames: [][]byte{}, }, @@ -4682,8 +4631,8 @@ func addClientAuthTests() { testType: serverTest, name: "TLS13-Empty-Client-CA-List", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ ExpectNoCertificateAuthoritiesExtension: true, }, @@ -5197,10 +5146,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, ClientAuth: RequireAnyClientCert, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) } tests = append(tests, testCase{ @@ -5210,10 +5156,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS13, ClientAuth: RequireAnyClientCert, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) if config.protocol != quic { tests = append(tests, testCase{ @@ -5223,10 +5166,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, ClientAuth: RequireAnyClientCert, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, }) } tests = append(tests, testCase{ @@ -5236,10 +5176,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS13, ClientAuth: RequireAnyClientCert, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, }) if config.protocol != quic { tests = append(tests, testCase{ @@ -5269,9 +5206,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-use-old-client-cert-callback", }, }) @@ -5283,9 +5219,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS13, ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-use-old-client-cert-callback", }, }) @@ -5294,8 +5229,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: serverTest, name: "ClientAuth-Server", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{"-require-any-client-certificate"}, }) @@ -5304,8 +5239,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: serverTest, name: "ClientAuth-Server-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{"-require-any-client-certificate"}, }) @@ -5319,10 +5254,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) tests = append(tests, testCase{ testType: serverTest, @@ -5331,10 +5263,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) tests = append(tests, testCase{ testType: serverTest, @@ -5343,10 +5272,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, }) tests = append(tests, testCase{ testType: serverTest, @@ -5355,9 +5281,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, }, + shimCertificate: &ed25519Certificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, ed25519CertificateFile), - "-key-file", path.Join(*resourceDir, ed25519KeyFile), "-verify-prefs", strconv.Itoa(int(signatureEd25519)), }, }) @@ -5440,8 +5365,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: clientTest, name: "ClientOCSPCallback-Pass-" + vers.name, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-enable-ocsp-stapling", @@ -5458,8 +5383,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: clientTest, name: "ClientOCSPCallback-Fail-" + vers.name, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-enable-ocsp-stapling", @@ -5478,8 +5403,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: clientTest, name: "ClientOCSPCallback-FailNoStaple-" + vers.name, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{certNoStaple}, + MaxVersion: vers.version, + Chains: []CertificateChain{certNoStaple}, }, flags: []string{ "-enable-ocsp-stapling", @@ -5588,8 +5513,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: testType, name: "CertificateVerificationSucceed" + suffix, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: append([]string{"-expect-verify-result"}, flags...), resumeSession: true, @@ -5598,8 +5523,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: testType, name: "CertificateVerificationFail" + suffix, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: append([]string{"-verify-fail"}, flags...), shouldFail: true, @@ -5611,8 +5536,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: testType, name: "CertificateVerificationDoesNotFailOnResume" + suffix, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: append([]string{"-on-resume-verify-fail"}, flags...), resumeSession: true, @@ -5622,8 +5547,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: testType, name: "CertificateVerificationFailsOnResume" + suffix, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: append([]string{ "-on-resume-verify-fail", @@ -5638,8 +5563,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: testType, name: "CertificateVerificationPassesOnResume" + suffix, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: append([]string{ "-reverify-on-resume", @@ -5769,8 +5694,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { testType: clientTest, name: "CertificateVerificationSoftFail-" + vers.name, config: Config{ - MaxVersion: vers.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: vers.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-verify-fail", @@ -5982,7 +5907,7 @@ read alert 1 0 name: "ChannelID-NPN-Server", config: Config{ MaxVersion: VersionTLS12, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, NextProtos: []string{"bar"}, }, flags: []string{ @@ -6036,7 +5961,7 @@ read alert 1 0 name: "ChannelID-Server-" + ver.name, config: Config{ MaxVersion: ver.version, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, }, flags: []string{ "-expect-channel-id", @@ -6053,7 +5978,7 @@ read alert 1 0 name: "InvalidChannelIDSignature-" + ver.name, config: Config{ MaxVersion: ver.version, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, Bugs: ProtocolBugs{ InvalidChannelIDSignature: true, }, @@ -6071,7 +5996,7 @@ read alert 1 0 config: Config{ MaxVersion: ver.version, CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, }, expectations: connectionExpectations{ channelID: false, @@ -6086,7 +6011,7 @@ read alert 1 0 config: Config{ MaxVersion: ver.version, CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, }, expectations: connectionExpectations{ channelID: false, @@ -8620,7 +8545,7 @@ func addExtensionTests() { resumeSession: true, }) - emptySCTListCert := *testCerts[0].cert + emptySCTListCert := rsaCertificate emptySCTListCert.SignedCertificateTimestampList = []byte{0, 0} // Test empty SCT list. @@ -8629,8 +8554,8 @@ func addExtensionTests() { name: "SignedCertificateTimestampListEmpty-Client-" + suffix, testType: clientTest, config: Config{ - MaxVersion: ver.version, - Certificates: []Certificate{emptySCTListCert}, + MaxVersion: ver.version, + Chains: []CertificateChain{emptySCTListCert}, }, flags: []string{ "-enable-signed-cert-timestamps", @@ -8639,7 +8564,7 @@ func addExtensionTests() { expectedError: ":ERROR_PARSING_EXTENSION:", }) - emptySCTCert := *testCerts[0].cert + emptySCTCert := rsaCertificate emptySCTCert.SignedCertificateTimestampList = []byte{0, 6, 0, 2, 1, 2, 0, 0} // Test empty SCT in non-empty list. @@ -8648,8 +8573,8 @@ func addExtensionTests() { name: "SignedCertificateTimestampListEmptySCT-Client-" + suffix, testType: clientTest, config: Config{ - MaxVersion: ver.version, - Certificates: []Certificate{emptySCTCert}, + MaxVersion: ver.version, + Chains: []CertificateChain{emptySCTCert}, }, flags: []string{ "-enable-signed-cert-timestamps", @@ -8881,8 +8806,8 @@ func addExtensionTests() { name: "SendExtensionOnClientCertificate-TLS13", testType: serverTest, config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ SendExtensionOnCertificate: testOCSPExtension, }, @@ -8911,8 +8836,8 @@ func addExtensionTests() { testCases = append(testCases, testCase{ name: "IgnoreExtensionsOnIntermediates-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaChainCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaChainCertificate}, Bugs: ProtocolBugs{ // Send different values on the intermediate. This tests // the intermediate's extensions do not override the @@ -8943,9 +8868,8 @@ func addExtensionTests() { ExpectNoExtensionsOnIntermediate: true, }, }, + shimCertificate: &rsaChainCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), - "-key-file", path.Join(*resourceDir, rsaChainKeyFile), "-ocsp-response", base64FlagValue(testOCSPResponse), "-signed-cert-timestamps", @@ -8960,9 +8884,8 @@ func addExtensionTests() { MaxVersion: VersionTLS13, ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-ocsp-response", base64FlagValue(testOCSPResponse), "-signed-cert-timestamps", @@ -9975,8 +9898,8 @@ func addRenegotiationTests() { testCases = append(testCases, testCase{ name: "Renegotiation-CertificateChange", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ RenegotiationCertificate: &rsaChainCertificate, }, @@ -9989,8 +9912,8 @@ func addRenegotiationTests() { testCases = append(testCases, testCase{ name: "Renegotiation-CertificateChange-2", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ RenegotiationCertificate: &rsa1024Certificate, }, @@ -10099,30 +10022,30 @@ func addDTLSReplayTests() { var testSignatureAlgorithms = []struct { name string id signatureAlgorithm - cert testCert + cert *CertificateChain // If non-zero, the curve that must be supported in TLS 1.2 for cert to be // accepted. curve CurveID }{ - {"RSA_PKCS1_SHA1", signatureRSAPKCS1WithSHA1, testCertRSA, 0}, - {"RSA_PKCS1_SHA256", signatureRSAPKCS1WithSHA256, testCertRSA, 0}, - {"RSA_PKCS1_SHA384", signatureRSAPKCS1WithSHA384, testCertRSA, 0}, - {"RSA_PKCS1_SHA512", signatureRSAPKCS1WithSHA512, testCertRSA, 0}, - {"ECDSA_SHA1", signatureECDSAWithSHA1, testCertECDSAP256, CurveP256}, + {"RSA_PKCS1_SHA1", signatureRSAPKCS1WithSHA1, &rsaCertificate, 0}, + {"RSA_PKCS1_SHA256", signatureRSAPKCS1WithSHA256, &rsaCertificate, 0}, + {"RSA_PKCS1_SHA384", signatureRSAPKCS1WithSHA384, &rsaCertificate, 0}, + {"RSA_PKCS1_SHA512", signatureRSAPKCS1WithSHA512, &rsaCertificate, 0}, + {"ECDSA_SHA1", signatureECDSAWithSHA1, &ecdsaP256Certificate, CurveP256}, // The “P256” in the following line is not a mistake. In TLS 1.2 the // hash function doesn't have to match the curve and so the same // signature algorithm works with P-224. - {"ECDSA_P224_SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP224, CurveP224}, - {"ECDSA_P256_SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP256, CurveP256}, - {"ECDSA_P384_SHA384", signatureECDSAWithP384AndSHA384, testCertECDSAP384, CurveP384}, - {"ECDSA_P521_SHA512", signatureECDSAWithP521AndSHA512, testCertECDSAP521, CurveP521}, - {"RSA_PSS_SHA256", signatureRSAPSSWithSHA256, testCertRSA, 0}, - {"RSA_PSS_SHA384", signatureRSAPSSWithSHA384, testCertRSA, 0}, - {"RSA_PSS_SHA512", signatureRSAPSSWithSHA512, testCertRSA, 0}, - {"Ed25519", signatureEd25519, testCertEd25519, 0}, + {"ECDSA_P224_SHA256", signatureECDSAWithP256AndSHA256, &ecdsaP224Certificate, CurveP224}, + {"ECDSA_P256_SHA256", signatureECDSAWithP256AndSHA256, &ecdsaP256Certificate, CurveP256}, + {"ECDSA_P384_SHA384", signatureECDSAWithP384AndSHA384, &ecdsaP384Certificate, CurveP384}, + {"ECDSA_P521_SHA512", signatureECDSAWithP521AndSHA512, &ecdsaP521Certificate, CurveP521}, + {"RSA_PSS_SHA256", signatureRSAPSSWithSHA256, &rsaCertificate, 0}, + {"RSA_PSS_SHA384", signatureRSAPSSWithSHA384, &rsaCertificate, 0}, + {"RSA_PSS_SHA512", signatureRSAPSSWithSHA512, &rsaCertificate, 0}, + {"Ed25519", signatureEd25519, &ed25519Certificate, 0}, // Tests for key types prior to TLS 1.2. - {"RSA", 0, testCertRSA, 0}, - {"ECDSA", 0, testCertECDSAP256, CurveP256}, + {"RSA", 0, &rsaCertificate, 0}, + {"ECDSA", 0, &ecdsaP256Certificate, CurveP256}, } const fakeSigAlg1 signatureAlgorithm = 0x2a01 @@ -10165,7 +10088,7 @@ func addSignatureAlgorithmTests() { } // SHA-224 has been removed from TLS 1.3 and, in 1.3, // the curve has to match the hash size. - if ver.version >= VersionTLS13 && alg.cert == testCertECDSAP224 { + if ver.version >= VersionTLS13 && alg.curve == CurveP224 { shouldFail = true } @@ -10215,13 +10138,8 @@ func addSignatureAlgorithmTests() { fakeSigAlg2, }, }, - flags: append( - []string{ - "-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)), - "-key-file", path.Join(*resourceDir, getShimKey(alg.cert)), - }, - curveFlags..., - ), + shimCertificate: alg.cert, + flags: curveFlags, shouldFail: shouldFail, expectedError: signError, expectedLocalError: signLocalError, @@ -10239,13 +10157,8 @@ func addSignatureAlgorithmTests() { MaxVersion: ver.version, VerifySignatureAlgorithms: allAlgorithms, }, - flags: append( - []string{ - "-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)), - "-key-file", path.Join(*resourceDir, getShimKey(alg.cert)), - }, - curveFlags..., - ), + shimCertificate: alg.cert, + flags: curveFlags, expectations: connectionExpectations{ peerSignatureAlgorithm: alg.id, }, @@ -10273,8 +10186,8 @@ func addSignatureAlgorithmTests() { testType: testType, name: prefix + "Verify" + suffix, config: Config{ - MaxVersion: ver.version, - Certificates: []Certificate{getRunnerCertificate(alg.cert)}, + MaxVersion: ver.version, + Chains: []CertificateChain{*alg.cert}, SignSignatureAlgorithms: []signatureAlgorithm{ alg.id, }, @@ -10304,8 +10217,8 @@ func addSignatureAlgorithmTests() { testType: testType, name: prefix + "VerifyDefault" + suffix, config: Config{ - MaxVersion: ver.version, - Certificates: []Certificate{getRunnerCertificate(alg.cert)}, + MaxVersion: ver.version, + Chains: []CertificateChain{*alg.cert}, SignSignatureAlgorithms: []signatureAlgorithm{ alg.id, }, @@ -10333,8 +10246,8 @@ func addSignatureAlgorithmTests() { testType: testType, name: prefix + "InvalidSignature" + suffix, config: Config{ - MaxVersion: ver.version, - Certificates: []Certificate{getRunnerCertificate(alg.cert)}, + MaxVersion: ver.version, + Chains: []CertificateChain{*alg.cert}, SignSignatureAlgorithms: []signatureAlgorithm{ alg.id, }, @@ -10386,9 +10299,8 @@ func addSignatureAlgorithmTests() { signatureECDSAWithP256AndSHA256, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)), "-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)), "-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)), @@ -10406,9 +10318,8 @@ func addSignatureAlgorithmTests() { signatureECDSAWithP256AndSHA256, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-expect-peer-verify-pref", strconv.Itoa(int(signatureRSAPSSWithSHA256)), "-expect-peer-verify-pref", strconv.Itoa(int(signatureEd25519)), "-expect-peer-verify-pref", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)), @@ -10429,10 +10340,7 @@ func addSignatureAlgorithmTests() { signatureECDSAWithSHA1, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, expectations: connectionExpectations{ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA384, }, @@ -10450,10 +10358,7 @@ func addSignatureAlgorithmTests() { signatureECDSAWithSHA1, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, expectations: connectionExpectations{ peerSignatureAlgorithm: signatureRSAPSSWithSHA384, }, @@ -10498,8 +10403,8 @@ func addSignatureAlgorithmTests() { testType: serverTest, name: "Verify-ClientAuth-SignatureType", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPKCS1WithSHA256, }, @@ -10518,8 +10423,8 @@ func addSignatureAlgorithmTests() { testType: serverTest, name: "Verify-ClientAuth-SignatureType-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPSSWithSHA256, }, @@ -10579,10 +10484,7 @@ func addSignatureAlgorithmTests() { NoSignatureAlgorithms: true, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) testCases = append(testCases, testCase{ @@ -10597,10 +10499,7 @@ func addSignatureAlgorithmTests() { NoSignatureAlgorithms: true, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, }) testCases = append(testCases, testCase{ @@ -10634,10 +10533,7 @@ func addSignatureAlgorithmTests() { NoSignatureAlgorithms: true, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, shouldFail: true, expectedError: ":DECODE_ERROR:", expectedLocalError: "remote error: error decoding message", @@ -10655,10 +10551,7 @@ func addSignatureAlgorithmTests() { NoSignatureAlgorithms: true, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, shouldFail: true, expectedError: ":DECODE_ERROR:", expectedLocalError: "remote error: error decoding message", @@ -10676,10 +10569,7 @@ func addSignatureAlgorithmTests() { NoSignatureAlgorithms: true, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, shouldFail: true, expectedError: ":DECODE_ERROR:", expectedLocalError: "remote error: error decoding message", @@ -10691,8 +10581,8 @@ func addSignatureAlgorithmTests() { testType: serverTest, name: "ClientAuth-Enforced", config: Config{ - MaxVersion: VersionTLS12, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPKCS1WithMD5, }, @@ -10724,8 +10614,8 @@ func addSignatureAlgorithmTests() { testType: serverTest, name: "ClientAuth-Enforced-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPKCS1WithMD5, }, @@ -10767,9 +10657,8 @@ func addSignatureAlgorithmTests() { signatureRSAPKCS1WithSHA1, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)), }, shouldFail: true, @@ -10785,9 +10674,8 @@ func addSignatureAlgorithmTests() { signatureRSAPSSWithSHA384, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA256)), }, shouldFail: true, @@ -10803,9 +10691,8 @@ func addSignatureAlgorithmTests() { signatureRSAPKCS1WithSHA256, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA1)), }, @@ -10822,9 +10709,8 @@ func addSignatureAlgorithmTests() { signatureRSAPKCS1WithSHA1, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA512)), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA1)), @@ -10845,10 +10731,7 @@ func addSignatureAlgorithmTests() { signatureECDSAWithSHA1, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, expectations: connectionExpectations{ peerSignatureAlgorithm: signatureRSAPKCS1WithSHA256, }, @@ -10865,9 +10748,8 @@ func addSignatureAlgorithmTests() { signatureRSAPKCS1WithSHA256, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-signing-prefs", strconv.Itoa(int(signatureECDSAWithP256AndSHA256)), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)), }, @@ -10883,7 +10765,7 @@ func addSignatureAlgorithmTests() { config: Config{ MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - Certificates: []Certificate{ecdsaP256Certificate}, + Chains: []CertificateChain{ecdsaP256Certificate}, }, flags: []string{"-curves", strconv.Itoa(int(CurveP384))}, shouldFail: true, @@ -10894,8 +10776,8 @@ func addSignatureAlgorithmTests() { testCases = append(testCases, testCase{ name: "CheckLeafCurve-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{ecdsaP256Certificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{ecdsaP256Certificate}, }, flags: []string{"-curves", strconv.Itoa(int(CurveP384))}, }) @@ -10906,7 +10788,7 @@ func addSignatureAlgorithmTests() { config: Config{ MaxVersion: VersionTLS12, CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - Certificates: []Certificate{ecdsaP256Certificate}, + Chains: []CertificateChain{ecdsaP256Certificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureECDSAWithP384AndSHA384, }, @@ -10917,8 +10799,8 @@ func addSignatureAlgorithmTests() { testCases = append(testCases, testCase{ name: "ECDSACurveMismatch-Verify-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{ecdsaP256Certificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{ecdsaP256Certificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureECDSAWithP384AndSHA384, }, @@ -10942,10 +10824,7 @@ func addSignatureAlgorithmTests() { signatureECDSAWithP256AndSHA256, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), - "-key-file", path.Join(*resourceDir, ecdsaP256KeyFile), - }, + shimCertificate: &ecdsaP256Certificate, expectations: connectionExpectations{ peerSignatureAlgorithm: signatureECDSAWithP256AndSHA256, }, @@ -10962,12 +10841,9 @@ func addSignatureAlgorithmTests() { signatureRSAPSSWithSHA512, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsa1024CertificateFile), - "-key-file", path.Join(*resourceDir, rsa1024KeyFile), - }, - shouldFail: true, - expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:", + shimCertificate: &rsa1024Certificate, + shouldFail: true, + expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:", }) // Test that RSA-PSS is enabled by default for TLS 1.2. @@ -11001,8 +10877,8 @@ func addSignatureAlgorithmTests() { testType: clientTest, name: "NoEd25519-TLS11-ServerAuth-Verify", config: Config{ - MaxVersion: VersionTLS11, - Certificates: []Certificate{ed25519Certificate}, + MaxVersion: VersionTLS11, + Chains: []CertificateChain{ed25519Certificate}, Bugs: ProtocolBugs{ // Sign with Ed25519 even though it is TLS 1.1. SigningAlgorithmForLegacyVersions: signatureEd25519, @@ -11019,8 +10895,8 @@ func addSignatureAlgorithmTests() { MaxVersion: VersionTLS11, }, flags: []string{ - "-cert-file", path.Join(*resourceDir, ed25519CertificateFile), - "-key-file", path.Join(*resourceDir, ed25519KeyFile), + "-cert-file", ed25519Certificate.ChainPath, + "-key-file", ed25519Certificate.KeyPath, }, shouldFail: true, expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:", @@ -11029,8 +10905,8 @@ func addSignatureAlgorithmTests() { testType: serverTest, name: "NoEd25519-TLS11-ClientAuth-Verify", config: Config{ - MaxVersion: VersionTLS11, - Certificates: []Certificate{ed25519Certificate}, + MaxVersion: VersionTLS11, + Chains: []CertificateChain{ed25519Certificate}, Bugs: ProtocolBugs{ // Sign with Ed25519 even though it is TLS 1.1. SigningAlgorithmForLegacyVersions: signatureEd25519, @@ -11050,12 +10926,9 @@ func addSignatureAlgorithmTests() { MaxVersion: VersionTLS11, ClientAuth: RequireAnyClientCert, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, ed25519CertificateFile), - "-key-file", path.Join(*resourceDir, ed25519KeyFile), - }, - shouldFail: true, - expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:", + shimCertificate: &ed25519Certificate, + shouldFail: true, + expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:", }) // Test Ed25519 is not advertised by default. @@ -11063,7 +10936,7 @@ func addSignatureAlgorithmTests() { testType: clientTest, name: "Ed25519DefaultDisable-NoAdvertise", config: Config{ - Certificates: []Certificate{ed25519Certificate}, + Chains: []CertificateChain{ed25519Certificate}, }, shouldFail: true, expectedLocalError: "tls: no common signature algorithms", @@ -11075,7 +10948,7 @@ func addSignatureAlgorithmTests() { testType: clientTest, name: "Ed25519DefaultDisable-NoAccept", config: Config{ - Certificates: []Certificate{ed25519Certificate}, + Chains: []CertificateChain{ed25519Certificate}, Bugs: ProtocolBugs{ IgnorePeerSignatureAlgorithmPreferences: true, }, @@ -11090,7 +10963,7 @@ func addSignatureAlgorithmTests() { testCases = append(testCases, testCase{ name: "VerifyPreferences-Advertised", config: Config{ - Certificates: []Certificate{rsaCertificate}, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, @@ -11108,7 +10981,7 @@ func addSignatureAlgorithmTests() { testCases = append(testCases, testCase{ name: "VerifyPreferences-NoCommonAlgorithms", config: Config{ - Certificates: []Certificate{rsaCertificate}, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA512, @@ -11125,7 +10998,7 @@ func addSignatureAlgorithmTests() { testCases = append(testCases, testCase{ name: "VerifyPreferences-Enforced", config: Config{ - Certificates: []Certificate{rsaCertificate}, + Chains: []CertificateChain{rsaCertificate}, SignSignatureAlgorithms: []signatureAlgorithm{ signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA512, @@ -11147,7 +11020,7 @@ func addSignatureAlgorithmTests() { testCases = append(testCases, testCase{ name: "VerifyPreferences-Ed25519", config: Config{ - Certificates: []Certificate{ed25519Certificate}, + Chains: []CertificateChain{ed25519Certificate}, }, flags: []string{ "-verify-prefs", strconv.Itoa(int(signatureEd25519)), @@ -11176,9 +11049,8 @@ func addSignatureAlgorithmTests() { ClientAuth: RequireAnyClientCert, VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPKCS1WithMD5AndSHA1}, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithMD5AndSHA1)), // Include a valid algorithm as well, to avoid an empty list // if filtered out. @@ -11194,17 +11066,16 @@ func addSignatureAlgorithmTests() { testType: testType, name: prefix + "NoVerify-RSA_PKCS1_MD5_SHA1", config: Config{ - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, Bugs: ProtocolBugs{ IgnorePeerSignatureAlgorithmPreferences: true, AlwaysSignAsLegacyVersion: true, SendSignatureAlgorithm: signatureRSAPKCS1WithMD5AndSHA1, }, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-verify-prefs", strconv.Itoa(int(signatureRSAPKCS1WithMD5AndSHA1)), // Include a valid algorithm as well, to avoid an empty list // if filtered out. @@ -13495,8 +13366,8 @@ func makePerMessageTests() []perMessageTest { protocol: protocol, name: "ClientCertificate" + suffix, config: Config{ - Certificates: []Certificate{rsaCertificate}, - MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, + MaxVersion: VersionTLS12, }, flags: []string{"-require-any-client-certificate"}, }, @@ -13509,8 +13380,8 @@ func makePerMessageTests() []perMessageTest { protocol: protocol, name: "CertificateVerify" + suffix, config: Config{ - Certificates: []Certificate{rsaCertificate}, - MaxVersion: VersionTLS12, + Chains: []CertificateChain{rsaCertificate}, + MaxVersion: VersionTLS12, }, flags: []string{"-require-any-client-certificate"}, }, @@ -13551,7 +13422,7 @@ func makePerMessageTests() []perMessageTest { name: "ChannelID" + suffix, config: Config{ MaxVersion: VersionTLS12, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, }, flags: []string{ "-expect-channel-id", @@ -13685,8 +13556,8 @@ func makePerMessageTests() []perMessageTest { protocol: protocol, name: "TLS13-ClientCertificate" + suffix, config: Config{ - Certificates: []Certificate{rsaCertificate}, - MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, + MaxVersion: VersionTLS13, }, flags: []string{"-require-any-client-certificate"}, }, @@ -13699,8 +13570,8 @@ func makePerMessageTests() []perMessageTest { protocol: protocol, name: "TLS13-ClientCertificateVerify" + suffix, config: Config{ - Certificates: []Certificate{rsaCertificate}, - MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, + MaxVersion: VersionTLS13, }, flags: []string{"-require-any-client-certificate"}, }, @@ -14595,12 +14466,9 @@ func addTLS13HandshakeTests() { SendRequestContext: []byte("request context"), }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, - shouldFail: true, - expectedError: ":DECODE_ERROR:", + shimCertificate: &rsaCertificate, + shouldFail: true, + expectedError: ":DECODE_ERROR:", }) testCases = append(testCases, testCase{ @@ -14613,10 +14481,7 @@ func addTLS13HandshakeTests() { SendCustomCertificateRequest: 0x1212, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, + shimCertificate: &rsaCertificate, }) testCases = append(testCases, testCase{ @@ -14629,12 +14494,9 @@ func addTLS13HandshakeTests() { OmitCertificateRequestAlgorithms: true, }, }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), - }, - shouldFail: true, - expectedError: ":DECODE_ERROR:", + shimCertificate: &rsaCertificate, + shouldFail: true, + expectedError: ":DECODE_ERROR:", }) testCases = append(testCases, testCase{ @@ -14705,12 +14567,12 @@ func addTLS13HandshakeTests() { testType: clientTest, name: "EarlyData-RejectTicket-Client-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, }, resumeConfig: &Config{ MaxVersion: VersionTLS13, - Certificates: []Certificate{ecdsaP256Certificate}, + Chains: []CertificateChain{ecdsaP256Certificate}, SessionTicketsDisabled: true, }, resumeSession: true, @@ -14721,9 +14583,9 @@ func addTLS13HandshakeTests() { "-on-retry-expect-early-data-reason", "session_not_resumed", // Test the peer certificate is reported correctly in each of the // three logical connections. - "-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), + "-on-initial-expect-peer-cert-file", rsaCertificate.ChainPath, + "-on-resume-expect-peer-cert-file", rsaCertificate.ChainPath, + "-on-retry-expect-peer-cert-file", ecdsaP256Certificate.ChainPath, // Session tickets are disabled, so the runner will not send a ticket. "-on-retry-expect-no-session", }, @@ -14800,12 +14662,12 @@ func addTLS13HandshakeTests() { testType: clientTest, name: "EarlyData-HRR-RejectTicket-Client-TLS13", config: Config{ - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, }, resumeConfig: &Config{ MaxVersion: VersionTLS13, - Certificates: []Certificate{ecdsaP256Certificate}, + Chains: []CertificateChain{ecdsaP256Certificate}, SessionTicketsDisabled: true, Bugs: ProtocolBugs{ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4}, @@ -14821,9 +14683,9 @@ func addTLS13HandshakeTests() { "-on-retry-expect-early-data-reason", "hello_retry_request", // Test the peer certificate is reported correctly in each of the // three logical connections. - "-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), + "-on-initial-expect-peer-cert-file", rsaCertificate.ChainPath, + "-on-resume-expect-peer-cert-file", rsaCertificate.ChainPath, + "-on-retry-expect-peer-cert-file", ecdsaP256Certificate.ChainPath, // Session tickets are disabled, so the runner will not send a ticket. "-on-retry-expect-no-session", }, @@ -15306,7 +15168,7 @@ func addTLS13HandshakeTests() { name: "EarlyDataChannelID-OfferBoth-Server-TLS13", config: Config{ MaxVersion: VersionTLS13, - ChannelID: channelIDKey, + ChannelID: &channelIDKey, }, resumeSession: true, earlyData: true, @@ -15455,19 +15317,18 @@ func addTLS13HandshakeTests() { testType: serverTest, name: "ServerSkipCertificateVerify-TLS13", config: Config{ - MinVersion: VersionTLS13, - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaChainCertificate}, + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaChainCertificate}, Bugs: ProtocolBugs{ SkipCertificateVerify: true, }, }, expectations: connectionExpectations{ - peerCertificate: &rsaChainCertificate, + peerCertificate: &rsaCertificate, }, + shimCertificate: &rsaCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), - "-key-file", path.Join(*resourceDir, rsaChainKeyFile), "-require-any-client-certificate", }, shouldFail: true, @@ -15478,20 +15339,17 @@ func addTLS13HandshakeTests() { testType: clientTest, name: "ClientSkipCertificateVerify-TLS13", config: Config{ - MinVersion: VersionTLS13, - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaChainCertificate}, + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaChainCertificate}, Bugs: ProtocolBugs{ SkipCertificateVerify: true, }, }, expectations: connectionExpectations{ - peerCertificate: &rsaChainCertificate, - }, - flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), - "-key-file", path.Join(*resourceDir, rsaChainKeyFile), + peerCertificate: &rsaCertificate, }, + shimCertificate: &rsaCertificate, shouldFail: true, expectedError: ":UNEXPECTED_MESSAGE:", expectedLocalError: "remote error: unexpected message", @@ -15995,18 +15853,17 @@ func addCertificateTests() { testType: clientTest, name: "SendReceiveIntermediate-Client-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaChainCertificate}, - ClientAuth: RequireAnyClientCert, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaChainCertificate}, + ClientAuth: RequireAnyClientCert, }, expectations: connectionExpectations{ peerCertificate: &rsaChainCertificate, }, + shimCertificate: &rsaChainCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), - "-key-file", path.Join(*resourceDir, rsaChainKeyFile), - "-expect-peer-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), + "-expect-peer-cert-file", rsaChainCertificate.ChainPath, }, }) @@ -16014,18 +15871,17 @@ func addCertificateTests() { testType: serverTest, name: "SendReceiveIntermediate-Server-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaChainCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaChainCertificate}, }, expectations: connectionExpectations{ peerCertificate: &rsaChainCertificate, }, + shimCertificate: &rsaChainCertificate, flags: []string{ - "-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), - "-key-file", path.Join(*resourceDir, rsaChainKeyFile), "-require-any-client-certificate", - "-expect-peer-cert-file", path.Join(*resourceDir, rsaChainCertificateFile), + "-expect-peer-cert-file", rsaChainCertificate.ChainPath, }, }) @@ -16034,9 +15890,9 @@ func addCertificateTests() { testType: clientTest, name: "GarbageCertificate-Client-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{garbageCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{garbageCertificate}, }, shouldFail: true, expectedError: ":CANNOT_PARSE_LEAF_CERT:", @@ -16047,9 +15903,9 @@ func addCertificateTests() { testType: serverTest, name: "GarbageCertificate-Server-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{garbageCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{garbageCertificate}, }, flags: []string{"-require-any-client-certificate"}, shouldFail: true, @@ -16084,9 +15940,9 @@ func addRetainOnlySHA256ClientCertTests() { testType: serverTest, name: "RetainOnlySHA256-Cert-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-verify-peer", @@ -16105,9 +15961,9 @@ func addRetainOnlySHA256ClientCertTests() { testType: serverTest, name: "RetainOnlySHA256-OnOff-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-verify-peer", @@ -16125,9 +15981,9 @@ func addRetainOnlySHA256ClientCertTests() { testType: serverTest, name: "RetainOnlySHA256-OffOn-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{rsaCertificate}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-verify-peer", @@ -16141,19 +15997,13 @@ func addRetainOnlySHA256ClientCertTests() { } func addECDSAKeyUsageTests() { - p256 := elliptic.P256() - priv, err := ecdsa.GenerateKey(p256, rand.Reader) - if err != nil { - panic(err) - } - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { panic(err) } - template := x509.Certificate{ + template := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Acme Co"}, @@ -16168,15 +16018,7 @@ func addECDSAKeyUsageTests() { BasicConstraintsValid: true, } - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - panic(err) - } - - cert := Certificate{ - Certificate: [][]byte{derBytes}, - PrivateKey: priv, - } + cert := generateSingleCertChain(template, &ecdsaP256Key, nil, nil) for _, ver := range tlsVersions { if ver.version < VersionTLS12 { @@ -16187,9 +16029,9 @@ func addECDSAKeyUsageTests() { testType: clientTest, name: "ECDSAKeyUsage-Client-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{cert}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{cert}, }, shouldFail: true, expectedError: ":KEY_USAGE_BIT_INCORRECT:", @@ -16199,9 +16041,9 @@ func addECDSAKeyUsageTests() { testType: serverTest, name: "ECDSAKeyUsage-Server-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{cert}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{cert}, }, flags: []string{"-require-any-client-certificate"}, shouldFail: true, @@ -16243,25 +16085,9 @@ func addRSAKeyUsageTests() { BasicConstraintsValid: true, } - dsDerBytes, err := x509.CreateCertificate(rand.Reader, &dsTemplate, &dsTemplate, &priv.PublicKey, priv) - if err != nil { - panic(err) - } + dsCert := generateSingleCertChain(&dsTemplate, priv, nil, nil) - encDerBytes, err := x509.CreateCertificate(rand.Reader, &encTemplate, &encTemplate, &priv.PublicKey, priv) - if err != nil { - panic(err) - } - - dsCert := Certificate{ - Certificate: [][]byte{dsDerBytes}, - PrivateKey: priv, - } - - encCert := Certificate{ - Certificate: [][]byte{encDerBytes}, - PrivateKey: priv, - } + encCert := generateSingleCertChain(&encTemplate, priv, nil, nil) dsSuites := []uint16{ TLS_AES_128_GCM_SHA256, @@ -16280,7 +16106,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{encCert}, + Chains: []CertificateChain{encCert}, CipherSuites: dsSuites, }, shouldFail: true, @@ -16296,7 +16122,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{dsCert}, + Chains: []CertificateChain{dsCert}, CipherSuites: dsSuites, }, flags: []string{ @@ -16312,7 +16138,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{encCert}, + Chains: []CertificateChain{encCert}, CipherSuites: encSuites, }, flags: []string{ @@ -16326,7 +16152,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{dsCert}, + Chains: []CertificateChain{dsCert}, CipherSuites: encSuites, }, shouldFail: true, @@ -16343,7 +16169,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{dsCert}, + Chains: []CertificateChain{dsCert}, CipherSuites: encSuites, }, flags: []string{"-expect-key-usage-invalid"}, @@ -16355,7 +16181,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{encCert}, + Chains: []CertificateChain{encCert}, CipherSuites: dsSuites, }, flags: []string{"-expect-key-usage-invalid"}, @@ -16370,7 +16196,7 @@ func addRSAKeyUsageTests() { config: Config{ MinVersion: ver.version, MaxVersion: ver.version, - Certificates: []Certificate{encCert}, + Chains: []CertificateChain{encCert}, CipherSuites: dsSuites, }, shouldFail: true, @@ -16383,9 +16209,9 @@ func addRSAKeyUsageTests() { testType: serverTest, name: "RSAKeyUsage-Server-WantSignature-GotEncipherment-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{encCert}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{encCert}, }, shouldFail: true, expectedError: ":KEY_USAGE_BIT_INCORRECT:", @@ -16396,9 +16222,9 @@ func addRSAKeyUsageTests() { testType: serverTest, name: "RSAKeyUsage-Server-WantSignature-GotSignature-" + ver.name, config: Config{ - MinVersion: ver.version, - MaxVersion: ver.version, - Certificates: []Certificate{dsCert}, + MinVersion: ver.version, + MaxVersion: ver.version, + Chains: []CertificateChain{dsCert}, }, flags: []string{"-require-any-client-certificate"}, }) @@ -17062,26 +16888,9 @@ func addJDK11WorkaroundTests() { } func addDelegatedCredentialTests() { - certPath := path.Join(*resourceDir, rsaCertificateFile) - pemBytes, err := os.ReadFile(certPath) - if err != nil { - panic(err) - } - - block, _ := pem.Decode(pemBytes) - if block == nil { - panic(fmt.Sprintf("no PEM block found in %q", certPath)) - } - parentDER := block.Bytes - - rsaPriv, _, err := loadRSAPrivateKey(rsaKeyFile) - if err != nil { - panic(err) - } - ecdsaDC, ecdsaPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{ algo: signatureRSAPSSWithSHA256, - }, parentDER, rsaPriv) + }, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey) if err != nil { panic(err) } @@ -17142,7 +16951,7 @@ func addDelegatedCredentialTests() { _, badTLSVersionPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{ algo: signatureRSAPSSWithSHA256, tlsVersion: 0x1234, - }, parentDER, rsaPriv) + }, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey) if err != nil { panic(err) } @@ -17950,7 +17759,7 @@ write hs 4 protocol: protocol, name: prefix + "ECH-Server-ClientAuth", config: Config{ - Certificates: []Certificate{rsaCertificate}, + Chains: []CertificateChain{rsaCertificate}, ClientECHConfig: echConfig.ECHConfig, }, flags: []string{ @@ -17969,7 +17778,7 @@ write hs 4 protocol: protocol, name: prefix + "ECH-Server-Decline-ClientAuth", config: Config{ - Certificates: []Certificate{rsaCertificate}, + Chains: []CertificateChain{rsaCertificate}, ClientECHConfig: echConfig.ECHConfig, Bugs: ProtocolBugs{ ExpectECHRetryConfigs: CreateECHConfigList(echConfig1.ECHConfig.Raw), @@ -19091,9 +18900,8 @@ write hs 4 ServerECHConfigs: []ServerECHConfig{echConfig}, ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: append([]string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), "-expect-ech-accept", }, flags...), @@ -19111,9 +18919,8 @@ write hs 4 MaxVersion: VersionTLS13, ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: append([]string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), }, flags...), shouldFail: true, @@ -19129,9 +18936,8 @@ write hs 4 MaxVersion: VersionTLS12, ClientAuth: RequireAnyClientCert, }, + shimCertificate: &rsaCertificate, flags: append([]string{ - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), }, flags...), shouldFail: true, @@ -19550,10 +19356,9 @@ func addHintMismatchTests() { signatureRSAPSSWithSHA384, }, }, + shimCertificate: &rsaCertificate, flags: []string{ "-allow-hint-mismatch", - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-on-shim-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA256)), "-on-handshaker-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)), }, @@ -19575,10 +19380,9 @@ func addHintMismatchTests() { signatureRSAPSSWithSHA384, }, }, + shimCertificate: &rsaCertificate, flags: []string{ "-allow-hint-mismatch", - "-cert-file", path.Join(*resourceDir, rsaCertificateFile), - "-key-file", path.Join(*resourceDir, rsaKeyFile), "-on-shim-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA256)), "-on-handshaker-signing-prefs", strconv.Itoa(int(signatureRSAPSSWithSHA384)), }, @@ -19664,9 +19468,9 @@ func addHintMismatchTests() { protocol: protocol, skipSplitHandshake: true, config: Config{ - MinVersion: VersionTLS13, - MaxVersion: VersionTLS13, - Certificates: []Certificate{rsaCertificate}, + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Chains: []CertificateChain{rsaCertificate}, }, flags: []string{ "-allow-hint-mismatch", @@ -19860,28 +19664,28 @@ var testMultipleCertSlotsAlgorithms = []struct { name string cipher uint16 id signatureAlgorithm - cert testCert + cert *CertificateChain // If non-zero, the curve that must be supported in TLS 1.2 for cert to be // accepted. curve CurveID }{ - {"RSA_PKCS1_SHA1", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA1, testCertRSA, 0}, - {"RSA_PKCS1_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA256, testCertRSA, 0}, - {"RSA_PKCS1_SHA384", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA384, testCertRSA, 0}, - {"RSA_PKCS1_SHA512", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA512, testCertRSA, 0}, - {"ECDSA_SHA1", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, signatureECDSAWithSHA1, testCertECDSAP256, CurveP256}, + {"RSA_PKCS1_SHA1", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA1, &rsaCertificate, 0}, + {"RSA_PKCS1_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA256, &rsaCertificate, 0}, + {"RSA_PKCS1_SHA384", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA384, &rsaCertificate, 0}, + {"RSA_PKCS1_SHA512", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPKCS1WithSHA512, &rsaCertificate, 0}, + {"ECDSA_SHA1", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, signatureECDSAWithSHA1, &ecdsaP256Certificate, CurveP256}, // The “P256” in the following line is not a mistake. In TLS 1.2 the // hash function doesn't have to match the curve and so the same // signature algorithm works with P-224. - {"ECDSA_P224_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, signatureECDSAWithP256AndSHA256, testCertECDSAP224, CurveP224}, - {"ECDSA_P256_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, signatureECDSAWithP256AndSHA256, testCertECDSAP256, CurveP256}, - {"RSA_PSS_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPSSWithSHA256, testCertRSA, 0}, - {"RSA_PSS_SHA384", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPSSWithSHA384, testCertRSA, 0}, - {"RSA_PSS_SHA512", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPSSWithSHA512, testCertRSA, 0}, - {"Ed25519", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, signatureEd25519, testCertEd25519, 0}, + {"ECDSA_P224_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, signatureECDSAWithP256AndSHA256, &ecdsaP224Certificate, CurveP224}, + {"ECDSA_P256_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, signatureECDSAWithP256AndSHA256, &ecdsaP256Certificate, CurveP256}, + {"RSA_PSS_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPSSWithSHA256, &rsaCertificate, 0}, + {"RSA_PSS_SHA384", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPSSWithSHA384, &rsaCertificate, 0}, + {"RSA_PSS_SHA512", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, signatureRSAPSSWithSHA512, &rsaCertificate, 0}, + {"Ed25519", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, signatureEd25519, &ed25519Certificate, 0}, // Tests for key types prior to TLS 1.2. - {"RSA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 0, testCertRSA, 0}, - {"ECDSA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 0, testCertECDSAP256, CurveP256}, + {"RSA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 0, &rsaCertificate, 0}, + {"ECDSA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 0, &ecdsaP256Certificate, CurveP256}, } func addMultipleCertSlotTests() { @@ -19893,13 +19697,13 @@ func addMultipleCertSlotTests() { } multipleCertsFlag := "-multiple-certs-slot" rsaCertSlot := []string{ - multipleCertsFlag, path.Join(*resourceDir, getShimCertificate(testCertRSA)) + "," + path.Join(*resourceDir, getShimKey(testCertRSA)), + multipleCertsFlag, rsaCertificate.ChainPath + "," + rsaCertificate.KeyPath, } ecdsaCertSlot := []string{ - multipleCertsFlag, path.Join(*resourceDir, getShimCertificate(testCertECDSAP256)) + "," + path.Join(*resourceDir, getShimKey(testCertECDSAP256)), + multipleCertsFlag, ecdsaP256Certificate.ChainPath + "," + ecdsaP256Certificate.KeyPath, } ed25519CertSlot := []string{ - multipleCertsFlag, path.Join(*resourceDir, getShimCertificate(testCertEd25519)) + "," + path.Join(*resourceDir, getShimKey(testCertEd25519)), + multipleCertsFlag, ed25519Certificate.ChainPath + "," + ed25519Certificate.KeyPath, } certificateSlotFlags := append([]string{}, rsaCertSlot...) certificateSlotFlags = append(certificateSlotFlags, ecdsaCertSlot...) @@ -19927,7 +19731,7 @@ func addMultipleCertSlotTests() { } // SHA-224 has been removed from TLS 1.3 and, in 1.3, // the curve has to match the hash size. - if ver.version >= VersionTLS13 && alg.cert == testCertECDSAP224 { + if ver.version >= VersionTLS13 && alg.cert == &ecdsaP224Certificate { shouldFail = true } @@ -19985,7 +19789,7 @@ func addMultipleCertSlotTests() { MaxVersion: ver.version, VerifySignatureAlgorithms: []signatureAlgorithm{alg.id}, ClientAuth: RequireAnyClientCert, - Certificates: []Certificate{rsaCertificate, ecdsaP256Certificate, ed25519Certificate}, + Chains: []CertificateChain{rsaCertificate, ecdsaP256Certificate, ed25519Certificate}, }, flags: func() []string { flags := append([]string{}, certificateSlotFlags...) @@ -20361,6 +20165,13 @@ var sslTransferHelper *ssl_transfer.TestHelper func main() { flag.Parse() *resourceDir = path.Clean(*resourceDir) + var err error + if tmpDir, err = os.MkdirTemp("", "testing-certs"); err != nil { + fmt.Fprintf(os.Stderr, "failed to make temporary directory: %s", err) + os.Exit(1) + } + defer os.RemoveAll(tmpDir) + initKeys() initCertificates() if len(*shimConfigFile) != 0 { diff --git a/ssl/test/runner/tls.go b/ssl/test/runner/tls.go index 6e57d18197..bd94275ce2 100644 --- a/ssl/test/runner/tls.go +++ b/ssl/test/runner/tls.go @@ -73,7 +73,7 @@ func NewListener(inner net.Listener, config *Config) net.Listener { // The configuration config must be non-nil and must have // at least one certificate. func Listen(network, laddr string, config *Config) (net.Listener, error) { - if config == nil || len(config.Certificates) == 0 { + if config == nil || len(config.Chains) == 0 { return nil, errors.New("tls.Listen: no certificates in configuration") } l, err := net.Listen(network, laddr) @@ -173,7 +173,7 @@ func Dial(network, addr string, config *Config) (*Conn, error) { // LoadX509KeyPair reads and parses a public/private key pair from a pair of // files. The files must contain PEM encoded data. -func LoadX509KeyPair(certFile, keyFile string) (cert Certificate, err error) { +func LoadX509KeyPair(certFile, keyFile string) (cert CertificateChain, err error) { certPEMBlock, err := os.ReadFile(certFile) if err != nil { return @@ -187,7 +187,7 @@ func LoadX509KeyPair(certFile, keyFile string) (cert Certificate, err error) { // X509KeyPair parses a public/private key pair from a pair of // PEM encoded data. -func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (cert Certificate, err error) { +func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (cert CertificateChain, err error) { var certDERBlock *pem.Block for { certDERBlock, certPEMBlock = pem.Decode(certPEMBlock) diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 8e3a0a2520..c73be95297 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -218,6 +218,7 @@ std::vector SortedFlags() { IntVectorFlag("-curves", &TestConfig::curves), StringFlag("-key-file", &TestConfig::key_file), StringFlag("-cert-file", &TestConfig::cert_file), + StringFlag("-trust-cert", &TestConfig::trust_cert), StringFlag("-expect-server-name", &TestConfig::expect_server_name), BoolFlag("-enable-ech-grease", &TestConfig::enable_ech_grease), Base64VectorFlag("-ech-server-config", &TestConfig::ech_server_configs), diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 05f73b3ed7..fbf1c60c8d 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -40,6 +40,7 @@ struct TestConfig { std::vector curves; std::string key_file; std::string cert_file; + std::string trust_cert; std::string expect_server_name; bool enable_ech_grease = false; std::vector ech_server_configs; diff --git a/util/run_android_tests.go b/util/run_android_tests.go index 752b8ab4b4..6f82a104b1 100644 --- a/util/run_android_tests.go +++ b/util/run_android_tests.go @@ -340,23 +340,14 @@ func main() { binaries = append(binaries, "ssl/test/bssl_shim") files = append(files, "BUILDING.md", - "ssl/test/runner/cert.pem", "ssl/test/runner/channel_id_key.pem", - "ssl/test/runner/ecdsa_p224_cert.pem", "ssl/test/runner/ecdsa_p224_key.pem", - "ssl/test/runner/ecdsa_p256_cert.pem", "ssl/test/runner/ecdsa_p256_key.pem", - "ssl/test/runner/ecdsa_p384_cert.pem", "ssl/test/runner/ecdsa_p384_key.pem", - "ssl/test/runner/ecdsa_p521_cert.pem", "ssl/test/runner/ecdsa_p521_key.pem", - "ssl/test/runner/ed25519_cert.pem", "ssl/test/runner/ed25519_key.pem", - "ssl/test/runner/key.pem", - "ssl/test/runner/rsa_1024_cert.pem", + "ssl/test/runner/rsa_2048_key.pem", "ssl/test/runner/rsa_1024_key.pem", - "ssl/test/runner/rsa_chain_cert.pem", - "ssl/test/runner/rsa_chain_key.pem", "util/all_tests.json", ) From 186d177bc19cce0b33c416d5be025aedc0774081 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Wed, 28 Feb 2024 13:15:53 -0500 Subject: [PATCH 6/8] runner: Use go:embed This removes the need to ship the PEM files with the built runner. Instead we can use go:embed to pick up the key files. We do, annoyingly, need to write the Channel ID file to a temporary, but it's not a huge deal. When/if we rework all this to JSON, we can avoid this. Change-Id: Ie0d187a5396546dc157906430639c26b3cc59ca2 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66627 Reviewed-by: Bob Beck Auto-Submit: David Benjamin Commit-Queue: Bob Beck (cherry picked from commit 1e8461cc151960ad941ee7dd0e0bb13337e3c556) --- ssl/test/runner/runner.go | 101 ++++++++++++++++++++------------------ util/run_android_tests.go | 13 ----- 2 files changed, 52 insertions(+), 62 deletions(-) diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 17bc2d61cb..80088cd789 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -24,6 +24,7 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" + _ "embed" "encoding/base64" "encoding/binary" "encoding/hex" @@ -37,7 +38,6 @@ import ( "net" "os" "os/exec" - "path" "path/filepath" "runtime" "strconv" @@ -70,7 +70,6 @@ var ( shimPath = flag.String("shim-path", "../../../build/ssl/test/bssl_shim", "The location of the shim binary.") shimExtraFlags = flag.String("shim-extra-flags", "", "Semicolon-separated extra flags to pass to the shim binary on each invocation.") handshakerPath = flag.String("handshaker-path", "../../../build/ssl/test/handshaker", "The location of the handshaker binary.") - resourceDir = flag.String("resource-dir", ".", "The directory in which to find certificate and key files.") fuzzer = flag.Bool("fuzzer", false, "If true, tests against a BoringSSL built in fuzzer mode.") transcriptDir = flag.String("transcript-dir", "", "The directory in which to write transcripts.") idleTimeout = flag.Duration("idle-timeout", 15*time.Second, "The number of seconds to wait for a read or write to bssl_shim.") @@ -118,19 +117,29 @@ var shimConfig ShimConfiguration = ShimConfiguration{ HalfRTTTickets: 2, } -const ( - rsa2048KeyFile = "rsa_2048_key.pem" - rsa1024KeyFile = "rsa_1024_key.pem" +//go:embed rsa_2048_key.pem +var rsa2048KeyPEM []byte - ecdsaP224KeyFile = "ecdsa_p224_key.pem" - ecdsaP256KeyFile = "ecdsa_p256_key.pem" - ecdsaP384KeyFile = "ecdsa_p384_key.pem" - ecdsaP521KeyFile = "ecdsa_p521_key.pem" +//go:embed rsa_1024_key.pem +var rsa1024KeyPEM []byte - ed25519KeyFile = "ed25519_key.pem" +//go:embed ecdsa_p224_key.pem +var ecdsaP224KeyPEM []byte - channelIDKeyFile = "channel_id_key.pem" -) +//go:embed ecdsa_p256_key.pem +var ecdsaP256KeyPEM []byte + +//go:embed ecdsa_p384_key.pem +var ecdsaP384KeyPEM []byte + +//go:embed ecdsa_p521_key.pem +var ecdsaP521KeyPEM []byte + +//go:embed ed25519_key.pem +var ed25519KeyPEM []byte + +//go:embed channel_id_key.pem +var channelIDKeyPEM []byte var ( rsa1024Key rsa.PrivateKey @@ -146,21 +155,23 @@ var ( channelIDKey ecdsa.PrivateKey ) +var channelIDKeyPath string + func initKeys() { // Since key generation is not particularly cheap (especially RSA), and the - // runner is intended to run on systems which may be resource constrained, + // runner is intended to run on systems which may be resouece constrained, // we load keys from disk instead of dynamically generating them. We treat // key files the same as dynamically generated certificates, writing them // out to temporary files before passing them to the shim. for _, k := range []struct { - path string - key *rsa.PrivateKey + pemBytes []byte + key *rsa.PrivateKey }{ - {rsa1024KeyFile, &rsa1024Key}, - {rsa2048KeyFile, &rsa2048Key}, + {rsa1024KeyPEM, &rsa1024Key}, + {rsa2048KeyPEM, &rsa2048Key}, } { - key, err := loadPEMKey(k.path) + key, err := loadPEMKey(k.pemBytes) if err != nil { panic(fmt.Sprintf("failed to load RSA test key: %s", err)) } @@ -168,27 +179,29 @@ func initKeys() { } for _, k := range []struct { - path string - key *ecdsa.PrivateKey + pemBytes []byte + key *ecdsa.PrivateKey }{ - {ecdsaP224KeyFile, &ecdsaP224Key}, - {ecdsaP256KeyFile, &ecdsaP256Key}, - {ecdsaP384KeyFile, &ecdsaP384Key}, - {ecdsaP521KeyFile, &ecdsaP521Key}, - {channelIDKeyFile, &channelIDKey}, + {ecdsaP224KeyPEM, &ecdsaP224Key}, + {ecdsaP256KeyPEM, &ecdsaP256Key}, + {ecdsaP384KeyPEM, &ecdsaP384Key}, + {ecdsaP521KeyPEM, &ecdsaP521Key}, + {channelIDKeyPEM, &channelIDKey}, } { - key, err := loadPEMKey(k.path) + key, err := loadPEMKey(k.pemBytes) if err != nil { panic(fmt.Sprintf("failed to load ECDSA test key: %s", err)) } *k.key = *(key.(*ecdsa.PrivateKey)) } - k, err := loadPEMKey(ed25519KeyFile) + k, err := loadPEMKey(ed25519KeyPEM) if err != nil { panic(fmt.Sprintf("failed to load Ed25519 test key: %s", err)) } ed25519Key = k.(ed25519.PrivateKey) + + channelIDKeyPath = writeTempKeyFile(&channelIDKey) } var channelIDBytes []byte @@ -213,8 +226,6 @@ var ( garbageCertificate CertificateChain ) -var testCerts []*CertificateChain - func initCertificates() { for _, def := range []struct { key crypto.Signer @@ -229,7 +240,6 @@ func initCertificates() { {ed25519Key, &ed25519Certificate}, } { *def.out = generateSingleCertChain(nil, def.key, testOCSPResponse, testSCTList) - testCerts = append(testCerts, def.out) } channelIDBytes = make([]byte, 64) @@ -299,25 +309,19 @@ type delegatedCredentialConfig struct { algo signatureAlgorithm } -func loadPEMKey(filename string) (crypto.PrivateKey, error) { - pemPath := path.Join(*resourceDir, filename) - pemBytes, err := os.ReadFile(pemPath) - if err != nil { - return nil, err - } - +func loadPEMKey(pemBytes []byte) (crypto.PrivateKey, error) { block, _ := pem.Decode(pemBytes) if block == nil { - return nil, fmt.Errorf("no PEM block found in %q", pemPath) + return nil, fmt.Errorf("no PEM block found") } if block.Type != "PRIVATE KEY" { - return nil, fmt.Errorf("unexpected PEM type in %q (expected \"PRIVATE KEY\"): %s", pemPath, block.Type) + return nil, fmt.Errorf("unexpected PEM type (expected \"PRIVATE KEY\"): %s", block.Type) } k, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("failed to parse PKCS#8 key from %q: %s", pemPath, err) + return nil, fmt.Errorf("failed to parse PKCS#8 key: %s", err) } return k, nil @@ -5892,7 +5896,7 @@ read alert 1 0 NextProtos: []string{"foo"}, }, flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, "-select-next-proto", "foo", }, resumeSession: true, @@ -5948,7 +5952,7 @@ read alert 1 0 MaxVersion: ver.version, RequestChannelID: true, }, - flags: []string{"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile)}, + flags: []string{"-send-channel-id", channelIDKeyPath}, resumeSession: true, expectations: connectionExpectations{ channelID: true, @@ -15117,7 +15121,7 @@ func addTLS13HandshakeTests() { expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:", expectedLocalError: "remote error: illegal parameter", flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, }, }) @@ -15140,7 +15144,7 @@ func addTLS13HandshakeTests() { channelID: true, }, flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, // The client never learns the reason was Channel ID. "-on-retry-expect-early-data-reason", "peer_declined", }, @@ -15157,7 +15161,7 @@ func addTLS13HandshakeTests() { resumeSession: true, earlyData: true, flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, }, }) @@ -18956,7 +18960,7 @@ write hs 4 RequestChannelID: true, }, flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), "-expect-ech-accept", }, @@ -18980,7 +18984,7 @@ write hs 4 }, }, flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), }, shouldFail: true, @@ -19000,7 +19004,7 @@ write hs 4 }, }, flags: []string{ - "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), + "-send-channel-id", channelIDKeyPath, "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), }, shouldFail: true, @@ -20164,7 +20168,6 @@ var sslTransferHelper *ssl_transfer.TestHelper func main() { flag.Parse() - *resourceDir = path.Clean(*resourceDir) var err error if tmpDir, err = os.MkdirTemp("", "testing-certs"); err != nil { fmt.Fprintf(os.Stderr, "failed to make temporary directory: %s", err) diff --git a/util/run_android_tests.go b/util/run_android_tests.go index 6f82a104b1..0f02776fe7 100644 --- a/util/run_android_tests.go +++ b/util/run_android_tests.go @@ -338,19 +338,6 @@ func main() { if enableSSLTests() { binaries = append(binaries, "ssl/test/bssl_shim") - files = append(files, - "BUILDING.md", - "ssl/test/runner/channel_id_key.pem", - "ssl/test/runner/ecdsa_p224_key.pem", - "ssl/test/runner/ecdsa_p256_key.pem", - "ssl/test/runner/ecdsa_p384_key.pem", - "ssl/test/runner/ecdsa_p521_key.pem", - "ssl/test/runner/ed25519_key.pem", - "ssl/test/runner/rsa_2048_key.pem", - "ssl/test/runner/rsa_1024_key.pem", - "util/all_tests.json", - ) - fmt.Printf("Building runner...\n") if err := goTool("test", "-c", "-o", filepath.Join(tmpDir, "ssl/test/runner/runner"), "./ssl/test/runner/"); err != nil { fmt.Printf("Error building runner: %s\n", err) From 20987043d1a299a843217e6ea8e2fa4f57fbfb99 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Fri, 23 Feb 2024 15:02:23 -0500 Subject: [PATCH 7/8] Make DelegatedCredentials-KeyMismatch test less confusing This is passing in a different TLS version, but the TLS version is both nonsense and doesn't figure into the delegated credential anyway. All this test is doing is generating a different keypair and mixing them up. Probably we should move it to ssl_test, as it's not really testing anything about the protocol, but I've just left it alone and fixed the test. Also fix another issue in the test: the getSigner / signMessage chord should just be a plain signMessage call. There were a few other issues of that shape, but they'll be fixed in a follow-up change because they reveal a deeper problem with https://boringssl-review.googlesource.com/c/34884 Change-Id: I090b41a081f694b4ff8d97f3895645d6a620904d Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66549 Commit-Queue: David Benjamin Reviewed-by: Bob Beck (cherry picked from commit 9f376b0694dfb8528fa2200369b48632563e972f) --- ssl/test/runner/runner.go | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 80088cd789..2d1d9d6bbd 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -301,9 +301,6 @@ type delegatedCredentialConfig struct { // dcAlgo is the signature scheme that should be used with this delegated // credential. If zero, ECDSA with P-256 is assumed. dcAlgo signatureAlgorithm - // tlsVersion is the version of TLS that should be used with this delegated - // credential. If zero, TLS 1.3 is assumed. - tlsVersion uint16 // algo is the signature algorithm that the delegated credential itself is // signed with. Cannot be zero. algo signatureAlgorithm @@ -379,14 +376,6 @@ func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byt if lifetimeSecs > 1<<32 { return nil, nil, fmt.Errorf("lifetime %s is too long to be expressed", lifetime) } - tlsVersion := config.tlsVersion - if tlsVersion == 0 { - tlsVersion = VersionTLS13 - } - - if tlsVersion < VersionTLS13 { - return nil, nil, fmt.Errorf("delegated credentials require TLS 1.3") - } // https://www.rfc-editor.org/rfc/rfc9345.html#section-4 dc = append(dc, byte(lifetimeSecs>>24), byte(lifetimeSecs>>16), byte(lifetimeSecs>>8), byte(lifetimeSecs)) @@ -401,12 +390,7 @@ func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byt dc = append(dc, pubBytes...) var dummyConfig Config - parentSigner, err := getSigner(tlsVersion, parentPriv, &dummyConfig, config.algo, false /* not for verification */) - if err != nil { - return nil, nil, err - } - - parentSignature, err := parentSigner.signMessage(parentPriv, &dummyConfig, delegatedCredentialSignedMessage(dc, config.algo, parentDER)) + parentSignature, err := signMessage(VersionTLS13, parentPriv, &dummyConfig, config.algo, delegatedCredentialSignedMessage(dc, config.algo, parentDER)) if err != nil { return nil, nil, err } @@ -16950,26 +16934,17 @@ func addDelegatedCredentialTests() { }, }) - // This flag value has mismatched public and private keys which should cause a - // configuration error in the shim. - _, badTLSVersionPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{ - algo: signatureRSAPSSWithSHA256, - tlsVersion: 0x1234, + // Generate another delegated credential, so we can get the keys out of sync. + _, ecdsaPKCS8Wrong, err := createDelegatedCredential(delegatedCredentialConfig{ + algo: signatureRSAPSSWithSHA256, }, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey) if err != nil { panic(err) } - mismatchFlagValue := fmt.Sprintf("%x,%x", ecdsaDC, badTLSVersionPKCS8) + mismatchFlagValue := fmt.Sprintf("%x,%x", ecdsaDC, ecdsaPKCS8Wrong) testCases = append(testCases, testCase{ testType: serverTest, name: "DelegatedCredentials-KeyMismatch", - config: Config{ - MinVersion: VersionTLS13, - MaxVersion: VersionTLS13, - Bugs: ProtocolBugs{ - FailIfDelegatedCredentials: true, - }, - }, flags: []string{ "-delegated-credential", mismatchFlagValue, }, From 4b14a26a3d133b08c7f033cfd9f937f1e0c86349 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Fri, 23 Feb 2024 17:56:50 -0500 Subject: [PATCH 8/8] Forbid RSA delegated credentials RFC 9345 has this bizarre special case forbiding the rsaEncryption OID for delegated credentials. This doesn't make much sense as DCs already constrain to a single signature algorithm. In fact, they didn't need to use SPKIs at all and could have just encoded the type-specific values. Nonetheless, this is where the spec went up. We have long rejected the RSASSA-PSS OID as being unusably complex, so this effectively means we will never permit RSA delegated credentials. This was another oversight in https://boringssl-review.googlesource.com/c/34884. Fix it separately before everything is reworked to SSL_CREDENTIAL. Bug: 249 Change-Id: I7eae1e8da9da8052b8d985e78388ef8f2b235942 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66567 Reviewed-by: Bob Beck Commit-Queue: David Benjamin (cherry picked from commit c9a9d8d5a90b55bea3ce019465821478e7036077) --- ssl/ssl_cert.cc | 10 ++++++++++ ssl/test/runner/runner.go | 21 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc index cd09d847bc..932b4801cb 100644 --- a/ssl/ssl_cert.cc +++ b/ssl/ssl_cert.cc @@ -859,6 +859,16 @@ UniquePtr DC::Parse(CRYPTO_BUFFER *in, uint8_t *out_alert) { return nullptr; } + // RFC 9345 forbids algorithms that use the rsaEncryption OID. As the + // RSASSA-PSS OID is unusably complicated, this effectively means we will not + // support RSA delegated credentials. + if (SSL_get_signature_algorithm_key_type(dc->dc_cert_verify_algorithm) == + EVP_PKEY_RSA) { + OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM); + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return nullptr; + } + return dc; } diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 2d1d9d6bbd..9d53ab6fbc 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -335,7 +335,7 @@ func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byt switch dcAlgo { case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512: pub = &rsa2048Key.PublicKey - privPKCS8, err = x509.MarshalPKCS8PrivateKey(rsa2048Key) + privPKCS8, err = x509.MarshalPKCS8PrivateKey(&rsa2048Key) if err != nil { return nil, nil, err } @@ -16951,6 +16951,25 @@ func addDelegatedCredentialTests() { shouldFail: true, expectedError: ":KEY_VALUES_MISMATCH:", }) + + // RSA delegated credentials should be rejected at configuration time. + rsaDC, rsaPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{ + algo: signatureRSAPSSWithSHA256, + dcAlgo: signatureRSAPSSWithSHA256, + }, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey) + if err != nil { + panic(err) + } + rsaFlagValue := fmt.Sprintf("%x,%x", rsaDC, rsaPKCS8) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "DelegatedCredentials-NoRSA", + flags: []string{ + "-delegated-credential", rsaFlagValue, + }, + shouldFail: true, + expectedError: ":INVALID_SIGNATURE_ALGORITHM:", + }) } type echCipher struct {