001 /*
002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.security.auth.kerberos;
027
028 import java.util.*;
029 import java.security.Permission;
030 import java.security.PermissionCollection;
031 import java.io.ObjectStreamField;
032 import java.io.ObjectOutputStream;
033 import java.io.ObjectInputStream;
034 import java.io.IOException;
035
036 /**
037 * This class is used to protect Kerberos services and the
038 * credentials necessary to access those services. There is a one to
039 * one mapping of a service principal and the credentials necessary
040 * to access the service. Therefore granting access to a service
041 * principal implicitly grants access to the credential necessary to
042 * establish a security context with the service principal. This
043 * applies regardless of whether the credentials are in a cache
044 * or acquired via an exchange with the KDC. The credential can
045 * be either a ticket granting ticket, a service ticket or a secret
046 * key from a key table.
047 * <p>
048 * A ServicePermission contains a service principal name and
049 * a list of actions which specify the context the credential can be
050 * used within.
051 * <p>
052 * The service principal name is the canonical name of the
053 * <code>KereberosPrincipal</code> supplying the service, that is
054 * the KerberosPrincipal represents a Kerberos service
055 * principal. This name is treated in a case sensitive manner.
056 * An asterisk may appear by itself, to signify any service principal.
057 * <p>
058 * Granting this permission implies that the caller can use a cached
059 * credential (TGT, service ticket or secret key) within the context
060 * designated by the action. In the case of the TGT, granting this
061 * permission also implies that the TGT can be obtained by an
062 * Authentication Service exchange.
063 * <p>
064 * The possible actions are:
065 * <p>
066 * <pre>
067 * initiate - allow the caller to use the credential to
068 * initiate a security context with a service
069 * principal.
070 *
071 * accept - allow the caller to use the credential to
072 * accept security context as a particular
073 * principal.
074 * </pre>
075 *
076 * For example, to specify the permission to access to the TGT to
077 * initiate a security context the permission is constructed as follows:
078 * <p>
079 * <pre>
080 * ServicePermission("krbtgt/EXAMPLE.COM@EXAMPLE.COM", "initiate");
081 * </pre>
082 * <p>
083 * To obtain a service ticket to initiate a context with the "host"
084 * service the permission is constructed as follows:
085 * <pre>
086 * ServicePermission("host/foo.example.com@EXAMPLE.COM", "initiate");
087 * </pre>
088 * <p>
089 * For a Kerberized server the action is "accept". For example, the permission
090 * necessary to access and use the secret key of the Kerberized "host"
091 * service (telnet and the likes) would be constructed as follows:
092 * <p>
093 * <pre>
094 * ServicePermission("host/foo.example.com@EXAMPLE.COM", "accept");
095 * </pre>
096 *
097 * @since 1.4
098 */
099
100 public final class ServicePermission extends Permission implements
101 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
136 // created and re-used in the getAction function.
137
138 /**
139 * Create a new <code>ServicePermission</code>
140 * with the specified <code>servicePrincipal</code>
141 * and <code>action</code>.
142 *
143 * @param servicePrincipal the name of the service principal.
144 * An asterisk may appear by itself, to signify any service principal.
145 * <p>
146 * @param action the action string
147 */
148 public ServicePermission(String servicePrincipal, String action) {
149 super (servicePrincipal);
150 init(servicePrincipal, getMask(action));
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(
160 "service principal can't be null");
161
162 if ((mask & ALL) != mask)
163 throw new IllegalArgumentException("invalid actions mask");
164
165 this .mask = mask;
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 boolean impliesIgnoreMask(ServicePermission p) {
189 return ((this .getName().equals("*")) || this .getName().equals(
190 p.getName()));
191 }
192
193 /**
194 * Checks two ServicePermission objects for equality.
195 * <P>
196 * @param obj the object to test for equality with this object.
197 *
198 * @return true if <i>obj</i> is a ServicePermission, and has the
199 * same service principal, and actions as this
200 * ServicePermission object.
201 */
202 public boolean equals(Object obj) {
203 if (obj == this )
204 return true;
205
206 if (!(obj instanceof ServicePermission))
207 return false;
208
209 ServicePermission that = (ServicePermission) obj;
210 return ((this .mask & that.mask) == that.mask)
211 && this .getName().equals(that.getName());
212
213 }
214
215 /**
216 * Returns the hash code value for this object.
217 *
218 * @return a hash code value for this object.
219 */
220
221 public int hashCode() {
222 return (getName().hashCode() ^ mask);
223 }
224
225 /**
226 * Returns the "canonical string representation" of the actions in the
227 * specified mask.
228 * Always returns present actions in the following order:
229 * initiate, accept.
230 *
231 * @param mask a specific integer action mask to translate into a string
232 * @return the canonical string representation of the actions
233 */
234 private static String getActions(int mask) {
235 StringBuilder sb = new StringBuilder();
236 boolean comma = false;
237
238 if ((mask & INITIATE) == INITIATE) {
239 if (comma)
240 sb.append(',');
241 else
242 comma = true;
243 sb.append("initiate");
244 }
245
246 if ((mask & ACCEPT) == ACCEPT) {
247 if (comma)
248 sb.append(',');
249 else
250 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 * 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
283 public PermissionCollection newPermissionCollection() {
284 return new KrbServicePermissionCollection();
285 }
286
287 /**
288 * Return the current action mask.
289 *
290 * @return the actions mask.
291 */
292
293 int getMask() {
294 return mask;
295 }
296
297 /**
298 * Convert an action string to an integer actions mask.
299 *
300 * @param action the action string
301 * @return the action mask
302 */
303
304 private static int getMask(String action) {
305
306 if (action == null) {
307 throw new NullPointerException("action can't be null");
308 }
309
310 if (action.equals("")) {
311 throw new IllegalArgumentException("action can't be empty");
312 }
313
314 int mask = NONE;
315
316 char[] a = action.toCharArray();
317
318 int i = a.length - 1;
319 if (i < 0)
320 return mask;
321
322 while (i != -1) {
323 char c;
324
325 // skip whitespace
326 while ((i != -1)
327 && ((c = a[i]) == ' ' || c == '\r' || c == '\n'
328 || c == '\f' || c == '\t'))
329 i--;
330
331 // check for the known strings
332 int matchlen;
333
334 if (i >= 7 && (a[i - 7] == 'i' || a[i - 7] == 'I')
335 && (a[i - 6] == 'n' || a[i - 6] == 'N')
336 && (a[i - 5] == 'i' || a[i - 5] == 'I')
337 && (a[i - 4] == 't' || a[i - 4] == 'T')
338 && (a[i - 3] == 'i' || a[i - 3] == 'I')
339 && (a[i - 2] == 'a' || a[i - 2] == 'A')
340 && (a[i - 1] == 't' || a[i - 1] == 'T')
341 && (a[i] == 'e' || a[i] == 'E')) {
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 matchlen = 6;
352 mask |= ACCEPT;
353
354 } else {
355 // parse error
356 throw new IllegalArgumentException(
357 "invalid permission: " + action);
358 }
359
360 // make sure we didn't just match the tail of a word
361 // like "ackbarfaccept". Also, skip to the comma.
362 boolean seencomma = false;
363 while (i >= matchlen && !seencomma) {
364 switch (a[i - matchlen]) {
365 case ',':
366 seencomma = true;
367 /*FALLTHROUGH*/
368 case ' ':
369 case '\r':
370 case '\n':
371 case '\f':
372 case '\t':
373 break;
374 default:
375 throw new IllegalArgumentException(
376 "invalid permission: " + action);
377 }
378 i--;
379 }
380
381 // point i at the location of the comma minus one (or -1).
382 i -= matchlen;
383 }
384
385 return mask;
386 }
387
388 /**
389 * WriteObject is called to save the state of the ServicePermission
390 * to a stream. The actions are serialized, and the superclass
391 * takes care of the name.
392 */
393 private void writeObject(java.io.ObjectOutputStream s)
394 throws IOException {
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 // Read in the action, then initialize the rest
409 s.defaultReadObject();
410 init(getName(), getMask(actions));
411 }
412
413 /*
414 public static void main(String args[]) throws Exception {
415 ServicePermission this_ =
416 new ServicePermission(args[0], "accept");
417 ServicePermission that_ =
418 new ServicePermission(args[1], "accept,initiate");
419 System.out.println("-----\n");
420 System.out.println("this.implies(that) = " + this_.implies(that_));
421 System.out.println("-----\n");
422 System.out.println("this = "+this_);
423 System.out.println("-----\n");
424 System.out.println("that = "+that_);
425 System.out.println("-----\n");
426
427 KrbServicePermissionCollection nps =
428 new KrbServicePermissionCollection();
429 nps.add(this_);
430 nps.add(new ServicePermission("nfs/example.com@EXAMPLE.COM",
431 "accept"));
432 nps.add(new ServicePermission("host/example.com@EXAMPLE.COM",
433 "initiate"));
434 System.out.println("nps.implies(that) = " + nps.implies(that_));
435 System.out.println("-----\n");
436
437 Enumeration e = nps.elements();
438
439 while (e.hasMoreElements()) {
440 ServicePermission x =
441 (ServicePermission) e.nextElement();
442 System.out.println("nps.e = " + x);
443 }
444
445 }
446 */
447
448 }
449
450 final class KrbServicePermissionCollection extends PermissionCollection
451 implements java.io.Serializable {
452
453 // Not serialized; see serialization section at end of class
454 private transient List<Permission> perms;
455
456 public KrbServicePermissionCollection() {
457 perms = new ArrayList<Permission>();
458 }
459
460 /**
461 * Check and see if this collection of permissions implies the permissions
462 * expressed in "permission".
463 *
464 * @param p the Permission object to compare
465 *
466 * @return true if "permission" is a proper subset of a permission in
467 * the collection, false if not.
468 */
469
470 public boolean implies(Permission permission) {
471 if (!(permission instanceof ServicePermission))
472 return false;
473
474 ServicePermission np = (ServicePermission) permission;
475 int desired = np.getMask();
476 int effective = 0;
477 int needed = desired;
478
479 synchronized (this ) {
480 int len = perms.size();
481
482 // need to deal with the case where the needed permission has
483 // more than one action and the collection has individual permissions
484 // that sum up to the needed.
485
486 for (int i = 0; i < len; i++) {
487 ServicePermission x = (ServicePermission) perms.get(i);
488
489 //System.out.println(" trying "+x);
490 if (((needed & x.getMask()) != 0)
491 && x.impliesIgnoreMask(np)) {
492 effective |= x.getMask();
493 if ((effective & desired) == desired)
494 return true;
495 needed = (desired ^ effective);
496 }
497 }
498 }
499 return false;
500 }
501
502 /**
503 * Adds a permission to the ServicePermissions. The key for
504 * the hash is the name.
505 *
506 * @param permission the Permission object to add.
507 *
508 * @exception IllegalArgumentException - if the permission is not a
509 * ServicePermission
510 *
511 * @exception SecurityException - if this PermissionCollection object
512 * has been marked readonly
513 */
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(
521 "attempt to add a Permission to a readonly PermissionCollection");
522
523 synchronized (this ) {
524 perms.add(0, permission);
525 }
526 }
527
528 /**
529 * Returns an enumeration of all the ServicePermission objects
530 * in the container.
531 *
532 * @return an enumeration of all the ServicePermission objects.
533 */
534
535 public Enumeration<Permission> elements() {
536 // Convert Iterator into Enumeration
537 synchronized (this ) {
538 return Collections.enumeration(perms);
539 }
540 }
541
542 private static final long serialVersionUID = -4118834211490102011L;
543
544 // Need to maintain serialization interoperability with earlier releases,
545 // which had the serializable field:
546 // private Vector permissions;
547
548 /**
549 * @serialField permissions java.util.Vector
550 * A list of ServicePermission objects.
551 */
552 private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField(
553 "permissions", Vector.class), };
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<Permission>(perms
567 .size());
568
569 synchronized (this ) {
570 permissions.addAll(perms);
571 }
572
573 ObjectOutputStream.PutField pfields = out.putFields();
574 pfields.put("permissions", permissions);
575 out.writeFields();
576 }
577
578 /*
579 * Reads in a Vector of ServicePermissions and saves them in the perms field.
580 */
581 private void readObject(ObjectInputStream in) throws IOException,
582 ClassNotFoundException {
583 // Don't call defaultReadObject()
584
585 // Read in serialized fields
586 ObjectInputStream.GetField gfields = in.readFields();
587
588 // Get the one we want
589 Vector<Permission> permissions = (Vector<Permission>) gfields
590 .get("permissions", null);
591 perms = new ArrayList<Permission>(permissions.size());
592 perms.addAll(permissions);
593 }
594 }
|