Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Stardust/Knowledge Base/Security/Single Sign-on/SSO using JAAS and GSS API with Apache DS and Kerberos"

m
m
 
(6 intermediate revisions by 3 users not shown)
Line 1: Line 1:
= Implementing SSO using JAAS and GSS API with Apache DS and Kerberos =
+
== Introduction ==
  
== Introduction:  ==
+
This tutorial explains what Kerberos protocol is and how it works at large. Then we will see how to implement SSO (Single Sign On) and secure communication using Kerberos. This tutorial will use the Apache DS 1.5.7 and simple Web Service application to demonstrate the SSO. We will use java security APIs like JAAS and Java GSS (Generic Security Services). It is assumed that you already know these technologies.
  
This tutorial explains what Kerberos protocol is and how it works at large. Then we will see how to implement SSO (Single Sign On) and secure communication using Kerberos. This tutorial will use the Apache DS 1.5.7 and simple Web Service application to demonstrate the SSO. I will use some java security APIs like JAAS and Java GSS (Generic Security Services). It is assumed that you know and understand how these technologies work.  
+
In rest of the article, we will see how Kerberos protocol works, how to setup Kerberos service on Apache DS, how to implement SSO and secure communication with detailed server and client side code. I have enclosed java source, properties and config files, discussed here in References section below.  
  
== What is Kerberos and how it works? ==
+
== Kerberos and how it works  ==
  
Kerberos is a network authentication protocol developed by Internet Engineering Task Force and has been wide adopted by both Java and .Net communities. This protocol doesn’t require that user password be sent over network for authentication.<br>Here is how Kerberos protocol works;<br>Consider there are two systems S1 and S2, which leverage the SSO and secure communication with Kerberos protocol. That means if user U1 is logged into S1, and S1 needs to talk to S2 for fulfilling user request, then user must not be prompted for authentication again but still, S2 must make sure that the user is one who says he is. Means S2 should authenticate the user seamlessly, without user being aware of it. Let’s see how SSO and secure communication works between S1 and S2;  
+
Kerberos is a network authentication protocol developed by Internet Engineering Task Force and has been widely adopted by both Java and .Net communities. This protocol doesn’t require that user password be sent over network for authentication. Rather, it uses concept of tokens and cryptographic keys.<br><br>Here is how Kerberos protocol works;<br>Consider there are two systems, S1 and S2, which leverage the SSO and secure communication with Kerberos protocol. That means, if user U1 is logs into S1, and S1 needs to talk to S2 for fulfilling user request, then user must not be prompted for authentication again, nor user credentials be passed to S2, but still, S2 must make sure that the user is one who says he is. Means, S2 must authenticate the user seamlessly, without user being aware of it and without user credentials being passed to S2. Let’s see how this happens with Kerberos protocol and how SSO and secure communication works between S1 and S2;  
  
 
#The user U1 logs into S1 with his/her credentials; typically username and password.  
 
#The user U1 logs into S1 with his/her credentials; typically username and password.  
#S1 request the Kerberos ticket form KDC (Key Distribution Center) by sending username only.  
+
#S1 request the Kerberos ticket form KDC (Key Distribution Center) by sending username only. For time being, you may consider KDC means any user repository supporting Kerberos protocol, for example, Microsoft Active Directory or Apache Directory Service.  
 
#The KDC issues an encrypted ticket containing session key. The ticket is encrypted by using key derived from user’s password. This ticket is called as TGT (Ticket Granting Ticket). Note that, although user password is not required for requesting TGT, password is required to decrypt it and fetch the session key in it. Without user password TGT is of no use.&nbsp;  
 
#The KDC issues an encrypted ticket containing session key. The ticket is encrypted by using key derived from user’s password. This ticket is called as TGT (Ticket Granting Ticket). Note that, although user password is not required for requesting TGT, password is required to decrypt it and fetch the session key in it. Without user password TGT is of no use.&nbsp;  
#S1 uses user’s password to decrypt the TGT and fetch session key from it and requests one more ticket from KDC for communication with S2. <br>Note that users and systems/services (like U1 and S2 here) must be registered with KDC prior.  
+
#S1 uses user’s password to decrypt the TGT and fetch session key from it and requests one more ticket from KDC for communication with S2. <br>Note that, users and systems/services (like U1 and S2 here) must already be registered with the KDC.  
#Now KDC creates a ticket called as Service Ticket and add the user’s authentication data and a new key called as sub-session key in it. The KDC encrypts the service ticket by key generated from S2’s password. Then, the KDC creates a message, adds encrypted service ticket and sub-session key in it and encrypts the complete message by a key generated from user’s password and sends it back to the S1.<br>Note that, now message contains the sub-session key twice, once directly in the message and then inside encrypted service ticket. The service ticket can be used only for communication between S1 and S2. This complete message can only be decrypted by the user and the enclosed encrypted service ticket can only be decrypted by S2.  
+
#Now the KDC creates a ticket, called as Service Ticket, and adds the user’s authentication data and a new key, called as sub-session key, in it. The KDC encrypts the service ticket by a key generated from S2’s password. Then, the KDC creates a message, adds encrypted service ticket and sub-session key in it, and again encrypts the complete message by a key generated from user’s password, and sends it back to the S1.<br>[Note that, now message contains the sub-session key twice, once directly in the message and inside encrypted service ticket. The service ticket can be used only for communication between S1 and S2. This complete message can only be decrypted by the user and the enclosed encrypted service ticket can only be decrypted by S2.]
 
#The S1 decrypts the message with user’s password and takes out service ticket and sub-session key.  
 
#The S1 decrypts the message with user’s password and takes out service ticket and sub-session key.  
#S1 sends the service ticket (which is encrypted) to S2. The S2 decrypts the service ticket and takes out U1’s authentication data and sub-session key.<br>Now, S2 will trust the user’s authentication data as it could decrypt service ticket with its own password and that means it has come from KDC (which already has authenticated the user) and hence it is authentic. Hence SSO!  
+
#S1 sends the service ticket (which is encrypted) to S2. The S2 decrypts the service ticket and takes out U1’s authentication data and sub-session key.<br>Now, S2 will trust the user’s authentication data as it could decrypt service ticket with its own password, that means it has come from KDC (which already has authenticated the user) and hence it is authentic. Hence SSO!  
#Now, both S1 and S2 have same key (sub-session key) which they will use for secure communication with each other. <br>
+
#Now both, S1 and S2, have the same key (sub-session key) which they will use for secure communication with each other. <br>
  
This is how SSO and secure communication is achieved using Kerberos.  
+
This is how SSO and secure communication is achieved using Kerberos. All these steps mention above are performed by JAAS and GSS API. We will use higher level methods of these security APIs to perform these steps.  
  
 
'''Note that KDC functionality is generally implemented by directory servers like MS AD and Apache DS. For this article you may consider KDC means Apache DS. <br>'''
 
'''Note that KDC functionality is generally implemented by directory servers like MS AD and Apache DS. For this article you may consider KDC means Apache DS. <br>'''
Line 24: Line 24:
 
== Setting up KDC with Apache DS  ==
 
== Setting up KDC with Apache DS  ==
  
