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