Context.java revision 9330:8b1f1c2a400f
1/* 2 * Copyright (c) 2008, 2013, 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 24import com.sun.security.auth.module.Krb5LoginModule; 25import java.security.Key; 26import java.security.PrivilegedActionException; 27import java.security.PrivilegedExceptionAction; 28import java.util.Arrays; 29import java.util.HashMap; 30import java.util.Map; 31import javax.security.auth.Subject; 32import javax.security.auth.kerberos.KerberosKey; 33import javax.security.auth.kerberos.KerberosTicket; 34import javax.security.auth.login.LoginContext; 35import org.ietf.jgss.GSSContext; 36import org.ietf.jgss.GSSCredential; 37import org.ietf.jgss.GSSException; 38import org.ietf.jgss.GSSManager; 39import org.ietf.jgss.GSSName; 40import org.ietf.jgss.MessageProp; 41import org.ietf.jgss.Oid; 42import com.sun.security.jgss.ExtendedGSSContext; 43import com.sun.security.jgss.InquireType; 44import com.sun.security.jgss.AuthorizationDataEntry; 45import com.sun.security.jgss.ExtendedGSSCredential; 46import java.io.ByteArrayInputStream; 47import java.io.ByteArrayOutputStream; 48import java.security.Principal; 49 50/** 51 * Context of a JGSS subject, encapsulating Subject and GSSContext. 52 * 53 * Three "constructors", which acquire the (private) credentials and fill 54 * it into the Subject: 55 * 56 * 1. static fromJAAS(): Creates a Context using a JAAS login config entry 57 * 2. static fromUserPass(): Creates a Context using a username and a password 58 * 3. delegated(): A new context which uses the delegated credentials from a 59 * previously established acceptor Context 60 * 61 * Two context initiators, which create the GSSContext object inside: 62 * 63 * 1. startAsClient() 64 * 2. startAsServer() 65 * 66 * Privileged action: 67 * doAs(): Performs an action in the name of the Subject 68 * 69 * Handshake process: 70 * static handShake(initiator, acceptor) 71 * 72 * A four-phase typical data communication which includes all four GSS 73 * actions (wrap, unwrap, getMic and veryfyMiC): 74 * static transmit(message, from, to) 75 */ 76public class Context { 77 78 private Subject s; 79 private ExtendedGSSContext x; 80 private String name; 81 private GSSCredential cred; // see static method delegated(). 82 83 static boolean usingStream = false; 84 85 private Context() {} 86 87 /** 88 * Using the delegated credentials from a previous acceptor 89 * @param c 90 */ 91 public Context delegated() throws Exception { 92 Context out = new Context(); 93 out.s = s; 94 try { 95 out.cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() { 96 @Override 97 public GSSCredential run() throws Exception { 98 GSSCredential cred = x.getDelegCred(); 99 if (cred == null && x.getCredDelegState() || 100 cred != null && !x.getCredDelegState()) { 101 throw new Exception("getCredDelegState not match"); 102 } 103 return cred; 104 } 105 }); 106 } catch (PrivilegedActionException pae) { 107 throw pae.getException(); 108 } 109 out.name = name + " as " + out.cred.getName().toString(); 110 return out; 111 } 112 113 /** 114 * No JAAS login at all, can be used to test JGSS without JAAS 115 */ 116 public static Context fromThinAir() throws Exception { 117 Context out = new Context(); 118 out.s = new Subject(); 119 return out; 120 } 121 122 /** 123 * Logins with a JAAS login config entry name 124 */ 125 public static Context fromJAAS(final String name) throws Exception { 126 Context out = new Context(); 127 out.name = name; 128 LoginContext lc = new LoginContext(name); 129 lc.login(); 130 out.s = lc.getSubject(); 131 return out; 132 } 133 134 /** 135 * Logins with username/password as a new Subject 136 */ 137 public static Context fromUserPass( 138 String user, char[] pass, boolean storeKey) throws Exception { 139 return fromUserPass(new Subject(), user, pass, storeKey); 140 } 141 142 /** 143 * Logins with username/password as an existing Subject. The 144 * same subject can be used multiple times to simulate multiple logins. 145 * @param s existing subject 146 */ 147 public static Context fromUserPass(Subject s, 148 String user, char[] pass, boolean storeKey) throws Exception { 149 Context out = new Context(); 150 out.name = user; 151 out.s = s; 152 Krb5LoginModule krb5 = new Krb5LoginModule(); 153 Map<String, String> map = new HashMap<>(); 154 Map<String, Object> shared = new HashMap<>(); 155 156 if (pass != null) { 157 map.put("useFirstPass", "true"); 158 shared.put("javax.security.auth.login.name", user); 159 shared.put("javax.security.auth.login.password", pass); 160 } else { 161 map.put("doNotPrompt", "true"); 162 map.put("useTicketCache", "true"); 163 if (user != null) { 164 map.put("principal", user); 165 } 166 } 167 if (storeKey) { 168 map.put("storeKey", "true"); 169 } 170 171 krb5.initialize(out.s, null, shared, map); 172 krb5.login(); 173 krb5.commit(); 174 return out; 175 } 176 177 /** 178 * Logins with username/keytab as an existing Subject. The 179 * same subject can be used multiple times to simulate multiple logins. 180 * @param s existing subject 181 */ 182 public static Context fromUserKtab( 183 String user, String ktab, boolean storeKey) throws Exception { 184 return fromUserKtab(new Subject(), user, ktab, storeKey); 185 } 186 187 /** 188 * Logins with username/keytab as a new subject, 189 */ 190 public static Context fromUserKtab(Subject s, 191 String user, String ktab, boolean storeKey) throws Exception { 192 Context out = new Context(); 193 out.name = user; 194 out.s = s; 195 Krb5LoginModule krb5 = new Krb5LoginModule(); 196 Map<String, String> map = new HashMap<>(); 197 198 map.put("isInitiator", "false"); 199 map.put("doNotPrompt", "true"); 200 map.put("useTicketCache", "false"); 201 map.put("useKeyTab", "true"); 202 map.put("keyTab", ktab); 203 map.put("principal", user); 204 if (storeKey) { 205 map.put("storeKey", "true"); 206 } 207 208 krb5.initialize(out.s, null, null, map); 209 krb5.login(); 210 krb5.commit(); 211 return out; 212 } 213 214 /** 215 * Starts as a client 216 * @param target communication peer 217 * @param mech GSS mech 218 * @throws java.lang.Exception 219 */ 220 public void startAsClient(final String target, final Oid mech) throws Exception { 221 doAs(new Action() { 222 @Override 223 public byte[] run(Context me, byte[] dummy) throws Exception { 224 GSSManager m = GSSManager.getInstance(); 225 me.x = (ExtendedGSSContext)m.createContext( 226 target.indexOf('@') < 0 ? 227 m.createName(target, null) : 228 m.createName(target, GSSName.NT_HOSTBASED_SERVICE), 229 mech, 230 cred, 231 GSSContext.DEFAULT_LIFETIME); 232 return null; 233 } 234 }, null); 235 } 236 237 /** 238 * Starts as a server 239 * @param mech GSS mech 240 * @throws java.lang.Exception 241 */ 242 public void startAsServer(final Oid mech) throws Exception { 243 startAsServer(null, mech, false); 244 } 245 246 public void startAsServer(final String name, final Oid mech) throws Exception { 247 startAsServer(name, mech, false); 248 } 249 /** 250 * Starts as a server with the specified service name 251 * @param name the service name 252 * @param mech GSS mech 253 * @throws java.lang.Exception 254 */ 255 public void startAsServer(final String name, final Oid mech, final boolean asInitiator) throws Exception { 256 doAs(new Action() { 257 @Override 258 public byte[] run(Context me, byte[] dummy) throws Exception { 259 GSSManager m = GSSManager.getInstance(); 260 me.cred = m.createCredential( 261 name == null ? null : 262 (name.indexOf('@') < 0 ? 263 m.createName(name, null) : 264 m.createName(name, GSSName.NT_HOSTBASED_SERVICE)), 265 GSSCredential.INDEFINITE_LIFETIME, 266 mech, 267 asInitiator? 268 GSSCredential.INITIATE_AND_ACCEPT: 269 GSSCredential.ACCEPT_ONLY); 270 me.x = (ExtendedGSSContext)m.createContext(me.cred); 271 return null; 272 } 273 }, null); 274 } 275 276 /** 277 * Accesses the internal GSSContext object. Currently it's used for -- 278 * 279 * 1. calling requestXXX() before handshake 280 * 2. accessing source name 281 * 282 * Note: If the application needs to do any privileged call on this 283 * object, please use doAs(). Otherwise, it can be done directly. The 284 * methods listed above are all non-privileged calls. 285 * 286 * @return the GSSContext object 287 */ 288 public ExtendedGSSContext x() { 289 return x; 290 } 291 292 /** 293 * Accesses the internal subject. 294 * @return the subject 295 */ 296 public Subject s() { 297 return s; 298 } 299 300 /** 301 * Returns the cred inside, if there is one 302 */ 303 public GSSCredential cred() { 304 return cred; 305 } 306 307 /** 308 * Disposes the GSSContext within 309 * @throws org.ietf.jgss.GSSException 310 */ 311 public void dispose() throws GSSException { 312 x.dispose(); 313 } 314 315 /** 316 * Does something using the Subject inside 317 * @param action the action 318 * @param in the input byte 319 * @return the output byte 320 * @throws java.lang.Exception 321 */ 322 public byte[] doAs(final Action action, final byte[] in) throws Exception { 323 try { 324 return Subject.doAs(s, new PrivilegedExceptionAction<byte[]>() { 325 326 @Override 327 public byte[] run() throws Exception { 328 return action.run(Context.this, in); 329 } 330 }); 331 } catch (PrivilegedActionException pae) { 332 throw pae.getException(); 333 } 334 } 335 336 /** 337 * Prints status of GSSContext and Subject 338 * @throws java.lang.Exception 339 */ 340 public void status() throws Exception { 341 System.out.println("STATUS OF " + name.toUpperCase()); 342 try { 343 StringBuffer sb = new StringBuffer(); 344 if (x.getAnonymityState()) { 345 sb.append("anon, "); 346 } 347 if (x.getConfState()) { 348 sb.append("conf, "); 349 } 350 if (x.getCredDelegState()) { 351 sb.append("deleg, "); 352 } 353 if (x.getIntegState()) { 354 sb.append("integ, "); 355 } 356 if (x.getMutualAuthState()) { 357 sb.append("mutual, "); 358 } 359 if (x.getReplayDetState()) { 360 sb.append("rep det, "); 361 } 362 if (x.getSequenceDetState()) { 363 sb.append("seq det, "); 364 } 365 if (x instanceof ExtendedGSSContext) { 366 if (((ExtendedGSSContext)x).getDelegPolicyState()) { 367 sb.append("deleg policy, "); 368 } 369 } 370 System.out.println("Context status of " + name + ": " + sb.toString()); 371 System.out.println(x.getSrcName() + " -> " + x.getTargName()); 372 } catch (Exception e) { 373 ;// Don't care 374 } 375 if (s != null) { 376 System.out.println("====== START SUBJECT CONTENT ====="); 377 for (Principal p: s.getPrincipals()) { 378 System.out.println(" Principal: " + p); 379 } 380 for (Object o : s.getPublicCredentials()) { 381 System.out.println(" " + o.getClass()); 382 System.out.println(" " + o); 383 } 384 System.out.println("====== Private Credentials Set ======"); 385 for (Object o : s.getPrivateCredentials()) { 386 System.out.println(" " + o.getClass()); 387 if (o instanceof KerberosTicket) { 388 KerberosTicket kt = (KerberosTicket) o; 389 System.out.println(" " + kt.getServer() + " for " + kt.getClient()); 390 } else if (o instanceof KerberosKey) { 391 KerberosKey kk = (KerberosKey) o; 392 System.out.print(" " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " "); 393 for (byte b : kk.getEncoded()) { 394 System.out.printf("%02X", b & 0xff); 395 } 396 System.out.println(); 397 } else if (o instanceof Map) { 398 Map map = (Map) o; 399 for (Object k : map.keySet()) { 400 System.out.println(" " + k + ": " + map.get(k)); 401 } 402 } else { 403 System.out.println(" " + o); 404 } 405 } 406 System.out.println("====== END SUBJECT CONTENT ====="); 407 } 408 if (x != null && x instanceof ExtendedGSSContext) { 409 if (x.isEstablished()) { 410 ExtendedGSSContext ex = (ExtendedGSSContext)x; 411 Key k = (Key)ex.inquireSecContext( 412 InquireType.KRB5_GET_SESSION_KEY); 413 if (k == null) { 414 throw new Exception("Session key cannot be null"); 415 } 416 System.out.println("Session key is: " + k); 417 boolean[] flags = (boolean[])ex.inquireSecContext( 418 InquireType.KRB5_GET_TKT_FLAGS); 419 if (flags == null) { 420 throw new Exception("Ticket flags cannot be null"); 421 } 422 System.out.println("Ticket flags is: " + Arrays.toString(flags)); 423 String authTime = (String)ex.inquireSecContext( 424 InquireType.KRB5_GET_AUTHTIME); 425 if (authTime == null) { 426 throw new Exception("Auth time cannot be null"); 427 } 428 System.out.println("AuthTime is: " + authTime); 429 if (!x.isInitiator()) { 430 AuthorizationDataEntry[] ad = (AuthorizationDataEntry[])ex.inquireSecContext( 431 InquireType.KRB5_GET_AUTHZ_DATA); 432 System.out.println("AuthzData is: " + Arrays.toString(ad)); 433 } 434 } 435 } 436 } 437 438 public byte[] wrap(byte[] t, final boolean privacy) 439 throws Exception { 440 return doAs(new Action() { 441 @Override 442 public byte[] run(Context me, byte[] input) throws Exception { 443 System.out.printf("wrap %s privacy from %s: ", privacy?"with":"without", me.name); 444 MessageProp p1 = new MessageProp(0, privacy); 445 byte[] out; 446 if (usingStream) { 447 ByteArrayOutputStream os = new ByteArrayOutputStream(); 448 me.x.wrap(new ByteArrayInputStream(input), os, p1); 449 out = os.toByteArray(); 450 } else { 451 out = me.x.wrap(input, 0, input.length, p1); 452 } 453 System.out.println(printProp(p1)); 454 return out; 455 } 456 }, t); 457 } 458 459 public byte[] unwrap(byte[] t, final boolean privacy) 460 throws Exception { 461 return doAs(new Action() { 462 @Override 463 public byte[] run(Context me, byte[] input) throws Exception { 464 System.out.printf("unwrap %s privacy from %s: ", privacy?"with":"without", me.name); 465 MessageProp p1 = new MessageProp(0, privacy); 466 byte[] bytes; 467 if (usingStream) { 468 ByteArrayOutputStream os = new ByteArrayOutputStream(); 469 me.x.unwrap(new ByteArrayInputStream(input), os, p1); 470 bytes = os.toByteArray(); 471 } else { 472 bytes = me.x.unwrap(input, 0, input.length, p1); 473 } 474 System.out.println(printProp(p1)); 475 return bytes; 476 } 477 }, t); 478 } 479 480 public byte[] getMic(byte[] t) throws Exception { 481 return doAs(new Action() { 482 @Override 483 public byte[] run(Context me, byte[] input) throws Exception { 484 MessageProp p1 = new MessageProp(0, true); 485 byte[] bytes; 486 p1 = new MessageProp(0, true); 487 System.out.printf("getMic from %s: ", me.name); 488 if (usingStream) { 489 ByteArrayOutputStream os = new ByteArrayOutputStream(); 490 me.x.getMIC(new ByteArrayInputStream(input), os, p1); 491 bytes = os.toByteArray(); 492 } else { 493 bytes = me.x.getMIC(input, 0, input.length, p1); 494 } 495 System.out.println(printProp(p1)); 496 return bytes; 497 } 498 }, t); 499 } 500 501 public void verifyMic(byte[] t, final byte[] msg) throws Exception { 502 doAs(new Action() { 503 @Override 504 public byte[] run(Context me, byte[] input) throws Exception { 505 MessageProp p1 = new MessageProp(0, true); 506 System.out.printf("verifyMic from %s: ", me.name); 507 if (usingStream) { 508 me.x.verifyMIC(new ByteArrayInputStream(input), 509 new ByteArrayInputStream(msg), p1); 510 } else { 511 me.x.verifyMIC(input, 0, input.length, 512 msg, 0, msg.length, 513 p1); 514 } 515 System.out.println(printProp(p1)); 516 return null; 517 } 518 }, t); 519 } 520 521 /** 522 * Transmits a message from one Context to another. The sender wraps the 523 * message and sends it to the receiver. The receiver unwraps it, creates 524 * a MIC of the clear text and sends it back to the sender. The sender 525 * verifies the MIC against the message sent earlier. 526 * @param message the message 527 * @param s1 the sender 528 * @param s2 the receiver 529 * @throws java.lang.Exception If anything goes wrong 530 */ 531 static public void transmit(final String message, final Context s1, 532 final Context s2) throws Exception { 533 final byte[] messageBytes = message.getBytes(); 534 System.out.printf("-------------------- TRANSMIT from %s to %s------------------------\n", 535 s1.name, s2.name); 536 byte[] wrapped = s1.wrap(messageBytes, true); 537 byte[] unwrapped = s2.unwrap(wrapped, true); 538 if (!Arrays.equals(messageBytes, unwrapped)) { 539 throw new Exception("wrap/unwrap mismatch"); 540 } 541 byte[] mic = s2.getMic(unwrapped); 542 s1.verifyMic(mic, messageBytes); 543 } 544 545 /** 546 * Returns a string description of a MessageProp object 547 * @param prop the object 548 * @return the description 549 */ 550 static public String printProp(MessageProp prop) { 551 StringBuffer sb = new StringBuffer(); 552 sb.append("MessagePop: "); 553 sb.append("QOP="+ prop.getQOP() + ", "); 554 sb.append(prop.getPrivacy()?"privacy, ":""); 555 sb.append(prop.isDuplicateToken()?"dup, ":""); 556 sb.append(prop.isGapToken()?"gap, ":""); 557 sb.append(prop.isOldToken()?"old, ":""); 558 sb.append(prop.isUnseqToken()?"unseq, ":""); 559 if (prop.getMinorStatus() != 0) { 560 sb.append(prop.getMinorString()+ "(" + prop.getMinorStatus()+")"); 561 } 562 return sb.toString(); 563 } 564 565 public Context impersonate(final String someone) throws Exception { 566 try { 567 GSSCredential creds = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() { 568 @Override 569 public GSSCredential run() throws Exception { 570 GSSManager m = GSSManager.getInstance(); 571 GSSName other = m.createName(someone, GSSName.NT_USER_NAME); 572 if (Context.this.cred == null) { 573 Context.this.cred = m.createCredential(GSSCredential.INITIATE_ONLY); 574 } 575 return ((ExtendedGSSCredential)Context.this.cred).impersonate(other); 576 } 577 }); 578 Context out = new Context(); 579 out.s = s; 580 out.cred = creds; 581 out.name = name + " as " + out.cred.getName().toString(); 582 return out; 583 } catch (PrivilegedActionException pae) { 584 throw pae.getException(); 585 } 586 } 587 588 public byte[] take(final byte[] in) throws Exception { 589 return doAs(new Action() { 590 @Override 591 public byte[] run(Context me, byte[] input) throws Exception { 592 if (me.x.isEstablished()) { 593 System.out.println(name + " side established"); 594 if (input != null) { 595 throw new Exception("Context established but " + 596 "still receive token at " + name); 597 } 598 return null; 599 } else { 600 if (me.x.isInitiator()) { 601 System.out.println(name + " call initSecContext"); 602 return me.x.initSecContext(input, 0, input.length); 603 } else { 604 System.out.println(name + " call acceptSecContext"); 605 return me.x.acceptSecContext(input, 0, input.length); 606 } 607 } 608 } 609 }, in); 610 } 611 612 /** 613 * Handshake (security context establishment process) between two Contexts 614 * @param c the initiator 615 * @param s the acceptor 616 * @throws java.lang.Exception 617 */ 618 static public void handshake(final Context c, final Context s) throws Exception { 619 byte[] t = new byte[0]; 620 while (true) { 621 if (t != null || !c.x.isEstablished()) t = c.take(t); 622 if (t != null || !s.x.isEstablished()) t = s.take(t); 623 if (c.x.isEstablished() && s.x.isEstablished()) break; 624 } 625 } 626} 627