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 }