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