Java SE認証、セキュアな通信およびシングル・サインオンでの高度なセキュリティ・プログラミングのソース・コード
Jaas.java
import javax.security.auth.Subject;
import javax.security.auth.login.*;
import javax.security.auth.callback.CallbackHandler;
import java.security.*;
import com.sun.security.auth.callback.TextCallbackHandler;
import java.io.File;
public class Jaas {
private static String name;
private static final boolean verbose = false;
public static void main(String[] args) throws Exception {
if (args.length > 0) {
name = args[0];
} else {
name = "client";
}
// Create action to perform
PrivilegedExceptionAction action = new MyAction();
loginAndAction(name, action);
}
static void loginAndAction(String name, PrivilegedExceptionAction action)
throws LoginException, PrivilegedActionException {
// Create a callback handler
CallbackHandler callbackHandler = new TextCallbackHandler();
LoginContext context = null;
try {
// Create a LoginContext with a callback handler
context = new LoginContext(name, callbackHandler);
// Perform authentication
context.login();
} catch (LoginException e) {
System.err.println("Login failed");
e.printStackTrace();
System.exit(-1);
}
// Perform action as authenticated user
Subject subject = context.getSubject();
if (verbose) {
System.out.println(subject.toString());
} else {
System.out.println("Authenticated principal: " +
subject.getPrincipals());
}
Subject.doAs(subject, action);
context.logout();
}
// Action to perform
static class MyAction implements PrivilegedExceptionAction {
MyAction() {
}
public Object run() throws Exception {
// Replace the following with an action to be performed
// by authenticated user
System.out.println("Performing secure action ...");
return null;
}
}
}
jaas-krb5.conf
client {
com.sun.security.auth.module.Krb5LoginModule required
principal="test";
};
server {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
storeKey=true
keyTab=sample.keytab
principal="host/machineName";
};
AppConnection.java
import java.io.*;
import java.net.Socket;
class AppConnection {
public static final int AUTH_CMD = 100;
public static final int DATA_CMD = 200;
public static final int SUCCESS = 0;
public static final int AUTH_INPROGRESS = 1;
public static final int FAILURE = 2;
private DataInputStream inStream;
private DataOutputStream outStream;
private Socket socket;
// Client application
AppConnection(String hostName, int port) throws IOException {
socket = new Socket(hostName, port);
inStream = new DataInputStream(socket.getInputStream());
outStream = new DataOutputStream(socket.getOutputStream());
System.out.println("Connected to address " +
socket.getInetAddress());
}
// Server side application
AppConnection(Socket socket) throws IOException {
this.socket = socket;
inStream = new DataInputStream(socket.getInputStream());
outStream = new DataOutputStream(socket.getOutputStream());
System.out.println("Got connection from client " +
socket.getInetAddress());
}
byte[] receive(int expected) throws IOException {
if (expected != -1) {
int cmd = inStream.readInt();
if (expected != cmd) {
throw new IOException("Received unexpected code: " + cmd);
}
//System.out.println("Read cmd: " + cmd);
}
byte[] reply = null;
int len;
try {
len = inStream.readInt();
//System.out.println("Read length: " + len);
} catch (IOException e) {
len = 0;
}
if (len > 0) {
reply = new byte[len];
inStream.readFully(reply);
} else {
reply = new byte[0];
}
return reply;
}
AppReply send(int cmd, byte[] bytes) throws IOException {
//System.out.println("Write cmd: " + cmd);
outStream.writeInt(cmd);
if (bytes != null) {
//System.out.println("Write length: " + bytes.length);
outStream.writeInt(bytes.length);
if (bytes.length > 0) {
outStream.write(bytes);
}
} else {
//System.out.println("Write length: " + 0);
outStream.writeInt(0);
}
outStream.flush();
if (cmd == SUCCESS || cmd == FAILURE) {
return null; // Done
}
int returnCode = inStream.readInt();
//System.out.println("Read cmd: " + returnCode);
byte[] reply = null;
if (returnCode != FAILURE) {
reply = receive(-1);
}
return new AppReply(returnCode, reply);
}
static class AppReply {
private int code;
private byte[] bytes;
AppReply(int code, byte[] bytes) {
this.bytes = bytes;
this.code = code;
}
int getStatus() {
return code;
}
byte[] getBytes() {
return bytes;
}
}
void close() {
try {
socket.close();
} catch (IOException e) {
}
}
}
GssServer.java
import org.ietf.jgss.*;
import java.io.*;
import java.net.Socket;
import java.net.ServerSocket;
import java.security.*;
import java.util.Date;
/**
* A sample server application that uses JGSS to do mutual authentication
* with a client using Kerberos as the underlying mechanism. It then
* exchanges data securely with the client.
*
* Every message exchanged with the client includes a 4-byte application-
* level header that contains the big-endian integer value for the number
* of bytes that will follow as part of the JGSS token.
*
* The protocol is:
* 1. Context establishment loop:
* a. client sends init sec context token to server
* b. server sends accept sec context token to client
* ....
* 2. client sends a wrap token to the server.
* 3. server sends a wrap token back to the client.
*
* Start GssServer first before starting GssClient.
*
* Usage: java <options> GssServer
*
* Example: java -Djava.security.auth.login.config=jaas-krb5.conf \
* GssServer
*
* Add -Djava.security.krb5.conf=krb5.conf to specify application-specific
* Kerberos configuration (different from operating system's Kerberos
* configuration).
*/
public class GssServer {
private static final int PORT = 4567;
private static final boolean verbose = false;
private static final int LOOP_LIMIT = 1;
private static int loopCount = 0;
public static void main(String[] args) throws Exception {
PrivilegedExceptionAction action = new GssServerAction(PORT);
Jaas.loginAndAction("server", action);
}
static class GssServerAction implements PrivilegedExceptionAction {
private int localPort;
GssServerAction(int port) {
this.localPort = port;
}
public Object run() throws Exception {
ServerSocket ss = new ServerSocket(localPort);
// Get own Kerberos credentials for accepting connection
GSSManager manager = GSSManager.getInstance();
Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2");
GSSCredential serverCreds = manager.createCredential(null,
GSSCredential.DEFAULT_LIFETIME,
krb5Mechanism,
GSSCredential.ACCEPT_ONLY);
while (loopCount++ < LOOP_LIMIT) {
System.out.println("Waiting for incoming connection...");
Socket socket = ss.accept();
DataInputStream inStream =
new DataInputStream(socket.getInputStream());
DataOutputStream outStream =
new DataOutputStream(socket.getOutputStream());
System.out.println("Got connection from client " +
socket.getInetAddress());
/*
* Create a GSSContext to receive the incoming request
* from the client. Use null for the server credentials
* passed in. This tells the underlying mechanism
* to use whatever credentials it has available that
* can be used to accept this connection.
*/
GSSContext context = manager.createContext(
(GSSCredential)serverCreds);
// Do the context establishment loop
byte[] token = null;
while (!context.isEstablished()) {
if (verbose) {
System.out.println("Reading ...");
}
token = new byte[inStream.readInt()];
if (verbose) {
System.out.println("Will read input token of size " +
token.length + " for processing by acceptSecContext");
}
inStream.readFully(token);
if (token.length == 0) {
if (verbose) {
System.out.println("skipping zero length token");
}
continue;
}
if (verbose) {
System.out.println("Token = " + getHexBytes(token));
System.out.println("acceptSecContext..");
}
token = context.acceptSecContext(token, 0, token.length);
// Send a token to the peer if one was generated by
// acceptSecContext
if (token != null) {
if (verbose) {
System.out.println("Will send token of size " +
token.length + " from acceptSecContext.");
}
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
}
}
System.out.println("Context Established! ");
System.out.println("Client principal is " + context.getSrcName());
System.out.println("Server principal is " + context.getTargName());
/*
* If mutual authentication did not take place, then
* only the client was authenticated to the
* server. Otherwise, both client and server were
* authenticated to each other.
*/
if (context.getMutualAuthState())
System.out.println("Mutual authentication took place!");
/*
* Create a MessageProp which unwrap will use to return
* information such as the Quality-of-Protection that was
* applied to the wrapped token, whether or not it was
* encrypted, etc. Since the initial MessageProp values
* are ignored, just set them to the defaults of 0 and false.
*/
MessageProp prop = new MessageProp(0, false);
/*
* Read the token. This uses the same token byte array
* as that used during context establishment.
*/
token = new byte[inStream.readInt()];
if (verbose) {
System.out.println("Will read token of size " + token.length);
}
inStream.readFully(token);
byte[] input = context.unwrap(token, 0, token.length, prop);
String str = new String(input, "UTF-8");
System.out.println("Received data \"" +
str + "\" of length " + str.length());
System.out.println("Confidentiality applied: " +
prop.getPrivacy());
/*
* Now generate reply that is the concatenation of the
* incoming string with the current time.
*/
/*
* First reset the QOP of the MessageProp to 0
* to ensure the default Quality-of-Protection
* is applied.
*/
prop.setQOP(0);
String now = new Date().toString();
byte[] nowBytes = now.getBytes("UTF-8");
int len = input.length + 1 + nowBytes.length;
byte[] reply = new byte[len];
System.arraycopy(input, 0, reply, 0, input.length);
reply[input.length] = ' ';
System.arraycopy(nowBytes, 0, reply, input.length+1,
nowBytes.length);
System.out.println("Sending: " + new String(reply, "UTF-8"));
token = context.wrap(reply, 0, reply.length, prop);
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
System.out.println("Closing connection with client " +
socket.getInetAddress());
context.dispose();
socket.close();
}
return null;
}
}
private static final String getHexBytes(byte[] bytes, int pos, int len) {
StringBuffer sb = new StringBuffer();
for (int i = pos; i < (pos+len); i++) {
int b1 = (bytes[i]>>4) & 0x0f;
int b2 = bytes[i] & 0x0f;
sb.append(Integer.toHexString(b1));
sb.append(Integer.toHexString(b2));
sb.append(' ');
}
return sb.toString();
}
private static final String getHexBytes(byte[] bytes) {
return getHexBytes(bytes, 0, bytes.length);
}
}
GssClient.java
import org.ietf.jgss.*;
import java.net.Socket;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.security.*;
import javax.security.auth.login.LoginException;
/**
* A sample client application that uses JGSS to do mutual authentication
* with a server using Kerberos as the underlying mechanism. It then
* exchanges data securely with the server.
*
* Every message sent to the server includes a 4-byte application-level
* header that contains the big-endian integer value for the number
* of bytes that will follow as part of the JGSS token.
*
* The protocol is:
* 1. Context establishment loop:
* a. client sends init sec context token to server
* b. server sends accept sec context token to client
* ....
* 2. client sends a wrapped token to the server.
* 3. server sends a wrapped token back to the client for the application
*
* Start GssServer first before starting GssClient.
*
* Usage: java <options> GssClient <service> <serverName>
*
* Example: java -Djava.security.auth.login.config=jaas-krb5.conf \
* GssClient host machine.imc.org
*
* Add -Djava.security.krb5.conf=krb5.conf to specify application-specific
* Kerberos configuration (different from operating system's Kerberos
* configuration).
*/
public class GssClient {
private static final int PORT = 4567;
private static final boolean verbose = false;
public static void main(String[] args) throws Exception {
// Obtain the command-line arguments and parse the server's principal
if (args.length < 2) {
System.err.println(
"Usage: java <options> GssClient <service> <serverName>");
System.exit(-1);
}
String serverPrinc = args[0] + "@" + args[1];
PrivilegedExceptionAction action =
new GssClientAction(serverPrinc, args[1], PORT);
Jaas.loginAndAction("client", action);
}
static class GssClientAction implements PrivilegedExceptionAction {
private String serverPrinc;
private String hostName;
private int port;
GssClientAction(String serverPrinc, String hostName, int port) {
this.serverPrinc = serverPrinc;
this.hostName = hostName;
this.port = port;
}
public Object run() throws Exception {
Socket socket = new Socket(hostName, port);
DataInputStream inStream =
new DataInputStream(socket.getInputStream());
DataOutputStream outStream =
new DataOutputStream(socket.getOutputStream());
System.out.println("Connected to address " +
socket.getInetAddress());
/*
* This Oid is used to represent the Kerberos version 5 GSS-API
* mechanism. It is defined in RFC 1964. We will use this Oid
* whenever we need to indicate to the GSS-API that it must
* use Kerberos for some purpose.
*/
Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
GSSManager manager = GSSManager.getInstance();
/*
* Create a GSSName out of the server's name.
*/
GSSName serverName = manager.createName(serverPrinc,
GSSName.NT_HOSTBASED_SERVICE);
/*
* Create a GSSContext for mutual authentication with the
* server.
* - serverName is the GSSName that represents the server.
* - krb5Oid is the Oid that represents the mechanism to
* use. The client chooses the mechanism to use.
* - null is passed in for client credentials
* - DEFAULT_LIFETIME lets the mechanism decide how long the
* context can remain valid.
* Note: Passing in null for the credentials asks GSS-API to
* use the default credentials. This means that the mechanism
* will look among the credentials stored in the current Subject
* to find the right kind of credentials that it needs.
*/
GSSContext context = manager.createContext(serverName,
krb5Oid,
null,
GSSContext.DEFAULT_LIFETIME);
// Set the desired optional features on the context. The client
// chooses these options.
context.requestMutualAuth(true); // Mutual authentication
context.requestConf(true); // Will use confidentiality later
context.requestInteg(true); // Will use integrity later
// Do the context eastablishment loop
byte[] token = new byte[0];
while (!context.isEstablished()) {
// token is ignored on the first call
token = context.initSecContext(token, 0, token.length);
// Send a token to the server if one was generated by
// initSecContext
if (token != null) {
if (verbose) {
System.out.println("Will send token of size " +
token.length + " from initSecContext.");
System.out.println("writing token = " +
getHexBytes(token));
}
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
}
// If the client is done with context establishment
// then there will be no more tokens to read in this loop
if (!context.isEstablished()) {
token = new byte[inStream.readInt()];
if (verbose) {
System.out.println("reading token = " +
getHexBytes(token));
System.out.println("Will read input token of size " +
token.length + " for processing by initSecContext");
}
inStream.readFully(token);
}
}
System.out.println("Context Established! ");
System.out.println("Client principal is " + context.getSrcName());
System.out.println("Server principal is " + context.getTargName());
/*
* If mutual authentication did not take place, then only the
* client was authenticated to the server. Otherwise, both
* client and server were authenticated to each other.
*/
if (context.getMutualAuthState())
System.out.println("Mutual authentication took place!");
byte[] messageBytes = "Hello There!".getBytes("UTF-8");
/*
* The first MessageProp argument is 0 to request
* the default Quality-of-Protection.
* The second argument is true to request
* privacy (encryption of the message).
*/
MessageProp prop = new MessageProp(0, true);
/*
* Encrypt the data and send it across. Integrity protection
* is always applied, irrespective of confidentiality
* (i.e., encryption).
* You can use the same token (byte array) as that used when
* establishing the context.
*/
System.out.println("Sending message: " +
new String(messageBytes, "UTF-8"));
token = context.wrap(messageBytes, 0, messageBytes.length, prop);
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
/*
* Now we will allow the server to decrypt the message,
* append a time/date on it, and send then it back.
*/
token = new byte[inStream.readInt()];
System.out.println("Will read token of size " + token.length);
inStream.readFully(token);
byte[] replyBytes = context.unwrap(token, 0, token.length, prop);
System.out.println("Received message: " +
new String(replyBytes, "UTF-8"));
System.out.println("Done.");
context.dispose();
socket.close();
return null;
}
}
private static final String getHexBytes(byte[] bytes, int pos, int len) {
StringBuffer sb = new StringBuffer();
for (int i = pos; i < (pos+len); i++) {
int b1 = (bytes[i]>>4) & 0x0f;
int b2 = bytes[i] & 0x0f;
sb.append(Integer.toHexString(b1));
sb.append(Integer.toHexString(b2));
sb.append(' ');
}
return sb.toString();
}
private static final String getHexBytes(byte[] bytes) {
return getHexBytes(bytes, 0, bytes.length);
}
}
SaslTestServer.java
import javax.security.sasl.*;
import javax.security.auth.callback.*;
import java.security.*;
import java.util.HashMap;
import java.net.*;
import java.util.Date;
/**
* A sample server application that uses SASL to authenticate clients
* using Kerberos as the underlying mechanism. It then
* exchanges data securely with the client.
*
* This sample program uses a ficticious application-level protocol.
* Every message exchanged between the client and server an 8-byte
* header that consists of two integers: the first integer represesents
* the application-level command or status code while the second integer
* indicates the length of the SASL buffer. This header is followed by
* the SASL buffer.
*
* The protocol is:
* 1. Authentication
* a. client sends initial response to server containing authentication
* information
* b. server accepts and evaluates response to generate challenge; it
* sends the challenge to the server.
* c. client evaluates challenge to generate response; it sends the
* response;
* d. Steps b and c are repeated until authentication succeeds or fails.
* 2. client sends an encrypted message to the server.
* 3. server decryptes the message and sends an encrypted one back
* that contains the original message plus the current time.
*
* Start SaslTestServer first before starting SaslTestClient.
*
* Usage: java <options> SaslTestServer service serverName
*
* Example: java -Djava.security.auth.login.config=jaas-krb5.conf \
* SaslTestServer host machine.imc.org
*
* Add -Djava.security.krb5.conf=krb5.conf to specify application-specific
* Kerberos configuration (different from operating system's Kerberos
* configuration).
*/
public class SaslTestServer {
private static final String MECH = "GSSAPI"; // SASL name for GSS-API/Kerberos
private static final int PORT = 4568;
private static final int LOOP_LIMIT = 1;
private static int loopCount = 0;
public static void main(String[] args) throws Exception {
// Obtain the command-line arguments and parse the server's principal
if (args.length < 2) {
System.err.println(
"Usage: java <options> SaslTestServer <service> <host>");
System.exit(-1);
}
PrivilegedExceptionAction action =
new SaslServerAction(args[0], args[1], PORT);
Jaas.loginAndAction("server", action);
}
static class SaslServerAction implements PrivilegedExceptionAction {
private String service; // used for SASL authentication
private String serverName; // named used for SASL authentication
private int localPort;
private CallbackHandler cbh = new TestCallbackHandler();
SaslServerAction(String service, String serverName, int port) {
this.service = service;
this.serverName = serverName;
this.localPort = port;
}
public Object run() throws Exception {
ServerSocket ss = new ServerSocket(localPort);
HashMap<String,Object> props = new HashMap<String,Object>();
props.put(Sasl.QOP, "auth-conf,auth-int,auth");
// Loop, accepting requests from any client
while (loopCount++ < LOOP_LIMIT) {
System.out.println("Waiting for incoming connection...");
Socket socket = ss.accept();
// Create application-level connection to handle request
AppConnection conn = new AppConnection(socket);
// Normally, the application protocol will negotiate which
// SASL mechanism to use. In this simplified example, we
// will always use "GSSAPI", the name of the mechanism that does
// Kerberos via GSS-API
// Create SaslServer to perform authentication
SaslServer srv = Sasl.createSaslServer(MECH,
service, serverName, props, cbh);
if (srv == null) {
throw new Exception(
"Unable to find server implementation for " + MECH);
}
boolean auth = false;
// Read initial response from client
byte[] response = conn.receive(AppConnection.AUTH_CMD);
AppConnection.AppReply clientMsg;
while (!srv.isComplete()) {
try {
// Generate challenge based on response
byte[] challenge = srv.evaluateResponse(response);
if (srv.isComplete()) {
conn.send(AppConnection.SUCCESS, challenge);
auth = true;
} else {
clientMsg = conn.send(AppConnection.AUTH_INPROGRESS,
challenge);
response = clientMsg.getBytes();
}
} catch (SaslException e) {
// e.printStackTrace();
// Send failure notification to client
conn.send(AppConnection.FAILURE, null);
break;
}
}
// Check status of authentication
if (srv.isComplete() && auth) {
System.out.print("Client authenticated; ");
System.out.println("authorized client is: " +
srv.getAuthorizationID());
} else {
// Go get another client
System.out.println("Authentication failed. ");
continue;
}
String qop = (String) srv.getNegotiatedProperty(Sasl.QOP);
System.out.println("Negotiated QOP: " + qop);
// Now try to use security layer
boolean sl = (qop.equals("auth-conf") || qop.equals("auth-int"));
byte[] msg = conn.receive(AppConnection.DATA_CMD);
byte[] realMsg = (sl ? srv.unwrap(msg, 0, msg.length) : msg);
System.out.println("Received: " + new String(realMsg, "UTF-8"));
// Construct reply to send to client
String now = new Date().toString();
byte[] nowBytes = now.getBytes("UTF-8");
int len = realMsg.length + 1 + nowBytes.length;
byte[] reply = new byte[len];
System.arraycopy(realMsg, 0, reply, 0, realMsg.length);
reply[realMsg.length] = ' ';
System.arraycopy(nowBytes, 0, reply, realMsg.length+1,
nowBytes.length);
System.out.println("Sending: " + new String(reply, "UTF-8"));
byte[] realReply = (sl ? srv.wrap(reply, 0, reply.length) : reply);
conn.send(AppConnection.SUCCESS, realReply);
}
return null;
}
}
static class TestCallbackHandler implements CallbackHandler {
public void handle(Callback[] callbacks)
throws UnsupportedCallbackException {
AuthorizeCallback acb = null;
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof AuthorizeCallback) {
acb = (AuthorizeCallback) callbacks[i];
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
if (acb != null) {
String authid = acb.getAuthenticationID();
String authzid = acb.getAuthorizationID();
if (authid.equals(authzid)) {
// Self is always authorized
acb.setAuthorized(true);
} else {
// Should check some database for mapping and decide.
// Current simplified policy is to reject authzids that
// don't match authid
acb.setAuthorized(false);
}
if (acb.isAuthorized()) {
// Set canonicalized name.
// Should look up database for canonical names
acb.setAuthorizedID(authzid);
}
}
}
}
}
SaslTestClient.java
import javax.security.sasl.*;
import javax.security.auth.callback.*;
import java.security.*;
import javax.security.auth.Subject;
import javax.security.auth.login.*;
import com.sun.security.auth.callback.*;
import java.util.HashMap;
/**
* A sample client application that uses SASL to authenticate to
* a server using Kerberos as the underlying mechanism. It then
* exchanges data securely with the server.
*
* This sample program uses a ficticious application-level protocol.
* Every message exchanged between the client and server an 8-byte
* header that consists of two integers: the first integer represesents
* the application-level command or status code while the second integer
* indicates the length of the SASL buffer. This header is followed by
* the SASL buffer.
*
* The protocol is:
* 1. Authentication
* a. client sends initial response to server containing authentication
* information
* b. server accepts and evaluates response to generate challenge; it
* sends the challenge to the server.
* c. client evaluates challenge to generate response; it sends the
* response;
* d. Steps b and c are repeated until authentication succeeds or fails.
* 2. client sends an encrypted message to the server.
* 3. server decryptes the message and sends an encrypted one back
* that contains the original message plus the current time.
*
* Start SaslTestServer first before starting SaslTestClient.
*
* Usage: java <options> SaslTestClient service serverName
*
* Example: java -Djava.security.auth.login.config=jaas-krb5.conf \
* SaslTestClient host machine.imc.org
*
* Add -Djava.security.krb5.conf=krb5.conf to specify application-specific
* Kerberos configuration (different from operating system's Kerberos
* configuration).
*/
public class SaslTestClient {
private static final String MECH = "GSSAPI"; // SASL name for GSS-API/Kerberos
private static final int PORT = 4568;
private static final byte[] EMPTY = new byte[0];
public static void main(String[] args) throws Exception {
// Obtain the command-line arguments and parse the server's principal
if (args.length < 2) {
System.err.println(
"Usage: java <options> SaslTestClient <service> <serverName>");
System.exit(-1);
}
PrivilegedExceptionAction action =
new SaslClientAction(args[0], args[1], PORT);
Jaas.loginAndAction("client", action);
}
static class SaslClientAction implements PrivilegedExceptionAction {
private String service; // used for SASL authentication
private String serverName; // name used for SASL authentication
private int port;
private CallbackHandler cbh = null; // Don't need handler for GSSAPI
SaslClientAction(String service, String serverName, int port) {
this.service = service;
this.serverName = serverName;
this.port = port;
}
public Object run() throws Exception {
// Create application-level connection
AppConnection conn = new AppConnection(serverName, port);
HashMap<String,Object> props = new HashMap<String,Object>();
// Request confidentiality
props.put(Sasl.QOP, "auth-conf");
// Create SaslClient to perform authentication
SaslClient clnt = Sasl.createSaslClient(
new String[]{MECH}, null, service, serverName, props, cbh);
if (clnt == null) {
throw new Exception(
"Unable to find client implementation for " + MECH);
}
byte[] response;
byte[] challenge;
// Get initial response for authentication
response = clnt.hasInitialResponse() ?
clnt.evaluateChallenge(EMPTY) : EMPTY;
// Send initial response to server
AppConnection.AppReply reply =
conn.send(AppConnection.AUTH_CMD, response);
// Repeat until authentication terminates
while (!clnt.isComplete() &&
(reply.getStatus() == AppConnection.AUTH_INPROGRESS ||
reply.getStatus() == AppConnection.SUCCESS)) {
// Evaluate challenge to generate response
challenge = reply.getBytes();
response = clnt.evaluateChallenge(challenge);
if (reply.getStatus() == AppConnection.SUCCESS) {
if (response != null) {
throw new Exception("Protocol error interacting with SASL");
}
break;
}
// Send response to server and read server's next challenge
reply = conn.send(AppConnection.AUTH_CMD, response);
}
// Check status of authentication
if (clnt.isComplete() && reply.getStatus() == AppConnection.SUCCESS) {
System.out.println("Client authenticated.");
} else {
throw new Exception("Authentication failed: " +
" connection status? " + reply.getStatus());
}
String qop = (String) clnt.getNegotiatedProperty(Sasl.QOP);
System.out.println("Negotiated QOP: " + qop);
// Try out security layer
boolean sl = (qop.equals("auth-conf") || qop.equals("auth-int"));
byte[] msg = "Hello There!".getBytes("UTF-8");
System.out.println("Sending: " + new String(msg, "UTF-8"));
byte[] encrypted = (sl ? clnt.wrap(msg, 0, msg.length) : msg);
reply = conn.send(AppConnection.DATA_CMD, encrypted);
if (reply.getStatus() == AppConnection.SUCCESS) {
byte[] encryptedReply = reply.getBytes();
byte[] clearReply = (sl ? clnt.unwrap(encryptedReply,
0, encryptedReply.length) : encryptedReply);
System.out.println("Received: " + new String(clearReply, "UTF-8"));
} else {
System.out.println("Failed exchange: " + reply.getStatus());
}
conn.close();
return null;
}
}
}
JsseServer.java
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.util.Date;
import java.security.PrivilegedExceptionAction;
import java.security.Principal;
/*
* Tests support for RFC 2712. Specify use of only a KRB5 cipher for both
* client and server, by first doing a JAAS login for the server
* without first doing a JAAS login for the client.
*/
public class JsseServer {
private static final String KRB5_CIPHER = "TLS_KRB5_WITH_3DES_EDE_CBC_SHA";
private static final int PORT = 4569;
private static final boolean verbose = false;
private static final int LOOP_LIMIT = 1;
private static int loopCount = 0;
public static void main(String[] args) throws Exception {
PrivilegedExceptionAction action = new JsseServerAction(PORT);
Jaas.loginAndAction("server", action);
}
static class JsseServerAction implements PrivilegedExceptionAction {
private int localPort;
JsseServerAction(int port) {
this.localPort = port;
}
public Object run() throws Exception {
SSLServerSocketFactory sslssf =
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket sslServerSocket =
(SSLServerSocket) sslssf.createServerSocket(localPort);
// Enable only a KRB5 cipher suite.
String enabledSuites[] = { KRB5_CIPHER };
sslServerSocket.setEnabledCipherSuites(enabledSuites);
// Should check for exception if enabledSuites is not supported
while (loopCount++ < LOOP_LIMIT) {
System.out.println("Waiting for incoming connection...");
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
System.out.println("Got connection from client " +
sslSocket.getInetAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(
sslSocket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
sslSocket.getOutputStream()));
String inStr = in.readLine();
System.out.println("Received " + inStr);
String outStr = inStr + " " + new Date().toString() + "\n";
out.write(outStr);
System.out.println("Sending " + outStr);
out.flush();
String cipherSuiteChosen = sslSocket.getSession().getCipherSuite();
System.out.println("Cipher suite in use: " + cipherSuiteChosen);
Principal self = sslSocket.getSession().getLocalPrincipal();
System.out.println("I am: " + self.toString());
Principal peer = sslSocket.getSession().getPeerPrincipal();
System.out.println("Client is: " + peer.toString());
sslSocket.close();
}
return null;
}
}
}
JsseClient.java
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.security.PrivilegedExceptionAction;
import java.security.Principal;
public class JsseClient {
private static final String KRB5_CIPHER = "TLS_KRB5_WITH_3DES_EDE_CBC_SHA";
private static final int PORT = 4569;
private static final boolean verbose = false;
public static void main(String[] args) throws Exception {
// Obtain the command-line arguments and parse the server's name
if (args.length < 1) {
System.err.println(
"Usage: java <options> JsseClient <serverName>");
System.exit(-1);
}
PrivilegedExceptionAction action = new JsseClientAction(args[0], PORT);
Jaas.loginAndAction("client", action);
}
static class JsseClientAction implements PrivilegedExceptionAction {
private String server;
private int port;
JsseClientAction(String server, int port) {
this.port = port;
this.server = server;
}
public Object run() throws Exception {
SSLSocketFactory sslsf =
(SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(server, port);
// Enable only a KRB5 cipher suite.
String enabledSuites[] = { KRB5_CIPHER };
sslSocket.setEnabledCipherSuites(enabledSuites);
// Should check for exception if enabledSuites is not supported
BufferedReader in = new BufferedReader(new InputStreamReader(
sslSocket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
sslSocket.getOutputStream()));
String outStr = "Hello There!\n";
out.write(outStr);
out.flush();
System.out.print("Sending " + outStr);
String inStr = in.readLine();
System.out.println("Received " + inStr);
String cipherSuiteChosen = sslSocket.getSession().getCipherSuite();
System.out.println("Cipher suite in use: " + cipherSuiteChosen);
Principal self = sslSocket.getSession().getLocalPrincipal();
System.out.println("I am: " + self.toString());
Principal peer = sslSocket.getSession().getPeerPrincipal();
System.out.println("Server is: " + peer.toString());
sslSocket.close();
return null;
}
}
}
krb5.conf
# krb5.conf template
# In order to complete this configuration file
# you will need to replace the __<name>__ placeholders
# with appropriate values for your network.
#
[libdefaults]
default_realm = J1LABS.EXAMPLE.COM
forwardable = true
default_tkt_enctypes = aes128-cts rc4-hmac des3-cbc-sha1
default_tgs_enctypes = aes128-cts rc4-hmac des3-cbc-sha1
permitted_enctypes = aes128-cts rc4-hmac des3-cbc-sha1
[realms]
J1LABS.EXAMPLE.COM = {
kdc = j1hol-1280
kdc = j1hol-004
admin_server = j1hol-1280
}
[domain_realm]
.example.com = J1LABS.EXAMPLE.COM
[logging]
default = FILE:/var/krb5/kdc.log
kdc = FILE:/var/krb5/kdc.log
kdc_rotate = {
# How often to rotate kdc.log. Logs will get rotated no more
# often than the period, and less often if the KDC is not used
# frequently.
period = 1d
# how many versions of kdc.log to keep around (kdc.log.0, kdc.log.1, ...)
versions = 10
}
[appdefaults]
gkadmin = {
help_url = http://localhost:8888/ab2/coll.384.1/SEAM
}
kinit = {
renewable = true
forwardable= true
}
rlogin = {
forwardable= true
}
rsh = {
forwardable= true
}
telnet = {
autologin = true
forwardable= true
}
GssSpNegoClient.java
import org.ietf.jgss.*;
import java.net.Socket;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.security.*;
import javax.security.auth.login.LoginException;
/**
* A sample client application that uses JGSS to do mutual authentication
* with a server using Kerberos as the underlying mechanism. It then
* exchanges data securely with the server.
*
* Every message sent to the server includes a 4-byte application-level
* header that contains the big-endian integer value for the number
* of bytes that will follow as part of the JGSS token.
*
* The protocol is:
* 1. Context establishment loop:
* a. client sends init sec context token to server
* b. server sends accept sec context token to client
* ....
* 2. client sends a wrapped token to the server.
* 3. server sends a wrapped token back to the client for the application
*
* Start GssServer first before starting GssClient.
*
* Usage: java <options> GssSpNegoClient <service> <serverName>
*
* Example: java -Djava.security.auth.login.config=jaas-krb5.conf \
* GssSpNegoClient host machine.imc.org
*
* Add -Djava.security.krb5.conf=krb5.conf to specify application-specific
* Kerberos configuration (different from operating system's Kerberos
* configuration).
*/
public class GssSpNegoClient {
private static final int PORT = 4567;
private static final boolean verbose = false;
public static void main(String[] args) throws Exception {
// Obtain the command-line arguments and parse the server's principal
if (args.length < 2) {
System.err.println(
"Usage: java <options> GssSpNegoClient <service> <serverName>");
System.exit(-1);
}
String serverPrinc = args[0] + "@" + args[1];
PrivilegedExceptionAction action =
new GssClientAction(serverPrinc, args[1], PORT);
Jaas.loginAndAction("client", action);
}
static class GssClientAction implements PrivilegedExceptionAction {
private String serverPrinc;
private String hostName;
private int port;
GssClientAction(String serverPrinc, String hostName, int port) {
this.serverPrinc = serverPrinc;
this.hostName = hostName;
this.port = port;
}
public Object run() throws Exception {
Socket socket = new Socket(hostName, port);
DataInputStream inStream =
new DataInputStream(socket.getInputStream());
DataOutputStream outStream =
new DataOutputStream(socket.getOutputStream());
System.out.println("Connected to address " +
socket.getInetAddress());
/*
* This Oid is used to represent the SPNEGO GSS-API
* mechanism. It is defined in RFC 2478. We will use this Oid
* whenever we need to indicate to the GSS-API that it must
* use SPNEGO for some purpose.
*/
Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
GSSManager manager = GSSManager.getInstance();
/*
* Create a GSSName out of the server's name.
*/
GSSName serverName = manager.createName(serverPrinc,
GSSName.NT_HOSTBASED_SERVICE, spnegoOid);
/*
* Create a GSSContext for mutual authentication with the
* server.
* - serverName is the GSSName that represents the server.
* - krb5Oid is the Oid that represents the mechanism to
* use. The client chooses the mechanism to use.
* - null is passed in for client credentials
* - DEFAULT_LIFETIME lets the mechanism decide how long the
* context can remain valid.
* Note: Passing in null for the credentials asks GSS-API to
* use the default credentials. This means that the mechanism
* will look among the credentials stored in the current Subject
* to find the right kind of credentials that it needs.
*/
GSSContext context = manager.createContext(serverName,
spnegoOid,
null,
GSSContext.DEFAULT_LIFETIME);
// Set the desired optional features on the context. The client
// chooses these options.
context.requestMutualAuth(true); // Mutual authentication
context.requestConf(true); // Will use confidentiality later
context.requestInteg(true); // Will use integrity later
// Do the context eastablishment loop
byte[] token = new byte[0];
while (!context.isEstablished()) {
// token is ignored on the first call
token = context.initSecContext(token, 0, token.length);
// Send a token to the server if one was generated by
// initSecContext
if (token != null) {
if (verbose) {
System.out.println("Will send token of size " +
token.length + " from initSecContext.");
System.out.println("writing token = " +
getHexBytes(token));
}
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
}
// If the client is done with context establishment
// then there will be no more tokens to read in this loop
if (!context.isEstablished()) {
token = new byte[inStream.readInt()];
if (verbose) {
System.out.println("reading token = " +
getHexBytes(token));
System.out.println("Will read input token of size " +
token.length + " for processing by initSecContext");
}
inStream.readFully(token);
}
}
System.out.println("Context Established! ");
System.out.println("Client principal is " + context.getSrcName());
System.out.println("Server principal is " + context.getTargName());
/*
* If mutual authentication did not take place, then only the
* client was authenticated to the server. Otherwise, both
* client and server were authenticated to each other.
*/
if (context.getMutualAuthState())
System.out.println("Mutual authentication took place!");
byte[] messageBytes = "Hello There!".getBytes("UTF-8");
/*
* The first MessageProp argument is 0 to request
* the default Quality-of-Protection.
* The second argument is true to request
* privacy (encryption of the message).
*/
MessageProp prop = new MessageProp(0, true);
/*
* Encrypt the data and send it across. Integrity protection
* is always applied, irrespective of confidentiality
* (i.e., encryption).
* You can use the same token (byte array) as that used when
* establishing the context.
*/
System.out.println("Sending message: " +
new String(messageBytes, "UTF-8"));
token = context.wrap(messageBytes, 0, messageBytes.length, prop);
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
/*
* Now we will allow the server to decrypt the message,
* append a time/date on it, and send then it back.
*/
token = new byte[inStream.readInt()];
System.out.println("Will read token of size " + token.length);
inStream.readFully(token);
byte[] replyBytes = context.unwrap(token, 0, token.length, prop);
System.out.println("Received message: " +
new String(replyBytes, "UTF-8"));
System.out.println("Done.");
context.dispose();
socket.close();
return null;
}
}
private static final String getHexBytes(byte[] bytes, int pos, int len) {
StringBuffer sb = new StringBuffer();
for (int i = pos; i < (pos+len); i++) {
int b1 = (bytes[i]>>4) & 0x0f;
int b2 = bytes[i] & 0x0f;
sb.append(Integer.toHexString(b1));
sb.append(Integer.toHexString(b2));
sb.append(' ');
}
return sb.toString();
}
private static final String getHexBytes(byte[] bytes) {
return getHexBytes(bytes, 0, bytes.length);
}
}
GssSpNegoServer.java
import org.ietf.jgss.*;
import java.io.*;
import java.net.Socket;
import java.net.ServerSocket;
import java.security.*;
import java.util.Date;
/**
* A sample server application that uses JGSS to do mutual authentication
* with a client using Kerberos as the underlying mechanism. It then
* exchanges data securely with the client.
*
* Every message exchanged with the client includes a 4-byte application-
* level header that contains the big-endian integer value for the number
* of bytes that will follow as part of the JGSS token.
*
* The protocol is:
* 1. Context establishment loop:
* a. client sends init sec context token to server
* b. server sends accept sec context token to client
* ....
* 2. client sends a wrap token to the server.
* 3. server sends a wrap token back to the client.
*
* Start GssSpNegoServer first before starting GssClient.
*
* Usage: java <options> GssSpNegoServer
*
* Example: java -Djava.security.auth.login.config=jaas-krb5.conf \
* GssSpNegoServer
*
* Add -Djava.security.krb5.conf=krb5.conf to specify application-specific
* Kerberos configuration (different from operating system's Kerberos
* configuration).
*/
public class GssSpNegoServer {
private static final int PORT = 4567;
private static final boolean verbose = false;
private static final int LOOP_LIMIT = 1;
private static int loopCount = 0;
public static void main(String[] args) throws Exception {
PrivilegedExceptionAction action = new GssServerAction(PORT);
Jaas.loginAndAction("server", action);
}
static class GssServerAction implements PrivilegedExceptionAction {
private int localPort;
GssServerAction(int port) {
this.localPort = port;
}
public Object run() throws Exception {
ServerSocket ss = new ServerSocket(localPort);
// Get own Kerberos credentials for accepting connection
GSSManager manager = GSSManager.getInstance();
Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
GSSCredential serverCreds = manager.createCredential(null,
GSSCredential.DEFAULT_LIFETIME,
spnegoOid,
GSSCredential.ACCEPT_ONLY);
while (loopCount++ < LOOP_LIMIT) {
System.out.println("Waiting for incoming connection...");
Socket socket = ss.accept();
DataInputStream inStream =
new DataInputStream(socket.getInputStream());
DataOutputStream outStream =
new DataOutputStream(socket.getOutputStream());
System.out.println("Got connection from client " +
socket.getInetAddress());
/*
* Create a GSSContext to receive the incoming request
* from the client. Use null for the server credentials
* passed in. This tells the underlying mechanism
* to use whatever credentials it has available that
* can be used to accept this connection.
*/
GSSContext context = manager.createContext(
(GSSCredential)serverCreds);
// Do the context establishment loop
byte[] token = null;
while (!context.isEstablished()) {
if (verbose) {
System.out.println("Reading ...");
}
token = new byte[inStream.readInt()];
if (verbose) {
System.out.println("Will read input token of size " +
token.length + " for processing by acceptSecContext");
}
inStream.readFully(token);
if (token.length == 0) {
if (verbose) {
System.out.println("skipping zero length token");
}
continue;
}
if (verbose) {
System.out.println("Token = " + getHexBytes(token));
System.out.println("acceptSecContext..");
}
token = context.acceptSecContext(token, 0, token.length);
// Send a token to the peer if one was generated by
// acceptSecContext
if (token != null) {
if (verbose) {
System.out.println("Will send token of size " +
token.length + " from acceptSecContext.");
}
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
}
}
System.out.println("Context Established! ");
System.out.println("Client principal is " + context.getSrcName());
System.out.println("Server principal is " + context.getTargName());
/*
* If mutual authentication did not take place, then
* only the client was authenticated to the
* server. Otherwise, both client and server were
* authenticated to each other.
*/
if (context.getMutualAuthState())
System.out.println("Mutual authentication took place!");
/*
* Create a MessageProp which unwrap will use to return
* information such as the Quality-of-Protection that was
* applied to the wrapped token, whether or not it was
* encrypted, etc. Since the initial MessageProp values
* are ignored, just set them to the defaults of 0 and false.
*/
MessageProp prop = new MessageProp(0, false);
/*
* Read the token. This uses the same token byte array
* as that used during context establishment.
*/
token = new byte[inStream.readInt()];
if (verbose) {
System.out.println("Will read token of size " + token.length);
}
inStream.readFully(token);
byte[] input = context.unwrap(token, 0, token.length, prop);
String str = new String(input, "UTF-8");
System.out.println("Received data \"" +
str + "\" of length " + str.length());
System.out.println("Confidentiality applied: " +
prop.getPrivacy());
/*
* Now generate reply that is the concatenation of the
* incoming string with the current time.
*/
/*
* First reset the QOP of the MessageProp to 0
* to ensure the default Quality-of-Protection
* is applied.
*/
prop.setQOP(0);
String now = new Date().toString();
byte[] nowBytes = now.getBytes("UTF-8");
int len = input.length + 1 + nowBytes.length;
byte[] reply = new byte[len];
System.arraycopy(input, 0, reply, 0, input.length);
reply[input.length] = ' ';
System.arraycopy(nowBytes, 0, reply, input.length+1,
nowBytes.length);
System.out.println("Sending: " + new String(reply, "UTF-8"));
token = context.wrap(reply, 0, reply.length, prop);
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
System.out.println("Closing connection with client " +
socket.getInetAddress());
context.dispose();
socket.close();
}
return null;
}
}
private static final String getHexBytes(byte[] bytes, int pos, int len) {
StringBuffer sb = new StringBuffer();
for (int i = pos; i < (pos+len); i++) {
int b1 = (bytes[i]>>4) & 0x0f;
int b2 = bytes[i] & 0x0f;
sb.append(Integer.toHexString(b1));
sb.append(Integer.toHexString(b2));
sb.append(' ');
}
return sb.toString();
}
private static final String getHexBytes(byte[] bytes) {
return getHexBytes(bytes, 0, bytes.length);
}
}