diff --git a/client/trino-client/src/main/java/io/trino/client/OkHttpUtil.java b/client/trino-client/src/main/java/io/trino/client/OkHttpUtil.java index 67d213db0d13..437c3136e971 100644 --- a/client/trino-client/src/main/java/io/trino/client/OkHttpUtil.java +++ b/client/trino-client/src/main/java/io/trino/client/OkHttpUtil.java @@ -328,4 +328,9 @@ public static void setupKerberos( clientBuilder.addInterceptor(handler); clientBuilder.authenticator(handler); } + + public static void setupAlternateHostnameVerification(OkHttpClient.Builder clientBuilder, String alternativeHostname) + { + clientBuilder.hostnameVerifier((hostname, session) -> LegacyHostnameVerifier.INSTANCE.verify(alternativeHostname, session)); + } } diff --git a/client/trino-jdbc/src/main/java/io/trino/jdbc/ConnectionProperties.java b/client/trino-jdbc/src/main/java/io/trino/jdbc/ConnectionProperties.java index 93d97d58f6fa..1a28f4e6a900 100644 --- a/client/trino-jdbc/src/main/java/io/trino/jdbc/ConnectionProperties.java +++ b/client/trino-jdbc/src/main/java/io/trino/jdbc/ConnectionProperties.java @@ -90,6 +90,7 @@ enum SslVerificationMode public static final ConnectionProperty SOURCE = new Source(); public static final ConnectionProperty> DNS_RESOLVER = new Resolver(); public static final ConnectionProperty DNS_RESOLVER_CONTEXT = new ResolverContext(); + public static final ConnectionProperty HOSTNAME_IN_CERTIFICATE = new HostnameInCertificate(); private static final Set> ALL_PROPERTIES = ImmutableSet.>builder() .add(USER) @@ -132,6 +133,7 @@ enum SslVerificationMode .add(EXTERNAL_AUTHENTICATION_REDIRECT_HANDLERS) .add(DNS_RESOLVER) .add(DNS_RESOLVER_CONTEXT) + .add(HOSTNAME_IN_CERTIFICATE) .build(); private static final Map> KEY_LOOKUP = unmodifiableMap(ALL_PROPERTIES.stream() @@ -347,6 +349,9 @@ private static class SslVerification static final Predicate IF_SSL_VERIFICATION_ENABLED = IF_SSL_ENABLED.and(checkedPredicate(properties -> !SSL_VERIFICATION.getValue(properties).orElse(SslVerificationMode.FULL).equals(SslVerificationMode.NONE))); + static final Predicate IF_FULL_SSL_VERIFICATION_ENABLED = + IF_SSL_VERIFICATION_ENABLED.and(checkedPredicate(properties -> !SSL_VERIFICATION.getValue(properties).orElse(SslVerificationMode.FULL).equals(SslVerificationMode.CA))); + public SslVerification() { super("SSLVerification", NOT_REQUIRED, IF_SSL_ENABLED, SslVerificationMode::valueOf); @@ -645,6 +650,15 @@ public ResolverContext() } } + private static class HostnameInCertificate + extends AbstractConnectionProperty + { + public HostnameInCertificate() + { + super("hostnameInCertificate", NOT_REQUIRED, SslVerification.IF_FULL_SSL_VERIFICATION_ENABLED, STRING_CONVERTER); + } + } + private static class MapPropertyParser { private static final CharMatcher PRINTABLE_ASCII = CharMatcher.inRange((char) 0x21, (char) 0x7E); diff --git a/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoDriverUri.java b/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoDriverUri.java index 4862ba3dad5d..191d1d604302 100644 --- a/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoDriverUri.java +++ b/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoDriverUri.java @@ -44,6 +44,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static io.trino.client.KerberosUtil.defaultCredentialCachePath; import static io.trino.client.OkHttpUtil.basicAuth; +import static io.trino.client.OkHttpUtil.setupAlternateHostnameVerification; import static io.trino.client.OkHttpUtil.setupCookieJar; import static io.trino.client.OkHttpUtil.setupHttpProxy; import static io.trino.client.OkHttpUtil.setupInsecureSsl; @@ -65,6 +66,7 @@ import static io.trino.jdbc.ConnectionProperties.EXTERNAL_AUTHENTICATION_TIMEOUT; import static io.trino.jdbc.ConnectionProperties.EXTERNAL_AUTHENTICATION_TOKEN_CACHE; import static io.trino.jdbc.ConnectionProperties.EXTRA_CREDENTIALS; +import static io.trino.jdbc.ConnectionProperties.HOSTNAME_IN_CERTIFICATE; import static io.trino.jdbc.ConnectionProperties.HTTP_PROXY; import static io.trino.jdbc.ConnectionProperties.KERBEROS_CONFIG_PATH; import static io.trino.jdbc.ConnectionProperties.KERBEROS_CREDENTIAL_CACHE_PATH; @@ -291,6 +293,11 @@ public void setupClient(OkHttpClient.Builder builder) SSL_USE_SYSTEM_TRUST_STORE.getValue(properties).orElse(false)); } + if (sslVerificationMode.equals(FULL)) { + HOSTNAME_IN_CERTIFICATE.getValue(properties).ifPresent(certHostname -> + setupAlternateHostnameVerification(builder, certHostname)); + } + if (sslVerificationMode.equals(CA)) { builder.hostnameVerifier((hostname, session) -> true); } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java index 42e8f897974b..931c25a884dd 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java @@ -105,14 +105,13 @@ public void testSuccessDefaultKey() .signWith(defaultKey) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); - ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + assertTrue(statement.execute("SELECT 123")); + ResultSet rs = statement.getResultSet(); + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 123); + assertFalse(rs.next()); } } @@ -126,14 +125,13 @@ public void testSuccessHmac() .signWith(hmac222) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); - ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + assertTrue(statement.execute("SELECT 123")); + ResultSet rs = statement.getResultSet(); + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 123); + assertFalse(rs.next()); } } @@ -147,14 +145,13 @@ public void testSuccessPublicKey() .signWith(privateKey33) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); - ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + assertTrue(statement.execute("SELECT 123")); + ResultSet rs = statement.getResultSet(); + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 123); + assertFalse(rs.next()); } } @@ -162,10 +159,9 @@ public void testSuccessPublicKey() public void testFailedNoToken() throws Exception { - try (Connection connection = createConnection(ImmutableMap.of())) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 123"); - } + try (Connection connection = createConnection(ImmutableMap.of()); + Statement statement = connection.createStatement()) { + statement.execute("SELECT 123"); } } @@ -177,10 +173,9 @@ public void testFailedUnsigned() .setSubject("test") .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 123"); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + statement.execute("SELECT 123"); } } @@ -194,10 +189,9 @@ public void testFailedBadHmacSignature() .signWith(badKey) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 123"); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + statement.execute("SELECT 123"); } } @@ -211,10 +205,9 @@ public void testFailedWrongPublicKey() .signWith(privateKey33) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 123"); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + statement.execute("SELECT 123"); } } @@ -228,10 +221,9 @@ public void testFailedUnknownPublicKey() .signWith(privateKey33) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken))) { - try (Statement statement = connection.createStatement()) { - statement.execute("SELECT 123"); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); + Statement statement = connection.createStatement()) { + statement.execute("SELECT 123"); } } @@ -245,17 +237,112 @@ public void testSuccessFullSslVerification() .signWith(privateKey33) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken, "SSLVerification", "FULL"))) { - try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); - ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken, "SSLVerification", "FULL")); + Statement statement = connection.createStatement()) { + assertTrue(statement.execute("SELECT 123")); + ResultSet rs = statement.getResultSet(); + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 123); + assertFalse(rs.next()); } } + @Test + public void testSuccessFullSslVerificationAlternateHostname() + throws Exception + { + String accessToken = newJwtBuilder() + .setSubject("test") + .setHeaderParam(KEY_ID, "33") + .signWith(privateKey33) + .compact(); + + String url = format("jdbc:trino://127.0.0.1:%s", server.getHttpsAddress().getPort()); + Properties properties = new Properties(); + properties.setProperty("SSL", "true"); + properties.setProperty("SSLVerification", "FULL"); + properties.setProperty("SSLTrustStorePath", new File(getResource("localhost.truststore").toURI()).getPath()); + properties.setProperty("SSLTrustStorePassword", "changeit"); + properties.setProperty("accessToken", accessToken); + properties.setProperty("hostnameInCertificate", "localhost"); + + try (Connection connection = DriverManager.getConnection(url, properties); + Statement statement = connection.createStatement()) { + assertTrue(statement.execute("SELECT 123")); + ResultSet rs = statement.getResultSet(); + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 123); + assertFalse(rs.next()); + } + } + + @Test + public void testFailedFullSslVerificationAlternateHostnameNotProvided() + throws Exception + { + String accessToken = newJwtBuilder() + .setSubject("test") + .setHeaderParam(KEY_ID, "33") + .signWith(privateKey33) + .compact(); + + String url = format("jdbc:trino://127.0.0.1:%s", server.getHttpsAddress().getPort()); + Properties properties = new Properties(); + properties.setProperty("SSL", "true"); + properties.setProperty("SSLVerification", "FULL"); + properties.setProperty("SSLTrustStorePath", new File(getResource("localhost.truststore").toURI()).getPath()); + properties.setProperty("SSLTrustStorePassword", "changeit"); + properties.setProperty("accessToken", accessToken); + + try (Connection connection = DriverManager.getConnection(url, properties); + Statement statement = connection.createStatement()) { + assertThatThrownBy(() -> statement.execute("SELECT 123")) + .isInstanceOf(SQLException.class).hasMessageContaining("Error executing query: javax.net.ssl.SSLPeerUnverifiedException: Hostname 127.0.0.1 not verified"); + } + } + + @Test + public void testFailedCaSslVerificationAlternateHostname() + { + String accessToken = newJwtBuilder() + .setSubject("test") + .setHeaderParam(KEY_ID, "33") + .signWith(privateKey33) + .compact(); + + String url = format("jdbc:trino://127.0.0.1:%s", server.getHttpsAddress().getPort()); + Properties properties = new Properties(); + properties.setProperty("SSL", "true"); + properties.setProperty("SSLVerification", "CA"); + properties.setProperty("accessToken", accessToken); + properties.setProperty("hostnameInCertificate", "localhost"); + + assertThatThrownBy(() -> DriverManager.getConnection(url, properties)) + .isInstanceOf(SQLException.class) + .hasMessage("Connection property 'hostnameInCertificate' is not allowed"); + } + + @Test + public void testFailedNoneSslVerificationAlternateHostname() + { + String accessToken = newJwtBuilder() + .setSubject("test") + .setHeaderParam(KEY_ID, "33") + .signWith(privateKey33) + .compact(); + + String url = format("jdbc:trino://127.0.0.1:%s", server.getHttpsAddress().getPort()); + Properties properties = new Properties(); + properties.setProperty("SSL", "true"); + properties.setProperty("SSLVerification", "NONE"); + properties.setProperty("accessToken", accessToken); + properties.setProperty("hostnameInCertificate", "localhost"); + + assertThatThrownBy(() -> DriverManager.getConnection(url, properties)) + .isInstanceOf(SQLException.class) + .hasMessage("Connection property 'hostnameInCertificate' is not allowed"); + } + @Test public void testSuccessCaSslVerification() throws Exception @@ -266,14 +353,13 @@ public void testSuccessCaSslVerification() .signWith(privateKey33) .compact(); - try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken, "SSLVerification", "CA"))) { - try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); - ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); - } + try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken, "SSLVerification", "CA")); + Statement statement = connection.createStatement()) { + assertTrue(statement.execute("SELECT 123")); + ResultSet rs = statement.getResultSet(); + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 123); + assertFalse(rs.next()); } } @@ -317,11 +403,12 @@ public void testFailedNoneSslVerificationWithSSL() public void testFailedNoneSslVerificationWithSSLUnsigned() throws Exception { - Connection connection = createBasicConnection(ImmutableMap.of("SSL", "true", "SSLVerification", "NONE")); - Statement statement = connection.createStatement(); - assertThatThrownBy(() -> statement.execute("SELECT 123")) - .isInstanceOf(SQLException.class) - .hasMessage("Authentication failed: Unauthorized"); + try (Connection connection = createBasicConnection(ImmutableMap.of("SSL", "true", "SSLVerification", "NONE")); + Statement statement = connection.createStatement()) { + assertThatThrownBy(() -> statement.execute("SELECT 123")) + .isInstanceOf(SQLException.class) + .hasMessage("Authentication failed: Unauthorized"); + } } private Connection createConnection(Map additionalProperties)