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 }