gRPC core: strip zone-id from IPv6 hosts before TLS verification

When initiating a connection to an IPv6 peer using an address that is
not globally scoped, there may be ambiguity regarding which zone the
destination address applies to when multiple links of the same scope
are present. The scoped address architecture and zone-id syntax are
described in rfc4007 and rfc 6874, respectively:

  * https://tools.ietf.org/html/rfc4007#section-6
  * https://tools.ietf.org/html/rfc6874

This patch allows host name verification performed during TLS session
establishment, and on a per-call basis, to work correctly when the peer
presents a certificate with a non-global IPv6 address listed as one of
its alternate names. Whether arbitrary certificate authorities choose
issue certificates of this nature, or not, is outside the scope of gRPC.

The zone-id is separated from the address using a percent (%) character.
It is considered a system implementation detail and guidance suggests it
be stripped from any paths or addresses egressing a host because it is
irrelevant and meaningless otherwise. It would not make sense for a
server to present a certificate containing non-global IPv6 addresses
with zone-ids present nor would it work unless two hosts happened to
be using the same zone-id.

ssl_host_matches_name is prefixed with grpc_ because it has been
promoted to the global namespace for testing.

Resolves #14371
pull/14387/head
David Cowden 7 years ago
parent 8acece911a
commit 116fd29a36
No known key found for this signature in database
GPG Key ID: FBB5103A9A0ED951
  1. 23
      src/core/lib/security/security_connector/security_connector.cc
  2. 1
      src/core/lib/security/security_connector/security_connector.h
  3. 21
      test/core/security/security_connector_test.cc

@ -786,17 +786,20 @@ static void ssl_server_add_handshakers(grpc_server_security_connector* sc,
tsi_create_adapter_handshaker(tsi_hs), &sc->base));
}
static int ssl_host_matches_name(const tsi_peer* peer, const char* peer_name) {
int grpc_ssl_host_matches_name(const tsi_peer* peer, const char* peer_name) {
char* allocated_name = nullptr;
int r;
if (strchr(peer_name, ':') != nullptr) {
char* ignored_port;
gpr_split_host_port(peer_name, &allocated_name, &ignored_port);
gpr_free(ignored_port);
peer_name = allocated_name;
if (!peer_name) return 0;
}
char* ignored_port;
gpr_split_host_port(peer_name, &allocated_name, &ignored_port);
gpr_free(ignored_port);
peer_name = allocated_name;
if (!peer_name) return 0;
// IPv6 zone-id should not be included in comparisons.
char* const zone_id = strchr(allocated_name, '%');
if (zone_id != nullptr) *zone_id = '\0';
r = tsi_ssl_peer_matches_name(peer, peer_name);
gpr_free(allocated_name);
return r;
@ -859,7 +862,7 @@ static grpc_error* ssl_check_peer(grpc_security_connector* sc,
}
/* Check the peer name if specified. */
if (peer_name != nullptr && !ssl_host_matches_name(peer, peer_name)) {
if (peer_name != nullptr && !grpc_ssl_host_matches_name(peer, peer_name)) {
char* msg;
gpr_asprintf(&msg, "Peer name %s is not in peer certificate", peer_name);
grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
@ -968,7 +971,7 @@ static bool ssl_channel_check_call_host(grpc_channel_security_connector* sc,
reinterpret_cast<grpc_ssl_channel_security_connector*>(sc);
grpc_security_status status = GRPC_SECURITY_ERROR;
tsi_peer peer = tsi_shallow_peer_from_ssl_auth_context(auth_context);
if (ssl_host_matches_name(&peer, host)) status = GRPC_SECURITY_OK;
if (grpc_ssl_host_matches_name(&peer, host)) status = GRPC_SECURITY_OK;
/* If the target name was overridden, then the original target_name was
'checked' transitively during the previous peer check at the end of the
handshake. */

@ -243,6 +243,7 @@ grpc_auth_context* tsi_ssl_peer_to_auth_context(const tsi_peer* peer);
tsi_peer tsi_shallow_peer_from_ssl_auth_context(
const grpc_auth_context* auth_context);
void tsi_shallow_peer_destruct(tsi_peer* peer);
int grpc_ssl_host_matches_name(const tsi_peer* peer, const char* peer_name);
/* --- Default SSL Root Store. --- */
namespace grpc_core {

@ -340,6 +340,26 @@ static grpc_ssl_roots_override_result override_roots_permanent_failure(
return GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY;
}
static void test_ipv6_address_san(void) {
const char* addresses[] = {
"2001:db8::1", "fe80::abcd:ef65:4321%em0", "fd11:feed:beef:0:cafe::4",
"128.10.0.1:8888", "[2001:db8::1]:8080", "[2001:db8::1%em1]:8080",
};
const char* san_ips[] = {
"2001:db8::1", "fe80::abcd:ef65:4321", "fd11:feed:beef:0:cafe::4",
"128.10.0.1", "2001:db8::1", "2001:db8::1",
};
tsi_peer peer;
GPR_ASSERT(tsi_construct_peer(1, &peer) == TSI_OK);
for (size_t i = 0; i < GPR_ARRAY_SIZE(addresses); i++) {
GPR_ASSERT(tsi_construct_string_peer_property_from_cstring(
TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY, san_ips[i],
&peer.properties[0]) == TSI_OK);
GPR_ASSERT(grpc_ssl_host_matches_name(&peer, addresses[i]));
tsi_peer_property_destruct(&peer.properties[0]);
}
tsi_peer_destruct(&peer);
}
namespace grpc_core {
namespace {
@ -416,6 +436,7 @@ int main(int argc, char** argv) {
test_cn_and_one_san_ssl_peer_to_auth_context();
test_cn_and_multiple_sans_ssl_peer_to_auth_context();
test_cn_and_multiple_sans_and_others_ssl_peer_to_auth_context();
test_ipv6_address_san();
test_default_ssl_roots();
grpc_shutdown();

Loading…
Cancel
Save