void enableSSL()

in src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java [1831:2116]


    void enableSSL(String host, int port, String clientCertificate, String clientKey,
            String clientKeyPassword) throws SQLServerException {
        // If enabling SSL fails, which it can for a number of reasons, the following items
        // are used in logging information to the TDS channel logger to help diagnose the problem.
        Provider tmfProvider = null; // TrustManagerFactory provider
        Provider sslContextProvider = null; // SSLContext provider
        Provider ksProvider = null; // KeyStore provider
        String tmfDefaultAlgorithm = null; // Default algorithm (typically X.509) used by the TrustManagerFactory
        SSLHandhsakeState handshakeState = SSLHandhsakeState.SSL_HANDHSAKE_NOT_STARTED;

        boolean isFips = false;
        String trustStoreType = null;
        String sslProtocol = null;

        // If anything in here fails, terminate the connection and throw an exception
        try {
            if (logger.isLoggable(Level.FINER))
                logger.finer(toString() + " Enabling SSL...");

            String trustStoreFileName = con.activeConnectionProperties
                    .getProperty(SQLServerDriverStringProperty.TRUST_STORE.toString());
            String trustStorePassword = con.activeConnectionProperties
                    .getProperty(SQLServerDriverStringProperty.TRUST_STORE_PASSWORD.toString());
            String hostNameInCertificate = con.activeConnectionProperties
                    .getProperty(SQLServerDriverStringProperty.HOSTNAME_IN_CERTIFICATE.toString());

            trustStoreType = con.activeConnectionProperties
                    .getProperty(SQLServerDriverStringProperty.TRUST_STORE_TYPE.toString());

            if (StringUtils.isEmpty(trustStoreType)) {
                trustStoreType = SQLServerDriverStringProperty.TRUST_STORE_TYPE.getDefaultValue();
            }

            isFips = Boolean.valueOf(
                    con.activeConnectionProperties.getProperty(SQLServerDriverBooleanProperty.FIPS.toString()));
            sslProtocol = con.activeConnectionProperties
                    .getProperty(SQLServerDriverStringProperty.SSL_PROTOCOL.toString());

            if (isFips) {
                validateFips(trustStoreType, trustStoreFileName);
            }

            assert TDS.ENCRYPT_OFF == con.getRequestedEncryptionLevel() || // Login only SSL
                    TDS.ENCRYPT_ON == con.getRequestedEncryptionLevel(); // Full SSL

            assert TDS.ENCRYPT_OFF == con.getNegotiatedEncryptionLevel() || // Login only SSL
                    TDS.ENCRYPT_ON == con.getNegotiatedEncryptionLevel() || // Full SSL
                    TDS.ENCRYPT_REQ == con.getNegotiatedEncryptionLevel(); // Full SSL

            // If encryption wasn't negotiated or trust server certificate is specified,
            // then we'll "validate" the server certificate using a naive TrustManager that trusts
            // everything it sees.
            TrustManager[] tm = null;
            if (TDS.ENCRYPT_OFF == con.getNegotiatedEncryptionLevel() || con.trustServerCertificate()) {
                if (logger.isLoggable(Level.FINER))
                    logger.finer(toString() + " SSL handshake will trust any certificate");

                tm = new TrustManager[] {new PermissiveX509TrustManager(this)};
            }
            // Otherwise, we'll check if a specific TrustManager implementation has been requested and
            // if so instantiate it, optionally specifying a constructor argument to customize it.
            else if (con.getTrustManagerClass() != null) {
                Object[] msgArgs = {"trustManagerClass", "javax.net.ssl.TrustManager"};
                tm = new TrustManager[] {Util.newInstance(TrustManager.class, con.getTrustManagerClass(),
                        con.getTrustManagerConstructorArg(), msgArgs)};
            }
            // Otherwise, we'll validate the certificate using a real TrustManager obtained
            // from the a security provider that is capable of validating X.509 certificates.
            else {
                if (logger.isLoggable(Level.FINER))
                    logger.finer(toString() + " SSL handshake will validate server certificate");

                KeyStore ks = null;

                // If we are using the system default trustStore and trustStorePassword
                // then we can skip all of the KeyStore loading logic below.
                // The security provider's implementation takes care of everything for us.
                if (null == trustStoreFileName && null == trustStorePassword) {
                    if (logger.isLoggable(Level.FINER))
                        logger.finer(toString() + " Using system default trust store and password");
                }

                // Otherwise either the trustStore, trustStorePassword, or both was specified.
                // In that case, we need to load up a KeyStore ourselves.
                else {
                    // First, obtain an interface to a KeyStore that can load trust material
                    // stored in Java Key Store (JKS) format.
                    if (logger.isLoggable(Level.FINEST))
                        logger.finest(toString() + " Finding key store interface");

                    ks = KeyStore.getInstance(trustStoreType);
                    ksProvider = ks.getProvider();

                    // Next, load up the trust store file from the specified location.
                    // Note: This function returns a null InputStream if the trust store cannot
                    // be loaded. This is by design. See the method comment and documentation
                    // for KeyStore.load for details.
                    InputStream is = loadTrustStore(trustStoreFileName);

                    // Finally, load the KeyStore with the trust material (if any) from the
                    // InputStream and close the stream.
                    if (logger.isLoggable(Level.FINEST))
                        logger.finest(toString() + " Loading key store");

                    try {
                        ks.load(is, (null == trustStorePassword) ? null : trustStorePassword.toCharArray());
                    } finally {
                        // We are also done with the trust store input stream.
                        if (null != is) {
                            try {
                                is.close();
                            } catch (IOException e) {
                                if (logger.isLoggable(Level.FINE))
                                    logger.fine(toString() + " Ignoring error closing trust material InputStream...");
                            }
                        }
                    }
                }

                // Either we now have a KeyStore populated with trust material or we are using the
                // default source of trust material (cacerts). Either way, we are now ready to
                // use a TrustManagerFactory to create a TrustManager that uses the trust material
                // to validate the server certificate.

                // Next step is to get a TrustManagerFactory that can produce TrustManagers
                // that understands X.509 certificates.
                TrustManagerFactory tmf = null;

                if (logger.isLoggable(Level.FINEST))
                    logger.finest(toString() + " Locating X.509 trust manager factory");

                tmfDefaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                tmf = TrustManagerFactory.getInstance(tmfDefaultAlgorithm);
                tmfProvider = tmf.getProvider();

                // Tell the TrustManagerFactory to give us TrustManagers that we can use to
                // validate the server certificate using the trust material in the KeyStore.
                if (logger.isLoggable(Level.FINEST))
                    logger.finest(toString() + " Getting trust manager");

                tmf.init(ks);
                tm = tmf.getTrustManagers();

                // if the host name in cert provided use it or use the host name Only if it is not FIPS
                if (!isFips) {
                    if (null != hostNameInCertificate) {
                        tm = new TrustManager[] {new HostNameOverrideX509TrustManager(this, (X509TrustManager) tm[0],
                                hostNameInCertificate)};
                    } else {
                        tm = new TrustManager[] {
                                new HostNameOverrideX509TrustManager(this, (X509TrustManager) tm[0], host)};
                    }
                }
            } // end if (!con.trustServerCertificate())

            // Now, with a real or fake TrustManager in hand, get a context for creating a
            // SSL sockets through a SSL socket factory. We require at least TLS support.
            SSLContext sslContext = null;

            if (logger.isLoggable(Level.FINEST))
                logger.finest(toString() + " Getting TLS or better SSL context");

            KeyManager[] km = (null != clientCertificate && clientCertificate.length() > 0) ? SQLServerCertificateUtils
                    .getKeyManagerFromFile(clientCertificate, clientKey, clientKeyPassword) : null;

            sslContext = SSLContext.getInstance(sslProtocol);
            sslContextProvider = sslContext.getProvider();

            if (logger.isLoggable(Level.FINEST))
                logger.finest(toString() + " Initializing SSL context");

            sslContext.init(km, tm, null);

            // Got the SSL context. Now create an SSL socket over our own proxy socket
            // which we can toggle between TDS-encapsulated and raw communications.
            // Initially, the proxy is set to encapsulate the SSL handshake in TDS packets.
            proxySocket = new ProxySocket(this);

            if (logger.isLoggable(Level.FINEST))
                logger.finest(toString() + " Creating SSL socket");

            // don't close proxy when SSL socket is closed
            sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(proxySocket, host, port, false);

            // At long last, start the SSL handshake ...
            if (logger.isLoggable(Level.FINER))
                logger.finer(toString() + " Starting SSL handshake");

            // TLS 1.2 intermittent exception happens here.
            handshakeState = SSLHandhsakeState.SSL_HANDHSAKE_STARTED;
            sslSocket.startHandshake();
            handshakeState = SSLHandhsakeState.SSL_HANDHSAKE_COMPLETE;

            // After SSL handshake is complete, rewire proxy socket to use raw TCP/IP streams ...
            if (logger.isLoggable(Level.FINEST))
                logger.finest(toString() + " Rewiring proxy streams after handshake");

            proxySocket.setStreams(inputStream, outputStream);

            // ... and rewire TDSChannel to use SSL streams.
            if (logger.isLoggable(Level.FINEST))
                logger.finest(toString() + " Getting SSL InputStream");

            inputStream = new ProxyInputStream(sslSocket.getInputStream());

            if (logger.isLoggable(Level.FINEST))
                logger.finest(toString() + " Getting SSL OutputStream");

            outputStream = sslSocket.getOutputStream();

            // SSL is now enabled; switch over the channel socket
            channelSocket = sslSocket;

            // Check the TLS version
            String tlsProtocol = sslSocket.getSession().getProtocol();
            if (SSLProtocol.TLS_V10.toString().equalsIgnoreCase(tlsProtocol)
                    || SSLProtocol.TLS_V11.toString().equalsIgnoreCase(tlsProtocol)) {
                String warningMsg = tlsProtocol
                        + " was negotiated. Please update server and client to use TLSv1.2 at minimum.";
                logger.warning(warningMsg);
                con.addWarning(warningMsg);
            }

            if (logger.isLoggable(Level.FINER))
                logger.finer(toString() + " SSL enabled");
        } catch (Exception e) {
            // Log the original exception and its source at FINER level
            if (logger.isLoggable(Level.FINER))
                logger.log(Level.FINER, e.getMessage(), e);

            // If enabling SSL fails, the following information may help diagnose the problem.
            // Do not use Level INFO or above which is sent to standard output/error streams.
            // This is because due to an intermittent TLS 1.2 connection issue, we will be retrying the connection and
            // do not want to print this message in console.
            if (logger.isLoggable(Level.FINER))
                logger.log(Level.FINER, "java.security path: " + JAVA_SECURITY + "\n" + "Security providers: "
                        + Arrays.asList(Security.getProviders()) + "\n"
                        + ((null != sslContextProvider) ? ("SSLContext provider info: " + sslContextProvider.getInfo()
                                + "\n" + "SSLContext provider services:\n" + sslContextProvider.getServices() + "\n")
                                                        : "")
                        + ((null != tmfProvider) ? ("TrustManagerFactory provider info: " + tmfProvider.getInfo()
                                + "\n") : "")
                        + ((null != tmfDefaultAlgorithm) ? ("TrustManagerFactory default algorithm: "
                                + tmfDefaultAlgorithm + "\n") : "")
                        + ((null != ksProvider) ? ("KeyStore provider info: " + ksProvider.getInfo() + "\n") : "")
                        + "java.ext.dirs: " + System.getProperty("java.ext.dirs"));
            // Retrieve the localized error message if possible.
            String localizedMessage = e.getLocalizedMessage();
            String errMsg = (localizedMessage != null) ? localizedMessage : e.getMessage();
            /*
             * Retrieve the error message of the cause too because actual error message can be wrapped into a different
             * message when re-thrown from underlying InputStream.
             */
            String causeErrMsg = null;
            Throwable cause = e.getCause();
            if (cause != null) {
                String causeLocalizedMessage = cause.getLocalizedMessage();
                causeErrMsg = (causeLocalizedMessage != null) ? causeLocalizedMessage : cause.getMessage();
            }

            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_sslFailed"));
            Object[] msgArgs = {errMsg};

            /*
             * The error message may have a connection id appended to it. Extract the message only for comparison. This
             * client connection id is appended in method checkAndAppendClientConnId().
             */
            if (errMsg != null && errMsg.contains(SQLServerException.LOG_CLIENT_CONNECTION_ID_PREFIX)) {
                errMsg = errMsg.substring(0, errMsg.indexOf(SQLServerException.LOG_CLIENT_CONNECTION_ID_PREFIX));
            }

            if (causeErrMsg != null && causeErrMsg.contains(SQLServerException.LOG_CLIENT_CONNECTION_ID_PREFIX)) {
                causeErrMsg = causeErrMsg.substring(0,
                        causeErrMsg.indexOf(SQLServerException.LOG_CLIENT_CONNECTION_ID_PREFIX));
            }

            // Isolate the TLS1.2 intermittent connection error.
            if (e instanceof IOException && (SSLHandhsakeState.SSL_HANDHSAKE_STARTED == handshakeState)
                    && (SQLServerException.getErrString("R_truncatedServerResponse").equals(errMsg)
                            || SQLServerException.getErrString("R_truncatedServerResponse").equals(causeErrMsg))) {
                con.terminate(SQLServerException.DRIVER_ERROR_INTERMITTENT_TLS_FAILED, form.format(msgArgs), e);
            } else {
                con.terminate(SQLServerException.DRIVER_ERROR_SSL_FAILED, form.format(msgArgs), e);
            }
        }
    }