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