Mirror of BoringSSL (grpc依赖) https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2022 lines
70 KiB

/* Copyright (c) 2014, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include "test_config.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <functional>
#include <limits>
#include <memory>
#include <type_traits>
#include <openssl/base64.h>
#include <openssl/hmac.h>
#include <openssl/hpke.h>
#include <openssl/rand.h>
#include <openssl/span.h>
#include <openssl/ssl.h>
#include "../../crypto/internal.h"
#include "../internal.h"
#include "handshake_util.h"
#include "mock_quic_transport.h"
#include "test_state.h"
namespace {
struct Flag {
const char *name;
bool has_param;
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
// skip_handshaker, if true, causes this flag to be skipped when
// forwarding flags to the handshaker. This should be used with flags
// that only impact connecting to the runner.
bool skip_handshaker;
// If |has_param| is false, |param| will be nullptr.
std::function<bool(TestConfig *config, const char *param)> set_param;
};
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
Flag BoolFlag(const char *name, bool TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, false, skip_handshaker,
[=](TestConfig *config, const char *) -> bool {
config->*field = true;
return true;
}};
}
template <typename T>
bool StringToInt(T *out, const char *str) {
static_assert(std::is_integral<T>::value, "not an integral type");
// |strtoull| allows leading '-' with wraparound. Additionally, both
// functions accept empty strings and leading whitespace.
if (!OPENSSL_isdigit(static_cast<unsigned char>(*str)) &&
(!std::is_signed<T>::value || *str != '-')) {
return false;
}
errno = 0;
char *end;
if (std::is_signed<T>::value) {
static_assert(sizeof(T) <= sizeof(long long),
"type too large for long long");
long long value = strtoll(str, &end, 10);
if (value < static_cast<long long>(std::numeric_limits<T>::min()) ||
value > static_cast<long long>(std::numeric_limits<T>::max())) {
return false;
}
*out = static_cast<T>(value);
} else {
static_assert(sizeof(T) <= sizeof(unsigned long long),
"type too large for unsigned long long");
unsigned long long value = strtoull(str, &end, 10);
if (value >
static_cast<unsigned long long>(std::numeric_limits<T>::max())) {
return false;
}
*out = static_cast<T>(value);
}
// Check for overflow and that the whole input was consumed.
return errno != ERANGE && *end == '\0';
}
template <typename T>
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
Flag IntFlag(const char *name, T TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
return StringToInt(&(config->*field), param);
}};
}
template <typename T>
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
Flag IntVectorFlag(const char *name, std::vector<T> TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
T value;
if (!StringToInt(&value, param)) {
return false;
}
(config->*field).push_back(value);
return true;
}};
}
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
Flag StringFlag(const char *name, std::string TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
config->*field = param;
return true;
}};
}
// TODO(davidben): When we can depend on C++17 or Abseil, switch this to
// std::optional or absl::optional.
Flag OptionalStringFlag(const char *name,
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
std::unique_ptr<std::string> TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
(config->*field) = std::make_unique<std::string>(param);
return true;
}};
}
bool DecodeBase64(std::string *out, const std::string &in) {
size_t len;
if (!EVP_DecodedLength(&len, in.size())) {
fprintf(stderr, "Invalid base64: %s.\n", in.c_str());
return false;
}
std::vector<uint8_t> buf(len);
if (!EVP_DecodeBase64(buf.data(), &len, buf.size(),
reinterpret_cast<const uint8_t *>(in.data()),
in.size())) {
fprintf(stderr, "Invalid base64: %s.\n", in.c_str());
return false;
}
out->assign(reinterpret_cast<const char *>(buf.data()), len);
return true;
}
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
Flag Base64Flag(const char *name, std::string TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
return DecodeBase64(&(config->*field), param);
}};
}
Flag Base64VectorFlag(const char *name,
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
std::vector<std::string> TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
std::string value;
if (!DecodeBase64(&value, param)) {
return false;
}
(config->*field).push_back(std::move(value));
return true;
}};
}
Flag StringPairVectorFlag(
const char *name,
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
std::vector<std::pair<std::string, std::string>> TestConfig::*field,
bool skip_handshaker = false) {
return Flag{name, true, skip_handshaker,
[=](TestConfig *config, const char *param) -> bool {
const char *comma = strchr(param, ',');
if (!comma) {
return false;
}
(config->*field)
.push_back(std::make_pair(std::string(param, comma - param),
std::string(comma + 1)));
return true;
}};
}
std::vector<Flag> SortedFlags() {
std::vector<Flag> flags = {
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
IntFlag("-port", &TestConfig::port, /*skip_handshaker=*/true),
BoolFlag("-ipv6", &TestConfig::ipv6, /*skip_handshaker=*/true),
IntFlag("-shim-id", &TestConfig::shim_id, /*skip_handshaker=*/true),
BoolFlag("-server", &TestConfig::is_server),
BoolFlag("-dtls", &TestConfig::is_dtls),
BoolFlag("-quic", &TestConfig::is_quic),
IntFlag("-resume-count", &TestConfig::resume_count),
StringFlag("-write-settings", &TestConfig::write_settings),
BoolFlag("-fallback-scsv", &TestConfig::fallback_scsv),
IntVectorFlag("-signing-prefs", &TestConfig::signing_prefs),
IntVectorFlag("-verify-prefs", &TestConfig::verify_prefs),
IntVectorFlag("-expect-peer-verify-pref",
&TestConfig::expect_peer_verify_prefs),
IntVectorFlag("-curves", &TestConfig::curves),
StringFlag("-key-file", &TestConfig::key_file),
StringFlag("-cert-file", &TestConfig::cert_file),
StringFlag("-expect-server-name", &TestConfig::expect_server_name),
BoolFlag("-enable-ech-grease", &TestConfig::enable_ech_grease),
Base64VectorFlag("-ech-server-config", &TestConfig::ech_server_configs),
Base64VectorFlag("-ech-server-key", &TestConfig::ech_server_keys),
IntVectorFlag("-ech-is-retry-config", &TestConfig::ech_is_retry_config),
BoolFlag("-expect-ech-accept", &TestConfig::expect_ech_accept),
StringFlag("-expect-ech-name-override",
&TestConfig::expect_ech_name_override),
BoolFlag("-expect-no-ech-name-override",
&TestConfig::expect_no_ech_name_override),
Base64Flag("-expect-ech-retry-configs",
&TestConfig::expect_ech_retry_configs),
BoolFlag("-expect-no-ech-retry-configs",
&TestConfig::expect_no_ech_retry_configs),
Base64Flag("-ech-config-list", &TestConfig::ech_config_list),
Base64Flag("-expect-certificate-types",
&TestConfig::expect_certificate_types),
BoolFlag("-require-any-client-certificate",
&TestConfig::require_any_client_certificate),
StringFlag("-advertise-npn", &TestConfig::advertise_npn),
StringFlag("-expect-next-proto", &TestConfig::expect_next_proto),
BoolFlag("-false-start", &TestConfig::false_start),
StringFlag("-select-next-proto", &TestConfig::select_next_proto),
BoolFlag("-async", &TestConfig::async),
BoolFlag("-write-different-record-sizes",
&TestConfig::write_different_record_sizes),
BoolFlag("-cbc-record-splitting", &TestConfig::cbc_record_splitting),
BoolFlag("-partial-write", &TestConfig::partial_write),
BoolFlag("-no-tls13", &TestConfig::no_tls13),
BoolFlag("-no-tls12", &TestConfig::no_tls12),
BoolFlag("-no-tls11", &TestConfig::no_tls11),
BoolFlag("-no-tls1", &TestConfig::no_tls1),
BoolFlag("-no-ticket", &TestConfig::no_ticket),
Base64Flag("-expect-channel-id", &TestConfig::expect_channel_id),
BoolFlag("-enable-channel-id", &TestConfig::enable_channel_id),
StringFlag("-send-channel-id", &TestConfig::send_channel_id),
BoolFlag("-shim-writes-first", &TestConfig::shim_writes_first),
StringFlag("-host-name", &TestConfig::host_name),
StringFlag("-advertise-alpn", &TestConfig::advertise_alpn),
StringFlag("-expect-alpn", &TestConfig::expect_alpn),
StringFlag("-expect-late-alpn", &TestConfig::expect_late_alpn),
StringFlag("-expect-advertised-alpn",
&TestConfig::expect_advertised_alpn),
StringFlag("-select-alpn", &TestConfig::select_alpn),
BoolFlag("-decline-alpn", &TestConfig::decline_alpn),
BoolFlag("-reject-alpn", &TestConfig::reject_alpn),
BoolFlag("-select-empty-alpn", &TestConfig::select_empty_alpn),
BoolFlag("-defer-alps", &TestConfig::defer_alps),
StringPairVectorFlag("-application-settings",
&TestConfig::application_settings),
OptionalStringFlag("-expect-peer-application-settings",
&TestConfig::expect_peer_application_settings),
Base64Flag("-quic-transport-params", &TestConfig::quic_transport_params),
Base64Flag("-expect-quic-transport-params",
&TestConfig::expect_quic_transport_params),
IntFlag("-quic-use-legacy-codepoint",
&TestConfig::quic_use_legacy_codepoint),
BoolFlag("-expect-session-miss", &TestConfig::expect_session_miss),
BoolFlag("-expect-extended-master-secret",
&TestConfig::expect_extended_master_secret),
StringFlag("-psk", &TestConfig::psk),
StringFlag("-psk-identity", &TestConfig::psk_identity),
StringFlag("-srtp-profiles", &TestConfig::srtp_profiles),
BoolFlag("-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling),
BoolFlag("-enable-signed-cert-timestamps",
&TestConfig::enable_signed_cert_timestamps),
Base64Flag("-expect-signed-cert-timestamps",
&TestConfig::expect_signed_cert_timestamps),
IntFlag("-min-version", &TestConfig::min_version),
IntFlag("-max-version", &TestConfig::max_version),
IntFlag("-expect-version", &TestConfig::expect_version),
IntFlag("-mtu", &TestConfig::mtu),
BoolFlag("-implicit-handshake", &TestConfig::implicit_handshake),
BoolFlag("-use-early-callback", &TestConfig::use_early_callback),
BoolFlag("-fail-early-callback", &TestConfig::fail_early_callback),
BoolFlag("-install-ddos-callback", &TestConfig::install_ddos_callback),
BoolFlag("-fail-ddos-callback", &TestConfig::fail_ddos_callback),
BoolFlag("-fail-cert-callback", &TestConfig::fail_cert_callback),
StringFlag("-cipher", &TestConfig::cipher),
BoolFlag("-handshake-never-done", &TestConfig::handshake_never_done),
IntFlag("-export-keying-material", &TestConfig::export_keying_material),
StringFlag("-export-label", &TestConfig::export_label),
StringFlag("-export-context", &TestConfig::export_context),
BoolFlag("-use-export-context", &TestConfig::use_export_context),
BoolFlag("-tls-unique", &TestConfig::tls_unique),
BoolFlag("-expect-ticket-renewal", &TestConfig::expect_ticket_renewal),
BoolFlag("-expect-no-session", &TestConfig::expect_no_session),
BoolFlag("-expect-ticket-supports-early-data",
&TestConfig::expect_ticket_supports_early_data),
BoolFlag("-expect-accept-early-data",
&TestConfig::expect_accept_early_data),
BoolFlag("-expect-reject-early-data",
&TestConfig::expect_reject_early_data),
BoolFlag("-expect-no-offer-early-data",
&TestConfig::expect_no_offer_early_data),
BoolFlag("-use-ticket-callback", &TestConfig::use_ticket_callback),
BoolFlag("-renew-ticket", &TestConfig::renew_ticket),
BoolFlag("-enable-early-data", &TestConfig::enable_early_data),
Base64Flag("-ocsp-response", &TestConfig::ocsp_response),
Base64Flag("-expect-ocsp-response", &TestConfig::expect_ocsp_response),
BoolFlag("-check-close-notify", &TestConfig::check_close_notify),
BoolFlag("-shim-shuts-down", &TestConfig::shim_shuts_down),
BoolFlag("-verify-fail", &TestConfig::verify_fail),
BoolFlag("-verify-peer", &TestConfig::verify_peer),
BoolFlag("-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc),
BoolFlag("-expect-verify-result", &TestConfig::expect_verify_result),
Base64Flag("-signed-cert-timestamps",
&TestConfig::signed_cert_timestamps),
IntFlag("-expect-total-renegotiations",
&TestConfig::expect_total_renegotiations),
BoolFlag("-renegotiate-once", &TestConfig::renegotiate_once),
BoolFlag("-renegotiate-freely", &TestConfig::renegotiate_freely),
BoolFlag("-renegotiate-ignore", &TestConfig::renegotiate_ignore),
BoolFlag("-renegotiate-explicit", &TestConfig::renegotiate_explicit),
BoolFlag("-forbid-renegotiation-after-handshake",
&TestConfig::forbid_renegotiation_after_handshake),
IntFlag("-expect-peer-signature-algorithm",
&TestConfig::expect_peer_signature_algorithm),
IntFlag("-expect-curve-id", &TestConfig::expect_curve_id),
BoolFlag("-use-old-client-cert-callback",
&TestConfig::use_old_client_cert_callback),
IntFlag("-initial-timeout-duration-ms",
&TestConfig::initial_timeout_duration_ms),
StringFlag("-use-client-ca-list", &TestConfig::use_client_ca_list),
StringFlag("-expect-client-ca-list", &TestConfig::expect_client_ca_list),
BoolFlag("-send-alert", &TestConfig::send_alert),
BoolFlag("-peek-then-read", &TestConfig::peek_then_read),
BoolFlag("-enable-grease", &TestConfig::enable_grease),
BoolFlag("-permute-extensions", &TestConfig::permute_extensions),
IntFlag("-max-cert-list", &TestConfig::max_cert_list),
Base64Flag("-ticket-key", &TestConfig::ticket_key),
BoolFlag("-use-exporter-between-reads",
&TestConfig::use_exporter_between_reads),
IntFlag("-expect-cipher-aes", &TestConfig::expect_cipher_aes),
IntFlag("-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes),
IntFlag("-expect-cipher", &TestConfig::expect_cipher),
StringFlag("-expect-peer-cert-file", &TestConfig::expect_peer_cert_file),
IntFlag("-resumption-delay", &TestConfig::resumption_delay),
BoolFlag("-retain-only-sha256-client-cert",
&TestConfig::retain_only_sha256_client_cert),
BoolFlag("-expect-sha256-client-cert",
&TestConfig::expect_sha256_client_cert),
BoolFlag("-read-with-unfinished-write",
&TestConfig::read_with_unfinished_write),
BoolFlag("-expect-secure-renegotiation",
&TestConfig::expect_secure_renegotiation),
BoolFlag("-expect-no-secure-renegotiation",
&TestConfig::expect_no_secure_renegotiation),
IntFlag("-max-send-fragment", &TestConfig::max_send_fragment),
IntFlag("-read-size", &TestConfig::read_size),
BoolFlag("-expect-session-id", &TestConfig::expect_session_id),
BoolFlag("-expect-no-session-id", &TestConfig::expect_no_session_id),
IntFlag("-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew),
BoolFlag("-no-op-extra-handshake", &TestConfig::no_op_extra_handshake),
BoolFlag("-handshake-twice", &TestConfig::handshake_twice),
BoolFlag("-allow-unknown-alpn-protos",
&TestConfig::allow_unknown_alpn_protos),
BoolFlag("-use-custom-verify-callback",
&TestConfig::use_custom_verify_callback),
StringFlag("-expect-msg-callback", &TestConfig::expect_msg_callback),
BoolFlag("-allow-false-start-without-alpn",
&TestConfig::allow_false_start_without_alpn),
BoolFlag("-handoff", &TestConfig::handoff),
BoolFlag("-handshake-hints", &TestConfig::handshake_hints),
BoolFlag("-allow-hint-mismatch", &TestConfig::allow_hint_mismatch),
BoolFlag("-use-ocsp-callback", &TestConfig::use_ocsp_callback),
BoolFlag("-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback),
BoolFlag("-decline-ocsp-callback", &TestConfig::decline_ocsp_callback),
BoolFlag("-fail-ocsp-callback", &TestConfig::fail_ocsp_callback),
BoolFlag("-install-cert-compression-algs",
&TestConfig::install_cert_compression_algs),
IntFlag("-install-one-cert-compression-alg",
&TestConfig::install_one_cert_compression_alg),
BoolFlag("-reverify-on-resume", &TestConfig::reverify_on_resume),
Default SSL_set_enforce_rsa_key_usage to enabled. This relands https://boringssl-review.googlesource.com/c/boringssl/+/54606, which was temporarily reverted. Update-Note: By default, clients will now require RSA server certificates used in TLS 1.2 and earlier to include the keyEncipherment or digitalSignature bit. keyEncipherment is required if using RSA key exchange. digitalSignature is required if using ECDHE_RSA key exchange. If unsure, TLS RSA server signatures should include both, but some deployments may wish to include only one if separating keys, or simply disabling RSA key exchange. The latter is useful to mitigate either the Bleichenbacher attack (from 1998, most recently resurfaced in 2017 as ROBOT), or to strengthen TLS 1.3 downgrade protections, which is particularly important for enterprise environments using client certificates (aka "mTLS") because, prior to TLS 1.3, the TLS client certificate flow was insufficiently encrypted or authenticated. Without reflecting an RSA key exchange disable into key usage, and then the client checking it, an attacker can spoof a CertificateRequest as coming from some server. This aligns with standard security requirements for using X.509 certificates, specified in RFC 5280, section 4.2.1.3, and reiterated in TLS as early as TLS 1.0, RFC 2246, section 7.4.2, published 24 years ago on January 1999. Constraints on usage of keys are important to mitigate cross-protocol attacks, a class of cryptographic attacks that is well-studied in the literature. We already checked this for each of ECDSA, TLS 1.3, and servers verifying client certificates, so this just fills in the remaining hole. As a result, this change is also important for avoiding some weird behaviors when configuration changes transition a server in or out of this hole. (We've seen connection failures get misattributed to TLS 1.3 when it was really a certificate misconfiguration.) Chrome has also enforced this for some time with publicly-trusted certificates. As a temporary measure for callers that need more time, the SSL_set_enforce_rsa_key_usage API, added to BoringSSL in January 2019, still exists where we need to turn this off. Fixed: 519 Change-Id: I91bf2cfb04c92aec7875e640f90ba6f837146dc1 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58805 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Bob Beck <bbe@google.com>
2 years ago
BoolFlag("-ignore-rsa-key-usage", &TestConfig::ignore_rsa_key_usage),
BoolFlag("-expect-key-usage-invalid",
&TestConfig::expect_key_usage_invalid),
BoolFlag("-is-handshaker-supported",
&TestConfig::is_handshaker_supported),
BoolFlag("-handshaker-resume", &TestConfig::handshaker_resume),
StringFlag("-handshaker-path", &TestConfig::handshaker_path),
BoolFlag("-jdk11-workaround", &TestConfig::jdk11_workaround),
BoolFlag("-server-preference", &TestConfig::server_preference),
BoolFlag("-export-traffic-secrets", &TestConfig::export_traffic_secrets),
BoolFlag("-key-update", &TestConfig::key_update),
BoolFlag("-expect-delegated-credential-used",
&TestConfig::expect_delegated_credential_used),
StringFlag("-delegated-credential", &TestConfig::delegated_credential),
StringFlag("-expect-early-data-reason",
&TestConfig::expect_early_data_reason),
BoolFlag("-expect-hrr", &TestConfig::expect_hrr),
BoolFlag("-expect-no-hrr", &TestConfig::expect_no_hrr),
BoolFlag("-wait-for-debugger", &TestConfig::wait_for_debugger),
StringFlag("-quic-early-data-context",
&TestConfig::quic_early_data_context),
IntFlag("-early-write-after-message",
&TestConfig::early_write_after_message),
BoolFlag("-fips-202205", &TestConfig::fips_202205),
BoolFlag("-wpa-202304", &TestConfig::wpa_202304),
};
std::sort(flags.begin(), flags.end(), [](const Flag &a, const Flag &b) {
return strcmp(a.name, b.name) < 0;
});
return flags;
}
const Flag *FindFlag(const char *name) {
static const std::vector<Flag> kSortedFlags = SortedFlags();
auto iter = std::lower_bound(kSortedFlags.begin(), kSortedFlags.end(), name,
[](const Flag &flag, const char *key) {
return strcmp(flag.name, key) < 0;
});
if (iter == kSortedFlags.end() || strcmp(iter->name, name) != 0) {
return nullptr;
}
return &*iter;
}
// RemovePrefix checks if |*str| begins with |prefix| + "-". If so, it advances
// |*str| past |prefix| (but not past the "-") and returns true. Otherwise, it
// returns false and leaves |*str| unmodified.
bool RemovePrefix(const char **str, const char *prefix) {
size_t prefix_len = strlen(prefix);
if (strncmp(*str, prefix, strlen(prefix)) == 0 && (*str)[prefix_len] == '-') {
*str += strlen(prefix);
return true;
}
return false;
}
} // namespace
bool ParseConfig(int argc, char **argv, bool is_shim,
TestConfig *out_initial,
TestConfig *out_resume,
TestConfig *out_retry) {
for (int i = 0; i < argc; i++) {
bool skip = false;
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
const char *arg = argv[i];
const char *name = arg;
// -on-shim and -on-handshaker prefixes enable flags only on the shim or
// handshaker.
if (RemovePrefix(&name, "-on-shim")) {
if (!is_shim) {
skip = true;
}
} else if (RemovePrefix(&name, "-on-handshaker")) {
if (is_shim) {
skip = true;
}
}
// The following prefixes allow different configurations for each of the
// initial, resumption, and 0-RTT retry handshakes.
TestConfig *out = nullptr;
if (RemovePrefix(&name, "-on-initial")) {
out = out_initial;
} else if (RemovePrefix(&name, "-on-resume")) {
out = out_resume;
} else if (RemovePrefix(&name, "-on-retry")) {
out = out_retry;
}
const Flag *flag = FindFlag(name);
if (flag == nullptr) {
fprintf(stderr, "Unrecognized flag: %s\n", name);
return false;
}
const char *param = nullptr;
if (flag->has_param) {
if (i >= argc) {
fprintf(stderr, "Missing parameter for %s\n", name);
return false;
}
i++;
param = argv[i];
}
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
if (!flag->skip_handshaker) {
out_initial->handshaker_args.push_back(arg);
if (flag->has_param) {
out_initial->handshaker_args.push_back(param);
}
}
if (!skip) {
if (out != nullptr) {
if (!flag->set_param(out, param)) {
fprintf(stderr, "Invalid parameter for %s: %s\n", name, param);
return false;
}
} else {
// Unprefixed flags apply to all three.
if (!flag->set_param(out_initial, param) ||
!flag->set_param(out_resume, param) ||
!flag->set_param(out_retry, param)) {
fprintf(stderr, "Invalid parameter for %s: %s\n", name, param);
return false;
}
}
}
}
Pass IPv6 vs IPv4 down to the shim The runner currently tries to listen on IPv6 and then falls back to IPv4 on error. The shim does the same. If they pick different ones, this breaks down. Normally, fallback happens because the system doesn't have IPv6, and both sides will make the same decision. But if binding to IPv6 fails for other reasons, they may mismatch. We're observing them fail due to what seems to port exhaustion. When this happens, shim and runner don't agree on the same address family. Instead, just tell the shim which address to connect to. This doesn't fix the underlying port exhaustion problem, but it does seem to fix the flakes. Although given we are still exhausting ports and falling back to IPv4, it doesn't truly fix it. Later CLs will address port exhaustion by using a single server port. This changes the runner <-> shim protocol, but hopefully in a fairly obvious way that others using BoGo can easily follow. It shouldn't break our cross-version tests because we keep runner and shim at the same versio there. To avoid needing to make an incompatible change to the shim <-> handshaker protocol, which would impact our cross-version tests, this introduces a mechanism for the shim omit flags when talking to the handshaker. The handshaker doesn't need to know how to connect to the runner. Also print the error string on Windows. Sadly this is a bit tedious. Change-Id: Ic8bda9a854a115c206c05a659a2e34f544b844a6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60885 Auto-Submit: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com>
1 year ago
out_resume->handshaker_args = out_initial->handshaker_args;
out_retry->handshaker_args = out_initial->handshaker_args;
return true;
}
static CRYPTO_once_t once = CRYPTO_ONCE_INIT;
static int g_config_index = 0;
static CRYPTO_BUFFER_POOL *g_pool = nullptr;
static bool InitGlobals() {
CRYPTO_once(&once, [] {
g_config_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
g_pool = CRYPTO_BUFFER_POOL_new();
});
return g_config_index >= 0 && g_pool != nullptr;
}
bool SetTestConfig(SSL *ssl, const TestConfig *config) {
if (!InitGlobals()) {
return false;
}
return SSL_set_ex_data(ssl, g_config_index, (void *)config) == 1;
}
const TestConfig *GetTestConfig(const SSL *ssl) {
if (!InitGlobals()) {
return nullptr;
}
return static_cast<const TestConfig *>(SSL_get_ex_data(ssl, g_config_index));
}
static int LegacyOCSPCallback(SSL *ssl, void *arg) {
const TestConfig *config = GetTestConfig(ssl);
if (!SSL_is_server(ssl)) {
return !config->fail_ocsp_callback;
}
if (!config->ocsp_response.empty() && config->set_ocsp_in_callback &&
!SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
config->ocsp_response.size())) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (config->fail_ocsp_callback) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (config->decline_ocsp_callback) {
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
static int ServerNameCallback(SSL *ssl, int *out_alert, void *arg) {
// SNI must be accessible from the SNI callback.
const TestConfig *config = GetTestConfig(ssl);
const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr ||
std::string(server_name) != config->expect_server_name) {
fprintf(stderr, "servername mismatch (got %s; want %s).\n", server_name,
config->expect_server_name.c_str());
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
return SSL_TLSEXT_ERR_OK;
}
static int NextProtoSelectCallback(SSL *ssl, uint8_t **out, uint8_t *outlen,
const uint8_t *in, unsigned inlen,
void *arg) {
const TestConfig *config = GetTestConfig(ssl);
if (config->select_next_proto.empty()) {
return SSL_TLSEXT_ERR_NOACK;
}
*out = (uint8_t *)config->select_next_proto.data();
*outlen = config->select_next_proto.size();
return SSL_TLSEXT_ERR_OK;
}
static int NextProtosAdvertisedCallback(SSL *ssl, const uint8_t **out,
unsigned int *out_len, void *arg) {
const TestConfig *config = GetTestConfig(ssl);
if (config->advertise_npn.empty()) {
return SSL_TLSEXT_ERR_NOACK;
}
if (config->advertise_npn.size() > UINT_MAX) {
fprintf(stderr, "NPN value too large.\n");
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
*out = reinterpret_cast<const uint8_t *>(config->advertise_npn.data());
*out_len = static_cast<unsigned>(config->advertise_npn.size());
return SSL_TLSEXT_ERR_OK;
}
static void MessageCallback(int is_write, int version, int content_type,
const void *buf, size_t len, SSL *ssl, void *arg) {
const uint8_t *buf_u8 = reinterpret_cast<const uint8_t *>(buf);
const TestConfig *config = GetTestConfig(ssl);
TestState *state = GetTestState(ssl);
if (!state->msg_callback_ok) {
return;
}
if (content_type == SSL3_RT_HEADER) {
size_t header_len =
config->is_dtls ? DTLS1_RT_HEADER_LENGTH : SSL3_RT_HEADER_LENGTH;
if (len != header_len) {
fprintf(stderr, "Incorrect length for record header: %zu.\n", len);
state->msg_callback_ok = false;
}
return;
}
state->msg_callback_text += is_write ? "write " : "read ";
switch (content_type) {
case 0:
if (version != SSL2_VERSION) {
fprintf(stderr, "Incorrect version for V2ClientHello: %x.\n",
static_cast<unsigned>(version));
state->msg_callback_ok = false;
return;
}
state->msg_callback_text += "v2clienthello\n";
return;
case SSL3_RT_CLIENT_HELLO_INNER:
case SSL3_RT_HANDSHAKE: {
CBS cbs;
CBS_init(&cbs, buf_u8, len);
uint8_t type;
uint32_t msg_len;
if (!CBS_get_u8(&cbs, &type) ||
// TODO(davidben): Reporting on entire messages would be more
// consistent than fragments.
(config->is_dtls &&
!CBS_skip(&cbs, 3 /* total */ + 2 /* seq */ + 3 /* frag_off */)) ||
!CBS_get_u24(&cbs, &msg_len) || !CBS_skip(&cbs, msg_len) ||
CBS_len(&cbs) != 0) {
fprintf(stderr, "Could not parse handshake message.\n");
state->msg_callback_ok = false;
return;
}
char text[16];
if (content_type == SSL3_RT_CLIENT_HELLO_INNER) {
if (type != SSL3_MT_CLIENT_HELLO) {
fprintf(stderr, "Invalid header for ClientHelloInner.\n");
state->msg_callback_ok = false;
return;
}
state->msg_callback_text += "clienthelloinner\n";
} else {
snprintf(text, sizeof(text), "hs %d\n", type);
state->msg_callback_text += text;
if (!is_write) {
state->last_message_received = type;
}
4 years ago
}
return;
}
case SSL3_RT_CHANGE_CIPHER_SPEC:
if (len != 1 || buf_u8[0] != 1) {
fprintf(stderr, "Invalid ChangeCipherSpec.\n");
state->msg_callback_ok = false;
return;
}
state->msg_callback_text += "ccs\n";
return;
case SSL3_RT_ALERT:
if (len != 2) {
fprintf(stderr, "Invalid alert.\n");
state->msg_callback_ok = false;
return;
}
char text[16];
snprintf(text, sizeof(text), "alert %d %d\n", buf_u8[0], buf_u8[1]);
state->msg_callback_text += text;
return;
default:
fprintf(stderr, "Invalid content_type: %d.\n", content_type);
state->msg_callback_ok = false;
}
}
static int TicketKeyCallback(SSL *ssl, uint8_t *key_name, uint8_t *iv,
EVP_CIPHER_CTX *ctx, HMAC_CTX *hmac_ctx,
int encrypt) {
if (!encrypt) {
if (GetTestState(ssl)->ticket_decrypt_done) {
fprintf(stderr, "TicketKeyCallback called after completion.\n");
return -1;
}
GetTestState(ssl)->ticket_decrypt_done = true;
}
// This is just test code, so use the all-zeros key.
static const uint8_t kZeros[16] = {0};
if (encrypt) {
OPENSSL_memcpy(key_name, kZeros, sizeof(kZeros));
RAND_bytes(iv, 16);
} else if (OPENSSL_memcmp(key_name, kZeros, 16) != 0) {
return 0;
}
if (!HMAC_Init_ex(hmac_ctx, kZeros, sizeof(kZeros), EVP_sha256(), NULL) ||
!EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, kZeros, iv, encrypt)) {
return -1;
}
if (!encrypt) {
return GetTestConfig(ssl)->renew_ticket ? 2 : 1;
}
return 1;
}
static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) {
// This callback is called as the handshake completes. |SSL_get_session|
// must continue to work and, historically, |SSL_in_init| returned false at
// this point.
if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
fprintf(stderr, "Invalid state for NewSessionCallback.\n");
abort();
}
GetTestState(ssl)->got_new_session = true;
GetTestState(ssl)->new_session.reset(session);
return 1;
}
static void InfoCallback(const SSL *ssl, int type, int val) {
if (type == SSL_CB_HANDSHAKE_DONE) {
if (GetTestConfig(ssl)->handshake_never_done) {
fprintf(stderr, "Handshake unexpectedly completed.\n");
// Abort before any expected error code is printed, to ensure the overall
// test fails.
abort();
}
// This callback is called when the handshake completes. |SSL_get_session|
// must continue to work and |SSL_in_init| must return false.
if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
fprintf(stderr, "Invalid state for SSL_CB_HANDSHAKE_DONE.\n");
abort();
}
GetTestState(ssl)->handshake_done = true;
}
}
static SSL_SESSION *GetSessionCallback(SSL *ssl, const uint8_t *data, int len,
int *copy) {
TestState *async_state = GetTestState(ssl);
if (async_state->session) {
*copy = 0;
return async_state->session.release();
} else if (async_state->pending_session) {
return SSL_magic_pending_session_ptr();
} else {
return NULL;
}
}
static void CurrentTimeCallback(const SSL *ssl, timeval *out_clock) {
*out_clock = *GetClock();
}
static int AlpnSelectCallback(SSL *ssl, const uint8_t **out, uint8_t *outlen,
const uint8_t *in, unsigned inlen, void *arg) {
if (GetTestState(ssl)->alpn_select_done) {
fprintf(stderr, "AlpnSelectCallback called after completion.\n");
exit(1);
}
GetTestState(ssl)->alpn_select_done = true;
const TestConfig *config = GetTestConfig(ssl);
if (config->decline_alpn) {
return SSL_TLSEXT_ERR_NOACK;
}
if (config->reject_alpn) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (!config->expect_advertised_alpn.empty() &&
(config->expect_advertised_alpn.size() != inlen ||
OPENSSL_memcmp(config->expect_advertised_alpn.data(), in, inlen) !=
0)) {
fprintf(stderr, "bad ALPN select callback inputs.\n");
exit(1);
}
if (config->defer_alps) {
for (const auto &pair : config->application_settings) {
if (!SSL_add_application_settings(
ssl, reinterpret_cast<const uint8_t *>(pair.first.data()),
pair.first.size(),
reinterpret_cast<const uint8_t *>(pair.second.data()),
pair.second.size())) {
fprintf(stderr, "error configuring ALPS.\n");
exit(1);
}
}
}
assert(config->select_alpn.empty() || !config->select_empty_alpn);
*out = (const uint8_t *)config->select_alpn.data();
*outlen = config->select_alpn.size();
return SSL_TLSEXT_ERR_OK;
}
static bool CheckVerifyCallback(SSL *ssl) {
const TestConfig *config = GetTestConfig(ssl);
if (!config->expect_ocsp_response.empty()) {
const uint8_t *data;
size_t len;
SSL_get0_ocsp_response(ssl, &data, &len);
if (len == 0) {
fprintf(stderr, "OCSP response not available in verify callback.\n");
return false;
}
}
Implement ClientHelloOuter handshakes. If a client offers ECH, but the server rejects it, the client completes the handshake with ClientHelloOuter in order to authenticate retry keys. Implement this flow. This is largely allowing the existing handshake to proceed, but with some changes: - Certificate verification uses the other name. This CL routes this up to the built-in verifier and adds SSL_get0_ech_name_override for the callback. - We need to disable False Start to pick up server Finished in TLS 1.2. - Client certificates, notably in TLS 1.3 where they're encrypted, should only be revealed to the true server. Fortunately, not sending client certs is always an option, so do that. Channel ID has a similar issue. I've just omitted the extension in ClientHelloOuter because it's deprecated and is unlikely to be used with ECH at this point. ALPS may be worth some pondering but, the way it's currently used, is not sensitive. (Possibly we should change the draft to terminate the handshake before even sending that flight...) - The session is never offered in ClientHelloOuter, but our internal book-keeping doesn't quite notice. I had to replace ech_accept with a tri-state ech_status to correctly handle an edge case in SSL_get0_ech_name_override: when ECH + 0-RTT + reverify_on_resume are all enabled, the first certificate verification is for the 0-RTT session and should be against the true name, yet we have selected_ech_config && !ech_accept. A tri-state tracks when ECH is actually rejected. I've maintained this on the server as well, though the server never actually cares. Bug: 275 Change-Id: Ie55966ca3dc4ffcc8c381479f0fe9bcacd34d0f8 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48135 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
3 years ago
const char *name_override;
size_t name_override_len;
SSL_get0_ech_name_override(ssl, &name_override, &name_override_len);
if (config->expect_no_ech_name_override && name_override_len != 0) {
fprintf(stderr, "Unexpected ECH name override.\n");
return false;
}
if (!config->expect_ech_name_override.empty() &&
config->expect_ech_name_override !=
std::string(name_override, name_override_len)) {
fprintf(stderr, "ECH name did not match expected value.\n");
return false;
}
if (GetTestState(ssl)->cert_verified) {
fprintf(stderr, "Certificate verified twice.\n");
return false;
}
return true;
}
static int CertVerifyCallback(X509_STORE_CTX *store_ctx, void *arg) {
SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(
store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
const TestConfig *config = GetTestConfig(ssl);
if (!CheckVerifyCallback(ssl)) {
return 0;
}
GetTestState(ssl)->cert_verified = true;
if (config->verify_fail) {
X509_STORE_CTX_set_error(store_ctx, X509_V_ERR_APPLICATION_VERIFICATION);
return 0;
}
return 1;
}
bool LoadCertificate(bssl::UniquePtr<X509> *out_x509,
bssl::UniquePtr<STACK_OF(X509)> *out_chain,
const std::string &file) {
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
return false;
}
out_x509->reset(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
if (!*out_x509) {
return false;
}
out_chain->reset(sk_X509_new_null());
if (!*out_chain) {
return false;
}
// Keep reading the certificate chain.
for (;;) {
bssl::UniquePtr<X509> cert(
PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
if (!cert) {
break;
}
if (!bssl::PushToStack(out_chain->get(), std::move(cert))) {
return false;
}
}
uint32_t err = ERR_peek_last_error();
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
return false;
}
ERR_clear_error();
return true;
}
bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) {
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
return nullptr;
}
return bssl::UniquePtr<EVP_PKEY>(
PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL));
}
static bool GetCertificate(SSL *ssl, bssl::UniquePtr<X509> *out_x509,
bssl::UniquePtr<STACK_OF(X509)> *out_chain,
bssl::UniquePtr<EVP_PKEY> *out_pkey) {
const TestConfig *config = GetTestConfig(ssl);
if (!config->signing_prefs.empty()) {
if (!SSL_set_signing_algorithm_prefs(ssl, config->signing_prefs.data(),
config->signing_prefs.size())) {
return false;
}
}
if (!config->key_file.empty()) {
*out_pkey = LoadPrivateKey(config->key_file.c_str());
if (!*out_pkey) {
return false;
}
}
if (!config->cert_file.empty() &&
!LoadCertificate(out_x509, out_chain, config->cert_file.c_str())) {
return false;
}
if (!config->ocsp_response.empty() && !config->set_ocsp_in_callback &&
!SSL_set_ocsp_response(ssl, (const uint8_t *)config->ocsp_response.data(),
config->ocsp_response.size())) {
return false;
}
return true;
}
static bool HexDecode(std::string *out, const std::string &in) {
if ((in.size() & 1) != 0) {
return false;
}
auto buf = std::make_unique<uint8_t[]>(in.size() / 2);
for (size_t i = 0; i < in.size() / 2; i++) {
uint8_t high, low;
if (!OPENSSL_fromxdigit(&high, in[i * 2]) ||
!OPENSSL_fromxdigit(&low, in[i * 2 + 1])) {
return false;
}
buf[i] = (high << 4) | low;
}
out->assign(reinterpret_cast<const char *>(buf.get()), in.size() / 2);
return true;
}
static std::vector<std::string> SplitParts(const std::string &in,
const char delim) {
std::vector<std::string> ret;
size_t start = 0;
for (size_t i = 0; i < in.size(); i++) {
if (in[i] == delim) {
ret.push_back(in.substr(start, i - start));
start = i + 1;
}
}
ret.push_back(in.substr(start, std::string::npos));
return ret;
}
static std::vector<std::string> DecodeHexStrings(
const std::string &hex_strings) {
std::vector<std::string> ret;
const std::vector<std::string> parts = SplitParts(hex_strings, ',');
for (const auto &part : parts) {
std::string binary;
if (!HexDecode(&binary, part)) {
fprintf(stderr, "Bad hex string: %s.\n", part.c_str());
return ret;
}
ret.push_back(binary);
}
return ret;
}
static bssl::UniquePtr<STACK_OF(X509_NAME)> DecodeHexX509Names(
const std::string &hex_names) {
const std::vector<std::string> der_names = DecodeHexStrings(hex_names);
bssl::UniquePtr<STACK_OF(X509_NAME)> ret(sk_X509_NAME_new_null());
if (!ret) {
return nullptr;
}
for (const auto &der_name : der_names) {
const uint8_t *const data =
reinterpret_cast<const uint8_t *>(der_name.data());
const uint8_t *derp = data;
bssl::UniquePtr<X509_NAME> name(
d2i_X509_NAME(nullptr, &derp, der_name.size()));
if (!name || derp != data + der_name.size()) {
fprintf(stderr, "Failed to parse X509_NAME.\n");
return nullptr;
}
if (!bssl::PushToStack(ret.get(), std::move(name))) {
return nullptr;
}
}
return ret;
}
static bool CheckPeerVerifyPrefs(SSL *ssl) {
const TestConfig *config = GetTestConfig(ssl);
if (!config->expect_peer_verify_prefs.empty()) {
const uint16_t *peer_sigalgs;
size_t num_peer_sigalgs =
SSL_get0_peer_verify_algorithms(ssl, &peer_sigalgs);
if (config->expect_peer_verify_prefs.size() != num_peer_sigalgs) {
fprintf(stderr,
"peer verify preferences length mismatch (got %zu, wanted %zu)\n",
num_peer_sigalgs, config->expect_peer_verify_prefs.size());
return false;
}
for (size_t i = 0; i < num_peer_sigalgs; i++) {
if (peer_sigalgs[i] != config->expect_peer_verify_prefs[i]) {
fprintf(stderr,
"peer verify preference %zu mismatch (got %04x, wanted %04x\n",
i, peer_sigalgs[i], config->expect_peer_verify_prefs[i]);
return false;
}
}
}
return true;
}
static bool CheckCertificateRequest(SSL *ssl) {
const TestConfig *config = GetTestConfig(ssl);
if (!CheckPeerVerifyPrefs(ssl)) {
return false;
}
if (!config->expect_certificate_types.empty()) {
const uint8_t *certificate_types;
size_t certificate_types_len =
SSL_get0_certificate_types(ssl, &certificate_types);
if (certificate_types_len != config->expect_certificate_types.size() ||
OPENSSL_memcmp(certificate_types,
config->expect_certificate_types.data(),
certificate_types_len) != 0) {
fprintf(stderr, "certificate types mismatch.\n");
return false;
}
}
if (!config->expect_client_ca_list.empty()) {
bssl::UniquePtr<STACK_OF(X509_NAME)> expected =
DecodeHexX509Names(config->expect_client_ca_list);
const size_t num_expected = sk_X509_NAME_num(expected.get());
const STACK_OF(X509_NAME) *received = SSL_get_client_CA_list(ssl);
const size_t num_received = sk_X509_NAME_num(received);
if (num_received != num_expected) {
Fix the easy -Wformat-signedness errors. GCC has a warning that complains about even more type mismatches in printf. Some of these are a bit messy and will be fixed in separate CLs. This covers the easy ones. The .*s stuff is unfortunate, but printf has no size_t-clean string printer. ALPN protocol lengths are bound by uint8_t, so it doesn't really matter. The IPv6 printing one is obnoxious and arguably a false positive. It's really a C language flaw: all types smaller than int get converted to int when you do arithmetic. So something like this first doesn't overflow the shift because it computes over int, but then the result overall is stored as an int. uint8_t a, b; (a << 8) | b On the one hand, this fixes a "missing" cast to uint16_t before the shift. At the same time, the incorrect final type means passing it to %x, which expects unsigned int. The compiler has forgotten this value actually fits in uint16_t and flags a warning. Mitigate this by storing in a uint16_t first. The story doesn't quite end here. Arguments passed to variadic functions go through integer promotion[0], so the argument is still passed to snprintf as an int! But then va_arg allows for a signedness mismatch[1], provided the value is representable in both types. The combination means that %x, though actually paired with unsigned, also accept uint8_t and uint16_t, because those are guaranteed to promote to an int that meets [1]. GCC recognizes [1] applies here. (There's also PRI16x, but that's a bit tedious to use and, in glibc, is defined as plain "x" anyway.) [0] https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions [1] https://en.cppreference.com/w/c/variadic/va_arg Bug: 450 Change-Id: Ic1d41356755a18ab922956dd2e07b560470341f4 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50765 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com>
3 years ago
fprintf(stderr, "expected %zu names in CertificateRequest but got %zu.\n",
num_expected, num_received);
return false;
}
for (size_t i = 0; i < num_received; i++) {
if (X509_NAME_cmp(sk_X509_NAME_value(received, i),
sk_X509_NAME_value(expected.get(), i)) != 0) {
Fix the easy -Wformat-signedness errors. GCC has a warning that complains about even more type mismatches in printf. Some of these are a bit messy and will be fixed in separate CLs. This covers the easy ones. The .*s stuff is unfortunate, but printf has no size_t-clean string printer. ALPN protocol lengths are bound by uint8_t, so it doesn't really matter. The IPv6 printing one is obnoxious and arguably a false positive. It's really a C language flaw: all types smaller than int get converted to int when you do arithmetic. So something like this first doesn't overflow the shift because it computes over int, but then the result overall is stored as an int. uint8_t a, b; (a << 8) | b On the one hand, this fixes a "missing" cast to uint16_t before the shift. At the same time, the incorrect final type means passing it to %x, which expects unsigned int. The compiler has forgotten this value actually fits in uint16_t and flags a warning. Mitigate this by storing in a uint16_t first. The story doesn't quite end here. Arguments passed to variadic functions go through integer promotion[0], so the argument is still passed to snprintf as an int! But then va_arg allows for a signedness mismatch[1], provided the value is representable in both types. The combination means that %x, though actually paired with unsigned, also accept uint8_t and uint16_t, because those are guaranteed to promote to an int that meets [1]. GCC recognizes [1] applies here. (There's also PRI16x, but that's a bit tedious to use and, in glibc, is defined as plain "x" anyway.) [0] https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions [1] https://en.cppreference.com/w/c/variadic/va_arg Bug: 450 Change-Id: Ic1d41356755a18ab922956dd2e07b560470341f4 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50765 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com>
3 years ago
fprintf(stderr, "names in CertificateRequest differ at index #%zu.\n",
i);
return false;
}
}
const STACK_OF(CRYPTO_BUFFER) *buffers = SSL_get0_server_requested_CAs(ssl);
if (sk_CRYPTO_BUFFER_num(buffers) != num_received) {
fprintf(stderr,
"Mismatch between SSL_get_server_requested_CAs and "
"SSL_get_client_CA_list.\n");
return false;
}
}
return true;
}
static int ClientCertCallback(SSL *ssl, X509 **out_x509, EVP_PKEY **out_pkey) {
if (!CheckCertificateRequest(ssl)) {
return -1;
}
if (GetTestConfig(ssl)->async && !GetTestState(ssl)->cert_ready) {
return -1;
}
bssl::UniquePtr<X509> x509;
bssl::UniquePtr<STACK_OF(X509)> chain;
bssl::UniquePtr<EVP_PKEY> pkey;
if (!GetCertificate(ssl, &x509, &chain, &pkey)) {
return -1;
}
// Return zero for no certificate.
if (!x509) {
return 0;
}
// Chains and asynchronous private keys are not supported with client_cert_cb.
*out_x509 = x509.release();
*out_pkey = pkey.release();
return 1;
}
static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
size_t *out_len,
size_t max_out);
static ssl_private_key_result_t AsyncPrivateKeySign(
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
TestState *test_state = GetTestState(ssl);
test_state->used_private_key = true;
if (!test_state->private_key_result.empty()) {
fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
abort();
}
if (EVP_PKEY_id(test_state->private_key.get()) !=
SSL_get_signature_algorithm_key_type(signature_algorithm)) {
fprintf(stderr, "Key type does not match signature algorithm.\n");
abort();
}
// Determine the hash.
const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm);
bssl::ScopedEVP_MD_CTX ctx;
EVP_PKEY_CTX *pctx;
if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr,
test_state->private_key.get())) {
return ssl_private_key_failure;
}
// Configure additional signature parameters.
if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
!EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1 /* salt len = hash len */)) {
return ssl_private_key_failure;
}
}
// Write the signature into |test_state|.
size_t len = 0;
if (!EVP_DigestSign(ctx.get(), nullptr, &len, in, in_len)) {
return ssl_private_key_failure;
}
test_state->private_key_result.resize(len);
if (!EVP_DigestSign(ctx.get(), test_state->private_key_result.data(), &len,
in, in_len)) {
return ssl_private_key_failure;
}
test_state->private_key_result.resize(len);
return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
}
static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
size_t *out_len,
size_t max_out,
const uint8_t *in,
size_t in_len) {
TestState *test_state = GetTestState(ssl);
test_state->used_private_key = true;
if (!test_state->private_key_result.empty()) {
fprintf(stderr, "AsyncPrivateKeyDecrypt called with operation pending.\n");
abort();
}
RSA *rsa = EVP_PKEY_get0_RSA(test_state->private_key.get());
if (rsa == NULL) {
fprintf(stderr, "AsyncPrivateKeyDecrypt called with incorrect key type.\n");
abort();
}
test_state->private_key_result.resize(RSA_size(rsa));
if (!RSA_decrypt(rsa, out_len, test_state->private_key_result.data(),
RSA_size(rsa), in, in_len, RSA_NO_PADDING)) {
return ssl_private_key_failure;
}
test_state->private_key_result.resize(*out_len);
return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
}
static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
size_t *out_len,
size_t max_out) {
TestState *test_state = GetTestState(ssl);
if (test_state->private_key_result.empty()) {
fprintf(stderr,
"AsyncPrivateKeyComplete called without operation pending.\n");
abort();
}
if (GetTestConfig(ssl)->async && test_state->private_key_retries < 2) {
// Only return the decryption on the second attempt, to test both incomplete
// |sign|/|decrypt| and |complete|.
return ssl_private_key_retry;
}
if (max_out < test_state->private_key_result.size()) {
fprintf(stderr, "Output buffer too small.\n");
return ssl_private_key_failure;
}
OPENSSL_memcpy(out, test_state->private_key_result.data(),
test_state->private_key_result.size());
*out_len = test_state->private_key_result.size();
test_state->private_key_result.clear();
test_state->private_key_retries = 0;
return ssl_private_key_success;
}
static const SSL_PRIVATE_KEY_METHOD g_async_private_key_method = {
AsyncPrivateKeySign,
AsyncPrivateKeyDecrypt,
AsyncPrivateKeyComplete,
};
static bool InstallCertificate(SSL *ssl) {
bssl::UniquePtr<X509> x509;
bssl::UniquePtr<STACK_OF(X509)> chain;
bssl::UniquePtr<EVP_PKEY> pkey;
if (!GetCertificate(ssl, &x509, &chain, &pkey)) {
return false;
}
if (pkey) {
TestState *test_state = GetTestState(ssl);
const TestConfig *config = GetTestConfig(ssl);
if (config->async || config->handshake_hints) {
// Install a custom private key if testing asynchronous callbacks, or if
// testing handshake hints. In the handshake hints case, we wish to check
// that hints only mismatch when allowed.
test_state->private_key = std::move(pkey);
SSL_set_private_key_method(ssl, &g_async_private_key_method);
} else if (!SSL_use_PrivateKey(ssl, pkey.get())) {
return false;
}
}
if (x509 && !SSL_use_certificate(ssl, x509.get())) {
return false;
}
if (sk_X509_num(chain.get()) > 0 && !SSL_set1_chain(ssl, chain.get())) {
return false;
}
return true;
}
static enum ssl_select_cert_result_t SelectCertificateCallback(
const SSL_CLIENT_HELLO *client_hello) {
SSL *ssl = client_hello->ssl;
const TestConfig *config = GetTestConfig(ssl);
TestState *test_state = GetTestState(ssl);
test_state->early_callback_called = true;
if (!config->expect_server_name.empty()) {
const char *server_name =
SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (server_name == nullptr ||
std::string(server_name) != config->expect_server_name) {
fprintf(stderr,
"Server name mismatch in early callback (got %s; want %s).\n",
server_name, config->expect_server_name.c_str());
return ssl_select_cert_error;
}
}
if (config->fail_early_callback) {
return ssl_select_cert_error;
}
// Simulate some asynchronous work in the early callback.
if ((config->use_early_callback || test_state->get_handshake_hints_cb) &&
config->async && !test_state->early_callback_ready) {
return ssl_select_cert_retry;
}
if (test_state->get_handshake_hints_cb &&
!test_state->get_handshake_hints_cb(client_hello)) {
return ssl_select_cert_error;
}
if (config->use_early_callback && !InstallCertificate(ssl)) {
return ssl_select_cert_error;
}
return ssl_select_cert_success;
}
static int SetQuicReadSecret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len) {
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->SetReadSecret(level, cipher, secret, secret_len);
}
static int SetQuicWriteSecret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len) {
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->SetWriteSecret(level, cipher, secret, secret_len);
}
static int AddQuicHandshakeData(SSL *ssl, enum ssl_encryption_level_t level,
const uint8_t *data, size_t len) {
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->WriteHandshakeData(level, data, len);
}
static int FlushQuicFlight(SSL *ssl) {
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->Flush();
}
static int SendQuicAlert(SSL *ssl, enum ssl_encryption_level_t level,
uint8_t alert) {
MockQuicTransport *quic_transport = GetTestState(ssl)->quic_transport.get();
if (quic_transport == nullptr) {
fprintf(stderr, "No QUIC transport.\n");
return 0;
}
return quic_transport->SendAlert(level, alert);
}
static const SSL_QUIC_METHOD g_quic_method = {
SetQuicReadSecret,
SetQuicWriteSecret,
AddQuicHandshakeData,
FlushQuicFlight,
SendQuicAlert,
};
static bool MaybeInstallCertCompressionAlg(
const TestConfig *config, SSL_CTX *ssl_ctx, uint16_t alg,
ssl_cert_compression_func_t compress,
ssl_cert_decompression_func_t decompress) {
if (!config->install_cert_compression_algs &&
config->install_one_cert_compression_alg != alg) {
return true;
}
return SSL_CTX_add_cert_compression_alg(ssl_ctx, alg, compress, decompress);
}
bssl::UniquePtr<SSL_CTX> TestConfig::SetupCtx(SSL_CTX *old_ctx) const {
if (!InitGlobals()) {
return nullptr;
}
bssl::UniquePtr<SSL_CTX> ssl_ctx(
SSL_CTX_new(is_dtls ? DTLS_method() : TLS_method()));
if (!ssl_ctx) {
return nullptr;
}
SSL_CTX_set0_buffer_pool(ssl_ctx.get(), g_pool);
std::string cipher_list = "ALL:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256";
if (!cipher.empty()) {
cipher_list = cipher;
SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
}
if (!SSL_CTX_set_strict_cipher_list(ssl_ctx.get(), cipher_list.c_str())) {
return nullptr;
}
if (async && is_server) {
// Disable the internal session cache. To test asynchronous session lookup,
// we use an external session cache.
SSL_CTX_set_session_cache_mode(
ssl_ctx.get(), SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_sess_set_get_cb(ssl_ctx.get(), GetSessionCallback);
} else {
SSL_CTX_set_session_cache_mode(ssl_ctx.get(), SSL_SESS_CACHE_BOTH);
}
SSL_CTX_set_select_certificate_cb(ssl_ctx.get(), SelectCertificateCallback);
if (use_old_client_cert_callback) {
SSL_CTX_set_client_cert_cb(ssl_ctx.get(), ClientCertCallback);
}
SSL_CTX_set_next_protos_advertised_cb(ssl_ctx.get(),
NextProtosAdvertisedCallback, NULL);
if (!select_next_proto.empty()) {
SSL_CTX_set_next_proto_select_cb(ssl_ctx.get(), NextProtoSelectCallback,
NULL);
}
if (!select_alpn.empty() || decline_alpn || reject_alpn ||
select_empty_alpn) {
SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), AlpnSelectCallback, NULL);
}
SSL_CTX_set_current_time_cb(ssl_ctx.get(), CurrentTimeCallback);
SSL_CTX_set_info_callback(ssl_ctx.get(), InfoCallback);
SSL_CTX_sess_set_new_cb(ssl_ctx.get(), NewSessionCallback);
if (use_ticket_callback || handshake_hints) {
// If using handshake hints, always enable the ticket callback, so we can
// check that hints only mismatch when allowed. The ticket callback also
// uses a constant key, which simplifies the test.
SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx.get(), TicketKeyCallback);
}
if (!use_custom_verify_callback) {
SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), CertVerifyCallback, NULL);
}
if (!signed_cert_timestamps.empty() &&
!SSL_CTX_set_signed_cert_timestamp_list(
ssl_ctx.get(), (const uint8_t *)signed_cert_timestamps.data(),
signed_cert_timestamps.size())) {
return nullptr;
}
if (!use_client_ca_list.empty()) {
if (use_client_ca_list == "<NULL>") {
SSL_CTX_set_client_CA_list(ssl_ctx.get(), nullptr);
} else if (use_client_ca_list == "<EMPTY>") {
bssl::UniquePtr<STACK_OF(X509_NAME)> names;
SSL_CTX_set_client_CA_list(ssl_ctx.get(), names.release());
} else {
bssl::UniquePtr<STACK_OF(X509_NAME)> names =
DecodeHexX509Names(use_client_ca_list);
SSL_CTX_set_client_CA_list(ssl_ctx.get(), names.release());
}
}
if (enable_grease) {
SSL_CTX_set_grease_enabled(ssl_ctx.get(), 1);
}
if (permute_extensions) {
SSL_CTX_set_permute_extensions(ssl_ctx.get(), 1);
}
if (!expect_server_name.empty()) {
SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), ServerNameCallback);
}
if (enable_early_data) {
SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);
}
if (allow_unknown_alpn_protos) {
SSL_CTX_set_allow_unknown_alpn_protos(ssl_ctx.get(), 1);
}
if (!verify_prefs.empty()) {
if (!SSL_CTX_set_verify_algorithm_prefs(ssl_ctx.get(), verify_prefs.data(),
verify_prefs.size())) {
return nullptr;
}
}
SSL_CTX_set_msg_callback(ssl_ctx.get(), MessageCallback);
if (allow_false_start_without_alpn) {
SSL_CTX_set_false_start_allowed_without_alpn(ssl_ctx.get(), 1);
}
if (use_ocsp_callback) {
SSL_CTX_set_tlsext_status_cb(ssl_ctx.get(), LegacyOCSPCallback);
}
if (old_ctx) {
uint8_t keys[48];
if (!SSL_CTX_get_tlsext_ticket_keys(old_ctx, &keys, sizeof(keys)) ||
!SSL_CTX_set_tlsext_ticket_keys(ssl_ctx.get(), keys, sizeof(keys))) {
return nullptr;
}
CopySessions(ssl_ctx.get(), old_ctx);
} else if (!ticket_key.empty() &&
!SSL_CTX_set_tlsext_ticket_keys(ssl_ctx.get(), ticket_key.data(),
ticket_key.size())) {
return nullptr;
}
// These mock compression algorithms match the corresponding ones in
// |addCertCompressionTests|.
if (!MaybeInstallCertCompressionAlg(
this, ssl_ctx.get(), 0xff02,
[](SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) -> int {
if (!CBB_add_u8(out, 1) || !CBB_add_u8(out, 2) ||
!CBB_add_u8(out, 3) || !CBB_add_u8(out, 4) ||
!CBB_add_bytes(out, in, in_len)) {
return 0;
}
return 1;
},
[](SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
const uint8_t *in, size_t in_len) -> int {
if (in_len < 4 || in[0] != 1 || in[1] != 2 || in[2] != 3 ||
in[3] != 4 || uncompressed_len != in_len - 4) {
return 0;
}
const bssl::Span<const uint8_t> uncompressed(in + 4, in_len - 4);
*out = CRYPTO_BUFFER_new(uncompressed.data(), uncompressed.size(),
nullptr);
return *out != nullptr;
}) ||
!MaybeInstallCertCompressionAlg(
this, ssl_ctx.get(), 0xff01,
[](SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) -> int {
if (in_len < 2 || in[0] != 0 || in[1] != 0) {
return 0;
}
return CBB_add_bytes(out, in + 2, in_len - 2);
},
[](SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
const uint8_t *in, size_t in_len) -> int {
if (uncompressed_len != 2 + in_len) {
return 0;
}
auto buf = std::make_unique<uint8_t[]>(2 + in_len);
buf[0] = 0;
buf[1] = 0;
OPENSSL_memcpy(&buf[2], in, in_len);
*out = CRYPTO_BUFFER_new(buf.get(), 2 + in_len, nullptr);
return *out != nullptr;
}) ||
!MaybeInstallCertCompressionAlg(
this, ssl_ctx.get(), 0xff03,
[](SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) -> int {
uint8_t byte;
return RAND_bytes(&byte, 1) && //
CBB_add_u8(out, byte) && //
CBB_add_bytes(out, in, in_len);
},
[](SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
const uint8_t *in, size_t in_len) -> int {
if (uncompressed_len + 1 != in_len) {
return 0;
}
*out = CRYPTO_BUFFER_new(in + 1, in_len - 1, nullptr);
return *out != nullptr;
})) {
fprintf(stderr, "SSL_CTX_add_cert_compression_alg failed.\n");
abort();
}
if (server_preference) {
SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
}
if (is_quic) {
SSL_CTX_set_quic_method(ssl_ctx.get(), &g_quic_method);
}
return ssl_ctx;
}
static int DDoSCallback(const SSL_CLIENT_HELLO *client_hello) {
const TestConfig *config = GetTestConfig(client_hello->ssl);
return config->fail_ddos_callback ? 0 : 1;
}
static unsigned PskClientCallback(SSL *ssl, const char *hint,
char *out_identity, unsigned max_identity_len,
uint8_t *out_psk, unsigned max_psk_len) {
const TestConfig *config = GetTestConfig(ssl);
if (config->psk_identity.empty()) {
if (hint != nullptr) {
fprintf(stderr, "Server PSK hint was non-null.\n");
return 0;
}
} else if (hint == nullptr ||
strcmp(hint, config->psk_identity.c_str()) != 0) {
fprintf(stderr, "Server PSK hint did not match.\n");
return 0;
}
// Account for the trailing '\0' for the identity.
if (config->psk_identity.size() >= max_identity_len ||
config->psk.size() > max_psk_len) {
fprintf(stderr, "PSK buffers too small.\n");
return 0;
}
OPENSSL_strlcpy(out_identity, config->psk_identity.c_str(), max_identity_len);
OPENSSL_memcpy(out_psk, config->psk.data(), config->psk.size());
return static_cast<unsigned>(config->psk.size());
}
static unsigned PskServerCallback(SSL *ssl, const char *identity,
uint8_t *out_psk, unsigned max_psk_len) {
const TestConfig *config = GetTestConfig(ssl);
if (strcmp(identity, config->psk_identity.c_str()) != 0) {
fprintf(stderr, "Client PSK identity did not match.\n");
return 0;
}
if (config->psk.size() > max_psk_len) {
fprintf(stderr, "PSK buffers too small.\n");
return 0;
}
OPENSSL_memcpy(out_psk, config->psk.data(), config->psk.size());
return static_cast<unsigned>(config->psk.size());
}
static ssl_verify_result_t CustomVerifyCallback(SSL *ssl, uint8_t *out_alert) {
const TestConfig *config = GetTestConfig(ssl);
if (!CheckVerifyCallback(ssl)) {
return ssl_verify_invalid;
}
if (config->async && !GetTestState(ssl)->custom_verify_ready) {
return ssl_verify_retry;
}
GetTestState(ssl)->cert_verified = true;
if (config->verify_fail) {
return ssl_verify_invalid;
}
return ssl_verify_ok;
}
static int CertCallback(SSL *ssl, void *arg) {
const TestConfig *config = GetTestConfig(ssl);
// Check the peer certificate metadata is as expected.
if ((!SSL_is_server(ssl) && !CheckCertificateRequest(ssl)) ||
!CheckPeerVerifyPrefs(ssl)) {
return -1;
}
if (config->fail_cert_callback) {
return 0;
}
// The certificate will be installed via other means.
if (!config->async || config->use_early_callback) {
return 1;
}
if (!GetTestState(ssl)->cert_ready) {
return -1;
}
if (!InstallCertificate(ssl)) {
return 0;
}
return 1;
}
bssl::UniquePtr<SSL> TestConfig::NewSSL(
SSL_CTX *ssl_ctx, SSL_SESSION *session,
std::unique_ptr<TestState> test_state) const {
bssl::UniquePtr<SSL> ssl(SSL_new(ssl_ctx));
if (!ssl) {
return nullptr;
}
if (!SetTestConfig(ssl.get(), this)) {
return nullptr;
}
if (test_state != nullptr) {
if (!SetTestState(ssl.get(), std::move(test_state))) {
return nullptr;
}
}
if (fallback_scsv && !SSL_set_mode(ssl.get(), SSL_MODE_SEND_FALLBACK_SCSV)) {
return nullptr;
}
// Install the certificate synchronously if nothing else will handle it.
if (!use_early_callback && !use_old_client_cert_callback && !async &&
!InstallCertificate(ssl.get())) {
return nullptr;
}
if (!use_old_client_cert_callback) {
SSL_set_cert_cb(ssl.get(), CertCallback, nullptr);
}
int mode = SSL_VERIFY_NONE;
if (require_any_client_certificate) {
mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
if (verify_peer) {
mode = SSL_VERIFY_PEER;
}
if (verify_peer_if_no_obc) {
// Set SSL_VERIFY_FAIL_IF_NO_PEER_CERT so testing whether client
// certificates were requested is easy.
mode = SSL_VERIFY_PEER | SSL_VERIFY_PEER_IF_NO_OBC |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
if (use_custom_verify_callback) {
SSL_set_custom_verify(ssl.get(), mode, CustomVerifyCallback);
} else if (mode != SSL_VERIFY_NONE) {
SSL_set_verify(ssl.get(), mode, NULL);
}
if (false_start) {
SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_FALSE_START);
}
if (cbc_record_splitting) {
SSL_set_mode(ssl.get(), SSL_MODE_CBC_RECORD_SPLITTING);
}
if (partial_write) {
SSL_set_mode(ssl.get(), SSL_MODE_ENABLE_PARTIAL_WRITE);
}
if (reverify_on_resume) {
SSL_CTX_set_reverify_on_resume(ssl_ctx, 1);
}
Default SSL_set_enforce_rsa_key_usage to enabled. This relands https://boringssl-review.googlesource.com/c/boringssl/+/54606, which was temporarily reverted. Update-Note: By default, clients will now require RSA server certificates used in TLS 1.2 and earlier to include the keyEncipherment or digitalSignature bit. keyEncipherment is required if using RSA key exchange. digitalSignature is required if using ECDHE_RSA key exchange. If unsure, TLS RSA server signatures should include both, but some deployments may wish to include only one if separating keys, or simply disabling RSA key exchange. The latter is useful to mitigate either the Bleichenbacher attack (from 1998, most recently resurfaced in 2017 as ROBOT), or to strengthen TLS 1.3 downgrade protections, which is particularly important for enterprise environments using client certificates (aka "mTLS") because, prior to TLS 1.3, the TLS client certificate flow was insufficiently encrypted or authenticated. Without reflecting an RSA key exchange disable into key usage, and then the client checking it, an attacker can spoof a CertificateRequest as coming from some server. This aligns with standard security requirements for using X.509 certificates, specified in RFC 5280, section 4.2.1.3, and reiterated in TLS as early as TLS 1.0, RFC 2246, section 7.4.2, published 24 years ago on January 1999. Constraints on usage of keys are important to mitigate cross-protocol attacks, a class of cryptographic attacks that is well-studied in the literature. We already checked this for each of ECDSA, TLS 1.3, and servers verifying client certificates, so this just fills in the remaining hole. As a result, this change is also important for avoiding some weird behaviors when configuration changes transition a server in or out of this hole. (We've seen connection failures get misattributed to TLS 1.3 when it was really a certificate misconfiguration.) Chrome has also enforced this for some time with publicly-trusted certificates. As a temporary measure for callers that need more time, the SSL_set_enforce_rsa_key_usage API, added to BoringSSL in January 2019, still exists where we need to turn this off. Fixed: 519 Change-Id: I91bf2cfb04c92aec7875e640f90ba6f837146dc1 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58805 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Bob Beck <bbe@google.com>
2 years ago
if (ignore_rsa_key_usage) {
SSL_set_enforce_rsa_key_usage(ssl.get(), 0);
}
if (no_tls13) {
SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_3);
}
if (no_tls12) {
SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_2);
}
if (no_tls11) {
SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1_1);
}
if (no_tls1) {
SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1);
}
if (no_ticket) {
SSL_set_options(ssl.get(), SSL_OP_NO_TICKET);
}
if (!expect_channel_id.empty() || enable_channel_id) {
SSL_set_tls_channel_id_enabled(ssl.get(), 1);
}
if (enable_ech_grease) {
SSL_set_enable_ech_grease(ssl.get(), 1);
}
if (static_cast<int>(fips_202205) + static_cast<int>(wpa_202304) > 1) {
fprintf(stderr, "Multiple policy options given\n");
return nullptr;
}
if (fips_202205 && !SSL_set_compliance_policy(
ssl.get(), ssl_compliance_policy_fips_202205)) {
fprintf(stderr, "SSL_set_compliance_policy failed\n");
return nullptr;
}
if (wpa_202304 && !SSL_set_compliance_policy(
ssl.get(), ssl_compliance_policy_wpa3_192_202304)) {
fprintf(stderr, "SSL_set_compliance_policy failed\n");
return nullptr;
}
Add most of an ECH client implementation. Based on an initial implementation by Dan McArdle at https://boringssl-review.googlesource.com/c/boringssl/+/46784 This CL contains most of a client implementation for draft-ietf-tls-esni-10. The pieces missing so far, which will be done in follow-up CLs are: 1. While the ClientHelloInner is padded, the server Certificate message is not. I'll add that once we resolve the spec discussions on how to do that. (We were originally going to use TLS record-level padding, but that doesn't work well with QUIC.) 2. The client should check the public name is a valid DNS name before copying it into ClientHelloOuter.server_name. 3. The ClientHelloOuter handshake flow is not yet implemented. This CL can detect when the server selects ClientHelloOuter, but for now the handshake immediately fails. A follow-up CL will remove that logic and instead add the APIs and extra checks needed. Otherwise, this should be complete, including padding and compression. The main interesting point design-wise is that we run through ClientHello construction multiple times. We need to construct ClientHelloInner and ClientHelloOuter. Then each of those has slight variants: EncodedClientHelloInner is the compressed form, and ClientHelloOuterAAD just has the ECH extension erased to avoid a circular dependency. I've computed ClientHelloInner and EncodedClientHelloInner concurrently because the compression scheme requires shifting the extensions around to be contiguous. However, I've computed ClientHelloOuterAAD and ClientHelloOuter by running through the logic twice. This probably can be done better, but the next draft revises the construction anyway, so I'm thinking I'll rework it then. (In the next draft, we use a placeholder payload of the same length, so we can construct the ClientHello once and fill in the payload.) Additionally, now that we have a client available in ssl_test, this adds a threading test to confirm that SSL_CTX_set1_ech_keys is properly synchronized. (Confirmed that, if I drop the lock in SSL_CTX_set1_ech_keys, TSan notices.) Change-Id: Icaff68b595035bdcc73c468ff638e67c84239ef4 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48004 Reviewed-by: Adam Langley <agl@google.com>
4 years ago
if (!ech_config_list.empty() &&
!SSL_set1_ech_config_list(
ssl.get(), reinterpret_cast<const uint8_t *>(ech_config_list.data()),
ech_config_list.size())) {
return nullptr;
}
if (ech_server_configs.size() != ech_server_keys.size() ||
ech_server_configs.size() != ech_is_retry_config.size()) {
fprintf(stderr,
"-ech-server-config, -ech-server-key, and -ech-is-retry-config "
"flags must match.\n");
return nullptr;
}
if (!ech_server_configs.empty()) {
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
if (!keys) {
return nullptr;
}
for (size_t i = 0; i < ech_server_configs.size(); i++) {
const std::string &ech_config = ech_server_configs[i];
const std::string &ech_private_key = ech_server_keys[i];
const int is_retry_config = ech_is_retry_config[i];
bssl::ScopedEVP_HPKE_KEY key;
if (!EVP_HPKE_KEY_init(
key.get(), EVP_hpke_x25519_hkdf_sha256(),
reinterpret_cast<const uint8_t *>(ech_private_key.data()),
ech_private_key.size()) ||
!SSL_ECH_KEYS_add(
keys.get(), is_retry_config,
reinterpret_cast<const uint8_t *>(ech_config.data()),
ech_config.size(), key.get())) {
return nullptr;
}
}
if (!SSL_CTX_set1_ech_keys(ssl_ctx, keys.get())) {
return nullptr;
}
}
if (!send_channel_id.empty()) {
bssl::UniquePtr<EVP_PKEY> pkey = LoadPrivateKey(send_channel_id);
if (!pkey || !SSL_set1_tls_channel_id(ssl.get(), pkey.get())) {
return nullptr;
}
}
if (!host_name.empty() &&
!SSL_set_tlsext_host_name(ssl.get(), host_name.c_str())) {
return nullptr;
}
if (!advertise_alpn.empty() &&
SSL_set_alpn_protos(
ssl.get(), reinterpret_cast<const uint8_t *>(advertise_alpn.data()),
advertise_alpn.size()) != 0) {
return nullptr;
}
if (!defer_alps) {
for (const auto &pair : application_settings) {
if (!SSL_add_application_settings(
ssl.get(), reinterpret_cast<const uint8_t *>(pair.first.data()),
pair.first.size(),
reinterpret_cast<const uint8_t *>(pair.second.data()),
pair.second.size())) {
return nullptr;
}
}
}
if (!psk.empty()) {
SSL_set_psk_client_callback(ssl.get(), PskClientCallback);
SSL_set_psk_server_callback(ssl.get(), PskServerCallback);
}
if (!psk_identity.empty() &&
!SSL_use_psk_identity_hint(ssl.get(), psk_identity.c_str())) {
return nullptr;
}
if (!srtp_profiles.empty() &&
!SSL_set_srtp_profiles(ssl.get(), srtp_profiles.c_str())) {
return nullptr;
}
if (enable_ocsp_stapling) {
SSL_enable_ocsp_stapling(ssl.get());
}
if (enable_signed_cert_timestamps) {
SSL_enable_signed_cert_timestamps(ssl.get());
}
if (min_version != 0 &&
!SSL_set_min_proto_version(ssl.get(), min_version)) {
return nullptr;
}
if (max_version != 0 &&
!SSL_set_max_proto_version(ssl.get(), max_version)) {
return nullptr;
}
if (mtu != 0) {
SSL_set_options(ssl.get(), SSL_OP_NO_QUERY_MTU);
SSL_set_mtu(ssl.get(), mtu);
}
if (install_ddos_callback) {
SSL_CTX_set_dos_protection_cb(ssl_ctx, DDoSCallback);
}
SSL_set_shed_handshake_config(ssl.get(), true);
if (renegotiate_once) {
SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_once);
}
if (renegotiate_freely || forbid_renegotiation_after_handshake) {
// |forbid_renegotiation_after_handshake| will disable renegotiation later.
SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
}
if (renegotiate_ignore) {
SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_ignore);
}
if (renegotiate_explicit) {
SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_explicit);
}
if (!check_close_notify) {
SSL_set_quiet_shutdown(ssl.get(), 1);
}
Align NIDs vs group IDs in TLS group APIs Right now we use NIDs to configure the group list, but group IDs (the TLS codepoints) to return the negotiated group. The NIDs come from OpenSSL, while the group ID was original our API. OpenSSL has since added SSL_get_negotiated_group, but we don't implement it. To add Kyber to QUIC, we'll need to add an API for configuring groups to QUICHE. Carrying over our inconsistency into QUICHE's public API would be unfortunate, so let's use this as the time to align things. We could either align with OpenSSL and say NIDs are now the group representation at the public API, or we could add a parallel group ID API. (Or we could make a whole new SSL_NAMED_GROUP object to pattern after SSL_CIPHER, which isn't wrong, but is even more new APIs.) Aligning with OpenSSL would be fewer APIs, but NIDs aren't a great representation. The numbers are ad-hoc and even diverge a bit between OpenSSL and BoringSSL. The TLS codepoints are better to export out to callers. Also QUICHE has exported the negotiated group using the codepoints, so the natural solution would be to use codepoints on input too. Thus, this CL adds SSL_CTX_set1_group_ids and SSL_set1_group_ids. It also rearranges the API docs slightly to put the group ID ones first, and leaves a little note about the NID representation before introducing those. While I'm here, I've added SSL_get_negotiated_group. NGINX seems to use it when available, so we may as well fill in that unnecessary compatibility hole. Bug: chromium:1442377 Change-Id: I47ca8ae52c274133f28da9893aed7fc70f942bf8 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60208 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Adam Langley <agl@google.com>
1 year ago
if (!curves.empty() &&
!SSL_set1_group_ids(ssl.get(), curves.data(), curves.size())) {
return nullptr;
}
if (initial_timeout_duration_ms > 0) {
DTLSv1_set_initial_timeout_duration(ssl.get(), initial_timeout_duration_ms);
}
if (max_cert_list > 0) {
SSL_set_max_cert_list(ssl.get(), max_cert_list);
}
if (retain_only_sha256_client_cert) {
SSL_set_retain_only_sha256_of_client_certs(ssl.get(), 1);
}
if (max_send_fragment > 0) {
SSL_set_max_send_fragment(ssl.get(), max_send_fragment);
}
if (quic_use_legacy_codepoint != -1) {
SSL_set_quic_use_legacy_codepoint(ssl.get(), quic_use_legacy_codepoint);
}
if (!quic_transport_params.empty()) {
if (!SSL_set_quic_transport_params(
ssl.get(),
reinterpret_cast<const uint8_t *>(quic_transport_params.data()),
quic_transport_params.size())) {
return nullptr;
}
}
if (jdk11_workaround) {
SSL_set_jdk11_workaround(ssl.get(), 1);
}
if (session != NULL) {
if (!is_server) {
if (SSL_set_session(ssl.get(), session) != 1) {
return nullptr;
}
} else if (async) {
// The internal session cache is disabled, so install the session
// manually.
SSL_SESSION_up_ref(session);
GetTestState(ssl.get())->pending_session.reset(session);
}
}
if (!delegated_credential.empty()) {
std::string::size_type comma = delegated_credential.find(',');
if (comma == std::string::npos) {
fprintf(stderr,
"failed to find comma in delegated credential argument.\n");
return nullptr;
}
const std::string dc_hex = delegated_credential.substr(0, comma);
const std::string pkcs8_hex = delegated_credential.substr(comma + 1);
std::string dc, pkcs8;
if (!HexDecode(&dc, dc_hex) || !HexDecode(&pkcs8, pkcs8_hex)) {
fprintf(stderr, "failed to hex decode delegated credential argument.\n");
return nullptr;
}
CBS dc_cbs(bssl::Span<const uint8_t>(
reinterpret_cast<const uint8_t *>(dc.data()), dc.size()));
CBS pkcs8_cbs(bssl::Span<const uint8_t>(
reinterpret_cast<const uint8_t *>(pkcs8.data()), pkcs8.size()));
bssl::UniquePtr<EVP_PKEY> priv(EVP_parse_private_key(&pkcs8_cbs));
if (!priv) {
fprintf(stderr, "failed to parse delegated credential private key.\n");
return nullptr;
}
bssl::UniquePtr<CRYPTO_BUFFER> dc_buf(
CRYPTO_BUFFER_new_from_CBS(&dc_cbs, nullptr));
if (!SSL_set1_delegated_credential(ssl.get(), dc_buf.get(),
priv.get(), nullptr)) {
fprintf(stderr, "SSL_set1_delegated_credential failed.\n");
return nullptr;
}
}
if (!quic_early_data_context.empty() &&
!SSL_set_quic_early_data_context(
ssl.get(),
reinterpret_cast<const uint8_t *>(quic_early_data_context.data()),
quic_early_data_context.size())) {
return nullptr;
}
return ssl;
}