Path: blob/master/src/java.security.jgss/share/classes/javax/security/auth/kerberos/ServicePermission.java
41161 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.security.auth.kerberos;2627import java.io.IOException;28import java.io.ObjectInputStream;29import java.io.ObjectOutputStream;30import java.io.ObjectStreamField;31import java.security.Permission;32import java.security.PermissionCollection;33import java.util.*;34import java.util.concurrent.ConcurrentHashMap;3536/**37* This class is used to protect Kerberos services and the38* credentials necessary to access those services. There is a one to39* one mapping of a service principal and the credentials necessary40* to access the service. Therefore granting access to a service41* principal implicitly grants access to the credential necessary to42* establish a security context with the service principal. This43* applies regardless of whether the credentials are in a cache44* or acquired via an exchange with the KDC. The credential can45* be either a ticket granting ticket, a service ticket or a secret46* key from a key table.47* <p>48* A ServicePermission contains a service principal name and49* a list of actions which specify the context the credential can be50* used within.51* <p>52* The service principal name is the canonical name of the53* {@code KerberosPrincipal} supplying the service, that is54* the KerberosPrincipal represents a Kerberos service55* 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 cached59* credential (TGT, service ticket or secret key) within the context60* designated by the action. In the case of the TGT, granting this61* permission also implies that the TGT can be obtained by an62* Authentication Service exchange.63* <p>64* Granting this permission also implies creating {@link KerberosPrincipal}65* or {@link org.ietf.jgss.GSSName GSSName} without providing a Kerberos66* realm, as long as the permission's service principal is in this realm.67* <p>68* The possible actions are:69*70* <pre>71* initiate - allow the caller to use the credential to72* initiate a security context with a service73* principal.74*75* accept - allow the caller to use the credential to76* accept security context as a particular77* principal.78* </pre>79*80* For example, to specify the permission to access to the TGT to81* initiate a security context the permission is constructed as follows:82*83* <pre>84* ServicePermission("krbtgt/[email protected]", "initiate");85* </pre>86* <p>87* To obtain a service ticket to initiate a context with the "host"88* service the permission is constructed as follows:89* <pre>90* ServicePermission("host/[email protected]", "initiate");91* </pre>92* <p>93* For a Kerberized server the action is "accept". For example, the permission94* necessary to access and use the secret key of the Kerberized "host"95* service (telnet and the likes) would be constructed as follows:96*97* <pre>98* ServicePermission("host/[email protected]", "accept");99* </pre>100*101* @since 1.4102*/103104public final class ServicePermission extends Permission105implements java.io.Serializable {106107private static final long serialVersionUID = -1227585031618624935L;108109/**110* Initiate a security context to the specified service111*/112private static final int INITIATE = 0x1;113114/**115* Accept a security context116*/117private static final int ACCEPT = 0x2;118119/**120* All actions121*/122private static final int ALL = INITIATE|ACCEPT;123124/**125* No actions.126*/127private static final int NONE = 0x0;128129// the actions mask130private transient int mask;131132/**133* the actions string.134*135* @serial136*/137138private String actions; // Left null as long as possible, then139// created and re-used in the getAction function.140141/**142* Create a new {@code ServicePermission}143* with the specified {@code servicePrincipal}144* and {@code action}.145*146* @param servicePrincipal the name of the service principal.147* An asterisk may appear by itself, to signify any service principal.148*149* @param action the action string150*/151public ServicePermission(String servicePrincipal, String action) {152// Note: servicePrincipal can be "@REALM" which means any principal in153// this realm implies it. action can be "-" which means any154// action implies it.155super(servicePrincipal);156init(servicePrincipal, getMask(action));157}158159/**160* Creates a ServicePermission object with the specified servicePrincipal161* and a pre-calculated mask. Avoids the overhead of re-computing the mask.162* Called by ServicePermissionCollection.163*/164ServicePermission(String servicePrincipal, int mask) {165super(servicePrincipal);166init(servicePrincipal, mask);167}168169/**170* Initialize the ServicePermission object.171*/172private void init(String servicePrincipal, int mask) {173174if (servicePrincipal == null)175throw new NullPointerException("service principal can't be null");176177if ((mask & ALL) != mask)178throw new IllegalArgumentException("invalid actions mask");179180this.mask = mask;181}182183184/**185* Checks if this Kerberos service permission object "implies" the186* specified permission.187* <P>188* More specifically, this method returns true if all of the following189* are true (and returns false if any of them are not):190* <ul>191* <li> <i>p</i> is an instanceof {@code ServicePermission},192* <li> <i>p</i>'s actions are a proper subset of this193* {@code ServicePermission}'s actions,194* <li> <i>p</i>'s name is equal to this {@code ServicePermission}'s name195* or this {@code ServicePermission}'s name is "*".196* </ul>197*198* @param p the permission to check against.199*200* @return true if the specified permission is implied by this object,201* false if not.202*/203@Override204public boolean implies(Permission p) {205if (!(p instanceof ServicePermission))206return false;207208ServicePermission that = (ServicePermission) p;209210return ((this.mask & that.mask) == that.mask) &&211impliesIgnoreMask(that);212}213214215boolean impliesIgnoreMask(ServicePermission p) {216return ((this.getName().equals("*")) ||217this.getName().equals(p.getName()) ||218(p.getName().startsWith("@") &&219this.getName().endsWith(p.getName())));220}221222/**223* Checks two ServicePermission objects for equality.224*225* @param obj the object to test for equality with this object.226*227* @return true if {@code obj} is a ServicePermission, and has the228* same service principal, and actions as this229* ServicePermission object.230*/231@Override232public boolean equals(Object obj) {233if (obj == this)234return true;235236if (! (obj instanceof ServicePermission))237return false;238239ServicePermission that = (ServicePermission) obj;240return (this.mask == that.mask) &&241this.getName().equals(that.getName());242243244}245246/**247* Returns the hash code value for this object.248*249* @return a hash code value for this object.250*/251@Override252public int hashCode() {253return (getName().hashCode() ^ mask);254}255256257/**258* Returns the "canonical string representation" of the actions in the259* specified mask.260* Always returns present actions in the following order:261* initiate, accept.262*263* @param mask a specific integer action mask to translate into a string264* @return the canonical string representation of the actions265*/266static String getActions(int mask)267{268StringBuilder sb = new StringBuilder();269boolean comma = false;270271if ((mask & INITIATE) == INITIATE) {272if (comma) sb.append(',');273else comma = true;274sb.append("initiate");275}276277if ((mask & ACCEPT) == ACCEPT) {278if (comma) sb.append(',');279else comma = true;280sb.append("accept");281}282283return sb.toString();284}285286/**287* Returns the canonical string representation of the actions.288* Always returns present actions in the following order:289* initiate, accept.290*/291@Override292public String getActions() {293if (actions == null)294actions = getActions(this.mask);295296return actions;297}298299300/**301* Returns a PermissionCollection object for storing302* ServicePermission objects.303* <br>304* ServicePermission objects must be stored in a manner that305* allows them to be inserted into the collection in any order, but306* that also enables the PermissionCollection implies method to307* be implemented in an efficient (and consistent) manner.308*309* @return a new PermissionCollection object suitable for storing310* ServicePermissions.311*/312@Override313public PermissionCollection newPermissionCollection() {314return new KrbServicePermissionCollection();315}316317/**318* Return the current action mask.319*320* @return the actions mask.321*/322int getMask() {323return mask;324}325326/**327* Convert an action string to an integer actions mask.328*329* Note: if action is "-", action will be NONE, which means any330* action implies it.331*332* @param action the action string.333* @return the action mask334*/335private static int getMask(String action) {336337if (action == null) {338throw new NullPointerException("action can't be null");339}340341if (action.equals("")) {342throw new IllegalArgumentException("action can't be empty");343}344345int mask = NONE;346347char[] a = action.toCharArray();348349if (a.length == 1 && a[0] == '-') {350return mask;351}352353int i = a.length - 1;354355while (i != -1) {356char c;357358// skip whitespace359while ((i!=-1) && ((c = a[i]) == ' ' ||360c == '\r' ||361c == '\n' ||362c == '\f' ||363c == '\t'))364i--;365366// check for the known strings367int matchlen;368369if (i >= 7 && (a[i-7] == 'i' || a[i-7] == 'I') &&370(a[i-6] == 'n' || a[i-6] == 'N') &&371(a[i-5] == 'i' || a[i-5] == 'I') &&372(a[i-4] == 't' || a[i-4] == 'T') &&373(a[i-3] == 'i' || a[i-3] == 'I') &&374(a[i-2] == 'a' || a[i-2] == 'A') &&375(a[i-1] == 't' || a[i-1] == 'T') &&376(a[i] == 'e' || a[i] == 'E'))377{378matchlen = 8;379mask |= INITIATE;380381} else if (i >= 5 && (a[i-5] == 'a' || a[i-5] == 'A') &&382(a[i-4] == 'c' || a[i-4] == 'C') &&383(a[i-3] == 'c' || a[i-3] == 'C') &&384(a[i-2] == 'e' || a[i-2] == 'E') &&385(a[i-1] == 'p' || a[i-1] == 'P') &&386(a[i] == 't' || a[i] == 'T'))387{388matchlen = 6;389mask |= ACCEPT;390391} else {392// parse error393throw new IllegalArgumentException(394"invalid permission: " + action);395}396397// make sure we didn't just match the tail of a word398// like "ackbarfaccept". Also, skip to the comma.399boolean seencomma = false;400while (i >= matchlen && !seencomma) {401switch(a[i-matchlen]) {402case ',':403seencomma = true;404break;405case ' ': case '\r': case '\n':406case '\f': case '\t':407break;408default:409throw new IllegalArgumentException(410"invalid permission: " + action);411}412i--;413}414415// point i at the location of the comma minus one (or -1).416i -= matchlen;417}418419return mask;420}421422423/**424* WriteObject is called to save the state of the ServicePermission425* to a stream. The actions are serialized, and the superclass426* takes care of the name.427*428* @param s the {@code ObjectOutputStream} to which data is written429* @throws IOException if an I/O error occurs430*/431private void writeObject(java.io.ObjectOutputStream s)432throws IOException433{434// Write out the actions. The superclass takes care of the name435// call getActions to make sure actions field is initialized436if (actions == null)437getActions();438s.defaultWriteObject();439}440441/**442* readObject is called to restore the state of the443* ServicePermission from a stream.444*445* @param s the {@code ObjectInputStream} from which data is read446* @throws IOException if an I/O error occurs447* @throws ClassNotFoundException if a serialized class cannot be loaded448*/449private void readObject(java.io.ObjectInputStream s)450throws IOException, ClassNotFoundException451{452// Read in the action, then initialize the rest453s.defaultReadObject();454init(getName(),getMask(actions));455}456457458/*459public static void main(String[] args) throws Exception {460ServicePermission this_ =461new ServicePermission(args[0], "accept");462ServicePermission that_ =463new ServicePermission(args[1], "accept,initiate");464System.out.println("-----\n");465System.out.println("this.implies(that) = " + this_.implies(that_));466System.out.println("-----\n");467System.out.println("this = "+this_);468System.out.println("-----\n");469System.out.println("that = "+that_);470System.out.println("-----\n");471472KrbServicePermissionCollection nps =473new KrbServicePermissionCollection();474nps.add(this_);475nps.add(new ServicePermission("nfs/[email protected]",476"accept"));477nps.add(new ServicePermission("host/[email protected]",478"initiate"));479System.out.println("nps.implies(that) = " + nps.implies(that_));480System.out.println("-----\n");481482Enumeration e = nps.elements();483484while (e.hasMoreElements()) {485ServicePermission x =486(ServicePermission) e.nextElement();487System.out.println("nps.e = " + x);488}489490}491*/492493}494495496final class KrbServicePermissionCollection extends PermissionCollection497implements java.io.Serializable {498499// Key is the service principal, value is the ServicePermission.500// Not serialized; see serialization section at end of class501private transient ConcurrentHashMap<String, Permission> perms;502503public KrbServicePermissionCollection() {504perms = new ConcurrentHashMap<>();505}506507/**508* Check and see if this collection of permissions implies the permissions509* expressed in "permission".510*511* @param permission the Permission object to compare512*513* @return true if "permission" is a proper subset of a permission in514* the collection, false if not.515*/516@Override517public boolean implies(Permission permission) {518if (! (permission instanceof ServicePermission))519return false;520521ServicePermission np = (ServicePermission) permission;522int desired = np.getMask();523524if (desired == 0) {525for (Permission p: perms.values()) {526ServicePermission sp = (ServicePermission)p;527if (sp.impliesIgnoreMask(np)) {528return true;529}530}531return false;532}533534535// first, check for wildcard principal536ServicePermission x = (ServicePermission)perms.get("*");537if (x != null) {538if ((x.getMask() & desired) == desired) {539return true;540}541}542543// otherwise, check for match on principal544x = (ServicePermission)perms.get(np.getName());545if (x != null) {546//System.out.println(" trying "+x);547if ((x.getMask() & desired) == desired) {548return true;549}550}551return false;552}553554/**555* Adds a permission to the ServicePermissions. The key for556* the hash is the name.557*558* @param permission the Permission object to add.559*560* @exception IllegalArgumentException - if the permission is not a561* ServicePermission562*563* @exception SecurityException - if this PermissionCollection object564* has been marked readonly565*/566@Override567public void add(Permission permission) {568if (! (permission instanceof ServicePermission))569throw new IllegalArgumentException("invalid permission: "+570permission);571if (isReadOnly())572throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");573574ServicePermission sp = (ServicePermission)permission;575String princName = sp.getName();576577// Add permission to map if it is absent, or replace with new578// permission if applicable. NOTE: cannot use lambda for579// remappingFunction parameter until JDK-8076596 is fixed.580perms.merge(princName, sp,581new java.util.function.BiFunction<>() {582@Override583public Permission apply(Permission existingVal,584Permission newVal) {585int oldMask = ((ServicePermission)existingVal).getMask();586int newMask = ((ServicePermission)newVal).getMask();587if (oldMask != newMask) {588int effective = oldMask | newMask;589if (effective == newMask) {590return newVal;591}592if (effective != oldMask) {593return new ServicePermission(princName, effective);594}595}596return existingVal;597}598}599);600}601602/**603* Returns an enumeration of all the ServicePermission objects604* in the container.605*606* @return an enumeration of all the ServicePermission objects.607*/608@Override609public Enumeration<Permission> elements() {610return perms.elements();611}612613private static final long serialVersionUID = -4118834211490102011L;614615// Need to maintain serialization interoperability with earlier releases,616// which had the serializable field:617// private Vector permissions;618619/**620* @serialField permissions java.util.Vector621* A list of ServicePermission objects.622*/623private static final ObjectStreamField[] serialPersistentFields = {624new ObjectStreamField("permissions", Vector.class),625};626627/**628* @serialData "permissions" field (a Vector containing the ServicePermissions).629*/630/*631* Writes the contents of the perms field out as a Vector for632* serialization compatibility with earlier releases.633*/634private void writeObject(ObjectOutputStream out) throws IOException {635// Don't call out.defaultWriteObject()636637// Write out Vector638Vector<Permission> permissions = new Vector<>(perms.values());639640ObjectOutputStream.PutField pfields = out.putFields();641pfields.put("permissions", permissions);642out.writeFields();643}644645/*646* Reads in a Vector of ServicePermissions and saves them in the perms field.647*/648@SuppressWarnings("unchecked")649private void readObject(ObjectInputStream in)650throws IOException, ClassNotFoundException651{652// Don't call defaultReadObject()653654// Read in serialized fields655ObjectInputStream.GetField gfields = in.readFields();656657// Get the one we want658Vector<Permission> permissions =659(Vector<Permission>)gfields.get("permissions", null);660perms = new ConcurrentHashMap<>(permissions.size());661for (Permission perm : permissions) {662perms.put(perm.getName(), perm);663}664}665}666667668