in pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java [598:849]
private void doAuthentication(PGStream 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 = PGProperty.PASSWORD.get(info);
/* SSPI negotiation state, if used */
ISSPIClient sspiClient = null;
/* SCRAM authentication state, if used */
org.postgresql.jre7.sasl.ScramAuthenticator scramAuthenticator = null;
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));
LOGGER.log(Level.FINEST, " <=BE ErrorMessage({0})", errorMsg);
throw new PSQLException(errorMsg, PGProperty.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 (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, " <=BE AuthenticationReqMD5(salt={0})", Utils.toHexString(md5Salt));
}
if (password == null) {
throw new PSQLException(
GT.tr(
"The server requested password-based authentication, but no password was provided."),
PSQLState.CONNECTION_REJECTED);
}
byte[] digest =
MD5Digest.encode(user.getBytes(StandardCharsets.UTF_8), password.getBytes(StandardCharsets.UTF_8), md5Salt);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, " FE=> Password(md5digest={0})", new String(digest, StandardCharsets.US_ASCII));
}
pgStream.sendChar('p');
pgStream.sendInteger4(4 + digest.length + 1);
pgStream.send(digest);
pgStream.sendChar(0);
pgStream.flush();
break;
}
case AUTH_REQ_PASSWORD: {
LOGGER.log(Level.FINEST, "<=BE AuthenticationReqPassword");
LOGGER.log(Level.FINEST, " FE=> Password(password=<not shown>)");
final AuthenticationPluginManager pluginManager = new AuthenticationPluginManager();
if (Boolean.parseBoolean(info.getProperty(PGProperty.USE_AWS_IAM.getName()))) {
// Ensure the AWS Java SDK for Amazon RDS exists in the classpath.
try {
Class.forName("com.amazonaws.auth.AWSCredentialsProvider");
} catch (final ClassNotFoundException e) {
LOGGER.log(Level.FINEST, "Attempt to use AWS IAM database authentication failed due to missing AWS Java SDK for Amazon RDS.");
throw new PSQLException(
GT.tr(
"Unable to connect using AWS IAM authentication due to missing AWS Java SDK for Amazon RDS. Add dependency to classpath."),
PSQLState.CONNECTION_REJECTED);
}
final String portProperty = info.getProperty(PGProperty.PG_PORT.getName());
final int port = (portProperty == null)
? DEFAULT_PORT
: Integer.parseInt(portProperty);
pluginManager.setPlugin(new AwsIamAuthenticationPlugin(host, port));
// Clear password if AWS IAM database authentication is in use.
password = "";
}
if (password == null) {
throw new PSQLException(
GT.tr(
"The server requested password-based authentication, but no password was provided."),
PSQLState.CONNECTION_REJECTED);
}
byte[] encodedPassword = pluginManager.getPassword(user, password);
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 = PGProperty.GSS_LIB.get(info);
final boolean usespnego = PGProperty.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 ("gssapi".equals(gsslib)) {
LOGGER.log(Level.FINE, "Using JSSE GSSAPI, param gsslib=gssapi");
} else if (areq == AUTH_REQ_GSS && !"sspi".equals(gsslib)) {
LOGGER.log(Level.FINE,
"Using JSSE GSSAPI, gssapi requested by server and gsslib=sspi not forced");
} else {
/* Determine if SSPI is supported by the client */
sspiClient = createSSPI(pgStream, PGProperty.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();
LOGGER.log(Level.FINE, "SSPI support detected: {0}", useSSPI);
if (!useSSPI) {
/* No need to dispose() if no SSPI used */
sspiClient = null;
if ("sspi".equals(gsslib)) {
throw new PSQLException(
"SSPI forced with gsslib=sspi, but SSPI not available; set loglevel=2 for details",
PSQLState.CONNECTION_UNABLE_TO_CONNECT);
}
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Using SSPI: {0}, gsslib={1} and SSPI support detected", new Object[]{useSSPI, gsslib});
}
}
if (useSSPI) {
/* SSPI requested and detected as available */
castNonNull(sspiClient).startSSPI();
} else {
/* Use JGSS's GSSAPI for this request */
org.postgresql.gss.MakeGSS.authenticate(false, pgStream, host, user, password,
PGProperty.JAAS_APPLICATION_NAME.get(info),
PGProperty.KERBEROS_SERVER_NAME.get(info), usespnego,
PGProperty.JAAS_LOGIN.getBoolean(info),
PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
}
break;
case AUTH_REQ_GSS_CONTINUE:
/*
* Only called for SSPI, as GSS is handled by an inner loop in MakeGSS.
*/
castNonNull(sspiClient).continueSSPI(msgLen - 8);
break;
case AUTH_REQ_SASL:
LOGGER.log(Level.FINEST, " <=BE AuthenticationSASL");
scramAuthenticator = new org.postgresql.jre7.sasl.ScramAuthenticator(user, castNonNull(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) {
throw new PSQLException(GT.tr(
"SCRAM authentication is not supported by this driver. You need JDK >= 8 and pgjdbc >= 42.2.0 (not \".jre\" versions)",
areq), PSQLState.CONNECTION_REJECTED);
}
break;
case AUTH_REQ_SASL_CONTINUE:
castNonNull(scramAuthenticator).processServerFirstMessage(msgLen - 4 - 4);
break;
case AUTH_REQ_SASL_FINAL:
castNonNull(scramAuthenticator).verifyServerSignature(msgLen - 4 - 4);
break;
case AUTH_REQ_OK:
/* Cleanup after successful authentication */
LOGGER.log(Level.FINEST, " <=BE AuthenticationOk");
break authloop; // We're done.
default:
LOGGER.log(Level.FINEST, " <=BE AuthenticationReq (unsupported type {0})", areq);
throw new PSQLException(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), PSQLState.CONNECTION_REJECTED);
}
break;
default:
throw new PSQLException(GT.tr("Protocol error. Session setup failed."),
PSQLState.PROTOCOL_VIOLATION);
}
}
} finally {
/* Cleanup after successful or failed authentication attempts */
if (sspiClient != null) {
try {
sspiClient.dispose();
} catch (RuntimeException ex) {
LOGGER.log(Level.FINE, "Unexpected error during SSPI context disposal", ex);
}
}
}
}