      1 /*
      2  * Copyright (c) 1999, 2021, 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;
     27
     28 import java.util.*;
     29 import java.io.IOException;
     30 import java.text.MessageFormat;
     31 import java.security.Permission;
     32 import java.security.PermissionCollection;
     33 import java.security.Principal;
     34 import sun.security.util.ResourcesMgr;
     35
     36 /**
     37  * This class is used to protect access to private Credentials
     38  * belonging to a particular {@code Subject}.  The {@code Subject}
     39  * is represented by a Set of Principals.
     40  *
     41  * <p> The target name of this {@code Permission} specifies
     42  * a Credential class name, and a Set of Principals.
     43  * The only valid value for this Permission's actions is, "read".
     44  * The target name must abide by the following syntax:
     45  *
     46  * <pre>
     47  *      CredentialClass {PrincipalClass "PrincipalName"}*
     48  * </pre>
     49  *
     50  * For example, the following permission grants access to the
     51  * com.sun.PrivateCredential owned by Subjects which have
     52  * a com.sun.Principal with the name, "duke".  Note that although
     53  * this example, as well as all the examples below, do not contain
     54  * Codebase, SignedBy, or Principal information in the grant statement
     55  * (for simplicity reasons), actual policy configurations should
     56  * specify that information when appropriate.
     57  *
     58  * <pre>
     59  *
     60  *    grant {
     61  *      permission javax.security.auth.PrivateCredentialPermission
     62  *              "com.sun.PrivateCredential com.sun.Principal \"duke\"",
     63  *              "read";
     64  *    };
     65  * </pre>
     66  *
     67  * If CredentialClass is "*", then access is granted to
     68  * all private Credentials belonging to the specified
     69  * {@code Subject}.
     70  * If "PrincipalName" is "*", then access is granted to the
     71  * specified Credential owned by any {@code Subject} that has the
     72  * specified {@code Principal} (the actual PrincipalName doesn't matter).
     73  * For example, the following grants access to the
     74  * a.b.Credential owned by any {@code Subject} that has
     75  * an a.b.Principal.
     76  *
     77  * <pre>
     78  *    grant {
     79  *      permission javax.security.auth.PrivateCredentialPermission
     80  *              "a.b.Credential a.b.Principal "*"",
     81  *              "read";
     82  *    };
     83  * </pre>
     84  *
     85  * If both the PrincipalClass and "PrincipalName" are "*",
     86  * then access is granted to the specified Credential owned by
     87  * any {@code Subject}.
     88  *
     89  * <p> In addition, the PrincipalClass/PrincipalName pairing may be repeated:
     90  *
     91  * <pre>
     92  *    grant {
     93  *      permission javax.security.auth.PrivateCredentialPermission
     94  *              "a.b.Credential a.b.Principal "duke" c.d.Principal "dukette"",
     95  *              "read";
     96  *    };
     97  * </pre>
     98  *
     99  * The above grants access to the private Credential, "a.b.Credential",
    100  * belonging to a {@code Subject} with at least two associated Principals:
    101  * "a.b.Principal" with the name, "duke", and "c.d.Principal", with the name,
    102  * "dukette".
    103  *
    104  * @since 1.4
    105  */
    106 public final class PrivateCredentialPermission extends Permission {
    107
    108     @java.io.Serial
    109     private static final long serialVersionUID = 5284372143517237068L;
    110
    111     private static final CredOwner[] EMPTY_PRINCIPALS = new CredOwner[0];
    112
    113     /**
    114      * @serial
    115      */
    116     private String credentialClass;
    117
    118     /**
    119      * @serial The Principals associated with this permission.
    120      *          The set contains elements of type,
    121      *          {@code PrivateCredentialPermission.CredOwner}.
    122      */
    123     @SuppressWarnings("serial") // Not statically typed as Serializable
    124     private Set<Principal> principals;  // ignored - kept around for compatibility
    125     private transient CredOwner[] credOwners;
    126
    127     /**
    128      * @serial
    129      */
    130     private boolean testing = false;
    131
    132     /**
    133      * Create a new {@code PrivateCredentialPermission}
    134      * with the specified {@code credentialClass} and Principals.
    135      */
    136     PrivateCredentialPermission(String credentialClass,
    137                         Set<Principal> principals) {
    138
    139         super(credentialClass);
    140         this.credentialClass = credentialClass;
    141
    142         synchronized(principals) {
    143             if (principals.size() == 0) {
    144                 this.credOwners = EMPTY_PRINCIPALS;
    145             } else {
    146                 this.credOwners = new CredOwner[principals.size()];
    147                 int index = 0;
    148                 Iterator<Principal> i = principals.iterator();
    149                 while (i.hasNext()) {
    150                     Principal p = i.next();
    151                     this.credOwners[index++] = new CredOwner
    152                                                 (p.getClass().getName(),
    153                                                 p.getName());
    154                 }
    155             }
    156         }
    157     }
    158
    159     /**
    160      * Creates a new {@code PrivateCredentialPermission}
    161      * with the specified {@code name}.  The {@code name}
    162      * specifies both a Credential class and a {@code Principal} Set.
    163      *
    164      * @param name the name specifying the Credential class and
    165      *          {@code Principal} Set.
    166      *
    167      * @param actions the actions specifying that the Credential can be read.
    168      *
    169      * @throws IllegalArgumentException if {@code name} does not conform
    170      *          to the correct syntax or if {@code actions} is not "read".
    171      */
    172     public PrivateCredentialPermission(String name, String actions) {
    173         super(name);
    174
    175         if (!"read".equalsIgnoreCase(actions))
    176             throw new IllegalArgumentException
    177                 (ResourcesMgr.getString("actions.can.only.be.read."));
    178         init(name);
    179     }
    180
    181     /**
    182      * Returns the Class name of the Credential associated with this
    183      * {@code PrivateCredentialPermission}.
    184      *
    185      * @return the Class name of the Credential associated with this
    186      *          {@code PrivateCredentialPermission}.
    187      */
    188     public String getCredentialClass() {
    189         return credentialClass;
    190     }
    191
    192     /**
    193      * Returns the {@code Principal} classes and names
    194      * associated with this {@code PrivateCredentialPermission}.
    195      * The information is returned as a two-dimensional array (array[x][y]).
    196      * The 'x' value corresponds to the number of {@code Principal}
    197      * class and name pairs.  When (y==0), it corresponds to
    198      * the {@code Principal} class value, and when (y==1),
    199      * it corresponds to the {@code Principal} name value.
    200      * For example, array[0][0] corresponds to the class name of
    201      * the first {@code Principal} in the array.  array[0][1]
    202      * corresponds to the {@code Principal} name of the
    203      * first {@code Principal} in the array.
    204      *
    205      * @return the {@code Principal} class and names associated
    206      *          with this {@code PrivateCredentialPermission}.
    207      */
    208     public String[][] getPrincipals() {
    209
    210         if (credOwners == null || credOwners.length == 0) {
    211             return new String[0][0];
    212         }
    213
    214         String[][] pArray = new String[credOwners.length][2];
    215         for (int i = 0; i < credOwners.length; i++) {
    216             pArray[i][0] = credOwners[i].principalClass;
    217             pArray[i][1] = credOwners[i].principalName;
    218         }
    219         return pArray;
    220     }
    221
    222     /**
    223      * Checks if this {@code PrivateCredentialPermission} implies
    224      * the specified {@code Permission}.
    225      *
    226      * <p>
    227      *
    228      * This method returns true if:
    229      * <ul>
    230      * <li> {@code p} is an instanceof PrivateCredentialPermission and
    231      * <li> the target name for {@code p} is implied by this object's
    232      *          target name.  For example:
    233      * <pre>
    234      *  [* P1 "duke"] implies [a.b.Credential P1 "duke"].
    235      *  [C1 P1 "duke"] implies [C1 P1 "duke" P2 "dukette"].
    236      *  [C1 P2 "dukette"] implies [C1 P1 "duke" P2 "dukette"].
    237      * </pre>
    238      * </ul>
    239      *
    240      * @param p the {@code Permission} to check against.
    241      *
    242      * @return true if this {@code PrivateCredentialPermission} implies
    243      * the specified {@code Permission}, false if not.
    244      */
    245     public boolean implies(Permission p) {
    246
    247         if (p == null || !(p instanceof PrivateCredentialPermission))
    248             return false;
    249
    250         PrivateCredentialPermission that = (PrivateCredentialPermission)p;
    251
    252         if (!impliesCredentialClass(credentialClass, that.credentialClass))
    253             return false;
    254
    255         return impliesPrincipalSet(credOwners, that.credOwners);
    256     }
    257
    258     /**
    259      * Checks two {@code PrivateCredentialPermission} objects for
    260      * equality.  Checks that {@code obj} is a
    261      * {@code PrivateCredentialPermission},
    262      * and has the same credential class as this object,
    263      * as well as the same Principals as this object.
    264      * The order of the Principals in the respective Permission's
    265      * target names is not relevant.
    266      *
    267      * @param obj the object we are testing for equality with this object.
    268      *
    269      * @return true if obj is a {@code PrivateCredentialPermission},
    270      *          has the same credential class as this object,
    271      *          and has the same Principals as this object.
    272      */
    273     public boolean equals(Object obj) {
    274         if (obj == this)
    275             return true;
    276
    277         if (! (obj instanceof PrivateCredentialPermission))
    278             return false;
    279
    280         PrivateCredentialPermission that = (PrivateCredentialPermission)obj;
    281
    282         return (this.implies(that) && that.implies(this));
    283     }
    284
    285     /**
    286      * Returns the hash code value for this object.
    287      *
    288      * @return a hash code value for this object.
    289      */
    290     public int hashCode() {
    291         return this.credentialClass.hashCode();
    292     }
    293
    294     /**
    295      * Returns the "canonical string representation" of the actions.
    296      * This method always returns the String, "read".
    297      *
    298      * @return the actions (always returns "read").
    299      */
    300     public String getActions() {
    301         return "read";
    302     }
    303
    304     /**
    305      * Return a homogeneous collection of PrivateCredentialPermissions
    306      * in a {@code PermissionCollection}.
    307      * No such {@code PermissionCollection} is defined,
    308      * so this method always returns {@code null}.
    309      *
    310      * @return null in all cases.
    311      */
    312     public PermissionCollection newPermissionCollection() {
    313         return null;
    314     }
    315
    316     private void init(String name) {
    317
    318         if (name == null || name.trim().isEmpty()) {
    319             throw new IllegalArgumentException("invalid empty name");
    320         }
    321
    322         ArrayList<CredOwner> pList = new ArrayList<>();
    323         StringTokenizer tokenizer = new StringTokenizer(name, " ", true);
    324         String principalClass = null;
    325         String principalName = null;
    326
    327         if (testing)
    328             System.out.println("whole name = " + name);
    329
    330         // get the Credential Class
    331         credentialClass = tokenizer.nextToken();
    332         if (testing)
    333             System.out.println("Credential Class = " + credentialClass);
    334
    335         if (tokenizer.hasMoreTokens() == false) {
    336             MessageFormat form = new MessageFormat(ResourcesMgr.getString
    337                 ("permission.name.name.syntax.invalid."));
    338             Object[] source = {name};
    339             throw new IllegalArgumentException
    340                 (form.format(source) + ResourcesMgr.getString
    341                         ("Credential.Class.not.followed.by.a.Principal.Class.and.Name"));
    342         }
    343
    344         while (tokenizer.hasMoreTokens()) {
    345
    346             // skip delimiter
    347             tokenizer.nextToken();
    348
    349             // get the Principal Class
    350             principalClass = tokenizer.nextToken();
    351             if (testing)
    352                 System.out.println("    Principal Class = " + principalClass);
    353
    354             if (tokenizer.hasMoreTokens() == false) {
    355                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
    356                         ("permission.name.name.syntax.invalid."));
    357                 Object[] source = {name};
    358                 throw new IllegalArgumentException
    359                         (form.format(source) + ResourcesMgr.getString
    360                         ("Principal.Class.not.followed.by.a.Principal.Name"));
    361             }
    362
    363             // skip delimiter
    364             tokenizer.nextToken();
    365
    366             // get the Principal Name
    367             principalName = tokenizer.nextToken();
    368
    369             if (!principalName.startsWith("\"")) {
    370                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
    371                         ("permission.name.name.syntax.invalid."));
    372                 Object[] source = {name};
    373                 throw new IllegalArgumentException
    374                         (form.format(source) + ResourcesMgr.getString
    375                         ("Principal.Name.must.be.surrounded.by.quotes"));
    376             }
    377
    378             if (!principalName.endsWith("\"")) {
    379
    380                 // we have a name with spaces in it --
    381                 // keep parsing until we find the end quote,
    382                 // and keep the spaces in the name
    383
    384                 while (tokenizer.hasMoreTokens()) {
    385                     principalName = principalName + tokenizer.nextToken();
    386                     if (principalName.endsWith("\""))
    387                         break;
    388                 }
    389
    390                 if (!principalName.endsWith("\"")) {
    391                     MessageFormat form = new MessageFormat
    392                         (ResourcesMgr.getString
    393                         ("permission.name.name.syntax.invalid."));
    394                     Object[] source = {name};
    395                     throw new IllegalArgumentException
    396                         (form.format(source) + ResourcesMgr.getString
    397                                 ("Principal.Name.missing.end.quote"));
    398                 }
    399             }
    400
    401             if (testing)
    402                 System.out.println("\tprincipalName = '" + principalName + "'");
    403
    404             principalName = principalName.substring
    405                                         (1, principalName.length() - 1);
    406
    407             if (principalClass.equals("*") &&
    408                 !principalName.equals("*")) {
    409                     throw new IllegalArgumentException(ResourcesMgr.getString
    410                         ("PrivateCredentialPermission.Principal.Class.can.not.be.a.wildcard.value.if.Principal.Name.is.not.a.wildcard.value"));
    411             }
    412
    413             if (testing)
    414                 System.out.println("\tprincipalName = '" + principalName + "'");
    415
    416             pList.add(new CredOwner(principalClass, principalName));
    417         }
    418
    419         this.credOwners = new CredOwner[pList.size()];
    420         pList.toArray(this.credOwners);
    421     }
    422
    423     private boolean impliesCredentialClass(String thisC, String thatC) {
    424
    425         // this should never happen
    426         if (thisC == null || thatC == null)
    427             return false;
    428
    429         if (testing)
    430             System.out.println("credential class comparison: " +
    431                                 thisC + "/" + thatC);
    432
    433         if (thisC.equals("*"))
    434             return true;
    435
    436         /**
    437          * XXX let's not enable this for now --
    438          *      if people want it, we'll enable it later
    439          */
    440         /*
    441         if (thisC.endsWith("*")) {
    442             String cClass = thisC.substring(0, thisC.length() - 2);
    443             return thatC.startsWith(cClass);
    444         }
    445         */
    446
    447         return thisC.equals(thatC);
    448     }
    449
    450     private boolean impliesPrincipalSet(CredOwner[] thisP, CredOwner[] thatP) {
    451
    452         // this should never happen
    453         if (thisP == null || thatP == null)
    454             return false;
    455
    456         if (thatP.length == 0)
    457             return true;
    458
    459         if (thisP.length == 0)
    460             return false;
    461
    462         for (int i = 0; i < thisP.length; i++) {
    463             boolean foundMatch = false;
    464             for (int j = 0; j < thatP.length; j++) {
    465                 if (thisP[i].implies(thatP[j])) {
    466                     foundMatch = true;
    467                     break;
    468                 }
    469             }
    470             if (!foundMatch) {
    471                 return false;
    472             }
    473         }
    474         return true;
    475     }
    476
    477     /**
    478      * Reads this object from a stream (i.e., deserializes it)
    479      *
    480      * @param  s the {@code ObjectInputStream} from which data is read
    481      * @throws IOException if an I/O error occurs
    482      * @throws ClassNotFoundException if a serialized class cannot be loaded
    483      */
    484     @java.io.Serial
    485     private void readObject(java.io.ObjectInputStream s) throws
    486                                         IOException,
    487                                         ClassNotFoundException {
    488
    489         s.defaultReadObject();
    490
    491         // perform new initialization from the permission name
    492
    493         if (getName().indexOf(' ') == -1 && getName().indexOf('"') == -1) {
    494
    495             // name only has a credential class specified
    496             credentialClass = getName();
    497             credOwners = EMPTY_PRINCIPALS;
    498
    499         } else {
    500
    501             // perform regular initialization
    502             init(getName());
    503         }
    504     }
    505
    506     /**
    507      * @serial include
    508      */
    509     static class CredOwner implements java.io.Serializable {
    510
    511         @java.io.Serial
    512         private static final long serialVersionUID = -5607449830436408266L;
    513
    514         /**
    515          * @serial
    516          */
    517         String principalClass;
    518         /**
    519          * @serial
    520          */
    521         String principalName;
    522
    523         CredOwner(String principalClass, String principalName) {
    524             this.principalClass = principalClass;
    525             this.principalName = principalName;
    526         }
    527
    528         public boolean implies(Object obj) {
    529             if (obj == null || !(obj instanceof CredOwner))
    530                 return false;
    531
    532             CredOwner that = (CredOwner)obj;
    533
    534             if (principalClass.equals("*") ||
    535                 principalClass.equals(that.principalClass)) {
    536
    537                 if (principalName.equals("*") ||
    538                     principalName.equals(that.principalName)) {
    539                     return true;
    540                 }
    541             }
    542
    543             /**
    544              * XXX no code yet to support a.b.*
    545              */
    546
    547             return false;
    548         }
    549
    550         public String toString() {
    551             MessageFormat form = new MessageFormat(ResourcesMgr.getString
    552                 ("CredOwner.Principal.Class.class.Principal.Name.name"));
    553             Object[] source = {principalClass, principalName};
    554             return (form.format(source));
    555         }
    556     }
    557 }