com.amazon.redshift.sspi.SSPIClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redshift-jdbc42 Show documentation
Show all versions of redshift-jdbc42 Show documentation
Java JDBC 4.2 (JRE 8+) driver for Redshift database
The newest version!
/*
* Copyright (c) 2003, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
// Copyright (c) 2004, Open Cloud Limited.
package com.amazon.redshift.sspi;
import com.amazon.redshift.core.RedshiftStream;
import com.amazon.redshift.logger.LogLevel;
import com.amazon.redshift.logger.RedshiftLogger;
import com.amazon.redshift.util.HostSpec;
import com.amazon.redshift.util.RedshiftException;
import com.amazon.redshift.util.RedshiftState;
import com.sun.jna.LastErrorException;
import com.sun.jna.Platform;
import com.sun.jna.platform.win32.Sspi;
import com.sun.jna.platform.win32.Sspi.SecBufferDesc;
import com.sun.jna.platform.win32.Win32Exception;
import waffle.windows.auth.IWindowsCredentialsHandle;
import waffle.windows.auth.impl.WindowsCredentialsHandleImpl;
import waffle.windows.auth.impl.WindowsSecurityContextImpl;
import java.io.IOException;
import java.sql.SQLException;
/**
* Use Waffle-JNI to support SSPI authentication when PgJDBC is running on a Windows client and
* talking to a Windows server.
*
* SSPI is not supported on a non-Windows client.
*
* @author craig
*/
public class SSPIClient implements ISSPIClient {
public static final String SSPI_DEFAULT_SPN_SERVICE_CLASS = "REDSHIFT";
private RedshiftLogger logger;
private final RedshiftStream rsStream;
private final String spnServiceClass;
private final boolean enableNegotiate;
private IWindowsCredentialsHandle clientCredentials;
private WindowsSecurityContextImpl sspiContext;
private String targetName;
/**
* Instantiate an SSPIClient for authentication of a connection.
*
* SSPIClient is not re-usable across connections.
*
* It is safe to instantiate SSPIClient even if Waffle and JNA are missing or on non-Windows
* platforms, however you may not call any methods other than isSSPISupported().
*
* @param rsStream Redshift connection stream
* @param spnServiceClass SSPI SPN service class, defaults to POSTGRES if null
* @param enableNegotiate enable negotiate
*/
public SSPIClient(RedshiftStream rsStream, String spnServiceClass, boolean enableNegotiate) {
this.logger = (rsStream != null)
? rsStream.getLogger()
: RedshiftLogger.getDriverLogger();
this.rsStream = rsStream;
if (spnServiceClass == null || spnServiceClass.isEmpty()) {
spnServiceClass = SSPI_DEFAULT_SPN_SERVICE_CLASS;
}
this.spnServiceClass = spnServiceClass;
/* If we're forcing Kerberos (no spnego), disable SSPI negotiation */
this.enableNegotiate = enableNegotiate;
}
/**
* Test whether we can attempt SSPI authentication. If false, do not attempt to call any other
* SSPIClient methods.
*
* @return true if it's safe to attempt SSPI authentication
*/
@Override
public boolean isSSPISupported() {
try {
/*
* SSPI is windows-only. Attempt to use JNA to identify the platform. If Waffle is missing we
* won't have JNA and this will throw a NoClassDefFoundError.
*/
if (!Platform.isWindows()) {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "SSPI not supported: non-Windows host");
return false;
}
/* Waffle must be on the CLASSPATH */
Class.forName("waffle.windows.auth.impl.WindowsSecurityContextImpl");
return true;
} catch (NoClassDefFoundError ex) {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.INFO, "SSPI unavailable (no Waffle/JNA libraries?)", ex);
return false;
} catch (ClassNotFoundException ex) {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.INFO, "SSPI unavailable (no Waffle/JNA libraries?)", ex);
return false;
}
}
private String makeSPN() throws RedshiftException {
final HostSpec hs = rsStream.getHostSpec();
try {
/*
The GSSAPI implementation does not use the port in the service name.
Force the port number to 0
Fixes issue 1482
*/
return NTDSAPIWrapper.instance.DsMakeSpn(spnServiceClass, hs.getHost(), null,
(short) 0, null);
} catch (LastErrorException ex) {
throw new RedshiftException("SSPI setup failed to determine SPN",
RedshiftState.CONNECTION_UNABLE_TO_CONNECT, ex);
}
}
/**
* Respond to an authentication request from the back-end for SSPI authentication (AUTH_REQ_SSPI).
*
* @throws SQLException on SSPI authentication handshake failure
* @throws IOException on network I/O issues
*/
@Override
public void startSSPI() throws SQLException, IOException {
/*
* We usually use SSPI negotiation (spnego), but it's disabled if the client asked for GSSPI and
* usespngo isn't explicitly turned on.
*/
final String securityPackage = enableNegotiate ? "negotiate" : "kerberos";
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "Beginning SSPI/Kerberos negotiation with SSPI package: {0}", securityPackage);
try {
/*
* Acquire a handle for the local Windows login credentials for the current user
*
* See AcquireCredentialsHandle
* (http://msdn.microsoft.com/en-us/library/windows/desktop/aa374712%28v=vs.85%29.aspx)
*
* This corresponds to pg_SSPI_startup in libpq/fe-auth.c .
*/
try {
clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
clientCredentials.initialize();
} catch (Win32Exception ex) {
throw new RedshiftException("Could not obtain local Windows credentials for SSPI",
RedshiftState.CONNECTION_UNABLE_TO_CONNECT /* TODO: Should be authentication error */, ex);
}
try {
targetName = makeSPN();
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "SSPI target name: {0}", targetName);
sspiContext = new WindowsSecurityContextImpl();
sspiContext.setPrincipalName(targetName);
sspiContext.setCredentialsHandle(clientCredentials);
sspiContext.setSecurityPackage(securityPackage);
sspiContext.initialize(null, null, targetName);
} catch (Win32Exception ex) {
throw new RedshiftException("Could not initialize SSPI security context",
RedshiftState.CONNECTION_UNABLE_TO_CONNECT /* TODO: Should be auth error */, ex);
}
sendSSPIResponse(sspiContext.getToken());
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "Sent first SSPI negotiation message");
} catch (NoClassDefFoundError ex) {
throw new RedshiftException(
"SSPI cannot be used, Waffle or its dependencies are missing from the classpath",
RedshiftState.NOT_IMPLEMENTED, ex);
}
}
/**
* Continue an existing authentication conversation with the back-end in resonse to an
* authentication request of type AUTH_REQ_GSS_CONT.
*
* @param msgLength Length of message to read, excluding length word and message type word
* @throws SQLException if something wrong happens
* @throws IOException if something wrong happens
*/
@Override
public void continueSSPI(int msgLength) throws SQLException, IOException {
if (sspiContext == null) {
throw new IllegalStateException("Cannot continue SSPI authentication that we didn't begin");
}
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "Continuing SSPI negotiation");
/* Read the response token from the server */
byte[] receivedToken = rsStream.receive(msgLength);
SecBufferDesc continueToken = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, receivedToken);
sspiContext.initialize(sspiContext.getHandle(), continueToken, targetName);
/*
* Now send the response token. If negotiation is complete there may be zero bytes to send, in
* which case we shouldn't send a reply as the server is not expecting one; see fe-auth.c in
* libpq for details.
*/
byte[] responseToken = sspiContext.getToken();
if (responseToken.length > 0) {
sendSSPIResponse(responseToken);
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "Sent SSPI negotiation continuation message");
} else {
if(RedshiftLogger.isEnable())
logger.log(LogLevel.DEBUG, "SSPI authentication complete, no reply required");
}
}
private void sendSSPIResponse(byte[] outToken) throws IOException {
/*
* The sspiContext now contains a token we can send to the server to start the handshake. Send a
* 'password' message containing the required data; the server knows we're doing SSPI
* negotiation and will deal with it appropriately.
*/
rsStream.sendChar('p');
rsStream.sendInteger4(4 + outToken.length);
rsStream.send(outToken);
rsStream.flush();
}
/**
* Clean up native win32 resources after completion or failure of SSPI authentication. This
* SSPIClient instance becomes unusable after disposal.
*/
@Override
public void dispose() {
if (sspiContext != null) {
sspiContext.dispose();
sspiContext = null;
}
if (clientCredentials != null) {
clientCredentials.dispose();
clientCredentials = null;
}
}
}