Context.java revision 678
0N/A/*
2362N/A * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
2362N/A * CA 95054 USA or visit www.sun.com if you need additional information or
2362N/A * have any questions.
0N/A */
0N/A
0N/Aimport com.sun.security.auth.module.Krb5LoginModule;
0N/Aimport java.security.PrivilegedActionException;
0N/Aimport java.security.PrivilegedExceptionAction;
0N/Aimport java.util.Arrays;
0N/Aimport java.util.HashMap;
0N/Aimport java.util.Map;
0N/Aimport javax.security.auth.Subject;
0N/Aimport javax.security.auth.kerberos.KerberosKey;
0N/Aimport javax.security.auth.kerberos.KerberosTicket;
0N/Aimport javax.security.auth.login.LoginContext;
0N/Aimport org.ietf.jgss.GSSContext;
0N/Aimport org.ietf.jgss.GSSCredential;
0N/Aimport org.ietf.jgss.GSSException;
0N/Aimport org.ietf.jgss.GSSManager;
0N/Aimport org.ietf.jgss.GSSName;
0N/Aimport org.ietf.jgss.MessageProp;
0N/Aimport org.ietf.jgss.Oid;
0N/A
0N/A/**
0N/A * Context of a JGSS subject, encapsulating Subject and GSSContext.
0N/A *
0N/A * Three "constructors", which acquire the (private) credentials and fill
0N/A * it into the Subject:
0N/A *
0N/A * 1. static fromJAAS(): Creates a Context using a JAAS login config entry
0N/A * 2. static fromUserPass(): Creates a Context using a username and a password
0N/A * 3. delegated(): A new context which uses the delegated credentials from a
0N/A * previously established acceptor Context
0N/A *
0N/A * Two context initiators, which create the GSSContext object inside:
0N/A *
0N/A * 1. startAsClient()
0N/A * 2. startAsServer()
0N/A *
0N/A * Privileged action:
0N/A * doAs(): Performs an action in the name of the Subject
0N/A *
0N/A * Handshake process:
0N/A * static handShake(initiator, acceptor)
0N/A *
0N/A * A four-phase typical data communication which includes all four GSS
0N/A * actions (wrap, unwrap, getMic and veryfyMiC):
0N/A * static transmit(message, from, to)
0N/A */
0N/Apublic class Context {
0N/A
0N/A private Subject s;
0N/A private GSSContext x;
0N/A private boolean f; // context established?
0N/A private String name;
0N/A private GSSCredential cred; // see static method delegated().
0N/A
0N/A private Context() {}
0N/A
0N/A /**
0N/A * Using the delegated credentials from a previous acceptor
0N/A * @param c
0N/A */
0N/A public Context delegated() throws Exception {
0N/A Context out = new Context();
0N/A out.s = s;
0N/A out.cred = x.getDelegCred();
0N/A out.name = name + " as " + out.cred.getName().toString();
0N/A return out;
0N/A }
0N/A
0N/A /**
0N/A * Logins with a JAAS login config entry name
0N/A */
0N/A public static Context fromJAAS(final String name) throws Exception {
0N/A Context out = new Context();
0N/A out.name = name;
0N/A LoginContext lc = new LoginContext(name);
0N/A lc.login();
0N/A out.s = lc.getSubject();
0N/A return out;
0N/A }
0N/A
0N/A /**
0N/A * Logins with a username and a password, using Krb5LoginModule directly
0N/A * @param storeKey true if key should be saved, used on acceptor side
0N/A */
0N/A public static Context fromUserPass(String user, char[] pass, boolean storeKey) throws Exception {
0N/A Context out = new Context();
0N/A out.name = user;
0N/A out.s = new Subject();
0N/A Krb5LoginModule krb5 = new Krb5LoginModule();
0N/A Map<String, String> map = new HashMap<String, String>();
0N/A map.put("tryFirstPass", "true");
0N/A if (storeKey) {
0N/A map.put("storeKey", "true");
0N/A }
0N/A Map<String, Object> shared = new HashMap<String, Object>();
0N/A shared.put("javax.security.auth.login.name", user);
0N/A shared.put("javax.security.auth.login.password", pass);
0N/A
0N/A krb5.initialize(out.s, null, shared, map);
0N/A krb5.login();
0N/A krb5.commit();
0N/A return out;
0N/A }
0N/A
0N/A /**
0N/A * Starts as a client
0N/A * @param target communication peer
0N/A * @param mech GSS mech
0N/A * @throws java.lang.Exception
0N/A */
0N/A public void startAsClient(final String target, final Oid mech) throws Exception {
0N/A doAs(new Action() {
0N/A @Override
0N/A public byte[] run(Context me, byte[] dummy) throws Exception {
0N/A GSSManager m = GSSManager.getInstance();
0N/A me.x = m.createContext(
0N/A target.indexOf('@') < 0 ?
0N/A m.createName(target, null) :
0N/A m.createName(target, GSSName.NT_HOSTBASED_SERVICE),
0N/A mech,
0N/A cred,
0N/A GSSContext.DEFAULT_LIFETIME);
0N/A return null;
0N/A }
0N/A }, null);
0N/A f = false;
0N/A }
0N/A
0N/A /**
0N/A * Starts as a server
0N/A * @param mech GSS mech
0N/A * @throws java.lang.Exception
0N/A */
0N/A public void startAsServer(final Oid mech) throws Exception {
0N/A doAs(new Action() {
0N/A @Override
0N/A public byte[] run(Context me, byte[] dummy) throws Exception {
0N/A GSSManager m = GSSManager.getInstance();
0N/A me.x = m.createContext(m.createCredential(
0N/A null,
0N/A GSSCredential.INDEFINITE_LIFETIME,
0N/A mech,
0N/A GSSCredential.ACCEPT_ONLY));
0N/A return null;
0N/A }
0N/A }, null);
0N/A f = false;
0N/A }
0N/A
0N/A /**
0N/A * Accesses the internal GSSContext object. Currently it's used for --
0N/A *
0N/A * 1. calling requestXXX() before handshake
0N/A * 2. accessing source name
0N/A *
0N/A * Note: If the application needs to do any privileged call on this
0N/A * object, please use doAs(). Otherwise, it can be done directly. The
0N/A * methods listed above are all non-privileged calls.
0N/A *
0N/A * @return the GSSContext object
0N/A */
0N/A public GSSContext x() {
0N/A return x;
0N/A }
0N/A
0N/A /**
0N/A * Disposes the GSSContext within
0N/A * @throws org.ietf.jgss.GSSException
0N/A */
0N/A public void dispose() throws GSSException {
0N/A x.dispose();
0N/A }
0N/A
0N/A /**
0N/A * Does something using the Subject inside
0N/A * @param action the action
0N/A * @param in the input byte
0N/A * @return the output byte
0N/A * @throws java.lang.Exception
0N/A */
0N/A public byte[] doAs(final Action action, final byte[] in) throws Exception {
0N/A try {
0N/A return Subject.doAs(s, new PrivilegedExceptionAction<byte[]>() {
0N/A
0N/A @Override
0N/A public byte[] run() throws Exception {
0N/A return action.run(Context.this, in);
0N/A }
0N/A });
0N/A } catch (PrivilegedActionException pae) {
0N/A throw pae.getException();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Prints status of GSSContext and Subject
0N/A * @throws java.lang.Exception
0N/A */
0N/A public void status() throws Exception {
0N/A System.out.println("STATUS OF " + name.toUpperCase());
0N/A try {
0N/A StringBuffer sb = new StringBuffer();
0N/A if (x.getAnonymityState()) {
0N/A sb.append("anon, ");
0N/A }
0N/A if (x.getConfState()) {
0N/A sb.append("conf, ");
0N/A }
0N/A if (x.getCredDelegState()) {
0N/A sb.append("deleg, ");
0N/A }
0N/A if (x.getIntegState()) {
0N/A sb.append("integ, ");
0N/A }
0N/A if (x.getMutualAuthState()) {
0N/A sb.append("mutual, ");
0N/A }
0N/A if (x.getReplayDetState()) {
0N/A sb.append("rep det, ");
0N/A }
0N/A if (x.getSequenceDetState()) {
0N/A sb.append("seq det, ");
0N/A }
0N/A System.out.println("Context status of " + name + ": " + sb.toString());
System.out.println(x.getSrcName() + " -> " + x.getTargName());
} catch (Exception e) {
;// Don't care
}
System.out.println("=====================================");
for (Object o : s.getPrivateCredentials()) {
System.out.println(" " + o.getClass());
if (o instanceof KerberosTicket) {
KerberosTicket kt = (KerberosTicket) o;
System.out.println(" " + kt.getServer() + " for " + kt.getClient());
} else if (o instanceof KerberosKey) {
KerberosKey kk = (KerberosKey) o;
System.out.print(" " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " ");
for (byte b : kk.getEncoded()) {
System.out.printf("%02X", b & 0xff);
}
System.out.println();
} else if (o instanceof Map) {
Map map = (Map) o;
for (Object k : map.keySet()) {
System.out.println(" " + k + ": " + map.get(k));
}
}
}
}
/**
* Transmits a message from one Context to another. The sender wraps the
* message and sends it to the receiver. The receiver unwraps it, creates
* a MIC of the clear text and sends it back to the sender. The sender
* verifies the MIC against the message sent earlier.
* @param message the message
* @param s1 the sender
* @param s2 the receiver
* @throws java.lang.Exception If anything goes wrong
*/
static public void transmit(final String message, final Context s1,
final Context s2) throws Exception {
final byte[] messageBytes = message.getBytes();
System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n",
s1.name, s2.name);
byte[] t = s1.doAs(new Action() {
@Override
public byte[] run(Context me, byte[] dummy) throws Exception {
System.out.println("wrap");
MessageProp p1 = new MessageProp(0, true);
byte[] out = me.x.wrap(messageBytes, 0, messageBytes.length, p1);
System.out.println(printProp(p1));
return out;
}
}, null);
t = s2.doAs(new Action() {
@Override
public byte[] run(Context me, byte[] input) throws Exception {
MessageProp p1 = new MessageProp(0, true);
byte[] bytes = me.x.unwrap(input, 0, input.length, p1);
if (!Arrays.equals(messageBytes, bytes))
throw new Exception("wrap/unwrap mismatch");
System.out.println("unwrap");
System.out.println(printProp(p1));
p1 = new MessageProp(0, true);
System.out.println("getMIC");
bytes = me.x.getMIC(bytes, 0, bytes.length, p1);
System.out.println(printProp(p1));
return bytes;
}
}, t);
// Re-unwrap should make p2.isDuplicateToken() returns true
s1.doAs(new Action() {
@Override
public byte[] run(Context me, byte[] input) throws Exception {
MessageProp p1 = new MessageProp(0, true);
System.out.println("verifyMIC");
me.x.verifyMIC(input, 0, input.length,
messageBytes, 0, messageBytes.length,
p1);
System.out.println(printProp(p1));
return null;
}
}, t);
}
/**
* Returns a string description of a MessageProp object
* @param prop the object
* @return the description
*/
static public String printProp(MessageProp prop) {
StringBuffer sb = new StringBuffer();
sb.append("MessagePop: ");
sb.append("QOP="+ prop.getQOP() + ", ");
sb.append(prop.getPrivacy()?"privacy, ":"");
sb.append(prop.isDuplicateToken()?"dup, ":"");
sb.append(prop.isGapToken()?"gap, ":"");
sb.append(prop.isOldToken()?"old, ":"");
sb.append(prop.isUnseqToken()?"unseq, ":"");
sb.append(prop.getMinorString()+ "(" + prop.getMinorStatus()+")");
return sb.toString();
}
/**
* Handshake (security context establishment process) between two Contexts
* @param c the initiator
* @param s the acceptor
* @throws java.lang.Exception
*/
static public void handshake(final Context c, final Context s) throws Exception {
byte[] t = new byte[0];
while (!c.f || !s.f) {
t = c.doAs(new Action() {
@Override
public byte[] run(Context me, byte[] input) throws Exception {
if (me.x.isEstablished()) {
me.f = true;
System.out.println(c.name + " side established");
return null;
} else {
System.out.println(c.name + " call initSecContext");
return me.x.initSecContext(input, 0, input.length);
}
}
}, t);
t = s.doAs(new Action() {
@Override
public byte[] run(Context me, byte[] input) throws Exception {
if (me.x.isEstablished()) {
me.f = true;
System.out.println(s.name + " side established");
return null;
} else {
System.out.println(s.name + " called acceptSecContext");
return me.x.acceptSecContext(input, 0, input.length);
}
}
}, t);
}
}
}