1 /* 2 * Copyright (c) 2000, 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. 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} 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} 139 * with the specified {@code servicePrincipal} 140 * and {@code action}. 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} 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 public String getActions() { 263 if (actions == null) 264 actions = getActions(this.mask); 265 266 return actions; 267 } 268 269 270 /** 271 * Returns a PermissionCollection object for storing 272 * ServicePermission objects. 273 * <br> 274 * ServicePermission objects must be stored in a manner that 275 * allows them to be inserted into the collection in any order, but 276 * that also enables the PermissionCollection implies method to 277 * be implemented in an efficient (and consistent) manner. 278 * 279 * @return a new PermissionCollection object suitable for storing 280 * ServicePermissions. 281 */ 282 public PermissionCollection newPermissionCollection() { 283 return new KrbServicePermissionCollection(); 284 } 285 286 /** 287 * Return the current action mask. 288 * 289 * @return the actions mask. 290 */ 291 int getMask() { 292 return mask; 293 } 294 295 /** 296 * Convert an action string to an integer actions mask. 297 * 298 * @param action the action string 299 * @return the action mask 300 */ 301 private static int getMask(String action) { 302 303 if (action == null) { 304 throw new NullPointerException("action can't be null"); 305 } 306 307 if (action.equals("")) { 308 throw new IllegalArgumentException("action can't be empty"); 309 } 310 311 int mask = NONE; 312 313 char[] a = action.toCharArray(); 314 315 int i = a.length - 1; 316 if (i < 0) 317 return mask; 318 319 while (i != -1) { 320 char c; 321 322 // skip whitespace 323 while ((i!=-1) && ((c = a[i]) == ' ' || 324 c == '\r' || 325 c == '\n' || 326 c == '\f' || 327 c == '\t')) 328 i--; 329 330 // check for the known strings 331 int matchlen; 332 333 if (i >= 7 && (a[i-7] == 'i' || a[i-7] == 'I') && 334 (a[i-6] == 'n' || a[i-6] == 'N') && 335 (a[i-5] == 'i' || a[i-5] == 'I') && 336 (a[i-4] == 't' || a[i-4] == 'T') && 337 (a[i-3] == 'i' || a[i-3] == 'I') && 338 (a[i-2] == 'a' || a[i-2] == 'A') && 339 (a[i-1] == 't' || a[i-1] == 'T') && 340 (a[i] == 'e' || a[i] == 'E')) 341 { 342 matchlen = 8; 343 mask |= INITIATE; 344 345 } else if (i >= 5 && (a[i-5] == 'a' || a[i-5] == 'A') && 346 (a[i-4] == 'c' || a[i-4] == 'C') && 347 (a[i-3] == 'c' || a[i-3] == 'C') && 348 (a[i-2] == 'e' || a[i-2] == 'E') && 349 (a[i-1] == 'p' || a[i-1] == 'P') && 350 (a[i] == 't' || a[i] == 'T')) 351 { 352 matchlen = 6; 353 mask |= ACCEPT; 354 355 } else { 356 // parse error 357 throw new IllegalArgumentException( 358 "invalid permission: " + action); 359 } 360 361 // make sure we didn't just match the tail of a word 362 // like "ackbarfaccept". Also, skip to the comma. 363 boolean seencomma = false; 364 while (i >= matchlen && !seencomma) { 365 switch(a[i-matchlen]) { 366 case ',': 367 seencomma = true; 368 break; 369 case ' ': case '\r': case '\n': 370 case '\f': case '\t': 371 break; 372 default: 373 throw new IllegalArgumentException( 374 "invalid permission: " + action); 375 } 376 i--; 377 } 378 379 // point i at the location of the comma minus one (or -1). 380 i -= matchlen; 381 } 382 383 return mask; 384 } 385 386 387 /** 388 * WriteObject is called to save the state of the ServicePermission 389 * to a stream. The actions are serialized, and the superclass 390 * takes care of the name. 391 */ 392 private void writeObject(java.io.ObjectOutputStream s) 393 throws IOException 394 { 395 // Write out the actions. The superclass takes care of the name 396 // call getActions to make sure actions field is initialized 397 if (actions == null) 398 getActions(); 399 s.defaultWriteObject(); 400 } 401 402 /** 403 * readObject is called to restore the state of the 404 * ServicePermission from a stream. 405 */ 406 private void readObject(java.io.ObjectInputStream s) 407 throws IOException, ClassNotFoundException 408 { 409 // Read in the action, then initialize the rest 410 s.defaultReadObject(); 411 init(getName(),getMask(actions)); 412 } 413 414 415 /* 416 public static void main(String args[]) throws Exception { 417 ServicePermission this_ = 418 new ServicePermission(args[0], "accept"); 419 ServicePermission that_ = 420 new ServicePermission(args[1], "accept,initiate"); 421 System.out.println("-----\n"); 422 System.out.println("this.implies(that) = " + this_.implies(that_)); 423 System.out.println("-----\n"); 424 System.out.println("this = "+this_); 425 System.out.println("-----\n"); 426 System.out.println("that = "+that_); 427 System.out.println("-----\n"); 428 429 KrbServicePermissionCollection nps = 430 new KrbServicePermissionCollection(); 431 nps.add(this_); 432 nps.add(new ServicePermission("nfs/example.com@EXAMPLE.COM", 433 "accept")); 434 nps.add(new ServicePermission("host/example.com@EXAMPLE.COM", 435 "initiate")); 436 System.out.println("nps.implies(that) = " + nps.implies(that_)); 437 System.out.println("-----\n"); 438 439 Enumeration e = nps.elements(); 440 441 while (e.hasMoreElements()) { 442 ServicePermission x = 443 (ServicePermission) e.nextElement(); 444 System.out.println("nps.e = " + x); 445 } 446 447 } 448 */ 449 450 } 451 452 453 final class KrbServicePermissionCollection extends PermissionCollection 454 implements java.io.Serializable { 455 456 // Not serialized; see serialization section at end of class 457 private transient List<Permission> perms; 458 459 public KrbServicePermissionCollection() { 460 perms = new ArrayList<Permission>(); 461 } 462 463 /** 464 * Check and see if this collection of permissions implies the permissions 465 * expressed in "permission". 466 * 467 * @param permission the Permission object to compare 468 * 469 * @return true if "permission" is a proper subset of a permission in 470 * the collection, false if not. 471 */ 472 public boolean implies(Permission permission) { 473 if (! (permission instanceof ServicePermission)) 474 return false; 475 476 ServicePermission np = (ServicePermission) permission; 477 int desired = np.getMask(); 478 int effective = 0; 479 int needed = desired; 480 481 synchronized (this) { 482 int len = perms.size(); 483 484 // need to deal with the case where the needed permission has 485 // more than one action and the collection has individual permissions 486 // that sum up to the needed. 487 488 for (int i = 0; i < len; i++) { 489 ServicePermission x = (ServicePermission) perms.get(i); 490 491 //System.out.println(" trying "+x); 492 if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(np)) { 493 effective |= x.getMask(); 494 if ((effective & desired) == desired) 495 return true; 496 needed = (desired ^ effective); 497 } 498 } 499 } 500 return false; 501 } 502 503 /** 504 * Adds a permission to the ServicePermissions. The key for 505 * the hash is the name. 506 * 507 * @param permission the Permission object to add. 508 * 509 * @exception IllegalArgumentException - if the permission is not a 510 * ServicePermission 511 * 512 * @exception SecurityException - if this PermissionCollection object 513 * has been marked readonly 514 */ 515 public void add(Permission permission) { 516 if (! (permission instanceof ServicePermission)) 517 throw new IllegalArgumentException("invalid permission: "+ 518 permission); 519 if (isReadOnly()) 520 throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection"); 521 522 synchronized (this) { 523 perms.add(0, permission); 524 } 525 } 526 527 /** 528 * Returns an enumeration of all the ServicePermission objects 529 * in the container. 530 * 531 * @return an enumeration of all the ServicePermission objects. 532 */ 533 534 public Enumeration<Permission> elements() { 535 // Convert Iterator into Enumeration 536 synchronized (this) { 537 return Collections.enumeration(perms); 538 } 539 } 540 541 private static final long serialVersionUID = -4118834211490102011L; 542 543 // Need to maintain serialization interoperability with earlier releases, 544 // which had the serializable field: 545 // private Vector permissions; 546 547 /** 548 * @serialField permissions java.util.Vector 549 * A list of ServicePermission objects. 550 */ 551 private static final ObjectStreamField[] serialPersistentFields = { 552 new ObjectStreamField("permissions", Vector.class), 553 }; 554 555 /** 556 * @serialData "permissions" field (a Vector containing the ServicePermissions). 557 */ 558 /* 559 * Writes the contents of the perms field out as a Vector for 560 * serialization compatibility with earlier releases. 561 */ 562 private void writeObject(ObjectOutputStream out) throws IOException { 563 // Don't call out.defaultWriteObject() 564 565 // Write out Vector 566 Vector<Permission> permissions = new Vector<>(perms.size()); 567 568 synchronized (this) { 569 permissions.addAll(perms); 570 } 571 572 ObjectOutputStream.PutField pfields = out.putFields(); 573 pfields.put("permissions", permissions); 574 out.writeFields(); 575 } 576 577 /* 578 * Reads in a Vector of ServicePermissions and saves them in the perms field. 579 */ 580 @SuppressWarnings("unchecked") 581 private void readObject(ObjectInputStream in) 582 throws IOException, ClassNotFoundException 583 { 584 // Don't call defaultReadObject() 585 586 // Read in serialized fields 587 ObjectInputStream.GetField gfields = in.readFields(); 588 589 // Get the one we want 590 Vector<Permission> permissions = 591 (Vector<Permission>)gfields.get("permissions", null); 592 perms = new ArrayList<Permission>(permissions.size()); 593 perms.addAll(permissions); 594 } 595 }