in src/main/java/com/amazon/redshift/core/v3/ConnectionFactoryImpl.java [571:904]
private void doAuthentication(RedshiftStream pgStream, String host, String user, Properties info) throws IOException, SQLException {
// Now get the response from the backend, either an error message
// or an authentication request
String password = RedshiftProperty.PASSWORD.get(info);
/* SSPI negotiation state, if used */
ISSPIClient sspiClient = null;
//JCP! if mvn.project.property.redshift.jdbc.spec >= "JDBC4.1"
/* SCRAM authentication state, if used */
//com.amazon.redshift.jre7.sasl.ScramAuthenticator scramAuthenticator =
// null;
//JCP! endif
try {
authloop: while (true) {
int beresp = pgStream.receiveChar();
switch (beresp) {
case 'E':
// An error occurred, so pass the error message to the
// user.
//
// The most common one to be thrown here is:
// "User authentication failed"
//
int elen = pgStream.receiveInteger4();
ServerErrorMessage errorMsg =
new ServerErrorMessage(pgStream.receiveErrorString(elen - 4));
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, " <=BE ErrorMessage({0})", errorMsg);
throw new RedshiftException(errorMsg, RedshiftProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
case 'R':
// Authentication request.
// Get the message length
int msgLen = pgStream.receiveInteger4();
// Get the type of request
int areq = pgStream.receiveInteger4();
// Process the request.
switch (areq) {
case AUTH_REQ_MD5: {
byte[] md5Salt = pgStream.receive(4);
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, " <=BE AuthenticationReqMD5");
}
if (password == null) {
throw new RedshiftException(
GT.tr(
"The server requested password-based authentication, but no password was provided."),
RedshiftState.CONNECTION_REJECTED);
}
byte[] digest =
MD5Digest.encode(user.getBytes("UTF-8"), password.getBytes("UTF-8"), md5Salt);
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, " FE=> Password(md5digest)");
}
pgStream.sendChar('p');
pgStream.sendInteger4(4 + digest.length + 1);
pgStream.send(digest);
pgStream.sendChar(0);
pgStream.flush();
break;
}
case AUTH_REQ_DIGEST: {
// Extensible user password hashing algorithm constant value
int algo = pgStream.receiveInteger4();
String[] algoNames = { "SHA-256" };
int saltLen = pgStream.receiveInteger4();
byte[] salt = pgStream.receive(saltLen);
int serverNonceLen = pgStream.receiveInteger4();
byte[] serverNonce = pgStream.receive(serverNonceLen);
String dateTimeString = Long.toString(new Date().getTime());
byte[] clientNonce = dateTimeString.getBytes();
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, " <=BE AuthenticationReqDigest: Algo:" + algo);
}
if (password == null) {
throw new RedshiftException(
GT.tr(
"The server requested password-based authentication, but no password was provided."),
RedshiftState.CONNECTION_REJECTED);
}
if (algo > algoNames.length) {
throw new RedshiftException(
GT.tr(
"The server requested password-based authentication, but requested algorithm " + algo + " is not supported."),
RedshiftState.CONNECTION_REJECTED);
}
byte[] digest =
ExtensibleDigest.encode(clientNonce,
password.getBytes("UTF-8"),
salt,
algoNames[algo],
serverNonce);
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, " FE=> Password(extensible digest)");
}
pgStream.sendChar('d');
pgStream.sendInteger4(4 + 4 + digest.length + 4 + clientNonce.length);
pgStream.sendInteger4(digest.length);
pgStream.send(digest);
pgStream.sendInteger4(clientNonce.length);
pgStream.send(clientNonce);
pgStream.flush();
break;
}
case AUTH_REQ_IDP: {
String aadToken = RedshiftProperty.WEB_IDENTITY_TOKEN.get(info);
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, " <=BE AuthenticationReqIDP");
}
if (aadToken == null || aadToken.length() == 0) {
throw new RedshiftException(
GT.tr(
"The server requested AAD token-based authentication, but no token was provided."),
RedshiftState.CONNECTION_REJECTED);
}
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, " FE=> IDP(AAD Token)");
}
byte[] token = aadToken.getBytes("UTF-8");
pgStream.sendChar('i');
pgStream.sendInteger4(4 + token.length + 1);
pgStream.send(token);
pgStream.sendChar(0);
pgStream.flush();
break;
}
case AUTH_REQ_PASSWORD: {
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, "<=BE AuthenticationReqPassword");
logger.log(LogLevel.DEBUG, " FE=> Password(password=<not shown>)");
}
if (password == null) {
throw new RedshiftException(
GT.tr(
"The server requested password-based authentication, but no password was provided."),
RedshiftState.CONNECTION_REJECTED);
}
byte[] encodedPassword = password.getBytes("UTF-8");
pgStream.sendChar('p');
pgStream.sendInteger4(4 + encodedPassword.length + 1);
pgStream.send(encodedPassword);
pgStream.sendChar(0);
pgStream.flush();
break;
}
case AUTH_REQ_GSS:
case AUTH_REQ_SSPI:
/*
* Use GSSAPI if requested on all platforms, via JSSE.
*
* For SSPI auth requests, if we're on Windows attempt native SSPI authentication if
* available, and if not disabled by setting a kerberosServerName. On other
* platforms, attempt JSSE GSSAPI negotiation with the SSPI server.
*
* Note that this is slightly different to libpq, which uses SSPI for GSSAPI where
* supported. We prefer to use the existing Java JSSE Kerberos support rather than
* going to native (via JNA) calls where possible, so that JSSE system properties
* etc continue to work normally.
*
* Note that while SSPI is often Kerberos-based there's no guarantee it will be; it
* may be NTLM or anything else. If the client responds to an SSPI request via
* GSSAPI and the other end isn't using Kerberos for SSPI then authentication will
* fail.
*/
final String gsslib = RedshiftProperty.GSS_LIB.get(info);
final boolean usespnego = RedshiftProperty.USE_SPNEGO.getBoolean(info);
boolean useSSPI = false;
/*
* Use SSPI if we're in auto mode on windows and have a request for SSPI auth, or if
* it's forced. Otherwise use gssapi. If the user has specified a Kerberos server
* name we'll always use JSSE GSSAPI.
*/
if (gsslib.equals("gssapi")) {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "Using JSSE GSSAPI, param gsslib=gssapi");
} else if (areq == AUTH_REQ_GSS && !gsslib.equals("sspi")) {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG,
"Using JSSE GSSAPI, gssapi requested by server and gsslib=sspi not forced");
} else {
/* Determine if SSPI is supported by the client */
sspiClient = createSSPI(pgStream, RedshiftProperty.SSPI_SERVICE_CLASS.get(info),
/* Use negotiation for SSPI, or if explicitly requested for GSS */
areq == AUTH_REQ_SSPI || (areq == AUTH_REQ_GSS && usespnego));
useSSPI = sspiClient.isSSPISupported();
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "SSPI support detected: {0}", useSSPI);
if (!useSSPI) {
/* No need to dispose() if no SSPI used */
sspiClient = null;
if (gsslib.equals("sspi")) {
throw new RedshiftException(
"SSPI forced with gsslib=sspi, but SSPI not available; set loglevel=2 for details",
RedshiftState.CONNECTION_UNABLE_TO_CONNECT);
}
}
if(RedshiftLogger.isEnable()) {
logger.log(LogLevel.DEBUG, "Using SSPI: {0}, gsslib={1} and SSPI support detected", new Object[]{useSSPI, gsslib});
}
}
if (useSSPI) {
/* SSPI requested and detected as available */
sspiClient.startSSPI();
} else {
/* Use JGSS's GSSAPI for this request */
com.amazon.redshift.gss.MakeGSS.authenticate(pgStream, host, user, password,
RedshiftProperty.JAAS_APPLICATION_NAME.get(info),
RedshiftProperty.KERBEROS_SERVER_NAME.get(info), usespnego,
RedshiftProperty.JAAS_LOGIN.getBoolean(info),
RedshiftProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info),
logger);
}
break;
case AUTH_REQ_GSS_CONTINUE:
/*
* Only called for SSPI, as GSS is handled by an inner loop in MakeGSS.
*/
sspiClient.continueSSPI(msgLen - 8);
break;
case AUTH_REQ_SASL:
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, " <=BE AuthenticationSASL");
//JCP! if mvn.project.property.redshift.jdbc.spec >= "JDBC4.1"
// scramAuthenticator = new com.amazon.redshift.jre7.sasl.ScramAuthenticator(user, password, pgStream);
// scramAuthenticator.processServerMechanismsAndInit();
// scramAuthenticator.sendScramClientFirstMessage();
// This works as follows:
// 1. When tests is run from IDE, it is assumed SCRAM library is on the classpath
// 2. In regular build for Java < 8 this `if` is deactivated and the code always throws
if (false) {
//JCP! else
//JCP> throw new RedshiftException(GT.tr(
//JCP> "SCRAM authentication is not supported by this driver. You need JDK >= 8 and pgjdbc >= 42.2.0 (not \".jre\" versions)",
//JCP> areq), RedshiftState.CONNECTION_REJECTED);
//JCP! endif
//JCP! if mvn.project.property.redshift.jdbc.spec >= "JDBC4.1"
}
break;
//JCP! endif
//JCP! if mvn.project.property.redshift.jdbc.spec >= "JDBC4.1"
// case AUTH_REQ_SASL_CONTINUE:
// scramAuthenticator.processServerFirstMessage(msgLen - 4 - 4);
// break;
//
// case AUTH_REQ_SASL_FINAL:
// scramAuthenticator.verifyServerSignature(msgLen - 4 - 4);
// break;
//JCP! endif
case AUTH_REQ_OK:
/* Cleanup after successful authentication */
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, " <=BE AuthenticationOk");
break authloop; // We're done.
default:
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, " <=BE AuthenticationReq (unsupported type {0})", areq);
throw new RedshiftException(GT.tr(
"The authentication type {0} is not supported. Check that you have configured the pg_hba.conf file to include the client''s IP address or subnet, and that it is using an authentication scheme supported by the driver.",
areq), RedshiftState.CONNECTION_REJECTED);
}
break;
default:
throw new RedshiftException(GT.tr("Protocol error. Session setup failed."),
RedshiftState.PROTOCOL_VIOLATION);
}
}
} finally {
/* Cleanup after successful or failed authentication attempts */
if (sspiClient != null) {
try {
sspiClient.dispose();
} catch (RuntimeException ex) {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, ex, "Unexpected error during SSPI context disposal");
}
}
}
}