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     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 }