Using the DataStax C++ Driver for Cassandra, if the cluster contact point is specified as a domain name (e.g., server1.example.com), TLS is used, the domain has a valid certificate with the domain name specified (as either commonName or DNS-type subjectAltName), and SSL is configured with CASS_SSL_VERIFY_PEER_IDENTITY_DNS, the driver is unable to connect to the contact point, reporting "Unable to establish a control connection to host 10.25.16.6 because of the following error: Underlying connection error: Error verifying peer certificate: Peer certificate subject name does not match" in cluster_connector.cpp.
Debugging reveals that the code in OpenSslVerifyIdentity::match_dns() is being passed a hostname_ which is just the empty string "".
Analysis
In the resolver, Resolver::on_resolve https://github.com/datastax/cpp-driver/blob/master/src/resolver.hpp#L111 uses the response from the resolver to call init_addresses(). This builds an Address from each ai_addr (IP address) in the response, and pushes them all onto the list of addresses. It does not preserve the hostname that corresponds to the address - the constructed Address has just "" as the server_name.
If the client requests peer identity verification (either CASS_SSL_VERIFY_PEER_IDENTITY which verifies the IP against the SANs if present, else the server_name against the CN, or CASS_SSL_VERIFY_PEER_IDENTITY_DNS which verifies the server_name against the SANs if present, else the server_name against the CN), then server name matching will never work - unless you somehow have a cert for the name "".
This is handled better in SocketConnector::on_resolve https://github.com/datastax/cpp-driver/blob/master/src/socket_connector.cpp#L292 ("// Keep the server name for debugging" :)), which explicitly sets the address_.server_name() into the resolved_address - but that's too late; the control connection doesn't do this, so we can't actually get a successful connection.
The fix is to copy the requested domain name into the Address in Resolver::on_resolve, so it can be used for peer identity verification. To avoid regression, the code for CASS_SSL_VERIFY_PEER_IDENTITY should be altered so it always checks the IP address in the CN case, even if a server_name is present.
Docs
Incidentally, the docs are not very clear that CASS_SSL_VERIFY_PEER_IDENTITY | CASS_SSL_VERIFY_PEER_IDENTITY_DNS means just VERIFY_PEER_IDENTITY will be done (these are alternatives). They're also confusing for CASS_SSL_VERIFY_PEER_IDENTITY wrt the CN behaviour. It would be good to improve these (see https://github.com/datastax/cpp-driver/blob/master/src/ssl/ssl_openssl_impl.cpp#L473 and #L264):
CASS_SSL_VERIFY_PEER_IDENTITY: If the cert has any SANs, it checks that the IP address matches one of the SANs. Else (i.e., the cert has no SANs), it checks that the server address string-matches the CN.
CASS_SSL_VERIFY_PEER_IDENTITY_DNS: If the cert has any SANs, it checks that the server hostname matches one of the SANs. Else (i.e., the cert has no SANs), it checks that the server hostname string-matches the CN.
Environment
None
Pull Requests
None
Activity
Show:
Keith Wansbrough
March 4, 2021 at 2:33 PM
Fix now available at . We’ve tested this locally and it resolves the issue.
Using the DataStax C++ Driver for Cassandra, if the cluster contact point is specified as a domain name (e.g.,
server1.example.com
), TLS is used, the domain has a valid certificate with the domain name specified (as either commonName or DNS-type subjectAltName), and SSL is configured withCASS_SSL_VERIFY_PEER_IDENTITY_DNS
, the driver is unable to connect to the contact point, reporting"Unable to establish a control connection to host 10.25.16.6 because of the following error: Underlying connection error: Error verifying peer certificate: Peer certificate subject name does not match"
in cluster_connector.cpp.Debugging reveals that the code in OpenSslVerifyIdentity::match_dns() is being passed a
hostname_
which is just the empty string""
.Analysis
In the resolver, Resolver::on_resolve https://github.com/datastax/cpp-driver/blob/master/src/resolver.hpp#L111 uses the response from the resolver to call init_addresses(). This builds an Address from each ai_addr (IP address) in the response, and pushes them all onto the list of addresses. It does not preserve the hostname that corresponds to the address - the constructed Address has just "" as the server_name.
If the client requests peer identity verification (either CASS_SSL_VERIFY_PEER_IDENTITY which verifies the IP against the SANs if present, else the server_name against the CN, or CASS_SSL_VERIFY_PEER_IDENTITY_DNS which verifies the server_name against the SANs if present, else the server_name against the CN), then server name matching will never work - unless you somehow have a cert for the name "".
This is handled better in SocketConnector::on_resolve https://github.com/datastax/cpp-driver/blob/master/src/socket_connector.cpp#L292 ("// Keep the server name for debugging" :)), which explicitly sets the address_.server_name() into the resolved_address - but that's too late; the control connection doesn't do this, so we can't actually get a successful connection.
The fix is to copy the requested domain name into the Address in Resolver::on_resolve, so it can be used for peer identity verification. To avoid regression, the code for CASS_SSL_VERIFY_PEER_IDENTITY should be altered so it always checks the IP address in the CN case, even if a server_name is present.
Docs
Incidentally, the docs are not very clear that CASS_SSL_VERIFY_PEER_IDENTITY | CASS_SSL_VERIFY_PEER_IDENTITY_DNS means just VERIFY_PEER_IDENTITY will be done (these are alternatives). They're also confusing for CASS_SSL_VERIFY_PEER_IDENTITY wrt the CN behaviour. It would be good to improve these (see https://github.com/datastax/cpp-driver/blob/master/src/ssl/ssl_openssl_impl.cpp#L473 and #L264):
CASS_SSL_VERIFY_PEER_IDENTITY: If the cert has any SANs, it checks that the IP address matches one of the SANs. Else (i.e., the cert has no SANs), it checks that the server address string-matches the CN.
CASS_SSL_VERIFY_PEER_IDENTITY_DNS: If the cert has any SANs, it checks that the server hostname matches one of the SANs. Else (i.e., the cert has no SANs), it checks that the server hostname string-matches the CN.