diff --git a/src/main/java/net/snowflake/client/core/HttpUtil.java b/src/main/java/net/snowflake/client/core/HttpUtil.java index 23b83df09..41ea57f32 100644 --- a/src/main/java/net/snowflake/client/core/HttpUtil.java +++ b/src/main/java/net/snowflake/client/core/HttpUtil.java @@ -345,9 +345,9 @@ public static CloseableHttpClient buildHttpClient( } TrustManager[] trustManagers = null; - if (key != null && key.getOcspMode() != OCSPMode.INSECURE) { - // A custom TrustManager is required only if insecureMode is disabled, - // which is by default in the production. insecureMode can be enabled + if (key != null && key.getOcspMode() != OCSPMode.DISABLE_OCSP_CHECKS) { + // A custom TrustManager is required only if disableOCSPChecks is disabled, + // which is by default in the production. disableOCSPChecks can be enabled // 1) OCSP service is down for reasons, 2) PowerMock test that doesn't // care OCSP checks. // OCSP FailOpen is ON by default @@ -742,7 +742,7 @@ public static String executeRequest( HttpClientSettingsKey ocspAndProxyKey, ExecTimeTelemetryData execTimeData) throws SnowflakeSQLException, IOException { - boolean ocspEnabled = !(ocspAndProxyKey.getOcspMode().equals(OCSPMode.INSECURE)); + boolean ocspEnabled = !(ocspAndProxyKey.getOcspMode().equals(OCSPMode.DISABLE_OCSP_CHECKS)); logger.debug("Executing request with OCSP enabled: {}", ocspEnabled); execTimeData.setOCSPStatus(ocspEnabled); return executeRequestInternal( diff --git a/src/main/java/net/snowflake/client/core/OCSPMode.java b/src/main/java/net/snowflake/client/core/OCSPMode.java index 1b9144a81..03c2a3bbb 100644 --- a/src/main/java/net/snowflake/client/core/OCSPMode.java +++ b/src/main/java/net/snowflake/client/core/OCSPMode.java @@ -15,8 +15,15 @@ public enum OCSPMode { */ FAIL_OPEN(1), - /** Insure mode. No OCSP check is made. */ - INSECURE(2); + /** + * @deprecated Use {@link #DISABLE_OCSP_CHECKS} for clarity. This configuration option is used to + * disable OCSP verification. Insure mode. No OCSP check is made. + */ + @Deprecated + INSECURE(2), + + /** Disable OCSP checks. It's used to disable OCSP verification. */ + DISABLE_OCSP_CHECKS(3); private final int value; diff --git a/src/main/java/net/snowflake/client/core/SFBaseSession.java b/src/main/java/net/snowflake/client/core/SFBaseSession.java index a7b374fde..6fc5cfb62 100644 --- a/src/main/java/net/snowflake/client/core/SFBaseSession.java +++ b/src/main/java/net/snowflake/client/core/SFBaseSession.java @@ -710,14 +710,28 @@ public void unsetInvalidProxyHostAndPort() { * Get OCSP mode * * @return {@link OCSPMode} + * @throws SnowflakeSQLException */ - public OCSPMode getOCSPMode() { + public OCSPMode getOCSPMode() throws SnowflakeSQLException { OCSPMode ret; + Boolean disableOCSPChecks = + (Boolean) connectionPropertiesMap.get(SFSessionProperty.DISABLE_OCSP_CHECKS); Boolean insecureMode = (Boolean) connectionPropertiesMap.get(SFSessionProperty.INSECURE_MODE); - if (insecureMode != null && insecureMode) { + + if ((disableOCSPChecks != null && insecureMode != null) + && (disableOCSPChecks != insecureMode)) { + logger.error( + "The values for 'disableOCSPChecks' and 'insecureMode' must be identical. " + + "Please ensure both properties are set to the same value or unset insecureMode."); + throw new SnowflakeSQLException( + ErrorCode.DISABLEOCSP_INSECUREMODE_VALUE_MISMATCH, + "The values for 'disableOCSPChecks' and 'insecureMode' " + "must be identical."); + } + if ((disableOCSPChecks != null && disableOCSPChecks) + || (insecureMode != null && insecureMode)) { // skip OCSP checks - ret = OCSPMode.INSECURE; + ret = OCSPMode.DISABLE_OCSP_CHECKS; } else if (!connectionPropertiesMap.containsKey(SFSessionProperty.OCSP_FAIL_OPEN) || (boolean) connectionPropertiesMap.get(SFSessionProperty.OCSP_FAIL_OPEN)) { // fail open (by default, not set) diff --git a/src/main/java/net/snowflake/client/core/SFSessionProperty.java b/src/main/java/net/snowflake/client/core/SFSessionProperty.java index a5e7276c5..a2da9e393 100644 --- a/src/main/java/net/snowflake/client/core/SFSessionProperty.java +++ b/src/main/java/net/snowflake/client/core/SFSessionProperty.java @@ -37,7 +37,13 @@ public enum SFSessionProperty { APP_ID("appId", false, String.class), APP_VERSION("appVersion", false, String.class), OCSP_FAIL_OPEN("ocspFailOpen", false, Boolean.class), + /** + * @deprecated Use {@link #DISABLE_OCSP_CHECKS} for clarity. This configuration option is used to + * disable OCSP verification. + */ + @Deprecated INSECURE_MODE("insecureMode", false, Boolean.class), + DISABLE_OCSP_CHECKS("disableOCSPChecks", false, Boolean.class), QUERY_TIMEOUT("queryTimeout", false, Integer.class), STRINGS_QUOTED("stringsQuotedForColumnDef", false, Boolean.class), APPLICATION("application", false, String.class), diff --git a/src/main/java/net/snowflake/client/core/SFTrustManager.java b/src/main/java/net/snowflake/client/core/SFTrustManager.java index 740c70fe3..275037eb0 100644 --- a/src/main/java/net/snowflake/client/core/SFTrustManager.java +++ b/src/main/java/net/snowflake/client/core/SFTrustManager.java @@ -841,10 +841,8 @@ private void executeRevocationStatusChecks( } private String generateFailOpenLog(String logData) { - return "WARNING!!! Using fail-open to connect. Driver is connecting to an " - + "HTTPS endpoint without OCSP based Certificate Revocation checking " - + "as it could not obtain a valid OCSP Response to use from the CA OCSP " - + "responder. Details: \n" + return "OCSP responder didn't respond correctly. Assuming certificate is " + + "not revoked. Details: " + logData; } @@ -981,7 +979,7 @@ private void executeOneRevocationStatusCheck( ocspLog = telemetryData.generateTelemetry(SF_OCSP_EVENT_TYPE_VALIDATION_ERROR, error); if (isOCSPFailOpen()) { // Log includes fail-open warning. - logger.error(generateFailOpenLog(ocspLog), false); + logger.debug(generateFailOpenLog(ocspLog), false); } else { // still not success, raise an error. logger.debug(ocspLog, false); @@ -1163,7 +1161,7 @@ private OCSPResp fetchOcspResponse( new DecorrelatedJitterBackoff(sleepTime, MAX_SLEEPING_TIME_IN_MILLISECONDS); boolean success = false; - final int maxRetryCounter = isOCSPFailOpen() ? 1 : 3; + final int maxRetryCounter = isOCSPFailOpen() ? 1 : 2; Exception savedEx = null; CloseableHttpClient httpClient = ocspCacheServerClient.computeIfAbsent( diff --git a/src/main/java/net/snowflake/client/jdbc/ErrorCode.java b/src/main/java/net/snowflake/client/jdbc/ErrorCode.java index b9cc71491..124cdd98a 100644 --- a/src/main/java/net/snowflake/client/jdbc/ErrorCode.java +++ b/src/main/java/net/snowflake/client/jdbc/ErrorCode.java @@ -83,7 +83,8 @@ public enum ErrorCode { INVALID_OKTA_USERNAME(200060, SqlState.CONNECTION_EXCEPTION), GCP_SERVICE_ERROR(200061, SqlState.SYSTEM_ERROR), AUTHENTICATOR_REQUEST_TIMEOUT(200062, SqlState.CONNECTION_EXCEPTION), - INVALID_STRUCT_DATA(200063, SqlState.DATA_EXCEPTION); + INVALID_STRUCT_DATA(200063, SqlState.DATA_EXCEPTION), + DISABLEOCSP_INSECUREMODE_VALUE_MISMATCH(200064, SqlState.INVALID_PARAMETER_VALUE); public static final String errorMessageResource = "net.snowflake.client.jdbc.jdbc_error_messages"; diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java index 7066e31c7..268bbf6c9 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionIT.java @@ -11,6 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -1014,6 +1015,28 @@ public void testFailOverOrgAccount() throws SQLException { } } + /** Test production connectivity with disableOCSPChecksMode enabled. */ + @Test + public void testDisableOCSPChecksMode() throws SQLException { + + String deploymentUrl = + "jdbc:snowflake://sfcsupport.snowflakecomputing.com?disableOCSPChecks=true"; + Properties properties = new Properties(); + + properties.put("user", "fakeuser"); + properties.put("password", "fakepwd"); + properties.put("account", "fakeaccount"); + SQLException thrown = + assertThrows( + SQLException.class, + () -> { + DriverManager.getConnection(deploymentUrl, properties); + }); + + assertThat( + thrown.getErrorCode(), anyOf(is(INVALID_CONNECTION_INFO_CODE), is(BAD_REQUEST_GS_CODE))); + } + private class ConcurrentConnections implements Runnable { ConcurrentConnections() {} diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionWithDisableOCSPModeLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionWithDisableOCSPModeLatestIT.java new file mode 100644 index 000000000..3144222c2 --- /dev/null +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionWithDisableOCSPModeLatestIT.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. All right reserved. + */ +package net.snowflake.client.jdbc; + +import static org.hamcrest.CoreMatchers.anyOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; +import net.snowflake.client.category.TestTags; +import net.snowflake.client.core.SFTrustManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** Tests for connection with DisableOCSPchecks and insecuremode settings. */ +@Tag(TestTags.CONNECTION) +public class ConnectionWithDisableOCSPModeLatestIT extends BaseJDBCTest { + public static final int INVALID_CONNECTION_INFO_CODE = 390100; + private static final int DISABLE_OCSP_INSECURE_MODE_MISMATCH = 200064; + public static final int BAD_REQUEST_GS_CODE = 390400; + + @BeforeEach + public void setUp() { + SFTrustManager.deleteCache(); + } + + @AfterEach + public void tearDown() { + SFTrustManager.cleanTestSystemParameters(); + } + + /** Test connectivity with disableOCSPChecksMode and insecure mode enabled. */ + @Test + public void testDisableOCSPChecksModeAndInsecureMode() throws SQLException { + + String deploymentUrl = + "jdbc:snowflake://sfcsupport.snowflakecomputing.com?disableOCSPChecks=true&insecureMode=true"; + Properties properties = new Properties(); + + properties.put("user", "fakeuser"); + properties.put("password", "fakepwd"); + properties.put("account", "fakeaccount"); + SQLException thrown = + assertThrows( + SQLException.class, + () -> { + DriverManager.getConnection(deploymentUrl, properties); + }); + + assertThat( + thrown.getErrorCode(), anyOf(is(INVALID_CONNECTION_INFO_CODE), is(BAD_REQUEST_GS_CODE))); + } + + /** Test connectivity with disableOCSPChecksMode enabled and insecure mode disabled. */ + @Test + public void testDisableOCSPChecksModeAndInsecureModeMismatched() throws SQLException { + + String deploymentUrl = + "jdbc:snowflake://sfcsupport.snowflakecomputing.com?disableOCSPChecks=true&insecureMode=false"; + Properties properties = new Properties(); + + properties.put("user", "fakeuser"); + properties.put("password", "fakepwd"); + properties.put("account", "fakeaccount"); + SQLException thrown = + assertThrows( + SQLException.class, + () -> { + DriverManager.getConnection(deploymentUrl, properties); + }); + + assertThat(thrown.getErrorCode(), anyOf(is(DISABLE_OCSP_INSECURE_MODE_MISMATCH))); + } + + /** Test production connectivity with only disableOCSPChecksMode enabled. */ + @Test + public void testDisableOCSPChecksModeSet() throws SQLException { + + String deploymentUrl = + "jdbc:snowflake://sfcsupport.snowflakecomputing.com?disableOCSPChecks=true"; + Properties properties = new Properties(); + + properties.put("user", "fakeuser"); + properties.put("password", "fakepwd"); + properties.put("account", "fakeaccount"); + SQLException thrown = + assertThrows( + SQLException.class, + () -> { + DriverManager.getConnection(deploymentUrl, properties); + }); + + assertThat( + thrown.getErrorCode(), anyOf(is(INVALID_CONNECTION_INFO_CODE), is(BAD_REQUEST_GS_CODE))); + } + + /** Test production connectivity with insecure mode enabled. */ + @Test + public void testEnableInsecureMode() throws SQLException { + String deploymentUrl = "jdbc:snowflake://sfcsupport.snowflakecomputing.com?insecureMode=true"; + Properties properties = new Properties(); + + properties.put("user", "fakeuser"); + properties.put("password", "fakepwd"); + properties.put("account", "fakeaccount"); + try { + DriverManager.getConnection(deploymentUrl, properties); + fail(); + } catch (SQLException e) { + assertThat( + e.getErrorCode(), anyOf(is(INVALID_CONNECTION_INFO_CODE), is(BAD_REQUEST_GS_CODE))); + } + } +} diff --git a/src/test/java/net/snowflake/client/jdbc/ConnectionWithOCSPModeIT.java b/src/test/java/net/snowflake/client/jdbc/ConnectionWithOCSPModeIT.java index 96b896247..fcdefc7c5 100644 --- a/src/test/java/net/snowflake/client/jdbc/ConnectionWithOCSPModeIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ConnectionWithOCSPModeIT.java @@ -42,6 +42,8 @@ public class ConnectionWithOCSPModeIT extends BaseJDBCTest { private final String testUser = "fakeuser"; private final String testPassword = "testpassword"; private final String testRevokedCertConnectString = "jdbc:snowflake://revoked.badssl.com/"; + public static final int INVALID_CONNECTION_INFO_CODE = 390100; + public static final int BAD_REQUEST_GS_CODE = 390400; private static int nameCounter = 0;