private void doAuthentication()

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

      }
    }

  }