1. /*
  2. * @(#)ServicePermission.java 1.12 04/03/29
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.security.auth.kerberos;
  8. import java.util.*;
  9. import java.security.Permission;
  10. import java.security.PermissionCollection;
  11. import java.io.ObjectStreamField;
  12. import java.io.ObjectOutputStream;
  13. import java.io.ObjectInputStream;
  14. import java.io.IOException;
  15. /**
  16. * This class is used to protect Kerberos services and the
  17. * credentials necessary to access those services. There is a one to
  18. * one mapping of a service principal and the credentials necessary
  19. * to access the service. Therefore granting access to a service
  20. * principal implicitly grants access to the credential necessary to
  21. * establish a security context with the service principal. This
  22. * applies regardless of whether the credentials are in a cache
  23. * or acquired via an exchange with the KDC. The credential can
  24. * be either a ticket granting ticket, a service ticket or a secret
  25. * key from a key table.
  26. * <p>
  27. * A ServicePermission contains a service principal name and
  28. * a list of actions which specify the context the credential can be
  29. * used within.
  30. * <p>
  31. * The service principal name is the canonical name of the
  32. * <code>KereberosPrincipal</code> supplying the service, that is
  33. * the KerberosPrincipal represents a Kerberos service
  34. * principal. This name is treated in a case sensitive manner.
  35. * An asterisk may appear by itself, to signify any service principal.
  36. * <p>
  37. * Granting this permission implies that the caller can use a cached
  38. * credential (TGT, service ticket or secret key) within the context
  39. * designated by the action. In the case of the TGT, granting this
  40. * permission also implies that the TGT can be obtained by an
  41. * Authentication Service exchange.
  42. * <p>
  43. * The possible actions are:
  44. * <p>
  45. * <pre>
  46. * initiate - allow the caller to use the credential to
  47. * initiate a security context with a service
  48. * principal.
  49. *
  50. * accept - allow the caller to use the credential to
  51. * accept security context as a particular
  52. * principal.
  53. * </pre>
  54. *
  55. * For example, to specify the permission to access to the TGT to
  56. * initiate a security context the permission is constructed as follows:
  57. * <p>
  58. * <pre>
  59. * ServicePermission("krbtgt/EXAMPLE.COM@EXAMPLE.COM", "initiate");
  60. * </pre>
  61. * <p>
  62. * To obtain a service ticket to initiate a context with the "host"
  63. * service the permission is constructed as follows:
  64. * <pre>
  65. * ServicePermission("host/foo.example.com@EXAMPLE.COM", "initiate");
  66. * </pre>
  67. * <p>
  68. * For a Kerberized server the action is "accept". For example, the permission
  69. * necessary to access and use the secret key of the Kerberized "host"
  70. * service (telnet and the likes) would be constructed as follows:
  71. * <p>
  72. * <pre>
  73. * ServicePermission("host/foo.example.com@EXAMPLE.COM", "accept");
  74. * </pre>
  75. *
  76. * @since JDK1.4
  77. */
  78. public final class ServicePermission extends Permission
  79. implements java.io.Serializable {
  80. private static final long serialVersionUID = -1227585031618624935L;
  81. /**
  82. * Initiate a security context to the specified service
  83. */
  84. private final static int INITIATE = 0x1;
  85. /**
  86. * Accept a security context
  87. */
  88. private final static int ACCEPT = 0x2;
  89. /**
  90. * All actions
  91. */
  92. private final static int ALL = INITIATE|ACCEPT;
  93. /**
  94. * No actions.
  95. */
  96. private final static int NONE = 0x0;
  97. // the actions mask
  98. private transient int mask;
  99. /**
  100. * the actions string.
  101. *
  102. * @serial
  103. */
  104. private String actions; // Left null as long as possible, then
  105. // created and re-used in the getAction function.
  106. /**
  107. * Create a new <code>ServicePermission</code>
  108. * with the specified <code>servicePrincipal</code>
  109. * and <code>action</code>.
  110. *
  111. * @param servicePrincipal the name of the service principal.
  112. * An asterisk may appear by itself, to signify any service principal.
  113. * <p>
  114. * @param action the action string
  115. */
  116. public ServicePermission(String servicePrincipal, String action) {
  117. super(servicePrincipal);
  118. init(servicePrincipal, getMask(action));
  119. }
  120. /**
  121. * Initialize the ServicePermission object.
  122. */
  123. private void init(String servicePrincipal, int mask) {
  124. if (servicePrincipal == null)
  125. throw new NullPointerException("service principal can't be null");
  126. if ((mask & ALL) != mask)
  127. throw new IllegalArgumentException("invalid actions mask");
  128. this.mask = mask;
  129. }
  130. /**
  131. * Checks if this Kerberos service permission object "implies" the
  132. * specified permission.
  133. * <P>
  134. * If none of the above are true, <code>implies</code> returns false.
  135. * @param p the permission to check against.
  136. *
  137. * @return true if the specified permission is implied by this object,
  138. * false if not.
  139. */
  140. public boolean implies(Permission p) {
  141. if (!(p instanceof ServicePermission))
  142. return false;
  143. ServicePermission that = (ServicePermission) p;
  144. return ((this.mask & that.mask) == that.mask) &&
  145. impliesIgnoreMask(that);
  146. }
  147. boolean impliesIgnoreMask(ServicePermission p) {
  148. return ((this.getName().equals("*")) ||
  149. this.getName().equals(p.getName()));
  150. }
  151. /**
  152. * Checks two ServicePermission objects for equality.
  153. * <P>
  154. * @param obj the object to test for equality with this object.
  155. *
  156. * @return true if <i>obj</i> is a ServicePermission, and has the
  157. * same service principal, and actions as this
  158. * ServicePermission object.
  159. */
  160. public boolean equals(Object obj) {
  161. if (obj == this)
  162. return true;
  163. if (! (obj instanceof ServicePermission))
  164. return false;
  165. ServicePermission that = (ServicePermission) obj;
  166. return ((this.mask & that.mask) == that.mask) &&
  167. this.getName().equals(that.getName());
  168. }
  169. /**
  170. * Returns the hash code value for this object.
  171. *
  172. * @return a hash code value for this object.
  173. */
  174. public int hashCode() {
  175. return (getName().hashCode() ^ mask);
  176. }
  177. /**
  178. * Returns the "canonical string representation" of the actions in the
  179. * specified mask.
  180. * Always returns present actions in the following order:
  181. * initiate, accept.
  182. *
  183. * @param mask a specific integer action mask to translate into a string
  184. * @return the canonical string representation of the actions
  185. */
  186. private static String getActions(int mask)
  187. {
  188. StringBuilder sb = new StringBuilder();
  189. boolean comma = false;
  190. if ((mask & INITIATE) == INITIATE) {
  191. if (comma) sb.append(',');
  192. else comma = true;
  193. sb.append("initiate");
  194. }
  195. if ((mask & ACCEPT) == ACCEPT) {
  196. if (comma) sb.append(',');
  197. else comma = true;
  198. sb.append("accept");
  199. }
  200. return sb.toString();
  201. }
  202. /**
  203. * Returns the canonical string representation of the actions.
  204. * Always returns present actions in the following order:
  205. * initiate, accept.
  206. */
  207. public String getActions() {
  208. if (actions == null)
  209. actions = getActions(this.mask);
  210. return actions;
  211. }
  212. /**
  213. * Returns a PermissionCollection object for storing
  214. * ServicePermission objects.
  215. * <br>
  216. * ServicePermission objects must be stored in a manner that
  217. * allows them to be inserted into the collection in any order, but
  218. * that also enables the PermissionCollection implies method to
  219. * be implemented in an efficient (and consistent) manner.
  220. *
  221. * @return a new PermissionCollection object suitable for storing
  222. * ServicePermissions.
  223. */
  224. public PermissionCollection newPermissionCollection() {
  225. return new KrbServicePermissionCollection();
  226. }
  227. /**
  228. * Return the current action mask.
  229. *
  230. * @return the actions mask.
  231. */
  232. int getMask() {
  233. return mask;
  234. }
  235. /**
  236. * Convert an action string to an integer actions mask.
  237. *
  238. * @param action the action string
  239. * @return the action mask
  240. */
  241. private static int getMask(String action) {
  242. if (action == null) {
  243. throw new NullPointerException("action can't be null");
  244. }
  245. if (action.equals("")) {
  246. throw new IllegalArgumentException("action can't be empty");
  247. }
  248. int mask = NONE;
  249. if (action == null) {
  250. return mask;
  251. }
  252. char[] a = action.toCharArray();
  253. int i = a.length - 1;
  254. if (i < 0)
  255. return mask;
  256. while (i != -1) {
  257. char c;
  258. // skip whitespace
  259. while ((i!=-1) && ((c = a[i]) == ' ' ||
  260. c == '\r' ||
  261. c == '\n' ||
  262. c == '\f' ||
  263. c == '\t'))
  264. i--;
  265. // check for the known strings
  266. int matchlen;
  267. if (i >= 7 && (a[i-7] == 'i' || a[i-7] == 'I') &&
  268. (a[i-6] == 'n' || a[i-6] == 'N') &&
  269. (a[i-5] == 'i' || a[i-5] == 'I') &&
  270. (a[i-4] == 't' || a[i-4] == 'T') &&
  271. (a[i-3] == 'i' || a[i-3] == 'I') &&
  272. (a[i-2] == 'a' || a[i-2] == 'A') &&
  273. (a[i-1] == 't' || a[i-1] == 'T') &&
  274. (a[i] == 'e' || a[i] == 'E'))
  275. {
  276. matchlen = 8;
  277. mask |= INITIATE;
  278. } else if (i >= 5 && (a[i-5] == 'a' || a[i-5] == 'A') &&
  279. (a[i-4] == 'c' || a[i-4] == 'C') &&
  280. (a[i-3] == 'c' || a[i-3] == 'C') &&
  281. (a[i-2] == 'e' || a[i-2] == 'E') &&
  282. (a[i-1] == 'p' || a[i-1] == 'P') &&
  283. (a[i] == 't' || a[i] == 'T'))
  284. {
  285. matchlen = 6;
  286. mask |= ACCEPT;
  287. } else {
  288. // parse error
  289. throw new IllegalArgumentException(
  290. "invalid permission: " + action);
  291. }
  292. // make sure we didn't just match the tail of a word
  293. // like "ackbarfaccept". Also, skip to the comma.
  294. boolean seencomma = false;
  295. while (i >= matchlen && !seencomma) {
  296. switch(a[i-matchlen]) {
  297. case ',':
  298. seencomma = true;
  299. /*FALLTHROUGH*/
  300. case ' ': case '\r': case '\n':
  301. case '\f': case '\t':
  302. break;
  303. default:
  304. throw new IllegalArgumentException(
  305. "invalid permission: " + action);
  306. }
  307. i--;
  308. }
  309. // point i at the location of the comma minus one (or -1).
  310. i -= matchlen;
  311. }
  312. return mask;
  313. }
  314. /**
  315. * WriteObject is called to save the state of the ServicePermission
  316. * to a stream. The actions are serialized, and the superclass
  317. * takes care of the name.
  318. */
  319. private synchronized void writeObject(java.io.ObjectOutputStream s)
  320. throws IOException
  321. {
  322. // Write out the actions. The superclass takes care of the name
  323. // call getActions to make sure actions field is initialized
  324. if (actions == null)
  325. getActions();
  326. s.defaultWriteObject();
  327. }
  328. /**
  329. * readObject is called to restore the state of the
  330. * ServicePermission from a stream.
  331. */
  332. private synchronized void readObject(java.io.ObjectInputStream s)
  333. throws IOException, ClassNotFoundException
  334. {
  335. // Read in the action, then initialize the rest
  336. s.defaultReadObject();
  337. init(getName(),getMask(actions));
  338. }
  339. /*
  340. public static void main(String args[]) throws Exception {
  341. ServicePermission this_ =
  342. new ServicePermission(args[0], "accept");
  343. ServicePermission that_ =
  344. new ServicePermission(args[1], "accept,initiate");
  345. System.out.println("-----\n");
  346. System.out.println("this.implies(that) = " + this_.implies(that_));
  347. System.out.println("-----\n");
  348. System.out.println("this = "+this_);
  349. System.out.println("-----\n");
  350. System.out.println("that = "+that_);
  351. System.out.println("-----\n");
  352. KrbServicePermissionCollection nps =
  353. new KrbServicePermissionCollection();
  354. nps.add(this_);
  355. nps.add(new ServicePermission("nfs/example.com@EXAMPLE.COM",
  356. "accept"));
  357. nps.add(new ServicePermission("host/example.com@EXAMPLE.COM",
  358. "initiate"));
  359. System.out.println("nps.implies(that) = " + nps.implies(that_));
  360. System.out.println("-----\n");
  361. Enumeration e = nps.elements();
  362. while (e.hasMoreElements()) {
  363. ServicePermission x =
  364. (ServicePermission) e.nextElement();
  365. System.out.println("nps.e = " + x);
  366. }
  367. }
  368. */
  369. }
  370. final class KrbServicePermissionCollection extends PermissionCollection
  371. implements java.io.Serializable {
  372. // Not serialized; see serialization section at end of class
  373. private transient List perms;
  374. public KrbServicePermissionCollection() {
  375. perms = new ArrayList();
  376. }
  377. /**
  378. * Check and see if this collection of permissions implies the permissions
  379. * expressed in "permission".
  380. *
  381. * @param p the Permission object to compare
  382. *
  383. * @return true if "permission" is a proper subset of a permission in
  384. * the collection, false if not.
  385. */
  386. public boolean implies(Permission permission) {
  387. if (! (permission instanceof ServicePermission))
  388. return false;
  389. ServicePermission np = (ServicePermission) permission;
  390. int desired = np.getMask();
  391. int effective = 0;
  392. int needed = desired;
  393. synchronized (this) {
  394. int len = perms.size();
  395. // need to deal with the case where the needed permission has
  396. // more than one action and the collection has individual permissions
  397. // that sum up to the needed.
  398. for (int i = 0; i < len; i++) {
  399. ServicePermission x = (ServicePermission) perms.get(i);
  400. //System.out.println(" trying "+x);
  401. if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(np)) {
  402. effective |= x.getMask();
  403. if ((effective & desired) == desired)
  404. return true;
  405. needed = (desired ^ effective);
  406. }
  407. }
  408. }
  409. return false;
  410. }
  411. /**
  412. * Adds a permission to the ServicePermissions. The key for
  413. * the hash is the name.
  414. *
  415. * @param permission the Permission object to add.
  416. *
  417. * @exception IllegalArgumentException - if the permission is not a
  418. * ServicePermission
  419. *
  420. * @exception SecurityException - if this PermissionCollection object
  421. * has been marked readonly
  422. */
  423. public void add(Permission permission) {
  424. if (! (permission instanceof ServicePermission))
  425. throw new IllegalArgumentException("invalid permission: "+
  426. permission);
  427. if (isReadOnly())
  428. throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");
  429. synchronized (this) {
  430. perms.add(0, permission);
  431. }
  432. }
  433. /**
  434. * Returns an enumeration of all the ServicePermission objects
  435. * in the container.
  436. *
  437. * @return an enumeration of all the ServicePermission objects.
  438. */
  439. public Enumeration elements() {
  440. // Convert Iterator into Enumeration
  441. synchronized (this) {
  442. return Collections.enumeration(perms);
  443. }
  444. }
  445. private static final long serialVersionUID = -4118834211490102011L;
  446. // Need to maintain serialization interoperability with earlier releases,
  447. // which had the serializable field:
  448. // private Vector permissions;
  449. /**
  450. * @serialField permissions java.util.Vector
  451. * A list of ServicePermission objects.
  452. */
  453. private static final ObjectStreamField[] serialPersistentFields = {
  454. new ObjectStreamField("permissions", Vector.class),
  455. };
  456. /**
  457. * @serialData "permissions" field (a Vector containing the ServicePermissions).
  458. */
  459. /*
  460. * Writes the contents of the perms field out as a Vector for
  461. * serialization compatibility with earlier releases.
  462. */
  463. private void writeObject(ObjectOutputStream out) throws IOException {
  464. // Don't call out.defaultWriteObject()
  465. // Write out Vector
  466. Vector permissions = new Vector(perms.size());
  467. synchronized (this) {
  468. permissions.addAll(perms);
  469. }
  470. ObjectOutputStream.PutField pfields = out.putFields();
  471. pfields.put("permissions", permissions);
  472. out.writeFields();
  473. }
  474. /*
  475. * Reads in a Vector of ServicePermissions and saves them in the perms field.
  476. */
  477. private void readObject(ObjectInputStream in) throws IOException,
  478. ClassNotFoundException {
  479. // Don't call defaultReadObject()
  480. // Read in serialized fields
  481. ObjectInputStream.GetField gfields = in.readFields();
  482. // Get the one we want
  483. Vector permissions = (Vector)gfields.get("permissions", null);
  484. perms = new ArrayList(permissions.size());
  485. perms.addAll(permissions);
  486. }
  487. }