1/* 2 * Copyright (c) 2000, 2015, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package javax.security.auth.kerberos; 27 28import java.io.IOException; 29import java.io.ObjectInputStream; 30import java.io.ObjectOutputStream; 31import java.io.ObjectStreamField; 32import java.security.Permission; 33import java.security.PermissionCollection; 34import java.util.*; 35import java.util.concurrent.ConcurrentHashMap; 36 37/** 38 * This class is used to protect Kerberos services and the 39 * credentials necessary to access those services. There is a one to 40 * one mapping of a service principal and the credentials necessary 41 * to access the service. Therefore granting access to a service 42 * principal implicitly grants access to the credential necessary to 43 * establish a security context with the service principal. This 44 * applies regardless of whether the credentials are in a cache 45 * or acquired via an exchange with the KDC. The credential can 46 * be either a ticket granting ticket, a service ticket or a secret 47 * key from a key table. 48 * <p> 49 * A ServicePermission contains a service principal name and 50 * a list of actions which specify the context the credential can be 51 * used within. 52 * <p> 53 * The service principal name is the canonical name of the 54 * {@code KerberosPrincipal} supplying the service, that is 55 * the KerberosPrincipal represents a Kerberos service 56 * principal. This name is treated in a case sensitive manner. 57 * An asterisk may appear by itself, to signify any service principal. 58 * <p> 59 * Granting this permission implies that the caller can use a cached 60 * credential (TGT, service ticket or secret key) within the context 61 * designated by the action. In the case of the TGT, granting this 62 * permission also implies that the TGT can be obtained by an 63 * Authentication Service exchange. 64 * <p> 65 * Granting this permission also implies creating {@link KerberosPrincipal} 66 * or {@link org.ietf.jgss.GSSName GSSName} without providing a Kerberos 67 * realm, as long as the permission's service principal is in this realm. 68 * <p> 69 * The possible actions are: 70 * 71 * <pre> 72 * initiate - allow the caller to use the credential to 73 * initiate a security context with a service 74 * principal. 75 * 76 * accept - allow the caller to use the credential to 77 * accept security context as a particular 78 * principal. 79 * </pre> 80 * 81 * For example, to specify the permission to access to the TGT to 82 * initiate a security context the permission is constructed as follows: 83 * 84 * <pre> 85 * ServicePermission("krbtgt/EXAMPLE.COM@EXAMPLE.COM", "initiate"); 86 * </pre> 87 * <p> 88 * To obtain a service ticket to initiate a context with the "host" 89 * service the permission is constructed as follows: 90 * <pre> 91 * ServicePermission("host/foo.example.com@EXAMPLE.COM", "initiate"); 92 * </pre> 93 * <p> 94 * For a Kerberized server the action is "accept". For example, the permission 95 * necessary to access and use the secret key of the Kerberized "host" 96 * service (telnet and the likes) would be constructed as follows: 97 * 98 * <pre> 99 * ServicePermission("host/foo.example.com@EXAMPLE.COM", "accept"); 100 * </pre> 101 * 102 * @since 1.4 103 */ 104 105public final class ServicePermission extends Permission 106 implements java.io.Serializable { 107 108 private static final long serialVersionUID = -1227585031618624935L; 109 110 /** 111 * Initiate a security context to the specified service 112 */ 113 private final static int INITIATE = 0x1; 114 115 /** 116 * Accept a security context 117 */ 118 private final static int ACCEPT = 0x2; 119 120 /** 121 * All actions 122 */ 123 private final static int ALL = INITIATE|ACCEPT; 124 125 /** 126 * No actions. 127 */ 128 private final static int NONE = 0x0; 129 130 // the actions mask 131 private transient int mask; 132 133 /** 134 * the actions string. 135 * 136 * @serial 137 */ 138 139 private String actions; // Left null as long as possible, then 140 // created and re-used in the getAction function. 141 142 /** 143 * Create a new {@code ServicePermission} 144 * with the specified {@code servicePrincipal} 145 * and {@code action}. 146 * 147 * @param servicePrincipal the name of the service principal. 148 * An asterisk may appear by itself, to signify any service principal. 149 * 150 * @param action the action string 151 */ 152 public ServicePermission(String servicePrincipal, String action) { 153 // Note: servicePrincipal can be "@REALM" which means any principal in 154 // this realm implies it. action can be "-" which means any 155 // action implies it. 156 super(servicePrincipal); 157 init(servicePrincipal, getMask(action)); 158 } 159 160 /** 161 * Creates a ServicePermission object with the specified servicePrincipal 162 * and a pre-calculated mask. Avoids the overhead of re-computing the mask. 163 * Called by ServicePermissionCollection. 164 */ 165 ServicePermission(String servicePrincipal, int mask) { 166 super(servicePrincipal); 167 init(servicePrincipal, mask); 168 } 169 170 /** 171 * Initialize the ServicePermission object. 172 */ 173 private void init(String servicePrincipal, int mask) { 174 175 if (servicePrincipal == null) 176 throw new NullPointerException("service principal can't be null"); 177 178 if ((mask & ALL) != mask) 179 throw new IllegalArgumentException("invalid actions mask"); 180 181 this.mask = mask; 182 } 183 184 185 /** 186 * Checks if this Kerberos service permission object "implies" the 187 * specified permission. 188 * <P> 189 * More specifically, this method returns true if all of the following 190 * are true (and returns false if any of them are not): 191 * <ul> 192 * <li> <i>p</i> is an instanceof {@code ServicePermission}, 193 * <li> <i>p</i>'s actions are a proper subset of this 194 * {@code ServicePermission}'s actions, 195 * <li> <i>p</i>'s name is equal to this {@code ServicePermission}'s name 196 * or this {@code ServicePermission}'s name is "*". 197 * </ul> 198 * 199 * @param p the permission to check against. 200 * 201 * @return true if the specified permission is implied by this object, 202 * false if not. 203 */ 204 @Override 205 public boolean implies(Permission p) { 206 if (!(p instanceof ServicePermission)) 207 return false; 208 209 ServicePermission that = (ServicePermission) p; 210 211 return ((this.mask & that.mask) == that.mask) && 212 impliesIgnoreMask(that); 213 } 214 215 216 boolean impliesIgnoreMask(ServicePermission p) { 217 return ((this.getName().equals("*")) || 218 this.getName().equals(p.getName()) || 219 (p.getName().startsWith("@") && 220 this.getName().endsWith(p.getName()))); 221 } 222 223 /** 224 * Checks two ServicePermission objects for equality. 225 * 226 * @param obj the object to test for equality with this object. 227 * 228 * @return true if {@code obj} is a ServicePermission, and has the 229 * same service principal, and actions as this 230 * ServicePermission object. 231 */ 232 @Override 233 public boolean equals(Object obj) { 234 if (obj == this) 235 return true; 236 237 if (! (obj instanceof ServicePermission)) 238 return false; 239 240 ServicePermission that = (ServicePermission) obj; 241 return ((this.mask & that.mask) == that.mask) && 242 this.getName().equals(that.getName()); 243 244 245 } 246 247 /** 248 * Returns the hash code value for this object. 249 * 250 * @return a hash code value for this object. 251 */ 252 @Override 253 public int hashCode() { 254 return (getName().hashCode() ^ mask); 255 } 256 257 258 /** 259 * Returns the "canonical string representation" of the actions in the 260 * specified mask. 261 * Always returns present actions in the following order: 262 * initiate, accept. 263 * 264 * @param mask a specific integer action mask to translate into a string 265 * @return the canonical string representation of the actions 266 */ 267 static String getActions(int mask) 268 { 269 StringBuilder sb = new StringBuilder(); 270 boolean comma = false; 271 272 if ((mask & INITIATE) == INITIATE) { 273 if (comma) sb.append(','); 274 else comma = true; 275 sb.append("initiate"); 276 } 277 278 if ((mask & ACCEPT) == ACCEPT) { 279 if (comma) sb.append(','); 280 else comma = true; 281 sb.append("accept"); 282 } 283 284 return sb.toString(); 285 } 286 287 /** 288 * Returns the canonical string representation of the actions. 289 * Always returns present actions in the following order: 290 * initiate, accept. 291 */ 292 @Override 293 public String getActions() { 294 if (actions == null) 295 actions = getActions(this.mask); 296 297 return actions; 298 } 299 300 301 /** 302 * Returns a PermissionCollection object for storing 303 * ServicePermission objects. 304 * <br> 305 * ServicePermission objects must be stored in a manner that 306 * allows them to be inserted into the collection in any order, but 307 * that also enables the PermissionCollection implies method to 308 * be implemented in an efficient (and consistent) manner. 309 * 310 * @return a new PermissionCollection object suitable for storing 311 * ServicePermissions. 312 */ 313 @Override 314 public PermissionCollection newPermissionCollection() { 315 return new KrbServicePermissionCollection(); 316 } 317 318 /** 319 * Return the current action mask. 320 * 321 * @return the actions mask. 322 */ 323 int getMask() { 324 return mask; 325 } 326 327 /** 328 * Convert an action string to an integer actions mask. 329 * 330 * Note: if action is "-", action will be NONE, which means any 331 * action implies it. 332 * 333 * @param action the action string. 334 * @return the action mask 335 */ 336 private static int getMask(String action) { 337 338 if (action == null) { 339 throw new NullPointerException("action can't be null"); 340 } 341 342 if (action.equals("")) { 343 throw new IllegalArgumentException("action can't be empty"); 344 } 345 346 int mask = NONE; 347 348 char[] a = action.toCharArray(); 349 350 if (a.length == 1 && a[0] == '-') { 351 return mask; 352 } 353 354 int i = a.length - 1; 355 356 while (i != -1) { 357 char c; 358 359 // skip whitespace 360 while ((i!=-1) && ((c = a[i]) == ' ' || 361 c == '\r' || 362 c == '\n' || 363 c == '\f' || 364 c == '\t')) 365 i--; 366 367 // check for the known strings 368 int matchlen; 369 370 if (i >= 7 && (a[i-7] == 'i' || a[i-7] == 'I') && 371 (a[i-6] == 'n' || a[i-6] == 'N') && 372 (a[i-5] == 'i' || a[i-5] == 'I') && 373 (a[i-4] == 't' || a[i-4] == 'T') && 374 (a[i-3] == 'i' || a[i-3] == 'I') && 375 (a[i-2] == 'a' || a[i-2] == 'A') && 376 (a[i-1] == 't' || a[i-1] == 'T') && 377 (a[i] == 'e' || a[i] == 'E')) 378 { 379 matchlen = 8; 380 mask |= INITIATE; 381 382 } else if (i >= 5 && (a[i-5] == 'a' || a[i-5] == 'A') && 383 (a[i-4] == 'c' || a[i-4] == 'C') && 384 (a[i-3] == 'c' || a[i-3] == 'C') && 385 (a[i-2] == 'e' || a[i-2] == 'E') && 386 (a[i-1] == 'p' || a[i-1] == 'P') && 387 (a[i] == 't' || a[i] == 'T')) 388 { 389 matchlen = 6; 390 mask |= ACCEPT; 391 392 } else { 393 // parse error 394 throw new IllegalArgumentException( 395 "invalid permission: " + action); 396 } 397 398 // make sure we didn't just match the tail of a word 399 // like "ackbarfaccept". Also, skip to the comma. 400 boolean seencomma = false; 401 while (i >= matchlen && !seencomma) { 402 switch(a[i-matchlen]) { 403 case ',': 404 seencomma = true; 405 break; 406 case ' ': case '\r': case '\n': 407 case '\f': case '\t': 408 break; 409 default: 410 throw new IllegalArgumentException( 411 "invalid permission: " + action); 412 } 413 i--; 414 } 415 416 // point i at the location of the comma minus one (or -1). 417 i -= matchlen; 418 } 419 420 return mask; 421 } 422 423 424 /** 425 * WriteObject is called to save the state of the ServicePermission 426 * to a stream. The actions are serialized, and the superclass 427 * takes care of the name. 428 */ 429 private void writeObject(java.io.ObjectOutputStream s) 430 throws IOException 431 { 432 // Write out the actions. The superclass takes care of the name 433 // call getActions to make sure actions field is initialized 434 if (actions == null) 435 getActions(); 436 s.defaultWriteObject(); 437 } 438 439 /** 440 * readObject is called to restore the state of the 441 * ServicePermission from a stream. 442 */ 443 private void readObject(java.io.ObjectInputStream s) 444 throws IOException, ClassNotFoundException 445 { 446 // Read in the action, then initialize the rest 447 s.defaultReadObject(); 448 init(getName(),getMask(actions)); 449 } 450 451 452 /* 453 public static void main(String[] args) throws Exception { 454 ServicePermission this_ = 455 new ServicePermission(args[0], "accept"); 456 ServicePermission that_ = 457 new ServicePermission(args[1], "accept,initiate"); 458 System.out.println("-----\n"); 459 System.out.println("this.implies(that) = " + this_.implies(that_)); 460 System.out.println("-----\n"); 461 System.out.println("this = "+this_); 462 System.out.println("-----\n"); 463 System.out.println("that = "+that_); 464 System.out.println("-----\n"); 465 466 KrbServicePermissionCollection nps = 467 new KrbServicePermissionCollection(); 468 nps.add(this_); 469 nps.add(new ServicePermission("nfs/example.com@EXAMPLE.COM", 470 "accept")); 471 nps.add(new ServicePermission("host/example.com@EXAMPLE.COM", 472 "initiate")); 473 System.out.println("nps.implies(that) = " + nps.implies(that_)); 474 System.out.println("-----\n"); 475 476 Enumeration e = nps.elements(); 477 478 while (e.hasMoreElements()) { 479 ServicePermission x = 480 (ServicePermission) e.nextElement(); 481 System.out.println("nps.e = " + x); 482 } 483 484 } 485 */ 486 487} 488 489 490final class KrbServicePermissionCollection extends PermissionCollection 491 implements java.io.Serializable { 492 493 // Key is the service principal, value is the ServicePermission. 494 // Not serialized; see serialization section at end of class 495 private transient ConcurrentHashMap<String, Permission> perms; 496 497 public KrbServicePermissionCollection() { 498 perms = new ConcurrentHashMap<>(); 499 } 500 501 /** 502 * Check and see if this collection of permissions implies the permissions 503 * expressed in "permission". 504 * 505 * @param permission the Permission object to compare 506 * 507 * @return true if "permission" is a proper subset of a permission in 508 * the collection, false if not. 509 */ 510 @Override 511 public boolean implies(Permission permission) { 512 if (! (permission instanceof ServicePermission)) 513 return false; 514 515 ServicePermission np = (ServicePermission) permission; 516 int desired = np.getMask(); 517 518 if (desired == 0) { 519 for (Permission p: perms.values()) { 520 ServicePermission sp = (ServicePermission)p; 521 if (sp.impliesIgnoreMask(np)) { 522 return true; 523 } 524 } 525 return false; 526 } 527 528 529 // first, check for wildcard principal 530 ServicePermission x = (ServicePermission)perms.get("*"); 531 if (x != null) { 532 if ((x.getMask() & desired) == desired) { 533 return true; 534 } 535 } 536 537 // otherwise, check for match on principal 538 x = (ServicePermission)perms.get(np.getName()); 539 if (x != null) { 540 //System.out.println(" trying "+x); 541 if ((x.getMask() & desired) == desired) { 542 return true; 543 } 544 } 545 return false; 546 } 547 548 /** 549 * Adds a permission to the ServicePermissions. The key for 550 * the hash is the name. 551 * 552 * @param permission the Permission object to add. 553 * 554 * @exception IllegalArgumentException - if the permission is not a 555 * ServicePermission 556 * 557 * @exception SecurityException - if this PermissionCollection object 558 * has been marked readonly 559 */ 560 @Override 561 public void add(Permission permission) { 562 if (! (permission instanceof ServicePermission)) 563 throw new IllegalArgumentException("invalid permission: "+ 564 permission); 565 if (isReadOnly()) 566 throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection"); 567 568 ServicePermission sp = (ServicePermission)permission; 569 String princName = sp.getName(); 570 571 // Add permission to map if it is absent, or replace with new 572 // permission if applicable. NOTE: cannot use lambda for 573 // remappingFunction parameter until JDK-8076596 is fixed. 574 perms.merge(princName, sp, 575 new java.util.function.BiFunction<>() { 576 @Override 577 public Permission apply(Permission existingVal, 578 Permission newVal) { 579 int oldMask = ((ServicePermission)existingVal).getMask(); 580 int newMask = ((ServicePermission)newVal).getMask(); 581 if (oldMask != newMask) { 582 int effective = oldMask | newMask; 583 if (effective == newMask) { 584 return newVal; 585 } 586 if (effective != oldMask) { 587 return new ServicePermission(princName, effective); 588 } 589 } 590 return existingVal; 591 } 592 } 593 ); 594 } 595 596 /** 597 * Returns an enumeration of all the ServicePermission objects 598 * in the container. 599 * 600 * @return an enumeration of all the ServicePermission objects. 601 */ 602 @Override 603 public Enumeration<Permission> elements() { 604 return perms.elements(); 605 } 606 607 private static final long serialVersionUID = -4118834211490102011L; 608 609 // Need to maintain serialization interoperability with earlier releases, 610 // which had the serializable field: 611 // private Vector permissions; 612 613 /** 614 * @serialField permissions java.util.Vector 615 * A list of ServicePermission objects. 616 */ 617 private static final ObjectStreamField[] serialPersistentFields = { 618 new ObjectStreamField("permissions", Vector.class), 619 }; 620 621 /** 622 * @serialData "permissions" field (a Vector containing the ServicePermissions). 623 */ 624 /* 625 * Writes the contents of the perms field out as a Vector for 626 * serialization compatibility with earlier releases. 627 */ 628 private void writeObject(ObjectOutputStream out) throws IOException { 629 // Don't call out.defaultWriteObject() 630 631 // Write out Vector 632 Vector<Permission> permissions = new Vector<>(perms.values()); 633 634 ObjectOutputStream.PutField pfields = out.putFields(); 635 pfields.put("permissions", permissions); 636 out.writeFields(); 637 } 638 639 /* 640 * Reads in a Vector of ServicePermissions and saves them in the perms field. 641 */ 642 @SuppressWarnings("unchecked") 643 private void readObject(ObjectInputStream in) 644 throws IOException, ClassNotFoundException 645 { 646 // Don't call defaultReadObject() 647 648 // Read in serialized fields 649 ObjectInputStream.GetField gfields = in.readFields(); 650 651 // Get the one we want 652 Vector<Permission> permissions = 653 (Vector<Permission>)gfields.get("permissions", null); 654 perms = new ConcurrentHashMap<>(permissions.size()); 655 for (Permission perm : permissions) { 656 perms.put(perm.getName(), perm); 657 } 658 } 659} 660