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);
}
}
}