1 /*
   2  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import com.sun.security.auth.module.Krb5LoginModule;
  25 import java.security.Key;
  26 import java.security.PrivilegedActionException;
  27 import java.security.PrivilegedExceptionAction;
  28 import java.util.Arrays;
  29 import java.util.HashMap;
  30 import java.util.Map;
  31 import javax.security.auth.Subject;
  32 import javax.security.auth.kerberos.KerberosKey;
  33 import javax.security.auth.kerberos.KerberosTicket;
  34 import javax.security.auth.login.LoginContext;
  35 import org.ietf.jgss.GSSContext;
  36 import org.ietf.jgss.GSSCredential;
  37 import org.ietf.jgss.GSSException;
  38 import org.ietf.jgss.GSSManager;
  39 import org.ietf.jgss.GSSName;
  40 import org.ietf.jgss.MessageProp;
  41 import org.ietf.jgss.Oid;
  42 import com.sun.security.jgss.ExtendedGSSContext;
  43 import com.sun.security.jgss.InquireType;
  44 import com.sun.security.jgss.AuthorizationDataEntry;
  45 import java.io.ByteArrayInputStream;
  46 import java.io.ByteArrayOutputStream;
  47 
  48 /**
  49  * Context of a JGSS subject, encapsulating Subject and GSSContext.
  50  *
  51  * Three "constructors", which acquire the (private) credentials and fill
  52  * it into the Subject:
  53  *
  54  * 1. static fromJAAS(): Creates a Context using a JAAS login config entry
  55  * 2. static fromUserPass(): Creates a Context using a username and a password
  56  * 3. delegated(): A new context which uses the delegated credentials from a
  57  *    previously established acceptor Context
  58  *
  59  * Two context initiators, which create the GSSContext object inside:
  60  *
  61  * 1. startAsClient()
  62  * 2. startAsServer()
  63  *
  64  * Privileged action:
  65  *    doAs(): Performs an action in the name of the Subject
  66  *
  67  * Handshake process:
  68  *    static handShake(initiator, acceptor)
  69  *
  70  * A four-phase typical data communication which includes all four GSS
  71  * actions (wrap, unwrap, getMic and veryfyMiC):
  72  *    static transmit(message, from, to)
  73  */
  74 public class Context {
  75 
  76     private Subject s;
  77     private ExtendedGSSContext x;
  78     private boolean f;      // context established?
  79     private String name;
  80     private GSSCredential cred;     // see static method delegated().
  81 
  82     static boolean usingStream = false;
  83 
  84     private Context() {}
  85 
  86     /**
  87      * Using the delegated credentials from a previous acceptor
  88      * @param c
  89      */
  90     public Context delegated() throws Exception {
  91         Context out = new Context();
  92         out.s = s;
  93         out.cred = x.getDelegCred();
  94         out.name = name + " as " + out.cred.getName().toString();
  95         return out;
  96     }
  97 
  98     /**
  99      * Logins with a JAAS login config entry name
 100      */
 101     public static Context fromJAAS(final String name) throws Exception {
 102         Context out = new Context();
 103         out.name = name;
 104         LoginContext lc = new LoginContext(name);
 105         lc.login();
 106         out.s = lc.getSubject();
 107         return out;
 108     }
 109 
 110     /**
 111      * Logins with a username and a password, using Krb5LoginModule directly
 112      * @param storeKey true if key should be saved, used on acceptor side
 113      */
 114     public static Context fromUserPass(String user, char[] pass, boolean storeKey)
 115             throws Exception {
 116         Context out = new Context();
 117         out.name = user;
 118         out.s = new Subject();
 119         Krb5LoginModule krb5 = new Krb5LoginModule();
 120         Map<String, String> map = new HashMap<>();
 121         Map<String, Object> shared = new HashMap<>();
 122 
 123         if (pass != null) {
 124             map.put("useFirstPass", "true");
 125             shared.put("javax.security.auth.login.name", user);
 126             shared.put("javax.security.auth.login.password", pass);
 127         } else {
 128             map.put("doNotPrompt", "true");
 129             map.put("useTicketCache", "true");
 130             if (user != null) {
 131                 map.put("principal", user);
 132             }
 133         }
 134         if (storeKey) {
 135             map.put("storeKey", "true");
 136         }
 137 
 138         krb5.initialize(out.s, null, shared, map);
 139         krb5.login();
 140         krb5.commit();
 141         return out;
 142     }
 143 
 144     /**
 145      * Logins with a username and a keytab, using Krb5LoginModule directly
 146      * @param storeKey true if key should be saved, used on acceptor side
 147      */
 148     public static Context fromUserKtab(String user, String ktab, boolean storeKey)
 149             throws Exception {
 150         Context out = new Context();
 151         out.name = user;
 152         out.s = new Subject();
 153         Krb5LoginModule krb5 = new Krb5LoginModule();
 154         Map<String, String> map = new HashMap<>();
 155 
 156         map.put("doNotPrompt", "true");
 157         map.put("useTicketCache", "false");
 158         map.put("useKeyTab", "true");
 159         map.put("keyTab", ktab);
 160         map.put("principal", user);
 161         if (storeKey) {
 162             map.put("storeKey", "true");
 163         }
 164 
 165         krb5.initialize(out.s, null, null, map);
 166         krb5.login();
 167         krb5.commit();
 168         return out;
 169     }
 170 
 171     /**
 172      * Starts as a client
 173      * @param target communication peer
 174      * @param mech GSS mech
 175      * @throws java.lang.Exception
 176      */
 177     public void startAsClient(final String target, final Oid mech) throws Exception {
 178         doAs(new Action() {
 179             @Override
 180             public byte[] run(Context me, byte[] dummy) throws Exception {
 181                 GSSManager m = GSSManager.getInstance();
 182                 me.x = (ExtendedGSSContext)m.createContext(
 183                           target.indexOf('@') < 0 ?
 184                             m.createName(target, null) :
 185                             m.createName(target, GSSName.NT_HOSTBASED_SERVICE),
 186                         mech,
 187                         cred,
 188                         GSSContext.DEFAULT_LIFETIME);
 189                 return null;
 190             }
 191         }, null);
 192         f = false;
 193     }
 194 
 195     /**
 196      * Starts as a server
 197      * @param mech GSS mech
 198      * @throws java.lang.Exception
 199      */
 200     public void startAsServer(final Oid mech) throws Exception {
 201         doAs(new Action() {
 202             @Override
 203             public byte[] run(Context me, byte[] dummy) throws Exception {
 204                 GSSManager m = GSSManager.getInstance();
 205                 me.x = (ExtendedGSSContext)m.createContext(m.createCredential(
 206                         null,
 207                         GSSCredential.INDEFINITE_LIFETIME,
 208                         mech,
 209                         GSSCredential.ACCEPT_ONLY));
 210                 return null;
 211             }
 212         }, null);
 213         f = false;
 214     }
 215 
 216     /**
 217      * Accesses the internal GSSContext object. Currently it's used for --
 218      *
 219      * 1. calling requestXXX() before handshake
 220      * 2. accessing source name
 221      *
 222      * Note: If the application needs to do any privileged call on this
 223      * object, please use doAs(). Otherwise, it can be done directly. The
 224      * methods listed above are all non-privileged calls.
 225      *
 226      * @return the GSSContext object
 227      */
 228     public ExtendedGSSContext x() {
 229         return x;
 230     }
 231 
 232     /**
 233      * Disposes the GSSContext within
 234      * @throws org.ietf.jgss.GSSException
 235      */
 236     public void dispose() throws GSSException {
 237         x.dispose();
 238     }
 239 
 240     /**
 241      * Does something using the Subject inside
 242      * @param action the action
 243      * @param in the input byte
 244      * @return the output byte
 245      * @throws java.lang.Exception
 246      */
 247     public byte[] doAs(final Action action, final byte[] in) throws Exception {
 248         try {
 249             return Subject.doAs(s, new PrivilegedExceptionAction<byte[]>() {
 250 
 251                 @Override
 252                 public byte[] run() throws Exception {
 253                     return action.run(Context.this, in);
 254                 }
 255             });
 256         } catch (PrivilegedActionException pae) {
 257             throw pae.getException();
 258         }
 259     }
 260 
 261     /**
 262      * Prints status of GSSContext and Subject
 263      * @throws java.lang.Exception
 264      */
 265     public void status() throws Exception {
 266         System.out.println("STATUS OF " + name.toUpperCase());
 267         try {
 268             StringBuffer sb = new StringBuffer();
 269             if (x.getAnonymityState()) {
 270                 sb.append("anon, ");
 271             }
 272             if (x.getConfState()) {
 273                 sb.append("conf, ");
 274             }
 275             if (x.getCredDelegState()) {
 276                 sb.append("deleg, ");
 277             }
 278             if (x.getIntegState()) {
 279                 sb.append("integ, ");
 280             }
 281             if (x.getMutualAuthState()) {
 282                 sb.append("mutual, ");
 283             }
 284             if (x.getReplayDetState()) {
 285                 sb.append("rep det, ");
 286             }
 287             if (x.getSequenceDetState()) {
 288                 sb.append("seq det, ");
 289             }
 290             if (x instanceof ExtendedGSSContext) {
 291                 if (((ExtendedGSSContext)x).getDelegPolicyState()) {
 292                     sb.append("deleg policy, ");
 293                 }
 294             }
 295             System.out.println("Context status of " + name + ": " + sb.toString());
 296             System.out.println(x.getSrcName() + " -> " + x.getTargName());
 297         } catch (Exception e) {
 298             ;// Don't care
 299         }
 300         System.out.println("=====================================");
 301         for (Object o : s.getPrivateCredentials()) {
 302             System.out.println("    " + o.getClass());
 303             if (o instanceof KerberosTicket) {
 304                 KerberosTicket kt = (KerberosTicket) o;
 305                 System.out.println("        " + kt.getServer() + " for " + kt.getClient());
 306             } else if (o instanceof KerberosKey) {
 307                 KerberosKey kk = (KerberosKey) o;
 308                 System.out.print("        " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " ");
 309                 for (byte b : kk.getEncoded()) {
 310                     System.out.printf("%02X", b & 0xff);
 311                 }
 312                 System.out.println();
 313             } else if (o instanceof Map) {
 314                 Map map = (Map) o;
 315                 for (Object k : map.keySet()) {
 316                     System.out.println("        " + k + ": " + map.get(k));
 317                 }
 318             }
 319         }
 320         if (x != null && x instanceof ExtendedGSSContext) {
 321             if (x.isEstablished()) {
 322                 ExtendedGSSContext ex = (ExtendedGSSContext)x;
 323                 Key k = (Key)ex.inquireSecContext(
 324                         InquireType.KRB5_GET_SESSION_KEY);
 325                 if (k == null) {
 326                     throw new Exception("Session key cannot be null");
 327                 }
 328                 System.out.println("Session key is: " + k);
 329                 boolean[] flags = (boolean[])ex.inquireSecContext(
 330                         InquireType.KRB5_GET_TKT_FLAGS);
 331                 if (flags == null) {
 332                     throw new Exception("Ticket flags cannot be null");
 333                 }
 334                 System.out.println("Ticket flags is: " + Arrays.toString(flags));
 335                 String authTime = (String)ex.inquireSecContext(
 336                         InquireType.KRB5_GET_AUTHTIME);
 337                 if (authTime == null) {
 338                     throw new Exception("Auth time cannot be null");
 339                 }
 340                 System.out.println("AuthTime is: " + authTime);
 341                 if (!x.isInitiator()) {
 342                     AuthorizationDataEntry[] ad = (AuthorizationDataEntry[])ex.inquireSecContext(
 343                             InquireType.KRB5_GET_AUTHZ_DATA);
 344                     System.out.println("AuthzData is: " + Arrays.toString(ad));
 345                 }
 346             }
 347         }
 348     }
 349 
 350     /**
 351      * Transmits a message from one Context to another. The sender wraps the
 352      * message and sends it to the receiver. The receiver unwraps it, creates
 353      * a MIC of the clear text and sends it back to the sender. The sender
 354      * verifies the MIC against the message sent earlier.
 355      * @param message the message
 356      * @param s1 the sender
 357      * @param s2 the receiver
 358      * @throws java.lang.Exception If anything goes wrong
 359      */
 360     static public void transmit(final String message, final Context s1,
 361             final Context s2) throws Exception {
 362         final byte[] messageBytes = message.getBytes();
 363         System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n",
 364                 s1.name, s2.name);
 365 
 366         byte[] t = s1.doAs(new Action() {
 367             @Override
 368             public byte[] run(Context me, byte[] dummy) throws Exception {
 369                 System.out.println("wrap");
 370                 MessageProp p1 = new MessageProp(0, true);
 371                 byte[] out;
 372                 if (usingStream) {
 373                     ByteArrayOutputStream os = new ByteArrayOutputStream();
 374                     me.x.wrap(new ByteArrayInputStream(messageBytes), os, p1);
 375                     out = os.toByteArray();
 376                 } else {
 377                     out = me.x.wrap(messageBytes, 0, messageBytes.length, p1);
 378                 }
 379                 System.out.println(printProp(p1));
 380                 return out;
 381             }
 382         }, null);
 383 
 384         t = s2.doAs(new Action() {
 385             @Override
 386             public byte[] run(Context me, byte[] input) throws Exception {
 387                 MessageProp p1 = new MessageProp(0, true);
 388                 byte[] bytes;
 389                 if (usingStream) {
 390                     ByteArrayOutputStream os = new ByteArrayOutputStream();
 391                     me.x.unwrap(new ByteArrayInputStream(input), os, p1);
 392                     bytes = os.toByteArray();
 393                 } else {
 394                     bytes = me.x.unwrap(input, 0, input.length, p1);
 395                 }
 396                 if (!Arrays.equals(messageBytes, bytes))
 397                     throw new Exception("wrap/unwrap mismatch");
 398                 System.out.println("unwrap");
 399                 System.out.println(printProp(p1));
 400                 p1 = new MessageProp(0, true);
 401                 System.out.println("getMIC");
 402                 if (usingStream) {
 403                     ByteArrayOutputStream os = new ByteArrayOutputStream();
 404                     me.x.getMIC(new ByteArrayInputStream(messageBytes), os, p1);
 405                     bytes = os.toByteArray();
 406                 } else {
 407                     bytes = me.x.getMIC(messageBytes, 0, messageBytes.length, p1);
 408                 }
 409                 System.out.println(printProp(p1));
 410                 return bytes;
 411             }
 412         }, t);
 413 
 414         // Re-unwrap should make p2.isDuplicateToken() returns true
 415         s1.doAs(new Action() {
 416             @Override
 417             public byte[] run(Context me, byte[] input) throws Exception {
 418                 MessageProp p1 = new MessageProp(0, true);
 419                 System.out.println("verifyMIC");
 420                 if (usingStream) {
 421                     me.x.verifyMIC(new ByteArrayInputStream(input),
 422                             new ByteArrayInputStream(messageBytes), p1);
 423                 } else {
 424                     me.x.verifyMIC(input, 0, input.length,
 425                             messageBytes, 0, messageBytes.length,
 426                             p1);
 427                 }
 428                 System.out.println(printProp(p1));
 429                 return null;
 430             }
 431         }, t);
 432     }
 433 
 434     /**
 435      * Returns a string description of a MessageProp object
 436      * @param prop the object
 437      * @return the description
 438      */
 439     static public String printProp(MessageProp prop) {
 440         StringBuffer sb = new StringBuffer();
 441         sb.append("MessagePop: ");
 442         sb.append("QOP="+ prop.getQOP() + ", ");
 443         sb.append(prop.getPrivacy()?"privacy, ":"");
 444         sb.append(prop.isDuplicateToken()?"dup, ":"");
 445         sb.append(prop.isGapToken()?"gap, ":"");
 446         sb.append(prop.isOldToken()?"old, ":"");
 447         sb.append(prop.isUnseqToken()?"unseq, ":"");
 448         if (prop.getMinorStatus() != 0) {
 449             sb.append(prop.getMinorString()+ "(" + prop.getMinorStatus()+")");
 450         }
 451         return sb.toString();
 452     }
 453 
 454     /**
 455      * Handshake (security context establishment process) between two Contexts
 456      * @param c the initiator
 457      * @param s the acceptor
 458      * @throws java.lang.Exception
 459      */
 460     static public void handshake(final Context c, final Context s) throws Exception {
 461         byte[] t = new byte[0];
 462         while (!c.f || !s.f) {
 463             t = c.doAs(new Action() {
 464                 @Override
 465                 public byte[] run(Context me, byte[] input) throws Exception {
 466                     if (me.x.isEstablished()) {
 467                         me.f = true;
 468                         System.out.println(c.name + " side established");
 469                         if (input != null) {
 470                             throw new Exception("Context established but " +
 471                                     "still receive token at " + c.name);
 472                         }
 473                         return null;
 474                     } else {
 475                         System.out.println(c.name + " call initSecContext");
 476                         if (usingStream) {
 477                             ByteArrayOutputStream os = new ByteArrayOutputStream();
 478                             me.x.initSecContext(new ByteArrayInputStream(input), os);
 479                             return os.size() == 0 ? null : os.toByteArray();
 480                         } else {
 481                             return me.x.initSecContext(input, 0, input.length);
 482                         }
 483                     }
 484                 }
 485             }, t);
 486 
 487             t = s.doAs(new Action() {
 488                 @Override
 489                 public byte[] run(Context me, byte[] input) throws Exception {
 490                     if (me.x.isEstablished()) {
 491                         me.f = true;
 492                         System.out.println(s.name + " side established");
 493                         if (input != null) {
 494                             throw new Exception("Context established but " +
 495                                     "still receive token at " + s.name);
 496                         }
 497                         return null;
 498                     } else {
 499                         System.out.println(s.name + " called acceptSecContext");
 500                         if (usingStream) {
 501                             ByteArrayOutputStream os = new ByteArrayOutputStream();
 502                             me.x.acceptSecContext(new ByteArrayInputStream(input), os);
 503                             return os.size() == 0 ? null : os.toByteArray();
 504                         } else {
 505                             return me.x.acceptSecContext(input, 0, input.length);
 506                         }
 507                     }
 508                 }
 509             }, t);
 510         }
 511     }
 512 }