blob: 49e85bdeb6d8593ff69fcdbd143fc1a166701b6a [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.security.auth.kerberos;
27
28import java.util.*;
29import java.security.Permission;
30import java.security.PermissionCollection;
31import java.io.ObjectStreamField;
32import java.io.ObjectOutputStream;
33import java.io.ObjectInputStream;
34import 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
100public 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
263 public String getActions() {
264 if (actions == null)
265 actions = getActions(this.mask);
266
267 return actions;
268 }
269
270
271 /**
272 * Returns a PermissionCollection object for storing
273 * ServicePermission objects.
274 * <br>
275 * ServicePermission objects must be stored in a manner that
276 * allows them to be inserted into the collection in any order, but
277 * that also enables the PermissionCollection implies method to
278 * be implemented in an efficient (and consistent) manner.
279 *
280 * @return a new PermissionCollection object suitable for storing
281 * ServicePermissions.
282 */
283
284 public PermissionCollection newPermissionCollection() {
285 return new KrbServicePermissionCollection();
286 }
287
288 /**
289 * Return the current action mask.
290 *
291 * @return the actions mask.
292 */
293
294 int getMask() {
295 return mask;
296 }
297
298 /**
299 * Convert an action string to an integer actions mask.
300 *
301 * @param action the action string
302 * @return the action mask
303 */
304
305 private static int getMask(String action) {
306
307 if (action == null) {
308 throw new NullPointerException("action can't be null");
309 }
310
311 if (action.equals("")) {
312 throw new IllegalArgumentException("action can't be empty");
313 }
314
315 int mask = NONE;
316
317 char[] a = action.toCharArray();
318
319 int i = a.length - 1;
320 if (i < 0)
321 return mask;
322
323 while (i != -1) {
324 char c;
325
326 // skip whitespace
327 while ((i!=-1) && ((c = a[i]) == ' ' ||
328 c == '\r' ||
329 c == '\n' ||
330 c == '\f' ||
331 c == '\t'))
332 i--;
333
334 // check for the known strings
335 int matchlen;
336
337 if (i >= 7 && (a[i-7] == 'i' || a[i-7] == 'I') &&
338 (a[i-6] == 'n' || a[i-6] == 'N') &&
339 (a[i-5] == 'i' || a[i-5] == 'I') &&
340 (a[i-4] == 't' || a[i-4] == 'T') &&
341 (a[i-3] == 'i' || a[i-3] == 'I') &&
342 (a[i-2] == 'a' || a[i-2] == 'A') &&
343 (a[i-1] == 't' || a[i-1] == 'T') &&
344 (a[i] == 'e' || a[i] == 'E'))
345 {
346 matchlen = 8;
347 mask |= INITIATE;
348
349 } else if (i >= 5 && (a[i-5] == 'a' || a[i-5] == 'A') &&
350 (a[i-4] == 'c' || a[i-4] == 'C') &&
351 (a[i-3] == 'c' || a[i-3] == 'C') &&
352 (a[i-2] == 'e' || a[i-2] == 'E') &&
353 (a[i-1] == 'p' || a[i-1] == 'P') &&
354 (a[i] == 't' || a[i] == 'T'))
355 {
356 matchlen = 6;
357 mask |= ACCEPT;
358
359 } else {
360 // parse error
361 throw new IllegalArgumentException(
362 "invalid permission: " + action);
363 }
364
365 // make sure we didn't just match the tail of a word
366 // like "ackbarfaccept". Also, skip to the comma.
367 boolean seencomma = false;
368 while (i >= matchlen && !seencomma) {
369 switch(a[i-matchlen]) {
370 case ',':
371 seencomma = true;
372 /*FALLTHROUGH*/
373 case ' ': case '\r': case '\n':
374 case '\f': case '\t':
375 break;
376 default:
377 throw new IllegalArgumentException(
378 "invalid permission: " + action);
379 }
380 i--;
381 }
382
383 // point i at the location of the comma minus one (or -1).
384 i -= matchlen;
385 }
386
387 return mask;
388 }
389
390
391 /**
392 * WriteObject is called to save the state of the ServicePermission
393 * to a stream. The actions are serialized, and the superclass
394 * takes care of the name.
395 */
396 private void writeObject(java.io.ObjectOutputStream s)
397 throws IOException
398 {
399 // Write out the actions. The superclass takes care of the name
400 // call getActions to make sure actions field is initialized
401 if (actions == null)
402 getActions();
403 s.defaultWriteObject();
404 }
405
406 /**
407 * readObject is called to restore the state of the
408 * ServicePermission from a stream.
409 */
410 private void readObject(java.io.ObjectInputStream s)
411 throws IOException, ClassNotFoundException
412 {
413 // Read in the action, then initialize the rest
414 s.defaultReadObject();
415 init(getName(),getMask(actions));
416 }
417
418
419 /*
420 public static void main(String args[]) throws Exception {
421 ServicePermission this_ =
422 new ServicePermission(args[0], "accept");
423 ServicePermission that_ =
424 new ServicePermission(args[1], "accept,initiate");
425 System.out.println("-----\n");
426 System.out.println("this.implies(that) = " + this_.implies(that_));
427 System.out.println("-----\n");
428 System.out.println("this = "+this_);
429 System.out.println("-----\n");
430 System.out.println("that = "+that_);
431 System.out.println("-----\n");
432
433 KrbServicePermissionCollection nps =
434 new KrbServicePermissionCollection();
435 nps.add(this_);
436 nps.add(new ServicePermission("nfs/example.com@EXAMPLE.COM",
437 "accept"));
438 nps.add(new ServicePermission("host/example.com@EXAMPLE.COM",
439 "initiate"));
440 System.out.println("nps.implies(that) = " + nps.implies(that_));
441 System.out.println("-----\n");
442
443 Enumeration e = nps.elements();
444
445 while (e.hasMoreElements()) {
446 ServicePermission x =
447 (ServicePermission) e.nextElement();
448 System.out.println("nps.e = " + x);
449 }
450
451 }
452 */
453
454}
455
456
457final class KrbServicePermissionCollection extends PermissionCollection
458 implements java.io.Serializable {
459
460 // Not serialized; see serialization section at end of class
461 private transient List<Permission> perms;
462
463 public KrbServicePermissionCollection() {
464 perms = new ArrayList<Permission>();
465 }
466
467 /**
468 * Check and see if this collection of permissions implies the permissions
469 * expressed in "permission".
470 *
471 * @param p the Permission object to compare
472 *
473 * @return true if "permission" is a proper subset of a permission in
474 * the collection, false if not.
475 */
476
477 public boolean implies(Permission permission) {
478 if (! (permission instanceof ServicePermission))
479 return false;
480
481 ServicePermission np = (ServicePermission) permission;
482 int desired = np.getMask();
483 int effective = 0;
484 int needed = desired;
485
486 synchronized (this) {
487 int len = perms.size();
488
489 // need to deal with the case where the needed permission has
490 // more than one action and the collection has individual permissions
491 // that sum up to the needed.
492
493 for (int i = 0; i < len; i++) {
494 ServicePermission x = (ServicePermission) perms.get(i);
495
496 //System.out.println(" trying "+x);
497 if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(np)) {
498 effective |= x.getMask();
499 if ((effective & desired) == desired)
500 return true;
501 needed = (desired ^ effective);
502 }
503 }
504 }
505 return false;
506 }
507
508 /**
509 * Adds a permission to the ServicePermissions. The key for
510 * the hash is the name.
511 *
512 * @param permission the Permission object to add.
513 *
514 * @exception IllegalArgumentException - if the permission is not a
515 * ServicePermission
516 *
517 * @exception SecurityException - if this PermissionCollection object
518 * has been marked readonly
519 */
520
521 public void add(Permission permission) {
522 if (! (permission instanceof ServicePermission))
523 throw new IllegalArgumentException("invalid permission: "+
524 permission);
525 if (isReadOnly())
526 throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");
527
528 synchronized (this) {
529 perms.add(0, permission);
530 }
531 }
532
533 /**
534 * Returns an enumeration of all the ServicePermission objects
535 * in the container.
536 *
537 * @return an enumeration of all the ServicePermission objects.
538 */
539
540 public Enumeration<Permission> elements() {
541 // Convert Iterator into Enumeration
542 synchronized (this) {
543 return Collections.enumeration(perms);
544 }
545 }
546
547 private static final long serialVersionUID = -4118834211490102011L;
548
549 // Need to maintain serialization interoperability with earlier releases,
550 // which had the serializable field:
551 // private Vector permissions;
552
553 /**
554 * @serialField permissions java.util.Vector
555 * A list of ServicePermission objects.
556 */
557 private static final ObjectStreamField[] serialPersistentFields = {
558 new ObjectStreamField("permissions", Vector.class),
559 };
560
561 /**
562 * @serialData "permissions" field (a Vector containing the ServicePermissions).
563 */
564 /*
565 * Writes the contents of the perms field out as a Vector for
566 * serialization compatibility with earlier releases.
567 */
568 private void writeObject(ObjectOutputStream out) throws IOException {
569 // Don't call out.defaultWriteObject()
570
571 // Write out Vector
572 Vector<Permission> permissions = new Vector<Permission>(perms.size());
573
574 synchronized (this) {
575 permissions.addAll(perms);
576 }
577
578 ObjectOutputStream.PutField pfields = out.putFields();
579 pfields.put("permissions", permissions);
580 out.writeFields();
581 }
582
583 /*
584 * Reads in a Vector of ServicePermissions and saves them in the perms field.
585 */
586 private void readObject(ObjectInputStream in) throws IOException,
587 ClassNotFoundException {
588 // Don't call defaultReadObject()
589
590 // Read in serialized fields
591 ObjectInputStream.GetField gfields = in.readFields();
592
593 // Get the one we want
594 Vector<Permission> permissions =
595 (Vector<Permission>)gfields.get("permissions", null);
596 perms = new ArrayList<Permission>(permissions.size());
597 perms.addAll(permissions);
598 }
599}