diff -Nur postgresql-jdbc-8.3-604.src.orig/doc/pgjdbc.xml postgresql-jdbc-8.3-604.src/doc/pgjdbc.xml
--- postgresql-jdbc-8.3-604.src.orig/doc/pgjdbc.xml 2008-01-08 07:56:26.000000000 +0100
+++ postgresql-jdbc-8.3-604.src/doc/pgjdbc.xml 2009-07-01 09:54:45.029843000 +0200
@@ -450,6 +450,17 @@
+ krbsrvname = String
+
+
+ Use the given string as Kerberos service name instead of
+ postgres. For more information see .
+
+
+
+
+
compatible = String
@@ -796,6 +807,247 @@
+
+ Using GSS
+
+
+ The Generic Security Services Application Program Interface
+ (GSS API or
+ GSSAPI) enables the JDBC driver to
+ authenticate the user via Kerberos.
+
+
+
+ Kerberos makes a single sign on solution possible. It is a very secure
+ authentication method and needs only a few changes to the system. The
+ JDBC driver supports Linux- and Windows-clients and
+ possibly other systems which have GSS
+ API support in Java.
+
+
+
+
+ PostgreSQL supports different Kerberos
+ authentication types (Kerberos 4, Kerberos 5, GSS API
+ and SSPI). This chapter describes only Kerberos 5 with
+ the help of the GSS API.
+
+
+
+
+ Server Configuration
+
+
+ Please read the documentation of the server for further details on
+ configuring the PostgreSQL server for
+ Kerberos/GSSAPI. A very, very short description:
+
+
+
+ You have to create a service principal
+ (postgres/fqdn@REALM), extract its key to a keytab which
+ is readable by the user running the server. Some variables should be set in
+ the configuration file:
+
+
+
+ krb_server_keyfile = '/path/to/keytab'
+ krb_server_hostname = 'fqdn-of-server'
+
+
+
+ Normally you should not need to define these values. But on a multihomed
+ host it is better to define the hostname. The path to the keytab is
+ neccessary because normally you would not use the system's keytab (e.g.
+ /etc/krb5.keytab) which should only be readable by the
+ superuser.
+
+
+
+ In the pg_hba.conf you should enable
+ gss:
+
+
+
+ # TYPE DATABASE USER CIDR-ADDRESS METHOD
+ host all all 10.0.0.0/8 gss
+
+
+
+
+ Client Configuration
+
+
+ You need to configure Kerberos on the client prior using the
+ JDBC driver. This section describes the client
+ configuration on Linux and Windows.
+
+
+
+ Linux
+
+
+ The system has to be configured for Kerberos. Some distributions
+ integrate Kerberos very well, so there is nothing to do except of using
+ it. To configure it manually you need to setup the Kerberos
+ configuration file /etc/krb5.conf:
+
+
+
+ [libdefaults]
+ default_realm = EXAMPLE.ORG
+
+ [realms]
+ EXAMPLE.ORG = {
+ kdc = kerberos.example.org
+ admin_server = kerberos.example.org
+ }
+
+ [domain_realm]
+ .example.org = EXAMPLE.ORG
+
+
+
+ This is a minimal krb5.conf. It contains the default
+ Kerberos realm (EXAMPLE.ORG) and which key
+ destribution center (KDC) and administration server
+ (AS) is responsible for this realm. For further
+ information you should take a look in the documentation of the Kerberos
+ implementation.
+
+
+
+ Afterwards you have to initialize the Kerberos environment by executing
+ kinit. With klist you can check, if everything is fine. It should
+ output something like this:
+
+
+
+ cju@box:~/> klist
+ Ticket cache: FILE:/tmp/krb5cc_1003
+ Default principal: cju@EXAMPLE.ORG
+
+ Valid starting Expires Service principal
+ 06/26/09 14:07:15 06/27/09 14:07:15 krbtgt/EXAMPLE.ORG@EXAMPLE.ORG
+
+
+ Kerberos 4 ticket cache: /tmp/tkt1003
+ klist: You have no tickets cached
+
+
+
+ klist should show you a valid Ticket Granting Ticket
+ (TGT). You can now configure the
+ JDBC driver and use it.
+
+
+
+
+ Windows
+
+
+ Windows 2000 Professional and Server, Windows XP and Windows 2003
+ Server support natively Kerberos as long as they are member of either a
+ Kerberos Realm or an Active Directory Domain.
+
+
+
+ Some changes are neccessary to use third-party software and especially
+ Java with Kerberos. To understand these changes, a descent knowledge of
+ the native Microsoft Kerberos implementation is useful:
+
+
+
+ Windows stores the credentials cache (CC) in memory
+ and protects this area against unauthorized applications. But it is
+ neccessary to allow applications access to the CC to implement a real
+ single sign on solution. In detail applications need access to a
+ current, valid TGT which is stored in the
+ CC.
+
+
+
+ Sun Java 1.6 should solve this problem by using the native Microsoft
+ libraries but in fact doesn't. Therefore the registry fix described in
+ described in and is needed.
+
+
+
+ You also need to create a Kerberos configuration file. On Windows it is
+ named krb5.ini and is searched for in the Windows system
+ directory (e.g. C:\Windows). An example configuration is
+ in .
+
+
+
+ Windows 2000 SP4, 2003 Server and later or Vista
+
+
+ Add under
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters
+ the DWORD key AllowTGTSessionKey
+ with the value 1.
+
+
+
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters
+ Value Name: AllowTGTSessionKey
+ Value Type: REG_DWORD
+ Value: 1
+
+
+
+
+ Windows XP
+
+
+ Add under
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos
+ the DWORD key AllowTGTSessionKey
+ with the value 1.
+
+
+
+ HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos
+ Value Name: AllowTGTSessionKey
+ Value Type: REG_DWORD
+ Value: 1
+
+
+
+
+
+
+ JDBC Configuration and Behaviour
+
+
+ You can use the JDBC driver without supplying a user
+ name and password. It will initialize the Kerberos environment,
+ extract the user name from the principal name of your
+ TGT (without realm and instance) and authenticate
+ with it.
+
+
+
+ If you supply a user name, the TGT with that user name is searched and
+ used. If it can not be found, an exception is thrown.
+
+
+
+ It is possible to define another service name for the Kerberos server
+ (default: postgres). This should not be neccessary.
+
+
+
+ The JDBC driver honors the setting of a keytab to
+ initialize the Kerberos stuff. If useKeyTab is not
+ defined or false, the driver tries to access the
+ credentials cache of the current user. For more information take a look
+ at the documentation of Krb5LoginModule.
+
+
+
Issuing a Query and Processing the Result
diff -Nur postgresql-jdbc-8.3-604.src.orig/org/postgresql/core/v3/ConnectionFactoryImpl.java postgresql-jdbc-8.3-604.src/org/postgresql/core/v3/ConnectionFactoryImpl.java
--- postgresql-jdbc-8.3-604.src.orig/org/postgresql/core/v3/ConnectionFactoryImpl.java 2008-01-08 07:56:27.000000000 +0100
+++ postgresql-jdbc-8.3-604.src/org/postgresql/core/v3/ConnectionFactoryImpl.java 2009-07-01 13:32:08.211901000 +0200
@@ -24,6 +24,7 @@
import org.postgresql.util.ServerErrorMessage;
import org.postgresql.util.UnixCrypt;
import org.postgresql.util.MD5Digest;
+import org.postgresql.util.GSSHelper;
import org.postgresql.util.GT;
/**
@@ -39,6 +40,12 @@
private static final int AUTH_REQ_CRYPT = 4;
private static final int AUTH_REQ_MD5 = 5;
private static final int AUTH_REQ_SCM = 6;
+ private static final int AUTH_REQ_GSS = 7; /* GSSAPI without wrap() */
+ private static final int AUTH_REQ_GSS_CONT = 8; /* Continue GSS exchanges */
+ private static final int AUTH_REQ_SSPI = 9; /* SSPI negotiate without wrap() */
+
+ // helper-object for GSS-API stuff
+ private GSSHelper gssHelper = null;
/** Marker exception; thrown when we want to fall back to using V2. */
private static class UnsupportedProtocolException extends IOException {
@@ -80,6 +87,27 @@
if (trySSL)
newStream = enableSSL(newStream, requireSSL, info, logger);
+ // try to initialize GSS API but don't fail here (we don't know if
+ // the server will ask us for GSS authentication...):
+ try
+ {
+ gssHelper = new GSSHelper(host, user, info, logger);
+
+ // if no user name was supplied, we'll take it from the
+ // currently logged in user
+ if (user == null || user.length() == 0)
+ {
+ user = gssHelper.getUserName();
+ if (logger.logDebug())
+ logger.debug("using user name " + user);
+ }
+ }
+ catch (PSQLException e)
+ {
+ if (logger.logDebug())
+ logger.debug("failed to initialize GSS API: " + e.toString());
+ }
+
// Construct and send a startup packet.
String[][] params = {
{ "user", user },
@@ -92,7 +120,7 @@
sendStartupPacket(newStream, params, logger);
// Do authentication (until AuthenticationOk).
- doAuthentication(newStream, user, info.getProperty("password"), logger);
+ doAuthentication(newStream, info, user, info.getProperty("password"), logger);
// Do final startup.
ProtocolConnectionImpl protoConnection = new ProtocolConnectionImpl(newStream, user, database, info, logger);
@@ -244,8 +272,10 @@
pgStream.flush();
}
- private void doAuthentication(PGStream pgStream, String user, String password, Logger logger) throws IOException, SQLException
+ private void doAuthentication(PGStream pgStream, Properties info, String user, String password, Logger logger) throws IOException, SQLException
{
+ boolean usingGSS = false;
+
// Now get the response from the backend, either an error message
// or an authentication request
@@ -363,9 +393,73 @@
break;
}
+ case AUTH_REQ_GSS:
+ {
+ if (logger.logDebug())
+ logger.debug(" <=BE AuthenticationReqGss");
+
+ if (gssHelper == null)
+ throw new PSQLException(
+ GT.tr(
+ "GSS authentication requested by server but GSS API not initialized."),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT);
+
+ usingGSS = true;
+
+ byte[] token = null;
+ token = gssHelper.establishContext(token);
+ if (token != null) {
+ if (logger.logDebug())
+ logger.debug(" FE=> GSS token (" + token.length + " bytes)");
+ pgStream.SendChar('p');
+ pgStream.SendInteger4(token.length + 4);
+ pgStream.Send(token);
+ pgStream.flush();
+ }
+
+ break;
+ }
+
+ case AUTH_REQ_GSS_CONT:
+ {
+ if (logger.logDebug())
+ logger.debug(" <=BE AuthenticationReqGssCont");
+
+ if (gssHelper == null)
+ throw new PSQLException(
+ GT.tr(
+ "GSS authentication continuation requested but GSS API not initialized."),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT);
+
+ // token length = msg_len - sizeof(msg_len) - sizeof(auth_type)
+ byte[] token = new byte[l_msgLen - 8];
+ pgStream.Receive(token, 0, token.length);
+
+ if (logger.logDebug())
+ logger.debug(" <=BE GSS token (" + token.length + " bytes)");
+
+ token = gssHelper.establishContext(token);
+ if (token != null) {
+ if (logger.logDebug())
+ logger.debug( " FE=> GSS token (" + token.length + " bytes)");
+ pgStream.SendChar('p');
+ pgStream.SendInteger4(token.length + 4);
+ pgStream.Send(token);
+ pgStream.flush();
+ }
+
+ break;
+ }
+
case AUTH_REQ_OK:
if (logger.logDebug())
logger.debug(" <=BE AuthenticationOk");
+
+ // throw an exception if the GSS API context is not completely established
+ if (usingGSS && !gssHelper.isContextEstablished())
+ {
+ throw new PSQLException(GT.tr("The GSS context between server and client is not fully established. Maybe the server is compromised."), PSQLState.CONNECTION_FAILURE);
+ }
return ; // We're done.
diff -Nur postgresql-jdbc-8.3-604.src.orig/org/postgresql/util/GSSHelper.java postgresql-jdbc-8.3-604.src/org/postgresql/util/GSSHelper.java
--- postgresql-jdbc-8.3-604.src.orig/org/postgresql/util/GSSHelper.java 1970-01-01 01:00:00.000000000 +0100
+++ postgresql-jdbc-8.3-604.src/org/postgresql/util/GSSHelper.java 2009-07-01 15:05:51.123096000 +0200
@@ -0,0 +1,296 @@
+/*-------------------------------------------------------------------------
+ * GSSHelper.java
+ *
+ * Author: Christian Jung
+ */
+package org.postgresql.util;
+
+import com.sun.security.auth.module.Krb5LoginModule;
+import java.lang.RuntimeException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.login.LoginException;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.postgresql.core.Logger;
+import org.postgresql.util.GT;
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+/**
+ * This class implements methods used for the GSS API authentication protocol
+ * on a PostgreSQL.
+ *
+ * @author Christian Jung (christian.jung@saarstahl.com)
+ */
+public class GSSHelper extends Object
+{
+ // constants:
+ private static final String PG_KRB_SRV_NAM = "postgres";
+ private static final org.ietf.jgss.Oid OID_KRB5_MECH;
+
+ // initialize complex class objects:
+ static
+ {
+ try
+ {
+ OID_KRB5_MECH = new org.ietf.jgss.Oid(
+ "1.2.840.113554.1.2.2");
+ }
+ catch (GSSException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Subject subject = null; // object which stores the private credentials of the local user
+ private GSSContext context = null; // GSS context
+ private String userPrincipalName = null; // principal name of the local user
+ private String krbServiceName = null; // service-part of server's principal name
+ private String fqdn = null; // full qualified domain name of the server
+
+
+ /**
+ * Creates a new GSSHelper object.
+ *
+ * It is used in the ConnectionFactory implementation. The constructor
+ * intializes the Kerberos credentials cache and the GSS API layer needed
+ * to authenticate via GSS.
+ *
+ * @param host hostname of the server to be connected to
+ * @param user user name (may be null, a Kerberos principal or a Postgres user)
+ * @param info driver configuration
+ * @param logger logger which will be used to log
+ * @exception PSQLException on error. All exceptions which are catched in
+ * the constructor are passed as the cause of the PSQLException.
+ */
+ public GSSHelper(String host, String user, Properties info, Logger logger) throws PSQLException
+ {
+ krbServiceName = info.getProperty("krbsrvname", PG_KRB_SRV_NAM);
+
+ subject = new Subject();
+
+ Map krb5LoginOptions = new HashMap();
+ krb5LoginOptions.put("doNotPrompt", "true");
+ // set useTicketCache only if useKeyTab is "false"
+ if (System.getProperty("useKeyTab", "false").equalsIgnoreCase("false") &&
+ info.getProperty("useKeyTab", "false").equalsIgnoreCase("false"))
+ krb5LoginOptions.put("useTicketCache", "true");
+
+ // initialize kerberos stuff:
+ try
+ {
+ Krb5LoginModule krb5Login = new Krb5LoginModule();
+ krb5Login.initialize(subject, null, null, krb5LoginOptions);
+ krb5Login.login();
+ krb5Login.commit();
+ }
+ catch (LoginException e)
+ {
+ throw new PSQLException(
+ GT.tr("failed to initialize kerberos environment ({0})",
+ e.toString()), PSQLState.CONNECTION_UNABLE_TO_CONNECT, e);
+ }
+
+ // check if we have a valid TGT:
+ Set privateCredentials = subject.getPrivateCredentials(KerberosTicket.class);
+ KerberosTicket krb5Tgt = null;
+ for (Iterator i = privateCredentials.iterator(); i.hasNext();)
+ {
+ KerberosTicket ticket = (KerberosTicket)i.next();
+
+ if (ticket.isInitial() && ticket.isCurrent())
+ {
+ if (logger.logDebug())
+ logger.debug("found valid TGT of user " + ticket.getClient().getName());
+
+ // check if the TGT is from the supplied user:
+ if (user != null && user.length() > 0)
+ {
+ String principalName = new StringTokenizer(
+ ticket.getClient().getName().toLowerCase(), "/@").nextToken();
+
+ if (!user.toLowerCase().equals(principalName))
+ {
+ logger.info("TGT principal name does not match requested user");
+ continue;
+ }
+ }
+
+ krb5Tgt = ticket;
+ break;
+ }
+ }
+
+ if (krb5Tgt == null)
+ throw new PSQLException(
+ GT.tr("no valid TGT found"),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT);
+
+ userPrincipalName = krb5Tgt.getClient().getName();
+
+ // get the full qualified domain name of the database server
+ try
+ {
+ fqdn = InetAddress.getByName(host).getCanonicalHostName();
+ }
+ catch (UnknownHostException e)
+ {
+ throw new PSQLException(
+ GT.tr("failed to get full qualified domain name of {0}", host),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT, e);
+ }
+
+ // intialize the GSS API
+ try
+ {
+ context = (GSSContext)Subject.doAsPrivileged(
+ subject,
+ new PrivilegedExceptionAction()
+ {
+ public Object run() throws GSSException
+ {
+ GSSManager manager = GSSManager.getInstance();
+
+ GSSName gssPeerName = manager.createName(
+ krbServiceName + "@" + fqdn,
+ GSSName.NT_HOSTBASED_SERVICE);
+
+ GSSName gssUserName = manager.createName(
+ userPrincipalName,
+ GSSName.NT_USER_NAME);
+
+ GSSCredential userCredential = manager.createCredential(
+ gssUserName,
+ GSSCredential.DEFAULT_LIFETIME,
+ OID_KRB5_MECH,
+ GSSCredential.INITIATE_ONLY);
+
+ return manager.createContext(
+ gssPeerName,
+ OID_KRB5_MECH,
+ userCredential,
+ GSSContext.DEFAULT_LIFETIME);
+ }
+ }, null);
+
+ // enable mutual authentication:
+ context.requestMutualAuth(true);
+
+ // disable confidentiality and integrity checking after
+ // context establishment (this is not needed in our case)
+ context.requestConf(false);
+ context.requestInteg(false);
+ }
+ catch (GSSException e)
+ {
+ throw new PSQLException(
+ GT.tr("GSS API initialization failed: {0}", e.toString()),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT, e);
+ }
+ catch (PrivilegedActionException e)
+ {
+ GSSException cause = (GSSException)e.getCause();
+
+ throw new PSQLException(
+ GT.tr("GSS API initialization failed: {0}", cause.toString()),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT, e);
+ }
+ }
+
+
+ private byte[] token;
+ /**
+ * Establish the GSS API context between client an server.
+ *
+ * An authentication via GSS API is done by exchanging tokens between
+ * client and server until all authentication issues are met. In our case
+ * these are normally only two tokens. One to the server and one from the
+ * server (mutual authentication).
+ *
+ * The first call of this method may be done with null or an empty byte
+ * array as token. Successive calls have to be made with the token send
+ * from the server.
+ *
+ * @param in token received from the server, null or empty byte array
+ * @return token which has to be send to the server
+ * @exception PSQLException on error. All exceptions which are catched in
+ * this method are passed as the cause of the PSQLException.
+ */
+ public byte[] establishContext( byte[] in ) throws PSQLException
+ {
+ token = (in == null ? new byte[0] : in);
+
+ try
+ {
+ Subject.doAsPrivileged(subject, new PrivilegedExceptionAction()
+ {
+ public Object run() throws GSSException
+ {
+ if (!context.isEstablished())
+ token = context.initSecContext(token, 0, token.length);
+
+ return (token == null ? 0 : token.length);
+ }
+ }, null);
+ }
+ catch (PrivilegedActionException e)
+ {
+ GSSException cause = (GSSException)e.getCause();
+
+ throw new PSQLException(
+ GT.tr("GSS error occured: {0}", cause.toString()),
+ PSQLState.CONNECTION_UNABLE_TO_CONNECT, e);
+ }
+
+ return token;
+ }
+
+
+ /**
+ * Returns whether the GSS context between server and client is
+ * established.
+ *
+ * @return true if the context is established otherwise false
+ */
+ public boolean isContextEstablished()
+ {
+ return (context != null ? context.isEstablished() : false);
+ }
+
+
+ /**
+ * Returns the Postgres user name of local principal name.
+ *
+ * The user name is derived from the principal name by stripping any
+ * instance and realm from it.
+ *
+ * 'cju/admin@EXAMPLE.ORG' is converted to 'cju' for example.
+ *
+ * @return Postgres user name
+ */
+ public String getUserName()
+ {
+ if (userPrincipalName == null)
+ return null;
+
+ // return only the user from the principal name (analogous to
+ // C-function pg_an_to_ln())
+ return new StringTokenizer(userPrincipalName, "/@").nextToken();
+
+ }
+}