Download Apache DS 1.5.7 and Apache Directory Studio 1.5.3 from Apache site and install it on your local machine. By default, it doesn’t have Kerberos service running. Follow the steps below to activate it. Note that these steps are only valid for DS version 1.5.7.  
+
As mentioned above, KDC functionality is generally implemented as part of user repositories like Microsoft Active Directory. For this article I have used Apache DS.<br>Download Apache DS 1.5.7 and Apache Directory Studio 1.5.3 from Apache site(http://directory.apache.org/) and install it on your local machine. By default, it doesn’t have Kerberos service running. Follow the steps below to activate&nbsp;Kerberos, and create user and server account SPNs (Service Principle Name). SPN uniquely identifies the&nbsp;user or service account in&nbsp;the directory server.&nbsp;Note that these steps are only valid for DS version 1.5.7.  
  
&nbsp; 1.&nbsp; Under your Apache Directory Server install (C:\Program Files\Apache Directory Server), navigate to the folder instances\default\conf. This folder contains the config files for your server instance. server.xml is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file up in a text editor.  
+
&nbsp; 1.&nbsp; Under your Apache Directory Server install (C:\Program Files\Apache Directory Server), navigate to the folder instances\default\conf. This folder contains the config files for your server instance. The file, server.xml, is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file in a text editor.  
  
&nbsp; 2.&nbsp; Set the enabled property to “true” to enable the KDC. By default, it is false.
+
&nbsp; 2.&nbsp; Set the enabled property to “true” to enable the KDC. By default, it is false. <source lang="XML">
<source lang="XML">  
+
 
<bean id="kdcConfiguration"  
 
<bean id="kdcConfiguration"  
  class="org.apache.directory.server.kerberos.kdc.KdcConfiguration">
+
class="org.apache.directory.server.kerberos.kdc.KdcConfiguration">
  <!-- Whether to enable the Kerberos protocol. -->  
+
<!-- Whether to enable the Kerberos protocol. -->  
  <property name="enabled" value="true" />  
+
<property name="enabled" value="true" />  
  <!-- The port to run the Kerberos protocol on. -->  
+
<!-- The port to run the Kerberos protocol on. -->  
  <property name="ipPort" value="88" />  
+
<property name="ipPort" value="88" />  
  <!-- <property name="searchBaseDn" value="ou=Users"/> -->  
+
<!-- <property name="searchBaseDn" value="ou=Users"/> -->  
 
</bean>
 
</bean>
</source>
+
</source> &nbsp; 3.&nbsp; Uncomment following bean. <source lang="XML">
&nbsp; 3.&nbsp; Uncomment following bean.  
+
<source lang="XML">
+
 
<bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
 
<bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
  <property name="name" value="keyDerivationService" />
+
<property name="name" value="keyDerivationService" />  
  <property name="interceptorClassName"  
+
<property name="interceptorClassName"  
  value="org.apache.directory.server.core.kerberos.KeyDerivationService" />
+
value="org.apache.directory.server.core.kerberos.KeyDerivationService" />
</source>
+
</source> &nbsp; 4. You will have to create user and service SPN in DS. You can do this using Apache Directory Studio, but the easiest way is to use LDIF file. You can use this file to define your SPN details. When you place this file at appropriate location, it would be loaded by DS on startup and it creates the specified SPNs. Now create a new .ldif file and add following content in it. It will create a user with name “monkey2” and service with name “webserver2/localhost” under example.com domain.<br>
&nbsp; 4. You will have to create user and service SPN in DS. You can do this using Apache Directory Studio, but the easiest way is to use LDIF file. You can use this file to define your SPN details. When you place this file at appropriate location, it would be loaded by DS on startup and it creates the specified SPNs. Now create a new .ldif file and add following content in it. It will create a user with name “monkey2” and service with name “webserver2/localhost” under example.com domain.<br>
+
 
<pre># Web server2 identity/service principal.
 
<pre># Web server2 identity/service principal.
 
dn: uid=webserver2,ou=users,dc=example,dc=com
 
dn: uid=webserver2,ou=users,dc=example,dc=com
Line 96: Line 92:
 
&nbsp; 8.&nbsp; If ldif file was loaded successfully, it will have setup the client principal, the server principal, and the krbtgt service. That is, you now have a fully functioning KDC. Now you can try running your Kerberos example against the KDC. <br>
 
&nbsp; 8.&nbsp; If ldif file was loaded successfully, it will have setup the client principal, the server principal, and the krbtgt service. That is, you now have a fully functioning KDC. Now you can try running your Kerberos example against the KDC. <br>
  
&nbsp;
+
&nbsp;  
  
 
== Writing the Server Part  ==
 
== Writing the Server Part  ==
Line 104: Line 100:
 
=== Server Class:  ===
 
=== Server Class:  ===
  
This class exposes the EchoService class as JAX-RS service&nbsp;at URL [http://localhost:9000/ http://localhost:9000/]  
+
This class exposes the EchoService class, given below,&nbsp;as JAX-RS service&nbsp;at URL [http://localhost:9000/ http://localhost:9000/] <source lang="Java">
<source lang="Java">
+
 
package com.gyan.rs;
 
package com.gyan.rs;
  
Line 113: Line 108:
 
public class Server {
 
public class Server {
  
protected Server() throws Exception {
+
protected Server() throws Exception {
        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(EchoService.class);
+
sf.setResourceClasses(EchoService.class);
        sf.setResourceProvider(EchoService.class,  
+
sf.setResourceProvider(EchoService.class,  
            new SingletonResourceProvider(new EchoService()));
+
new SingletonResourceProvider(new EchoService()));
        sf.setAddress("http://localhost:9000/");
+
sf.setAddress("http://localhost:9000/");
       
+
        sf.create();
+
sf.create();
    }
+
}
  
    public static void main(String args[]) throws Exception {
+
public static void main(String args[]) throws Exception {
        new Server();
+
new Server();
        System.out.println("Server ready...");
+
System.out.println("Server ready...");
        Thread.sleep(5 * 60 * 1000);
+
Thread.sleep(5 * 60 * 1000);
        System.out.println("Server exiting");
+
System.out.println("Server exiting");
        System.exit(0);
+
System.exit(0);
    }
+
}
 
}
 
}
</source> &nbsp;
+
</source> &nbsp;  
  
 
=== EchoService Class:  ===
 
=== EchoService Class:  ===
  
This class is part of our example business service layer. It will just extract the authentication details, Kerberos token in this case, from incoming request and verify it with already initialized server context using utility class called GSSServer. If verification fails, it throws the security exception. Note the usage of Base 64 encoding and decoding to avoid sending raw Kerberos tokens and URL sage messages across client and server.  
+
This class is part of our example business service layer. It will just extract the authentication details, Kerberos token in this case, from incoming request and verify it with already initialized server context using a utility class, called GSSServer. If verification fails, it throws the security exception. Note the usage of Base 64 encoding and decoding to avoid sending raw Kerberos tokens and URL safe messages across client and server.&nbsp;If you are&nbsp;not passing the token as part of URL, you may not need to&nbsp;use&nbsp;encoding here.
<source lang="Java">
+
 
 +
Also note, normally, you will implement security features, like authentication, as part of separate component. To avoid complexity, I have implemented the Kerberos token verification as part of this service.
 +
 
 +
&nbsp;<source lang="Java">
 
package com.gyan.rs;
 
package com.gyan.rs;
  
Line 152: Line 150:
 
public class EchoService {
 
public class EchoService {
  
private GSSServer gssServer;
+
private GSSServer gssServer;  
public EchoService(){
+
public EchoService(){
gssServer = new GSSServer();
+
gssServer = new GSSServer();
        gssServer.startServer();
+
gssServer.startServer();
}
+
}
@GET
+
@GET
@Path("/{msg}/")
+
@Path("/{msg}/")
public String saySomthing(@PathParam("msg") String clientMsg, @DefaultValue("NA")  
+
public String saySomthing(@PathParam("msg") String clientMsg, @DefaultValue("NA")  
@HeaderParam("Authorization") String kerberosToken) throws GSSException {
+
@HeaderParam("Authorization") String kerberosToken) throws GSSException {
+
System.out.println("auth:"+ kerberosToken);
+
System.out.println("auth:"+ kerberosToken);
System.out.println("Encrypted message as is: "+ clientMsg);
+
System.out.println("Encrypted message as is: "+ clientMsg);
byte[] convert = Base64.decodeBase64(kerberosToken);
+
byte[] convert = Base64.decodeBase64(kerberosToken);
try {
+
try {
gssServer.authenticate(convert);
+
gssServer.authenticate(convert);
byte[] unWrapMessage = gssServer.unWrapMessage(Base64.decodeBase64(clientMsg));
+
byte[] unWrapMessage = gssServer.unWrapMessage(Base64.decodeBase64(clientMsg));
clientMsg = new String(unWrapMessage);
+
clientMsg = new String(unWrapMessage);
} catch (GSSException e) {
+
} catch (GSSException e) {
e.printStackTrace();
+
e.printStackTrace();
}
+
}
System.out.println("Decrypted Msg: "+ clientMsg);
+
System.out.println("Decrypted Msg: "+ clientMsg);
return Base64.encodeBase64URLSafeString(gssServer.wrapMessage(
+
  return Base64.encodeBase64URLSafeString(gssServer.wrapMessage(
("You said '" + clientMsg + "'").getBytes()));
+
("You said '" + clientMsg + "'").getBytes()));
}
+
}  
 
}
 
}
  
</source>
+
</source>  
  
 
=== GSS Server Properties:  ===
 
=== GSS Server Properties:  ===
  
&nbsp;KDC and other properties are store as part of gssserver.properties file as shown below. It is used to store location of KDC server, realm/domain, and JAAS config details.  
+
The&nbsp;KDC and other properties are store as part of gssserver.properties file as shown below. It contains location of KDC server, server SPN, realm/domain and JAAS config details.  
 
<pre>#This file contains the KDC server details and Server service SPN details
 
<pre>#This file contains the KDC server details and Server service SPN details
 
Server.SPN = webserver2/localhost@EXAMPLE.COM
 
Server.SPN = webserver2/localhost@EXAMPLE.COM
Line 192: Line 190:
  
 
</pre>
 
</pre>
 +
Note, you will have to change the JAAS config path as per your settings. It is an absolute path.
 +
 
=== JAAS Config File:  ===
 
=== JAAS Config File:  ===
  
The JAAS conf file for client and server side details looks like following;  
+
The JAAS conf file for client and server&nbsp;looks like following;  
<pre>
+
<pre>GSSClient{
GSSClient{
+
 
   com.sun.security.auth.module.Krb5LoginModule required
 
   com.sun.security.auth.module.Krb5LoginModule required
 
   useTicketCache=false;
 
   useTicketCache=false;
Line 210: Line 209:
 
=== GSSServer Class:  ===
 
=== GSSServer Class:  ===
  
This class is initialized during service creation. It abstracts out the authentication and communication with KDC server using JAAS and GSS API. This class uses the server properties mentioned above to initialize the security context. Later on when, client request comes in, client’s Kerberos token will be validated against this security context. Note the usage of GSSContext class which is part of GSS API to authenticate the client’s Kerberos token and encrypt (wrap method) and decrypt (unwrap method) the messages between client and server. Initialized GSSContext will have the required keys for given client.  
+
This class is initialized during service creation. It abstracts out the communication with KDC server using JAAS and GSS API. This class uses the server properties, mentioned above, to initialize the security context. Later on when, client request comes in, client’s Kerberos token will be validated against this security context. Note the usage of GSSContext class, which is part of GSS API, to authenticate the client’s Kerberos token and encrypt (wrap method) and decrypt (unwrap method) the messages between client and server. Initialized GSSContext will have the required keys for given client.  
  
<br>
+
<br><source lang="Java">
<source lang="Java">
+
 
package org.gyan.server;
 
package org.gyan.server;
  
Line 236: Line 234:
 
public class GSSServer implements java.security.PrivilegedAction {
 
public class GSSServer implements java.security.PrivilegedAction {
  
// Handles callback from the JAAS framework.
+
// Handles callback from the JAAS framework.
BeanCallbackHandler beanCallbackHandler = null;
+
BeanCallbackHandler beanCallbackHandler = null;
  
// The main object that handles all JAAS login.
+
// The main object that handles all JAAS login.
LoginContext serverLC = null;
+
LoginContext serverLC = null;
  
// The context for secure communication with client.
+
// The context for secure communication with client.
GSSContext serverGSSContext = null;
+
GSSContext serverGSSContext = null;
  
// Socket and streams used for communication.
+
// Socket and streams used for communication.
ServerSocket serverSocket = null;
+
ServerSocket serverSocket = null;
DataInputStream inStream = null;
+
DataInputStream inStream = null;
DataOutputStream outStream = null;
+
DataOutputStream outStream = null;
  
// Name and port of server.
+
// Name and port of server.
String serverName = null;
+
String serverName = null;
int serverPort;
+
int serverPort;
  
// Configuration file and the name of the client configuration.
+
// Configuration file and the name of the client configuration.
String confFile = null;
+
String confFile = null;
String confName = null;
+
String confName = null;
  
private String clientName;
+
private String clientName;
  
public static void main(String[] args) throws IOException, GSSException {
+
public static void main(String[] args) throws IOException, GSSException {
// Starting the server.
+
// Starting the server.
new GSSServer().startServer();
+
new GSSServer().startServer();
  
}// main
+
}// main
  
// GSSServer constructor
+
// GSSServer constructor
public GSSServer() {
+
public GSSServer() {
Properties env = new Properties();
+
Properties env = new Properties();
try {
+
try {
env.load(this.getClass().getClassLoader()
+
env.load(this.getClass().getClassLoader()
.getResourceAsStream("gssserver.properties"));
+
.getResourceAsStream("gssserver.properties"));
} catch (IOException e) {
+
} catch (IOException e) {
e.printStackTrace();
+
e.printStackTrace();
}
+
}
  
this.serverName = (String) env.getProperty("Server.SPN");
+
this.serverName = (String) env.getProperty("Server.SPN");
this.confName = (String) env.getProperty("JAAS.Config.Name");
+
this.confName = (String) env.getProperty("JAAS.Config.Name");
  
beanCallbackHandler = new BeanCallbackHandler(this.serverName,
+
beanCallbackHandler = new BeanCallbackHandler(this.serverName,
(String) env.getProperty("Server.Password"));
+
(String) env.getProperty("Server.Password"));
setSystemProperties((String) env.getProperty("KDC.Realm"),
+
setSystemProperties((String) env.getProperty("KDC.Realm"),
(String) env.getProperty("KDC.Address"),
+
(String) env.getProperty("KDC.Address"),
(String) env.getProperty("JAAS.Config.Path"));
+
(String) env.getProperty("JAAS.Config.Path"));
}// GSSServer
+
}// GSSServer
  
private void setSystemProperties(String kerberosRealm, String kdcAddress,
+
private void setSystemProperties(String kerberosRealm, String kdcAddress,
String confFile) {
+
String confFile) {
System.setProperty("java.security.krb5.realm", kerberosRealm);
+
System.setProperty("java.security.krb5.realm", kerberosRealm);
System.setProperty("java.security.krb5.kdc", kdcAddress);
+
System.setProperty("java.security.krb5.kdc", kdcAddress);
System.setProperty("java.security.auth.login.config", confFile);
+
System.setProperty("java.security.auth.login.config", confFile);
System.setProperty("sun.security.krb5.debug", "true");
+
System.setProperty("sun.security.krb5.debug", "true");
}
+
}
  
public boolean startServer() {
+
public boolean startServer() {
try {
+
try {
serverLC = new LoginContext(confName, beanCallbackHandler);
+
serverLC = new LoginContext(confName, beanCallbackHandler);
serverLC.login();
+
serverLC.login();
Subject.doAs(serverLC.getSubject(), this);
+
Subject.doAs(serverLC.getSubject(), this);
return true;
+
return true;
} catch (Exception e) {
+
} catch (Exception e) {
e.printStackTrace();
+
e.printStackTrace();
System.out
+
System.out
.println(">>> GSSServer...Secure Context not established..");
+
.println(">>> GSSServer...Secure Context not established..");
return false;
+
return false;
}// catch
+
}// catch
  
}// start
+
}// start
  
public Object run() {
+
public Object run() {
try {
+
try {
GSSManager manager = GSSManager.getInstance();
+
GSSManager manager = GSSManager.getInstance();
Oid kerberos = new Oid("1.2.840.113554.1.2.2");
+
Oid kerberos = new Oid("1.2.840.113554.1.2.2");
GSSName serverGSSName = manager.createName(serverName,
+
GSSName serverGSSName = manager.createName(serverName,
GSSName.NT_USER_NAME);
+
GSSName.NT_USER_NAME);
GSSCredential serverGSSCreds = manager.createCredential(
+
GSSCredential serverGSSCreds = manager.createCredential(
serverGSSName, GSSCredential.INDEFINITE_LIFETIME, kerberos,
+
serverGSSName, GSSCredential.INDEFINITE_LIFETIME, kerberos,
// The server accepts secure context request.
+
// The server accepts secure context request.
GSSCredential.ACCEPT_ONLY);
+
GSSCredential.ACCEPT_ONLY);
serverGSSContext = manager.createContext(serverGSSCreds);
+
serverGSSContext = manager.createContext(serverGSSCreds);
}// try
+
}// try
catch (java.lang.Exception e) {
+
catch (java.lang.Exception e) {
e.printStackTrace();
+
e.printStackTrace();
}
+
}
return null;
+
return null;
}// run
+
}// run
  
public void authenticate(byte[] kerberosToken) throws GSSException {
+
public void authenticate(byte[] kerberosToken) throws GSSException {
if (!serverGSSContext.isEstablished()) {
+
if (!serverGSSContext.isEstablished()) {
serverGSSContext.acceptSecContext(kerberosToken, 0,
+
serverGSSContext.acceptSecContext(kerberosToken, 0,
kerberosToken.length);
+
kerberosToken.length);
clientName = serverGSSContext.getTargName().toString();
+
clientName = serverGSSContext.getTargName().toString();
}
+
}
}
+
}
  
public byte[] wrapMessage(byte[] msg) throws GSSException {
+
public byte[] wrapMessage(byte[] msg) throws GSSException {
MessageProp msgProp = new MessageProp(0, false);
+
MessageProp msgProp = new MessageProp(0, false);
return serverGSSContext.wrap(msg, 0, msg.length, msgProp);
+
return serverGSSContext.wrap(msg, 0, msg.length, msgProp);
}
+
}
  
public byte[] unWrapMessage(byte[] msg) throws GSSException {
+
public byte[] unWrapMessage(byte[] msg) throws GSSException {
MessageProp msgProp = new MessageProp(0, false);
+
MessageProp msgProp = new MessageProp(0, false);
return serverGSSContext.unwrap(msg, 0, msg.length, msgProp);
+
return serverGSSContext.unwrap(msg, 0, msg.length, msgProp);
}
+
}
  
public void closeServerContext() throws GSSException {
+
public void closeServerContext() throws GSSException {
// Disposing and closing client and server sockets.
+
// Disposing and closing client and server sockets.
serverGSSContext.dispose();
+
serverGSSContext.dispose();
}
+
}
  
public String getClientName() {
+
public String getClientName() {
return clientName;
+
return clientName;
}
+
}
 
}// End of GSSServer
 
}// End of GSSServer
  
</source>
+
</source> <br>
<br>
+
  
 
== How Client Side Looks Like  ==
 
== How Client Side Looks Like  ==
Line 368: Line 365:
 
The client class uses the GSSClient class to pass username and password to authenticate the user and get the Kerberos token (service ticket for given server). Note that token is in byte array format. So we used Base 64 encoding to convert it into string and then we simply add it as part of authentication header. That’s it there in client. So, actual stuff happens in GSSClient which is discussed next;  
 
The client class uses the GSSClient class to pass username and password to authenticate the user and get the Kerberos token (service ticket for given server). Note that token is in byte array format. So we used Base 64 encoding to convert it into string and then we simply add it as part of authentication header. That’s it there in client. So, actual stuff happens in GSSClient which is discussed next;  
  
<br>
+
<br><source lang="Java">
<source lang="Java">
+
 
package com.gyan.rs;
 
package com.gyan.rs;
  
Line 383: Line 379:
 
public class Client1 {
 
public class Client1 {
  
private static GSSClient gssClient;
+
private static GSSClient gssClient;
  
/**
+
/**
* @param args
+
* @param args
* @throws IOException
+
* @throws IOException
* @throws HttpException
+
* @throws HttpException
* @throws InterruptedException  
+
* @throws InterruptedException  
*/
+
*/
public static void main(String[] args) throws HttpException, IOException, InterruptedException {
+
public static void main(String[] args) throws HttpException, IOException, InterruptedException {
+
String theClientCred = authenticateAndGetKerberosToken();
+
String theClientCred = authenticateAndGetKerberosToken();
+
+
String serviceAddress = "http://localhost:9000/echoservice/";
+
String serviceAddress = "http://localhost:9000/echoservice/";
String message = "Hi man, how are u?";
+
String message = "Hi man, how are u?";
System.out.println(message);
+
System.out.println(message);
try {
+
try {
byte[] wrapedMsg = gssClient.wrapMessage(message.getBytes());
+
byte[] wrapedMsg = gssClient.wrapMessage(message.getBytes());
serviceAddress += Base64.encodeBase64URLSafeString(wrapedMsg);
+
serviceAddress += Base64.encodeBase64URLSafeString(wrapedMsg);
} catch (GSSException e) {
+
} catch (GSSException e) {
e.printStackTrace();
+
e.printStackTrace();
}
+
}
GetMethod get = new GetMethod(serviceAddress);
+
GetMethod get = new GetMethod(serviceAddress);
  
get.setRequestHeader("Authorization", theClientCred);
+
get.setRequestHeader("Authorization", theClientCred);
HttpClient httpclient = new HttpClient();
+
HttpClient httpclient = new HttpClient();
  
try {
+
try {
int result = httpclient.executeMethod(get);
+
int result = httpclient.executeMethod(get);
System.out.println("Response status code: " + result);
+
System.out.println("Response status code: " + result);
System.out.println("Response body: ");
+
System.out.println("Response body: ");
System.out.println("Return message from server as is: "
+
System.out.println("Return message from server as is: "
+ get.getResponseBodyAsString());
+
+ get.getResponseBodyAsString());
System.out.println("After decryptionm: "
+
System.out.println("After decryptionm: "
+ new String(gssClient.unWrapMessage(Base64
+
+ new String(gssClient.unWrapMessage(Base64
.decodeBase64(get.getResponseBodyAsString()))));
+
.decodeBase64(get.getResponseBodyAsString()))));
} catch (GSSException e) {
+
} catch (GSSException e) {
// TODO Auto-generated catch block
+
// TODO Auto-generated catch block
e.printStackTrace();
+
e.printStackTrace();
} finally {
+
} finally {
// Release current connection to the connection pool once you are done
+
// Release current connection to the connection pool once you are done
get.releaseConnection();
+
get.releaseConnection();
}
+
}
// Thread.sleep(10000);
+
// Thread.sleep(10000);
}
+
}
  
private static String authenticateAndGetKerberosToken() {
+
private static String authenticateAndGetKerberosToken() {
gssClient = new GSSClient("monkey", "Password99");
+
gssClient = new GSSClient("monkey", "Password99");
String serviceTicket = Base64.encodeBase64URLSafeString(gssClient.login());
+
String serviceTicket = Base64.encodeBase64URLSafeString(gssClient.login());
return serviceTicket;
+
return serviceTicket;
}
+
}
 
}//End of Class
 
}//End of Class
  
</source>
+
</source> &nbsp;  
&nbsp;  
+
  
 
=== Client Side Properties:  ===
 
=== Client Side Properties:  ===
Line 455: Line 450:
 
=== GSSClient Class:  ===
 
=== GSSClient Class:  ===
  
This class uses the JAAS and GSS API to authenticate and create security context against given KDC. Once client initializes the security context, it can be used for SSO and secure communication with given server.<br>Here KDC details are loaded from gssclient.properties file. Note that&nbsp;how client side uses initSecContext method of GSSContext to&nbsp;establish connection with the server.
+
This class uses the JAAS and GSS API to authenticate and create security context against given KDC. Once client initializes the security context, it can be used for SSO and secure communication with given server.<br>Here KDC details are loaded from gssclient.properties file. Note that&nbsp;how client side uses initSecContext method of GSSContext to&nbsp;establish connection with the server.  
  
<br>
+
<br><source lang="Java">
<source lang="Java">
+
 
package org.gyan;
 
package org.gyan;
  
Line 481: Line 475:
 
public class GSSClient implements java.security.PrivilegedAction<byte[]> {
 
public class GSSClient implements java.security.PrivilegedAction<byte[]> {
  
// Handles callback from the JAAS framework.
+
// Handles callback from the JAAS framework.
private BeanCallbackHandler beanCallbackHandler = null;
+
private BeanCallbackHandler beanCallbackHandler = null;
  
// The main object that handles all JAAS login.
+
// The main object that handles all JAAS login.
LoginContext peerLC = null;
+
LoginContext peerLC = null;
  
// Socket and streams used for communication.
+
// Socket and streams used for communication.
Socket socket = null;
+
Socket socket = null;
DataInputStream inStream;
+
DataInputStream inStream;
DataOutputStream outStream;
+
DataOutputStream outStream;
  
// This and remote clients.
+
// This and remote clients.
String clientName = null;
+
String clientName = null;
String serverName = null;
+
String serverName = null;
  
// Address and port of remote server.
+
// Address and port of remote server.
String serverAddress = null;
+
String serverAddress = null;
int serverPort;
+
int serverPort;
  
// The name of the client configuration.
+
// The name of the client configuration.
String confName = null;
+
String confName = null;
  
private GSSContext clientGSSContext = null;
+
private GSSContext clientGSSContext = null;
  
/**
+
/**
* @param clientName, Client SPN  
+
* @param clientName, Client SPN  
* @param password, client password
+
* @param password, client password
*/
+
*/
public GSSClient(String clientName, String password){
+
public GSSClient(String clientName, String password){
Properties env = new Properties();
+
Properties env = new Properties();
try {
+
try {
env.load(this.getClass().getClassLoader().getResourceAsStream("gssclient.properties"));
+
env.load(this.getClass().getClassLoader().getResourceAsStream("gssclient.properties"));
} catch (IOException e) {
+
} catch (IOException e) {
// TODO Auto-generated catch block
+
// TODO Auto-generated catch block
e.printStackTrace();
+
e.printStackTrace();
}
+
}
this.clientName = clientName;
+
this.clientName = clientName;
this.serverName = (String)env.getProperty("Server.SPN");
+
this.serverName = (String)env.getProperty("Server.SPN");
this.serverAddress = (String)env.getProperty("Server.Address");
+
this.serverAddress = (String)env.getProperty("Server.Address");
this.confName = (String)env.getProperty("JAAS.Config.Name");
+
this.confName = (String)env.getProperty("JAAS.Config.Name");
+
beanCallbackHandler = new BeanCallbackHandler(clientName, password);
+
beanCallbackHandler = new BeanCallbackHandler(clientName, password);
setSystemProperties((String)env.getProperty("KDC.Realm"),
+
setSystemProperties((String)env.getProperty("KDC.Realm"),
(String)env.getProperty("KDC.Address"),
+
(String)env.getProperty("KDC.Address"),
(String)env.getProperty("JAAS.Config.Path"));
+
(String)env.getProperty("JAAS.Config.Path"));
}
+
}
/**
+
/**
* The GSSClient constructor only sets all the required parameters.
+
* The GSSClient constructor only sets all the required parameters.
*  
+
*  
* @param clientName, Client SPN  
+
* @param clientName, Client SPN  
* @param password, client password
+
* @param password, client password
* @param serverName, Remote server/service SPN
+
* @param serverName, Remote server/service SPN
* @param serverAddress, Remote Server address IP or Hostname  
+
* @param serverAddress, Remote Server address IP or Hostname  
* @param kerberosRealm, KDC realm or domain name
+
* @param kerberosRealm, KDC realm or domain name
* @param kdcAddress, KDC Server address IP or Hostname  
+
* @param kdcAddress, KDC Server address IP or Hostname  
* @param confFile, fll path of JAAS COnfig file
+
* @param confFile, fll path of JAAS COnfig file
* @param confName, JAAS config name
+
* @param confName, JAAS config name
*/
+
*/
public GSSClient(String clientName, String password, String serverName,
+
public GSSClient(String clientName, String password, String serverName,
String serverAddress, String kerberosRealm,
+
String serverAddress, String kerberosRealm,
String kdcAddress, String confFile, String confName){
+
String kdcAddress, String confFile, String confName){
// The beanCallbackHandler will require the name and password of the
+
// The beanCallbackHandler will require the name and password of the
// client.
+
// client.
beanCallbackHandler = new BeanCallbackHandler(clientName, password);
+
beanCallbackHandler = new BeanCallbackHandler(clientName, password);
this.clientName = clientName;
+
this.clientName = clientName;
this.serverName = serverName;
+
this.serverName = serverName;
this.serverAddress = serverAddress;
+
this.serverAddress = serverAddress;
this.confName = confName;
+
this.confName = confName;
setSystemProperties(kerberosRealm, kdcAddress, confFile);
+
setSystemProperties(kerberosRealm, kdcAddress, confFile);
}// KerberoseLoginBean
+
}// KerberoseLoginBean
+
private void setSystemProperties(String kerberosRealm, String kdcAddress,
+
private void setSystemProperties(String kerberosRealm, String kdcAddress,
String confFile) {
+
String confFile) {
System.setProperty("java.security.krb5.realm", kerberosRealm);
+
System.setProperty("java.security.krb5.realm", kerberosRealm);
System.setProperty("java.security.krb5.kdc", kdcAddress);
+
System.setProperty("java.security.krb5.kdc", kdcAddress);
System.setProperty("java.security.auth.login.config", confFile);
+
System.setProperty("java.security.auth.login.config", confFile);
System.setProperty("sun.security.krb5.debug", "true");
+
System.setProperty("sun.security.krb5.debug", "true");
}
+
}
  
public byte[] login() {
+
public byte[] login() {
try {
+
try {
peerLC = new LoginContext(confName, beanCallbackHandler);
+
peerLC = new LoginContext(confName, beanCallbackHandler);
peerLC.login();
+
peerLC.login();
return Subject.doAs(peerLC.getSubject(), this);
+
return Subject.doAs(peerLC.getSubject(), this);
} catch (Exception e) {
+
} catch (Exception e) {
System.out
+
System.out
.println(">>>> GSSClient....Secure Context not established..");
+
.println(">>>> GSSClient....Secure Context not established..");
e.printStackTrace();
+
e.printStackTrace();
return null;
+
return null;
}// catch
+
}// catch
  
}// establishSecureContextWithServer
+
}// establishSecureContextWithServer
  
// This is the only method in PrivilegedAction interface.
+
// This is the only method in PrivilegedAction interface.
// It receives control only in case of successful authentication of the
+
// It receives control only in case of successful authentication of the
// client.
+
// client.
public byte[] run() {
+
public byte[] run() {
  
try {
+
try {
GSSManager manager = GSSManager.getInstance();
+
GSSManager manager = GSSManager.getInstance();
Oid kerberos = new Oid("1.2.840.113554.1.2.2");
+
Oid kerberos = new Oid("1.2.840.113554.1.2.2");
GSSName clientPeerName = manager.createName(
+
GSSName clientPeerName = manager.createName(
// Name of the client for which we want to create this GSSName
+
// Name of the client for which we want to create this GSSName
// object.
+
// object.
clientName,
+
clientName,
// Type of GSSName. Our client is a Windows user,
+
// Type of GSSName. Our client is a Windows user,
// which we can specifiy using GSSName.NT_USER_NAME
+
// which we can specifiy using GSSName.NT_USER_NAME
// property.
+
// property.
GSSName.NT_USER_NAME);
+
GSSName.NT_USER_NAME);
// GSSName remotePeerName2 = manager.createName("FakeSERver",
+
// GSSName remotePeerName2 = manager.createName("FakeSERver",
// GSSName.NT_USER_NAME);
+
// GSSName.NT_USER_NAME);
GSSName remotePeerName = manager.createName(serverName,
+
GSSName remotePeerName = manager.createName(serverName,
GSSName.NT_USER_NAME);
+
GSSName.NT_USER_NAME);
System.out.println(">>> GSSClient... Getting client credentials");
+
System.out.println(">>> GSSClient... Getting client credentials");
  
GSSCredential peerCredentials = manager.createCredential(
+
GSSCredential peerCredentials = manager.createCredential(
// The GSSName object of the client.
+
// The GSSName object of the client.
clientPeerName,
+
clientPeerName,
// Time for which credentials whill be valid.
+
// Time for which credentials whill be valid.
10 * 600,
+
10 * 600,
// Kerberos mecahnism identifier.
+
// Kerberos mecahnism identifier.
kerberos,
+
kerberos,
// The client only intiates the secure context request.
+
// The client only intiates the secure context request.
GSSCredential.INITIATE_ONLY);
+
GSSCredential.INITIATE_ONLY);
  
System.out
+
System.out
.println(">>> GSSClient...GSSManager creating security context");
+
.println(">>> GSSClient...GSSManager creating security context");
clientGSSContext= manager.createContext(remotePeerName, kerberos,
+
clientGSSContext= manager.createContext(remotePeerName, kerberos,
peerCredentials, GSSContext.DEFAULT_LIFETIME);
+
peerCredentials, GSSContext.DEFAULT_LIFETIME);
  
// peerContext.requestCredDeleg(true);
+
// peerContext.requestCredDeleg(true);
clientGSSContext.requestConf(true);
+
clientGSSContext.requestConf(true);
clientGSSContext.requestMutualAuth(false);// I don't want to authenticate the  
+
clientGSSContext.requestMutualAuth(false);// I don't want to authenticate the  
// Server.
+
// Server.
byte[] byteToken = new byte[0];
+
byte[] byteToken = new byte[0];
  
byteToken = clientGSSContext.initSecContext(byteToken, 0,
+
byteToken = clientGSSContext.initSecContext(byteToken, 0,
byteToken.length);
+
byteToken.length);
return byteToken;
+
return byteToken;
}// try
+
}// try
catch (org.ietf.jgss.GSSException ge) {
+
catch (org.ietf.jgss.GSSException ge) {
ge.printStackTrace();
+
ge.printStackTrace();
System.out.println(">>> GSSClient... GSS Exception "
+
System.out.println(">>> GSSClient... GSS Exception "
+ ge.getMessage());
+
+ ge.getMessage());
}
+
}
  
catch (java.lang.Exception e) {
+
catch (java.lang.Exception e) {
e.printStackTrace();
+
e.printStackTrace();
System.out.println(">>> GSSClient... Exception " + e.getMessage());
+
System.out.println(">>> GSSClient... Exception " + e.getMessage());
}// catch
+
}// catch
return null;
+
return null;
}// run
+
}// run
  
// It returns the established login context to client.
+
// It returns the established login context to client.
public LoginContext getLoginContext() {
+
public LoginContext getLoginContext() {
return peerLC;
+
return peerLC;
}// getloginContext
+
}// getloginContext
  
public byte[] wrapMessage(byte[] msg) throws GSSException {
+
public byte[] wrapMessage(byte[] msg) throws GSSException {
MessageProp msgProp = new MessageProp(0, false);
+
MessageProp msgProp = new MessageProp(0, false);
return clientGSSContext.wrap(msg, 0, msg.length, msgProp);
+
return clientGSSContext.wrap(msg, 0, msg.length, msgProp);
}
+
}
  
public byte[] unWrapMessage(byte[] msg) throws GSSException {
+
public byte[] unWrapMessage(byte[] msg) throws GSSException {
MessageProp msgProp = new MessageProp(0, false);
+
MessageProp msgProp = new MessageProp(0, false);
return clientGSSContext.unwrap(msg, 0, msg.length, msgProp);
+
return clientGSSContext.unwrap(msg, 0, msg.length, msgProp);
}
+
}
  
 
}// End of GSSClient
 
}// End of GSSClient
  
  
</source>
+
</source>  
  
 
== Summary  ==
 
== Summary  ==
  
Now you can run the server side with Server class’s main method. Note that before your run Server, Apache DS must be running and required user and service/server SPN must be created as mentioned above.<br>
+
Now you can run the server side with Server class’s main method. Note that before your run Server, Apache DS must be running and required user and service/server SPN must be created as mentioned above.
  
 
If server is started successfully, you can run the client side, Client1 class here, and see that initSecContext method on GSSContext goes fine. This means user authentication details have been securely communicated to the server and it has accepted the credentials. Meaning SSO has taken place. That is user has been authenticated by server without asking end user its credentials and without passing the user credentials across network. Here security context is established means, user has been authenticated and appropriate cryptographic keys have been made between client and server. These keys are available in GSSContext and you can use this context’s high level methods to encrypt and decrypt the messages for secure communication between client and server.  
 
If server is started successfully, you can run the client side, Client1 class here, and see that initSecContext method on GSSContext goes fine. This means user authentication details have been securely communicated to the server and it has accepted the credentials. Meaning SSO has taken place. That is user has been authenticated by server without asking end user its credentials and without passing the user credentials across network. Here security context is established means, user has been authenticated and appropriate cryptographic keys have been made between client and server. These keys are available in GSSContext and you can use this context’s high level methods to encrypt and decrypt the messages for secure communication between client and server.  
Line 666: Line 660:
 
== References  ==
 
== References  ==
  
1.&nbsp;The java source code, properties files, ldif and config files discussed in this article are enclosed here;&nbsp;  
+
I have used following resources for this article;
 +
 
 +
<br>1.&nbsp;The java source code, properties files, ldif and config files discussed in this article are enclosed here;&nbsp;  
  
 
[http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip SSO_Example_Source.zip]&nbsp;([http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip])  
 
[http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip SSO_Example_Source.zip]&nbsp;([http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip])  
Line 674: Line 670:
 
3.&nbsp;[http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html], for setting Apache DS as KDC  
 
3.&nbsp;[http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html], for setting Apache DS as KDC  
  
4. [http://directory.apache.org/ http://directory.apache.org/], the Apache Directory™ Project
+
4. [http://directory.apache.org/ http://directory.apache.org/], the Apache Directory™ Project  
  
&nbsp;
+
&nbsp;  
  
 
&nbsp;
 
&nbsp;

Latest revision as of 03:49, 19 March 2012

Introduction

This tutorial explains what Kerberos protocol is and how it works at large. Then we will see how to implement SSO (Single Sign On) and secure communication using Kerberos. This tutorial will use the Apache DS 1.5.7 and simple Web Service application to demonstrate the SSO. We will use java security APIs like JAAS and Java GSS (Generic Security Services). It is assumed that you already know these technologies.

In rest of the article, we will see how Kerberos protocol works, how to setup Kerberos service on Apache DS, how to implement SSO and secure communication with detailed server and client side code. I have enclosed java source, properties and config files, discussed here in References section below.

Kerberos and how it works

Kerberos is a network authentication protocol developed by Internet Engineering Task Force and has been widely adopted by both Java and .Net communities. This protocol doesn’t require that user password be sent over network for authentication. Rather, it uses concept of tokens and cryptographic keys.

Here is how Kerberos protocol works;
Consider there are two systems, S1 and S2, which leverage the SSO and secure communication with Kerberos protocol. That means, if user U1 is logs into S1, and S1 needs to talk to S2 for fulfilling user request, then user must not be prompted for authentication again, nor user credentials be passed to S2, but still, S2 must make sure that the user is one who says he is. Means, S2 must authenticate the user seamlessly, without user being aware of it and without user credentials being passed to S2. Let’s see how this happens with Kerberos protocol and how SSO and secure communication works between S1 and S2;

  1. The user U1 logs into S1 with his/her credentials; typically username and password.
  2. S1 request the Kerberos ticket form KDC (Key Distribution Center) by sending username only. For time being, you may consider KDC means any user repository supporting Kerberos protocol, for example, Microsoft Active Directory or Apache Directory Service.
  3. The KDC issues an encrypted ticket containing session key. The ticket is encrypted by using key derived from user’s password. This ticket is called as TGT (Ticket Granting Ticket). Note that, although user password is not required for requesting TGT, password is required to decrypt it and fetch the session key in it. Without user password TGT is of no use. 
  4. S1 uses user’s password to decrypt the TGT and fetch session key from it and requests one more ticket from KDC for communication with S2.
    Note that, users and systems/services (like U1 and S2 here) must already be registered with the KDC.
  5. Now the KDC creates a ticket, called as Service Ticket, and adds the user’s authentication data and a new key, called as sub-session key, in it. The KDC encrypts the service ticket by a key generated from S2’s password. Then, the KDC creates a message, adds encrypted service ticket and sub-session key in it, and again encrypts the complete message by a key generated from user’s password, and sends it back to the S1.
    [Note that, now message contains the sub-session key twice, once directly in the message and inside encrypted service ticket. The service ticket can be used only for communication between S1 and S2. This complete message can only be decrypted by the user and the enclosed encrypted service ticket can only be decrypted by S2.]
  6. The S1 decrypts the message with user’s password and takes out service ticket and sub-session key.
  7. S1 sends the service ticket (which is encrypted) to S2. The S2 decrypts the service ticket and takes out U1’s authentication data and sub-session key.
    Now, S2 will trust the user’s authentication data as it could decrypt service ticket with its own password, that means it has come from KDC (which already has authenticated the user) and hence it is authentic. Hence SSO!
  8. Now both, S1 and S2, have the same key (sub-session key) which they will use for secure communication with each other.

This is how SSO and secure communication is achieved using Kerberos. All these steps mention above are performed by JAAS and GSS API. We will use higher level methods of these security APIs to perform these steps.

Note that KDC functionality is generally implemented by directory servers like MS AD and Apache DS. For this article you may consider KDC means Apache DS.

Setting up KDC with Apache DS

As mentioned above, KDC functionality is generally implemented as part of user repositories like Microsoft Active Directory. For this article I have used Apache DS.
Download Apache DS 1.5.7 and Apache Directory Studio 1.5.3 from Apache site(http://directory.apache.org/) and install it on your local machine. By default, it doesn’t have Kerberos service running. Follow the steps below to activate Kerberos, and create user and server account SPNs (Service Principle Name). SPN uniquely identifies the user or service account in the directory server. Note that these steps are only valid for DS version 1.5.7.

  1.  Under your Apache Directory Server install (C:\Program Files\Apache Directory Server), navigate to the folder instances\default\conf. This folder contains the config files for your server instance. The file, server.xml, is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file in a text editor.

  2.  Set the enabled property to “true” to enable the KDC. By default, it is false.
<bean id="kdcConfiguration" 
 class="org.apache.directory.server.kerberos.kdc.KdcConfiguration">
 <!-- Whether to enable the Kerberos protocol. --> 
 <property name="enabled" value="true" /> 
 <!-- The port to run the Kerberos protocol on. --> 
 <property name="ipPort" value="88" /> 
 <!-- <property name="searchBaseDn" value="ou=Users"/> --> 
</bean>
  3.  Uncomment following bean.
<bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
<property name="name" value="keyDerivationService" /> 
<property name="interceptorClassName" 
value="org.apache.directory.server.core.kerberos.KeyDerivationService" />
  4. You will have to create user and service SPN in DS. You can do this using Apache Directory Studio, but the easiest way is to use LDIF file. You can use this file to define your SPN details. When you place this file at appropriate location, it would be loaded by DS on startup and it creates the specified SPNs. Now create a new .ldif file and add following content in it. It will create a user with name “monkey2” and service with name “webserver2/localhost” under example.com domain.
# Web server2 identity/service principal.
dn: uid=webserver2,ou=users,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: krb5Principal
objectclass: krb5KDCEntry
cn: Web Server2
sn: Web Server2
uid: webserver2
userpassword: Password99
krb5PrincipalName: webserver2/localhost@EXAMPLE.COM
# User / client2 principal.
dn: uid=monkey2,ou=users,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: krb5Principal
objectclass: krb5KDCEntry
cn: Monkey2
sn: Monkey2
uid: monkey2
userpassword: Password100
krb5PrincipalName:monkey2@EXAMPLE.COM
# Ticket Granting Service2.
dn: uid=krbtgt2,ou=users,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: krb5Principal
objectclass: krb5KDCEntry
cn: KDC Service2
sn: KDC Service2
uid: krbtgt2
userpassword: randomKey
krb5PrincipalName: krbtgt2/EXAMPLE.COM@EXAMPLE.COM

  5.  Store this file under ldifDirectory directory. For location of your ldifDirectory search server.xml.

  6.  And in the end restart the DS server for changes to apply.

  7. To verify that your ldif file is loaded properly. Start the Apache Directory Studio and create new connection to your DS server with following details;

Hostname: localhost
Port: 10389
Encryption Method: No encryption
Authentication Method: Simple Authentication
Bind DN or user: uid=admin,ou=system
Bind password: secret 

  8.  If ldif file was loaded successfully, it will have setup the client principal, the server principal, and the krbtgt service. That is, you now have a fully functioning KDC. Now you can try running your Kerberos example against the KDC.

 

Writing the Server Part

  Here I have implemented the server part as simple JAX-RS web service using CXF. You may take the core idea form here and implement the server part as per your requirement like Servlet Filter or SOAP handler or as any component that fits your need.

Server Class:

This class exposes the EchoService class, given below, as JAX-RS service at URL http://localhost:9000/
package com.gyan.rs;
 
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
 
public class Server {
 
 protected Server() throws Exception {
 JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
 sf.setResourceClasses(EchoService.class);
 sf.setResourceProvider(EchoService.class, 
 new SingletonResourceProvider(new EchoService()));
 sf.setAddress("http://localhost:9000/");
 
 sf.create();
 }
 
 public static void main(String args[]) throws Exception {
 new Server();
 System.out.println("Server ready...");
 Thread.sleep(5 * 60 * 1000);
 System.out.println("Server exiting");
 System.exit(0);
 }
}
 

EchoService Class:

This class is part of our example business service layer. It will just extract the authentication details, Kerberos token in this case, from incoming request and verify it with already initialized server context using a utility class, called GSSServer. If verification fails, it throws the security exception. Note the usage of Base 64 encoding and decoding to avoid sending raw Kerberos tokens and URL safe messages across client and server. If you are not passing the token as part of URL, you may not need to use encoding here.

Also note, normally, you will implement security features, like authentication, as part of separate component. To avoid complexity, I have implemented the Kerberos token verification as part of this service.

 
package com.gyan.rs;
 
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
 
import org.apache.commons.codec.binary.Base64;
import org.gyan.server.GSSServer;
import org.ietf.jgss.GSSException;
 
@Path("/echoservice/")
public class EchoService {
 
 private GSSServer gssServer; 
 public EchoService(){
 gssServer = new GSSServer();
 gssServer.startServer();
 }
 @GET
 @Path("/{msg}/")
 public String saySomthing(@PathParam("msg") String clientMsg, @DefaultValue("NA") 
 @HeaderParam("Authorization") String kerberosToken) throws GSSException {
 
 System.out.println("auth:"+ kerberosToken);
 System.out.println("Encrypted message as is: "+ clientMsg);
 byte[] convert = Base64.decodeBase64(kerberosToken);
 try {
 gssServer.authenticate(convert);
 byte[] unWrapMessage = gssServer.unWrapMessage(Base64.decodeBase64(clientMsg));
 clientMsg = new String(unWrapMessage);
 } catch (GSSException e) {
 e.printStackTrace();
 }
 System.out.println("Decrypted Msg: "+ clientMsg);
 return Base64.encodeBase64URLSafeString(gssServer.wrapMessage(
 ("You said '" + clientMsg + "'").getBytes()));
 } 
}

GSS Server Properties:

The KDC and other properties are store as part of gssserver.properties file as shown below. It contains location of KDC server, server SPN, realm/domain and JAAS config details.

#This file contains the KDC server details and Server service SPN details
Server.SPN = webserver2/localhost@EXAMPLE.COM
Server.Password = Password99
KDC.Realm = EXAMPLE.COM
KDC.Address = localhost
JAAS.Config.Path = C:/Users/ganesh.lawande/workspace/SSOProject/resources/jaas.conf
JAAS.Config.Name = GSSServer

Note, you will have to change the JAAS config path as per your settings. It is an absolute path.

JAAS Config File:

The JAAS conf file for client and server looks like following;

GSSClient{
  com.sun.security.auth.module.Krb5LoginModule required
  useTicketCache=false;
};
GSSServer{
  com.sun.security.auth.module.Krb5LoginModule required
  storeKey=true;
};


GSSServer Class:

This class is initialized during service creation. It abstracts out the communication with KDC server using JAAS and GSS API. This class uses the server properties, mentioned above, to initialize the security context. Later on when, client request comes in, client’s Kerberos token will be validated against this security context. Note the usage of GSSContext class, which is part of GSS API, to authenticate the client’s Kerberos token and encrypt (wrap method) and decrypt (unwrap method) the messages between client and server. Initialized GSSContext will have the required keys for given client.


package org.gyan.server;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Properties;
 
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
 
import org.gyan.BeanCallbackHandler;
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.MessageProp;
import org.ietf.jgss.Oid;
 
public class GSSServer implements java.security.PrivilegedAction {
 
 // Handles callback from the JAAS framework.
 BeanCallbackHandler beanCallbackHandler = null;
 
 // The main object that handles all JAAS login.
 LoginContext serverLC = null;
 
 // The context for secure communication with client.
 GSSContext serverGSSContext = null;
 
 // Socket and streams used for communication.
 ServerSocket serverSocket = null;
 DataInputStream inStream = null;
 DataOutputStream outStream = null;
 
 // Name and port of server.
 String serverName = null;
 int serverPort;
 
 // Configuration file and the name of the client configuration.
 String confFile = null;
 String confName = null;
 
 private String clientName;
 
 public static void main(String[] args) throws IOException, GSSException {
 // Starting the server.
 new GSSServer().startServer();
 
 }// main
 
 // GSSServer constructor
 public GSSServer() {
 Properties env = new Properties();
 try {
 env.load(this.getClass().getClassLoader()
 .getResourceAsStream("gssserver.properties"));
 } catch (IOException e) {
 e.printStackTrace();
 }
 
 this.serverName = (String) env.getProperty("Server.SPN");
 this.confName = (String) env.getProperty("JAAS.Config.Name");
 
 beanCallbackHandler = new BeanCallbackHandler(this.serverName,
 (String) env.getProperty("Server.Password"));
 setSystemProperties((String) env.getProperty("KDC.Realm"),
 (String) env.getProperty("KDC.Address"),
 (String) env.getProperty("JAAS.Config.Path"));
 }// GSSServer
 
 private void setSystemProperties(String kerberosRealm, String kdcAddress,
 String confFile) {
 System.setProperty("java.security.krb5.realm", kerberosRealm);
 System.setProperty("java.security.krb5.kdc", kdcAddress);
 System.setProperty("java.security.auth.login.config", confFile);
 System.setProperty("sun.security.krb5.debug", "true");
 }
 
 public boolean startServer() {
 try {
 serverLC = new LoginContext(confName, beanCallbackHandler);
 serverLC.login();
 Subject.doAs(serverLC.getSubject(), this);
 return true;
 } catch (Exception e) {
 e.printStackTrace();
 System.out
 .println(">>> GSSServer...Secure Context not established..");
 return false;
 }// catch
 
 }// start
 
 public Object run() {
 try {
 GSSManager manager = GSSManager.getInstance();
 Oid kerberos = new Oid("1.2.840.113554.1.2.2");
 GSSName serverGSSName = manager.createName(serverName,
 GSSName.NT_USER_NAME);
 GSSCredential serverGSSCreds = manager.createCredential(
 serverGSSName, GSSCredential.INDEFINITE_LIFETIME, kerberos,
 // The server accepts secure context request.
 GSSCredential.ACCEPT_ONLY);
 serverGSSContext = manager.createContext(serverGSSCreds);
 }// try
 catch (java.lang.Exception e) {
 e.printStackTrace();
 }
 return null;
 }// run
 
 public void authenticate(byte[] kerberosToken) throws GSSException {
 if (!serverGSSContext.isEstablished()) {
 serverGSSContext.acceptSecContext(kerberosToken, 0,
 kerberosToken.length);
 clientName = serverGSSContext.getTargName().toString();
 }
 }
 
 public byte[] wrapMessage(byte[] msg) throws GSSException {
 MessageProp msgProp = new MessageProp(0, false);
 return serverGSSContext.wrap(msg, 0, msg.length, msgProp);
 }
 
 public byte[] unWrapMessage(byte[] msg) throws GSSException {
 MessageProp msgProp = new MessageProp(0, false);
 return serverGSSContext.unwrap(msg, 0, msg.length, msgProp);
 }
 
 public void closeServerContext() throws GSSException {
 // Disposing and closing client and server sockets.
 serverGSSContext.dispose();
 }
 
 public String getClientName() {
 return clientName;
 }
}// End of GSSServer

How Client Side Looks Like

Like server side, at client site I have used utility class, GSSClient, which abstracts out the communication with KDC.


Client Class:

The client class uses the GSSClient class to pass username and password to authenticate the user and get the Kerberos token (service ticket for given server). Note that token is in byte array format. So we used Base 64 encoding to convert it into string and then we simply add it as part of authentication header. That’s it there in client. So, actual stuff happens in GSSClient which is discussed next;


package com.gyan.rs;
 
import java.io.IOException;
 
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.gyan.GSSClient;
import org.ietf.jgss.GSSException;
 
public class Client1 {
 
 private static GSSClient gssClient;
 
 /**
 * @param args
 * @throws IOException
 * @throws HttpException
 * @throws InterruptedException 
 */
 public static void main(String[] args) throws HttpException, IOException, InterruptedException {
 
 String theClientCred = authenticateAndGetKerberosToken();
 
 
 String serviceAddress = "http://localhost:9000/echoservice/";
 String message = "Hi man, how are u?";
 System.out.println(message);
 try {
 byte[] wrapedMsg = gssClient.wrapMessage(message.getBytes());
 serviceAddress += Base64.encodeBase64URLSafeString(wrapedMsg);
 } catch (GSSException e) {
 e.printStackTrace();
 }
 GetMethod get = new GetMethod(serviceAddress);
 
 get.setRequestHeader("Authorization", theClientCred);
 HttpClient httpclient = new HttpClient();
 
 try {
 int result = httpclient.executeMethod(get);
 System.out.println("Response status code: " + result);
 System.out.println("Response body: ");
 System.out.println("Return message from server as is: "
 + get.getResponseBodyAsString());
 System.out.println("After decryptionm: "
 + new String(gssClient.unWrapMessage(Base64
 .decodeBase64(get.getResponseBodyAsString()))));
 } catch (GSSException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 } finally {
 // Release current connection to the connection pool once you are done
 get.releaseConnection();
 }
// Thread.sleep(10000);
 }
 
 private static String authenticateAndGetKerberosToken() {
 gssClient = new GSSClient("monkey", "Password99");
 String serviceTicket = Base64.encodeBase64URLSafeString(gssClient.login());
 return serviceTicket;
 }
}//End of Class
 

Client Side Properties:

The client side properties are stored as part of gssclient.properties file. It contains the server SPN, KDC Server Address, realm/domain, and JAAS properties. 

#This file contains the KDC server details and Remote peer details
Server.SPN = webserver2/localhost@EXAMPLE.COM
Server.Address = localhost
KDC.Realm = EXAMPLE.COM
KDC.Address = localhost
JAAS.Config.Path = C:/Users/ganesh.lawande/workspace/SSOProject/resources/jaas.conf
JAAS.Config.Name = GSSClient


GSSClient Class:

This class uses the JAAS and GSS API to authenticate and create security context against given KDC. Once client initializes the security context, it can be used for SSO and secure communication with given server.
Here KDC details are loaded from gssclient.properties file. Note that how client side uses initSecContext method of GSSContext to establish connection with the server.


package org.gyan;
 
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Properties;
 
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
 
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.MessageProp;
import org.ietf.jgss.Oid;
 
public class GSSClient implements java.security.PrivilegedAction<byte[]> {
 
 // Handles callback from the JAAS framework.
 private BeanCallbackHandler beanCallbackHandler = null;
 
 // The main object that handles all JAAS login.
 LoginContext peerLC = null;
 
 // Socket and streams used for communication.
 Socket socket = null;
 DataInputStream inStream;
 DataOutputStream outStream;
 
 // This and remote clients.
 String clientName = null;
 String serverName = null;
 
 // Address and port of remote server.
 String serverAddress = null;
 int serverPort;
 
 // The name of the client configuration.
 String confName = null;
 
 private GSSContext clientGSSContext = null;
 
 /**
 * @param clientName, Client SPN 
 * @param password, client password
 */
 public GSSClient(String clientName, String password){
 Properties env = new Properties();
 try {
 env.load(this.getClass().getClassLoader().getResourceAsStream("gssclient.properties"));
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 this.clientName = clientName;
 this.serverName = (String)env.getProperty("Server.SPN");
 this.serverAddress = (String)env.getProperty("Server.Address");
 this.confName = (String)env.getProperty("JAAS.Config.Name");
 
 beanCallbackHandler = new BeanCallbackHandler(clientName, password);
 setSystemProperties((String)env.getProperty("KDC.Realm"),
 (String)env.getProperty("KDC.Address"),
 (String)env.getProperty("JAAS.Config.Path"));
 }
 /**
 * The GSSClient constructor only sets all the required parameters.
 * 
 * @param clientName, Client SPN 
 * @param password, client password
 * @param serverName, Remote server/service SPN
 * @param serverAddress, Remote Server address IP or Hostname 
 * @param kerberosRealm, KDC realm or domain name
 * @param kdcAddress, KDC Server address IP or Hostname 
 * @param confFile, fll path of JAAS COnfig file
 * @param confName, JAAS config name
 */
 public GSSClient(String clientName, String password, String serverName,
 String serverAddress, String kerberosRealm,
 String kdcAddress, String confFile, String confName){
 // The beanCallbackHandler will require the name and password of the
 // client.
 beanCallbackHandler = new BeanCallbackHandler(clientName, password);
 this.clientName = clientName;
 this.serverName = serverName;
 this.serverAddress = serverAddress;
 this.confName = confName;
 setSystemProperties(kerberosRealm, kdcAddress, confFile);
 }// KerberoseLoginBean
 
 private void setSystemProperties(String kerberosRealm, String kdcAddress,
 String confFile) {
 System.setProperty("java.security.krb5.realm", kerberosRealm);
 System.setProperty("java.security.krb5.kdc", kdcAddress);
 System.setProperty("java.security.auth.login.config", confFile);
 System.setProperty("sun.security.krb5.debug", "true");
 }
 
 public byte[] login() {
 try {
 peerLC = new LoginContext(confName, beanCallbackHandler);
 peerLC.login();
 return Subject.doAs(peerLC.getSubject(), this);
 } catch (Exception e) {
 System.out
 .println(">>>> GSSClient....Secure Context not established..");
 e.printStackTrace();
 return null;
 }// catch
 
 }// establishSecureContextWithServer
 
 // This is the only method in PrivilegedAction interface.
 // It receives control only in case of successful authentication of the
 // client.
 public byte[] run() {
 
 try {
 GSSManager manager = GSSManager.getInstance();
 Oid kerberos = new Oid("1.2.840.113554.1.2.2");
 GSSName clientPeerName = manager.createName(
 // Name of the client for which we want to create this GSSName
 // object.
 clientName,
 // Type of GSSName. Our client is a Windows user,
 // which we can specifiy using GSSName.NT_USER_NAME
 // property.
 GSSName.NT_USER_NAME);
 // GSSName remotePeerName2 = manager.createName("FakeSERver",
 // GSSName.NT_USER_NAME);
 GSSName remotePeerName = manager.createName(serverName,
 GSSName.NT_USER_NAME);
 System.out.println(">>> GSSClient... Getting client credentials");
 
 GSSCredential peerCredentials = manager.createCredential(
 // The GSSName object of the client.
 clientPeerName,
 // Time for which credentials whill be valid.
 10 * 600,
 // Kerberos mecahnism identifier.
 kerberos,
 // The client only intiates the secure context request.
 GSSCredential.INITIATE_ONLY);
 
 System.out
 .println(">>> GSSClient...GSSManager creating security context");
 clientGSSContext= manager.createContext(remotePeerName, kerberos,
 peerCredentials, GSSContext.DEFAULT_LIFETIME);
 
 // peerContext.requestCredDeleg(true);
 clientGSSContext.requestConf(true);
 clientGSSContext.requestMutualAuth(false);// I don't want to authenticate the 
 // Server.
 byte[] byteToken = new byte[0];
 
 byteToken = clientGSSContext.initSecContext(byteToken, 0,
 byteToken.length);
 return byteToken;
 }// try
 catch (org.ietf.jgss.GSSException ge) {
 ge.printStackTrace();
 System.out.println(">>> GSSClient... GSS Exception "
 + ge.getMessage());
 }
 
 catch (java.lang.Exception e) {
 e.printStackTrace();
 System.out.println(">>> GSSClient... Exception " + e.getMessage());
 }// catch
 return null;
 }// run
 
 // It returns the established login context to client.
 public LoginContext getLoginContext() {
 return peerLC;
 }// getloginContext
 
 public byte[] wrapMessage(byte[] msg) throws GSSException {
 MessageProp msgProp = new MessageProp(0, false);
 return clientGSSContext.wrap(msg, 0, msg.length, msgProp);
 }
 
 public byte[] unWrapMessage(byte[] msg) throws GSSException {
 MessageProp msgProp = new MessageProp(0, false);
 return clientGSSContext.unwrap(msg, 0, msg.length, msgProp);
 }
 
}// End of GSSClient

Summary

Now you can run the server side with Server class’s main method. Note that before your run Server, Apache DS must be running and required user and service/server SPN must be created as mentioned above.

If server is started successfully, you can run the client side, Client1 class here, and see that initSecContext method on GSSContext goes fine. This means user authentication details have been securely communicated to the server and it has accepted the credentials. Meaning SSO has taken place. That is user has been authenticated by server without asking end user its credentials and without passing the user credentials across network. Here security context is established means, user has been authenticated and appropriate cryptographic keys have been made between client and server. These keys are available in GSSContext and you can use this context’s high level methods to encrypt and decrypt the messages for secure communication between client and server.

Now, once authentication has taken place and security context is created, client can securely communicate with server by using encrypting the messages and sending then over to server using normal URL or Form parameters over HTTP. This is end-to-end security as opposed to point to point security offered by HTTPS. Here we attach the encrypted message to URL, which server side will extract like normal URL parameter and decrypt is using already shared keys, using GSSContext at server side. Here we have used Base 64 encoding to convert encrypted message into URL safe format. Also you will see at server side this URL parameter is first decoded using Base 64 and then decrypted using GSSContext.


References

I have used following resources for this article;


1. The java source code, properties files, ldif and config files discussed in this article are enclosed here; 

SSO_Example_Source.zip (http://wiki.eclipse.org/images/9/91/SSO_Example_Source.zip)

2. http://www.ibm.com/developerworks/java/library/j-gss-sso/, by Faheem Khan

3. http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html, for setting Apache DS as KDC

4. http://directory.apache.org/, the Apache Directory™ Project

 

 

Back to the top