001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.midp.security;
028:
029: import javax.microedition.io.*;
030:
031: import javax.microedition.lcdui.*;
032:
033: import com.sun.midp.lcdui.*;
034:
035: import com.sun.midp.midlet.*;
036:
037: import com.sun.midp.i18n.Resource;
038: import com.sun.midp.i18n.ResourceConstants;
039:
040: import com.sun.midp.events.EventQueue;
041:
042: import com.sun.midp.io.j2me.storage.*;
043: import com.sun.midp.configurator.Constants;
044:
045: import com.sun.midp.log.Logging;
046: import com.sun.midp.log.LogChannels;
047:
048: /**
049: * Contains methods to handle with the various security state information of a
050: * a MIDlet suite.
051: */
052: public final class SecurityHandler {
053: /** Session level interaction has not occured. */
054: private final static byte NOT_ASKED = 0;
055:
056: /** User granted permission for this session. */
057: private final static byte GRANTED = 1;
058:
059: /** User denied permission for this session. */
060: private final static byte DENIED = -1;
061:
062: /** The security token for this class. */
063: private static SecurityToken classSecurityToken;
064:
065: /** The standard security exception message. */
066: public static final String STD_EX_MSG = "Application not authorized "
067: + "to access the restricted API";
068:
069: /** Permission list. */
070: private byte permissions[];
071:
072: /** A flag for the session value of each permission. */
073: private byte sessionValues[];
074:
075: /** Maximum permission level list. */
076: private byte maxPermissionLevels[];
077:
078: /** True, if trusted. */
079: private boolean trusted;
080:
081: /**
082: * Creates a security domain with a list of permitted actions or no list
083: * to indicate all actions. The caller must be have permission for
084: * <code>Permissions.MIDP</code> or be the first caller of
085: * the method for this instance of the VM.
086: *
087: * @param apiPermissions for the token
088: * @param domain name of the security domain
089: *
090: * @exception SecurityException if caller is not permitted to call this
091: * method
092: */
093: public SecurityHandler(byte[] apiPermissions, String domain) {
094: MIDletStateHandler midletStateHandler = MIDletStateHandler
095: .getMidletStateHandler();
096: MIDletSuite midletSuite = midletStateHandler.getMIDletSuite();
097:
098: midletSuite.checkIfPermissionAllowed(Permissions.AMS);
099: init(apiPermissions, domain);
100: }
101:
102: /**
103: * Creates a security domain with a list of permitted actions or no list
104: * to indicate all actions. The caller must be have permission for
105: * <code>Permissions.MIDP</code> or be the first caller of
106: * the method for this instance of the VM.
107: *
108: * @param securityToken security token of the caller
109: * @param apiPermissions for the token, can be null
110: * @param domain name of the security domain
111: *
112: * @exception SecurityException if caller is not permitted to call this
113: * method
114: */
115: public SecurityHandler(SecurityToken securityToken,
116: byte[] apiPermissions, String domain) {
117: securityToken.checkIfPermissionAllowed(Permissions.AMS);
118: init(apiPermissions, domain);
119: }
120:
121: /**
122: * Creates a security domain with a list of permitted actions or no list
123: * to indicate all actions. The caller must be have permission for
124: * <code>Permissions.MIDP</code> or be the first caller of
125: * the method for this instance of the VM.
126: *
127: * @param apiPermissions for the token
128: * @param domain name of the security domain
129: *
130: * @exception SecurityException if caller is not permitted to call this
131: * method
132: */
133: private void init(byte[] apiPermissions, String domain) {
134: MIDletStateHandler midletStateHandler = MIDletStateHandler
135: .getMidletStateHandler();
136: MIDletSuite midletSuite = midletStateHandler.getMIDletSuite();
137:
138: maxPermissionLevels = (Permissions.forDomain(domain))[Permissions.MAX_LEVELS];
139:
140: permissions = apiPermissions;
141:
142: sessionValues = new byte[permissions.length];
143:
144: trusted = Permissions.isTrusted(domain);
145: }
146:
147: /**
148: * Get the status of the specified permission.
149: * If no API on the device defines the specific permission
150: * requested then it must be reported as denied.
151: * If the status of the permission is not known because it might
152: * require a user interaction then it should be reported as unknown.
153: *
154: * @param permission to check if denied, allowed, or unknown.
155: * @return 0 if the permission is denied; 1 if the permission is allowed;
156: * -1 if the status is unknown
157: */
158: public int checkPermission(String permission) {
159: boolean found = false;
160: int i;
161:
162: synchronized (this ) {
163: for (i = 0; i < Permissions.NUMBER_OF_PERMISSIONS; i++) {
164: if (Permissions.getName(i).equals(permission)) {
165: found = true;
166: break;
167: }
168: }
169:
170: if (!found) {
171: // report denied
172: return 0;
173: }
174:
175: switch (permissions[i]) {
176: case Permissions.ALLOW:
177: case Permissions.BLANKET_GRANTED:
178: // report allowed
179: return 1;
180:
181: case Permissions.SESSION:
182: if (sessionValues[i] == GRANTED) {
183: // report allowed
184: return 1;
185: }
186:
187: if (sessionValues[i] == DENIED) {
188: // report denied
189: return 0;
190: }
191:
192: // fall through
193: case Permissions.BLANKET:
194: case Permissions.ONESHOT:
195: // report unknown
196: return -1;
197:
198: default:
199: // Permissions.NEVER
200: break;
201: }
202:
203: // report denied
204: return 0;
205: }
206: }
207:
208: /**
209: * Check for permission and throw an exception if not allowed.
210: * May block to ask the user a question.
211: * <p>
212: * The title, and question strings will be translated,
213: * if a string resource is available.
214: * Since the strings can have substitution token in them, if there is a
215: * "%" it must changed to "%%". If a string has a %1, the app parameter
216: * will be substituted for it. If a string has a "%2, the resource
217: * parameter will be substituted for it. If a string has a %3, the
218: * extraValue parameter will be substituted for it.
219: *
220: * @param permission ID of the permission to check for,
221: * the ID must be from
222: * {@link com.sun.midp.security.Permissions}
223: * @param title Resource constant for the title of the dialog
224: * @param question Resource constant for the question to ask the user
225: * @param oneshotQuestion Resource constant for the oneshot question to
226: * ask the user
227: * @param app name of the application to insert into a string
228: * can be null if no %1 a string
229: * @param resource string to insert into a string,
230: * can be null if no %2 in a string
231: * @param extraValue string to insert into a string,
232: * can be null if no %3 in a string
233: *
234: * @return <code>true</code> if the permission interaction has permanently
235: * changed and the new state should be saved, this will only happen
236: * if the permission granted
237: *
238: * @exception SecurityException if the permission is not
239: * allowed by this token
240: * @exception InterruptedException if another thread interrupts the
241: * calling thread while this method is waiting to preempt the
242: * display.
243: */
244: public boolean checkForPermission(int permission, int title,
245: int question, int oneshotQuestion, String app,
246: String resource, String extraValue)
247: throws InterruptedException {
248:
249: return checkForPermission(permission, title, question,
250: oneshotQuestion, app, resource, extraValue, STD_EX_MSG);
251: }
252:
253: /**
254: * Check for permission and throw an exception if not allowed.
255: * May block to ask the user a question.
256: * <p>
257: * The title, question, and answer strings will be translated,
258: * if a string resource is available.
259: * Since the strings can have substitution token in them, if there is a
260: * "%" it must changed to "%%". If a string has a %1, the app parameter
261: * will be substituted for it. If a string has a "%2, the resource
262: * parameter will be substituted for it. If a string has a %3, the
263: * extraValue parameter will be substituted for it.
264: *
265: * @param permission ID of the permission to check for,
266: * the ID must be from
267: * {@link com.sun.midp.security.Permissions}
268: * @param title Resource constant for the title of the dialog
269: * @param question Resource constant for the question to ask user
270: * @param oneShotQuestion Resource constant for the oneshot question to
271: * ask the user
272: * @param app name of the application to insert into a string
273: * can be null if no %1 a string
274: * @param resource string to insert into a string,
275: * can be null if no %2 in a string
276: * @param extraValue string to insert into a string,
277: * can be null if no %3 in a string
278: * @param exceptionMsg message if a security exception is thrown
279: *
280: * @return <code>true</code> if the permission interaction has permanently
281: * changed and the new state should be saved, this will only happen
282: * if the permission granted
283: *
284: * @exception SecurityException if the permission is not
285: * allowed by this token
286: * @exception InterruptedException if another thread interrupts the
287: * calling thread while this method is waiting to preempt the
288: * display.
289: */
290: public boolean checkForPermission(int permission, int title,
291: int question, int oneShotQuestion, String app,
292: String resource, String extraValue, String exceptionMsg)
293: throws InterruptedException {
294:
295: if (permissions == null) {
296: /* totally trusted, all permissions allowed */
297: return false;
298: }
299:
300: synchronized (this ) {
301: if (permission >= 0 && permission < permissions.length) {
302: switch (permissions[permission]) {
303: case Permissions.ALLOW:
304: case Permissions.BLANKET_GRANTED:
305: return false;
306:
307: case Permissions.BLANKET:
308: /* This level means the question has not been asked yet. */
309: if (askUserForPermission(classSecurityToken,
310: trusted, title, question, app, resource,
311: extraValue)) {
312:
313: Permissions
314: .setPermissionGroup(permissions,
315: permission,
316: Permissions.BLANKET_GRANTED);
317:
318: return true;
319: }
320:
321: Permissions.setPermissionGroup(permissions,
322: permission, Permissions.BLANKET_DENIED);
323: break;
324:
325: case Permissions.SESSION:
326: if (sessionValues[permission] == GRANTED) {
327: return false;
328: }
329:
330: if (sessionValues[permission] == DENIED) {
331: break;
332: }
333:
334: if (askUserForPermission(classSecurityToken,
335: trusted, title, question, app, resource,
336: extraValue)) {
337: /*
338: * Save the fact that the question has already
339: * been asked this session.
340: */
341: Permissions.setPermissionGroup(sessionValues,
342: permission, GRANTED);
343:
344: return false;
345: }
346:
347: /*
348: * Save the fact that the question has already
349: * been asked this session.
350: */
351: Permissions.setPermissionGroup(sessionValues,
352: permission, DENIED);
353: break;
354:
355: case Permissions.ONESHOT:
356: if (askUserForPermission(classSecurityToken,
357: trusted, title, oneShotQuestion, app,
358: resource, extraValue)) {
359: return false;
360: }
361:
362: break;
363:
364: default:
365: // Permissions.NEVER
366: break;
367: } // switch
368: } // if
369:
370: throw new SecurityException(exceptionMsg);
371: } // synchronized
372: }
373:
374: /**
375: * Ask the user yes/no permission question.
376: *
377: * @param token security token with the permission to preempt the
378: * foreground display
379: * @param trusted true to display the trusted icon, false to display the
380: * untrusted icon
381: * @param title Resource constant for the title of the dialog
382: * @param question Resource constant for the question to ask user
383: * @param app name of the application to insert into a string
384: * can be null if no %1 a string
385: * @param resource string to insert into a string,
386: * can be null if no %2 in a string
387: * @param extraValue string to insert into a string,
388: * can be null if no %3 in a string
389: *
390: * @return true if the user says yes else false
391: *
392: * @exception InterruptedException if another thread interrupts the
393: * calling thread while this method is waiting to preempt the
394: * display.
395: */
396: public static boolean askUserForPermission(SecurityToken token,
397: boolean trusted, int title, int question, String app,
398: String resource, String extraValue)
399: throws InterruptedException {
400:
401: PermissionDialog dialog = new PermissionDialog(token, trusted,
402: title, question, app, resource, extraValue);
403:
404: return dialog.waitForAnswer();
405: }
406:
407: /**
408: * Initializes the security token for this class, so it can
409: * perform actions that a normal MIDlet Suite cannot.
410: *
411: * @param token security token for this class.
412: */
413: static void initSecurityToken(SecurityToken token) {
414: if (classSecurityToken != null) {
415: return;
416: }
417:
418: classSecurityToken = token;
419: }
420: }
421:
422: /** Implements security permission dialog. */
423: class PermissionDialog implements CommandListener {
424: /** Caches the display manager reference. */
425: private DisplayEventHandler displayEventHandler;
426:
427: /** Permission Alert. */
428: private Alert alert;
429:
430: /** Command object for "Yes" command. */
431: private Command yesCmd = new Command(Resource
432: .getString(ResourceConstants.YES), Command.OK, 1);
433: /** Command object for "No" command. */
434: private Command noCmd = new Command(Resource
435: .getString(ResourceConstants.NO), Command.BACK, 1);
436: /** Holds the preempt token so the form can end. */
437: private Object preemptToken;
438:
439: /** Holds the answer to the security question. */
440: private boolean answer;
441:
442: /**
443: * Construct permission dialog.
444: * <p>
445: * The title, question, and answer strings will be translated,
446: * if a string resource is available.
447: * Since the strings can have substitution token in them, if there is a
448: * "%" it must changed to "%%". If a string has a %1, the app parameter
449: * will be substituted for it. If a string has a "%2, the resource
450: * parameter will be substituted for it. If a string has a %3, the
451: * extraValue parameter will be substituted for it.
452: *
453: * @param token security token with the permission to preempt the
454: * foreground display
455: * @param trusted true to display the trusted icon, false to display the
456: * untrusted icon
457: * @param title Resource constant for the title of the dialog
458: * @param question Resource constant for the question to ask user
459: * @param app name of the application to insert into a string
460: * can be null if no %1 a string
461: * @param resource string to insert into a string,
462: * can be null if no %2 in a string
463: * @param extraValue string to insert into a string,
464: * can be null if no %3 in a string
465: *
466: * @exception InterruptedException if another thread interrupts the
467: * calling thread while this method is waiting to preempt the
468: * display.
469: */
470: PermissionDialog(SecurityToken token, boolean trusted, int title,
471: int question, String app, String resource, String extraValue)
472: throws InterruptedException {
473: String[] substitutions = { app, resource, extraValue };
474: String iconFilename;
475: RandomAccessStream stream;
476: byte[] rawPng;
477: Image icon;
478: String configRoot = File
479: .getConfigRoot(Constants.INTERNAL_STORAGE_ID);
480:
481: alert = new Alert(Resource.getString(title, substitutions));
482:
483: displayEventHandler = DisplayEventHandlerFactory
484: .getDisplayEventHandler(token);
485:
486: if (trusted) {
487: iconFilename = configRoot + "trusted_icon.png";
488: } else {
489: iconFilename = configRoot + "untrusted_icon.png";
490: }
491:
492: stream = new RandomAccessStream(token);
493: try {
494: stream.connect(iconFilename, Connector.READ);
495: rawPng = new byte[stream.getSizeOf()];
496: stream.readBytes(rawPng, 0, rawPng.length);
497: stream.disconnect();
498: icon = Image.createImage(rawPng, 0, rawPng.length);
499: alert.setImage(icon);
500: } catch (java.io.IOException noImage) {
501: }
502:
503: alert.setString(Resource.getString(question, substitutions));
504: alert.addCommand(noCmd);
505: alert.addCommand(yesCmd);
506: alert.setCommandListener(this );
507: preemptToken = displayEventHandler.preemptDisplay(alert, true);
508: }
509:
510: /**
511: * Waits for the user's answer.
512: *
513: * @return user's answer
514: */
515: boolean waitForAnswer() {
516: synchronized (this ) {
517: if (preemptToken == null) {
518: return false;
519: }
520:
521: if (EventQueue.isDispatchThread()) {
522: // Developer programming error
523: throw new RuntimeException(
524: "Blocking call performed in the event thread");
525: }
526:
527: try {
528: wait();
529: } catch (Throwable t) {
530: return false;
531: }
532:
533: return answer;
534: }
535: }
536:
537: /**
538: * Sets the user's answer and notifies waitForAnswer and
539: * ends the form.
540: *
541: * @param theAnswer user's answer
542: */
543: private void setAnswer(boolean theAnswer) {
544: synchronized (this ) {
545: answer = theAnswer;
546:
547: /*
548: * Since this may be the only display, clear the alert,
549: * so the user will not be confused by alert text still
550: * displaying.
551: *
552: * The case should happen when running TCK test MIDlets in
553: * SVM mode.
554: */
555: alert.setTitle(null);
556: alert.setString(null);
557: alert.setImage(null);
558: alert.addCommand(new Command("", 1, 1));
559: alert.removeCommand(noCmd);
560: alert.removeCommand(yesCmd);
561:
562: displayEventHandler.donePreempting(preemptToken);
563:
564: notify();
565: }
566:
567: }
568:
569: /**
570: * Respond to a command issued on security question form.
571: *
572: * @param c command activated by the user
573: * @param s the Displayable the command was on.
574: */
575: public void commandAction(Command c, Displayable s) {
576: if (c == yesCmd) {
577: setAnswer(true);
578: return;
579: }
580:
581: setAnswer(false);
582: }
583: }
|