diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_ssl.py b/graalpython/com.oracle.graal.python.test/src/tests/test_ssl.py index f39f880ed5..daf4fa5fbb 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_ssl.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_ssl.py @@ -405,6 +405,30 @@ def test_alpn(self): server, client = check_handshake(server_context, client_context) self.assertEqual(client.selected_alpn_protocol(), "http/1.1") + +class ProtocolSelectionTests(unittest.TestCase): + + def check_protocol_availability_controls_selection(self, has_name, protocol_name, protocol_id): + if getattr(ssl, has_name): + self.assertTrue(hasattr(ssl, protocol_name)) + ssl.SSLContext(getattr(ssl, protocol_name)) + else: + protocol = getattr(ssl, protocol_name, protocol_id) + with self.assertRaisesRegex(ValueError, "invalid or unsupported protocol version"): + ssl.SSLContext(protocol) + + def test_single_version_availability_controls_selection(self): + self.check_protocol_availability_controls_selection("HAS_SSLv3", "PROTOCOL_SSLv3", 1) + self.check_protocol_availability_controls_selection("HAS_TLSv1", "PROTOCOL_TLSv1", 3) + self.check_protocol_availability_controls_selection("HAS_TLSv1_1", "PROTOCOL_TLSv1_1", 4) + self.check_protocol_availability_controls_selection("HAS_TLSv1_2", "PROTOCOL_TLSv1_2", 5) + + def test_generic_protocols_keep_sslv3_disabled(self): + for protocol in (ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLS_CLIENT, ssl.PROTOCOL_TLS_SERVER): + with self.subTest(protocol=protocol): + self.assertTrue(ssl.SSLContext(protocol).options & ssl.OP_NO_SSLv3) + + def get_cipher_list(cipher_string): context = ssl.SSLContext() context.set_ciphers(cipher_string) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ssl/SSLContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ssl/SSLContextBuiltins.java index d1f6df16fc..2746186462 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ssl/SSLContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ssl/SSLContextBuiltins.java @@ -173,7 +173,7 @@ static PSSLContext createContext(VirtualFrame frame, Object type, int protocol, @Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode, @Cached PRaiseNode raiseNode) { SSLMethod method = SSLMethod.fromPythonId(protocol); - if (method == null) { + if (method == null || isUnsupportedSingleVersion(method)) { throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.INVALID_OR_UNSUPPORTED_PROTOCOL_VERSION, "NULL"); } try { @@ -190,6 +190,9 @@ static PSSLContext createContext(VirtualFrame frame, Object type, int protocol, createSSLContext()); long options = SSLOptions.SSL_OP_ALL; if (method != SSLMethod.SSL3) { + // supportedProtocols describes runtime availability. This per-context option still keeps + // generic TLS contexts from negotiating SSLv3. Only an explicit, supported SSLv3 + // context may leave it enabled. options |= SSLOptions.SSL_OP_NO_SSLv3; } context.setOptions(options); @@ -201,6 +204,19 @@ static PSSLContext createContext(VirtualFrame frame, Object type, int protocol, } } + @TruffleBoundary + private static boolean isUnsupportedSingleVersion(SSLMethod method) { + if (!method.isSingleVersion()) { + return false; + } + for (SSLProtocol supportedProtocol : SSLModuleBuiltins.getSupportedProtocols()) { + if (method.allowsProtocol(supportedProtocol)) { + return false; + } + } + return true; + } + @TruffleBoundary private static SSLContext createSSLContext() throws NoSuchAlgorithmException, KeyManagementException { SSLContext context = SSLContext.getInstance("TLS");