      1 /*
      2  * Copyright (c) 2000, 2019, 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.io.IOException;
     29 import java.io.ObjectInputStream;
     30 import java.io.ObjectOutputStream;
     31 import java.io.ObjectStreamField;
     32 import java.security.BasicPermission;
     33 import java.security.Permission;
     34 import java.security.PermissionCollection;
     35 import java.util.*;
     36 import java.util.concurrent.ConcurrentHashMap;
     37
     38 /**
     39  * This class is used to restrict the usage of the Kerberos
     40  * delegation model, ie: forwardable and proxiable tickets.
     41  * <p>
     42  * The target name of this {@code Permission} specifies a pair of
     43  * kerberos service principals. The first is the subordinate service principal
     44  * being entrusted to use the TGT. The second service principal designates
     45  * the target service the subordinate service principal is to
     46  * interact with on behalf of the initiating KerberosPrincipal. This
     47  * latter service principal is specified to restrict the use of a
     48  * proxiable ticket.
     49  * <p>
     50  * For example, to specify the "host" service use of a forwardable TGT the
     51  * target permission is specified as follows:
     52  *
     53  * <pre>
     54  *  DelegationPermission("\"host/foo.example.com@EXAMPLE.COM\" \"krbtgt/EXAMPLE.COM@EXAMPLE.COM\"");
     55  * </pre>
     56  * <p>
     57  * To give the "backup" service a proxiable nfs service ticket the target permission
     58  * might be specified:
     59  *
     60  * <pre>
     61  *  DelegationPermission("\"backup/bar.example.com@EXAMPLE.COM\" \"nfs/home.EXAMPLE.COM@EXAMPLE.COM\"");
     62  * </pre>
     63  *
     64  * @since 1.4
     65  */
     66
     67 public final class DelegationPermission extends BasicPermission
     68     implements java.io.Serializable {
     69
     70     private static final long serialVersionUID = 883133252142523922L;
     71
     72     private transient String subordinate, service;
     73
     74     /**
     75      * Create a new {@code DelegationPermission}
     76      * with the specified subordinate and target principals.
     77      *
     78      * @param principals the name of the subordinate and target principals
     79      *
     80      * @throws NullPointerException if {@code principals} is {@code null}.
     81      * @throws IllegalArgumentException if {@code principals} is empty,
     82      *      or does not contain a pair of principals, or is improperly quoted
     83      */
     84     public DelegationPermission(String principals) {
     85         super(principals);
     86         init(principals);
     87     }
     88
     89     /**
     90      * Create a new {@code DelegationPermission}
     91      * with the specified subordinate and target principals.
     92      *
     93      * @param principals the name of the subordinate and target principals
     94      *
     95      * @param actions should be null.
     96      *
     97      * @throws NullPointerException if {@code principals} is {@code null}.
     98      * @throws IllegalArgumentException if {@code principals} is empty,
     99      *      or does not contain a pair of principals, or is improperly quoted
    100      */
    101     public DelegationPermission(String principals, String actions) {
    102         super(principals, actions);
    103         init(principals);
    104     }
    105
    106
    107     /**
    108      * Initialize the DelegationPermission object.
    109      */
    110     private void init(String target) {
    111
    112         // 7 tokens in a string:
    113         //    "subordinate@R1" "service@R2"
    114         //    1<------2----->345<----6--->7
    115         StringTokenizer t = new StringTokenizer(target, "\"", true);
    116         try {
    117             if (!t.nextToken().equals("\"")) { // 1
    118                 throw new IllegalArgumentException("Illegal input [" + target
    119                         + "]: improperly quoted");
    120             }
    121             subordinate = t.nextToken(); // 2
    122             if (subordinate.equals("\"")) {
    123                 throw new IllegalArgumentException("Illegal input [" + target
    124                         + "]: bad subordinate name");
    125             }
    126             t.nextToken(); // 3
    127             if (!t.nextToken().trim().isEmpty()) { // 4
    128                 throw new IllegalArgumentException("Illegal input [" + target
    129                         + "]: improperly separated");
    130             }
    131             t.nextToken(); // 5
    132             service = t.nextToken(); // 6
    133             if (service.equals("\"")) {
    134                 throw new IllegalArgumentException("Illegal input [" + target
    135                         + "]: bad service name");
    136             }
    137             t.nextToken(); // 7
    138         } catch (NoSuchElementException e) {
    139             throw new IllegalArgumentException("Illegal input [" + target
    140                     + "]: not enough input");
    141         }
    142         if (t.hasMoreTokens()) {
    143             throw new IllegalArgumentException("Illegal input [" + target
    144                     + "]: extra input");
    145         }
    146     }
    147
    148     /**
    149      * Checks if this Kerberos delegation permission object "implies" the
    150      * specified permission.
    151      * <P>
    152      * This method returns true if this {@code DelegationPermission}
    153      * is equal to {@code p}, and returns false otherwise.
    154      *
    155      * @param p the permission to check against.
    156      *
    157      * @return true if the specified permission is implied by this object,
    158      * false if not.
    159      */
    160     @Override
    161     public boolean implies(Permission p) {
    162         return equals(p);
    163     }
    164
    165     /**
    166      * Checks two DelegationPermission objects for equality.
    167      *
    168      * @param obj the object to test for equality with this object.
    169      *
    170      * @return true if {@code obj} is a DelegationPermission, and
    171      *  has the same subordinate and service principal as this
    172      *  DelegationPermission object.
    173      */
    174     @Override
    175     public boolean equals(Object obj) {
    176         if (obj == this) {
    177             return true;
    178         }
    179
    180         if (!(obj instanceof DelegationPermission)) {
    181             return false;
    182         }
    183
    184         DelegationPermission that = (DelegationPermission) obj;
    185
    186         return this.subordinate.equals(that.subordinate) &&
    187                 this.service.equals(that.service);
    188     }
    189
    190     /**
    191      * Returns the hash code value for this object.
    192      *
    193      * @return a hash code value for this object.
    194      */
    195     @Override
    196     public int hashCode() {
    197         return 17 * subordinate.hashCode() + 31 * service.hashCode();
    198     }
    199
    200     /**
    201      * Returns a PermissionCollection object for storing
    202      * DelegationPermission objects.
    203      * <br>
    204      * DelegationPermission objects must be stored in a manner that
    205      * allows them to be inserted into the collection in any order, but
    206      * that also enables the PermissionCollection implies method to
    207      * be implemented in an efficient (and consistent) manner.
    208      *
    209      * @return a new PermissionCollection object suitable for storing
    210      * DelegationPermissions.
    211      */
    212     @Override
    213     public PermissionCollection newPermissionCollection() {
    214         return new KrbDelegationPermissionCollection();
    215     }
    216
    217     /**
    218      * WriteObject is called to save the state of the DelegationPermission
    219      * to a stream. The actions are serialized, and the superclass
    220      * takes care of the name.
    221      */
    222     private synchronized void writeObject(java.io.ObjectOutputStream s)
    223         throws IOException
    224     {
    225         s.defaultWriteObject();
    226     }
    227
    228     /**
    229      * readObject is called to restore the state of the
    230      * DelegationPermission from a stream.
    231      */
    232     private synchronized void readObject(java.io.ObjectInputStream s)
    233          throws IOException, ClassNotFoundException
    234     {
    235         // Read in the action, then initialize the rest
    236         s.defaultReadObject();
    237         init(getName());
    238     }
    239
    240 }
    241
    242
    243 final class KrbDelegationPermissionCollection extends PermissionCollection
    244     implements java.io.Serializable {
    245
    246     // Not serialized; see serialization section at end of class.
    247     private transient ConcurrentHashMap<Permission, Boolean> perms;
    248
    249     public KrbDelegationPermissionCollection() {
    250         perms = new ConcurrentHashMap<>();
    251     }
    252
    253     /**
    254      * Check and see if this collection of permissions implies the permissions
    255      * expressed in "permission".
    256      *
    257      * @param permission the Permission object to compare
    258      *
    259      * @return true if "permission" is a proper subset of a permission in
    260      * the collection, false if not.
    261      */
    262     @Override
    263     public boolean implies(Permission permission) {
    264         if (! (permission instanceof DelegationPermission))
    265             return false;
    266
    267         // if map contains key, then it automatically implies it
    268         return perms.containsKey(permission);
    269     }
    270
    271     /**
    272      * Adds a permission to the DelegationPermissions. The key for
    273      * the hash is the name.
    274      *
    275      * @param permission the Permission object to add.
    276      *
    277      * @exception IllegalArgumentException - if the permission is not a
    278      *                                       DelegationPermission
    279      *
    280      * @exception SecurityException - if this PermissionCollection object
    281      *                                has been marked readonly
    282      */
    283     @Override
    284     public void add(Permission permission) {
    285         if (! (permission instanceof DelegationPermission))
    286             throw new IllegalArgumentException("invalid permission: "+
    287                                                permission);
    288         if (isReadOnly())
    289             throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");
    290
    291         perms.put(permission, Boolean.TRUE);
    292     }
    293
    294     /**
    295      * Returns an enumeration of all the DelegationPermission objects
    296      * in the container.
    297      *
    298      * @return an enumeration of all the DelegationPermission objects.
    299      */
    300     @Override
    301     public Enumeration<Permission> elements() {
    302         return perms.keys();
    303     }
    304
    305     private static final long serialVersionUID = -3383936936589966948L;
    306
    307     // Need to maintain serialization interoperability with earlier releases,
    308     // which had the serializable field:
    309     //    private Vector permissions;
    310     /**
    311      * @serialField permissions java.util.Vector
    312      *     A list of DelegationPermission objects.
    313      */
    314     private static final ObjectStreamField[] serialPersistentFields = {
    315         new ObjectStreamField("permissions", Vector.class),
    316     };
    317
    318     /**
    319      * @serialData "permissions" field (a Vector containing the DelegationPermissions).
    320      */
    321     /*
    322      * Writes the contents of the perms field out as a Vector for
    323      * serialization compatibility with earlier releases.
    324      */
    325     private void writeObject(ObjectOutputStream out) throws IOException {
    326         // Don't call out.defaultWriteObject()
    327
    328         // Write out Vector
    329         Vector<Permission> permissions = new Vector<>(perms.keySet());
    330
    331         ObjectOutputStream.PutField pfields = out.putFields();
    332         pfields.put("permissions", permissions);
    333         out.writeFields();
    334     }
    335
    336     /*
    337      * Reads in a Vector of DelegationPermissions and saves them in the perms field.
    338      */
    339     @SuppressWarnings("unchecked")
    340     private void readObject(ObjectInputStream in)
    341         throws IOException, ClassNotFoundException
    342     {
    343         // Don't call defaultReadObject()
    344
    345         // Read in serialized fields
    346         ObjectInputStream.GetField gfields = in.readFields();
    347
    348         // Get the one we want
    349         Vector<Permission> permissions =
    350             (Vector<Permission>)gfields.get("permissions", null);
    351         perms = new ConcurrentHashMap<>(permissions.size());
    352         for (Permission perm : permissions) {
    353             perms.put(perm, Boolean.TRUE);
    354         }
    355     }
    356 }