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