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.satsa.pki;
028:
029: import com.sun.midp.io.j2me.storage.File;
030: import com.sun.midp.io.j2me.storage.RandomAccessStream;
031: import com.sun.midp.io.j2me.apdu.APDUManager;
032: import com.sun.midp.i18n.Resource;
033: import com.sun.midp.i18n.ResourceConstants;
034: import com.sun.midp.midlet.MIDletStateHandler;
035: import com.sun.midp.main.Configuration;
036: import com.sun.midp.configurator.Constants;
037: import com.sun.midp.security.ImplicitlyTrustedClass;
038: import com.sun.midp.security.SecurityToken;
039: import com.sun.satsa.security.SecurityInitializer;
040: import com.sun.satsa.util.*;
041:
042: import javax.microedition.io.Connector;
043: import javax.microedition.pki.UserCredentialManager;
044: import javax.microedition.pki.UserCredentialManagerException;
045: import javax.microedition.securityservice.CMSMessageSignatureServiceException;
046: import java.io.DataInputStream;
047: import java.io.DataOutputStream;
048: import java.io.IOException;
049: import java.util.Vector;
050: import com.sun.midp.security.Permissions;
051:
052: /**
053: * This class provides implementation of methods defined by
054: * javax.microedition.pki.UserCredentialManager and
055: * javax.microedition.securityservice.CMSMessageSignatureService
056: * classes.
057: */
058: public class PKIManager {
059:
060: /** Signature operation identifier. */
061: public static final int AUTHENTICATE_DATA = 0;
062: /** Signature operation identifier. */
063: public static final int AUTHENTICATE_STRING = 1;
064: /** Signature operation identifier. */
065: public static final int SIGN_STRING = 2;
066:
067: /**
068: * Inner class to request security token from SecurityInitializer.
069: * SecurityInitializer should be able to check this inner class name.
070: */
071: static private class SecurityTrusted implements
072: ImplicitlyTrustedClass {
073: };
074:
075: /** This class has a different security domain than the MIDlet suite */
076: private static SecurityToken classSecurityToken = SecurityInitializer
077: .requestToken(new SecurityTrusted());
078:
079: /**
080: * Storage name for identifiers of public keys for which
081: * certificates are expected. */
082: private static final String CSR_ID_FILE = "_csr.id";
083:
084: /**
085: * Contains identifiers of public keys for which certificates are
086: * expected if this information is not stored in persistent storage.
087: */
088: private static Vector tmpCSRList;
089:
090: /**
091: * Creates a DER encoded PKCS#10 certificate enrollment request.
092: * @param nameInfo the distinguished name to be included in the
093: * PKCS#10 certificate signing request.
094: * @param algorithm the Object Identifier (OID) for the public key
095: * algorithm to use.
096: * @param keyLen the key length.
097: * @param keyUsage the functionality for which the key is marked
098: * inside the security element.
099: * @param securityElementID identifies the security element on which
100: * the key resides.
101: * @param securityElementPrompt guides a user to insert the correct
102: * security element, if a suitable security element is removable and
103: * not detected.
104: * @param forceKeyGen if set to true a new key MUST be generated.
105: * @return DER encoded PKCS#10 certificate enrollment request
106: * @throws UserCredentialManagerException if an error occurs while
107: * generating the certificate request
108: * @throws CMSMessageSignatureServiceException if an error occurs
109: * while signing the certificate request
110: * @throws SecurityException if a PIN is blocked due to an excessive
111: * number of incorrect PIN entries
112: */
113: public static synchronized byte[] generateCSR(String nameInfo,
114: String algorithm, int keyLen, int keyUsage,
115: String securityElementID, String securityElementPrompt,
116: boolean forceKeyGen) throws UserCredentialManagerException,
117: CMSMessageSignatureServiceException {
118:
119: if (nameInfo != null && nameInfo.trim().length() == 0) {
120: throw new IllegalArgumentException("Invalid name");
121: }
122:
123: try {
124: Utils.StringToOID(algorithm);
125: } catch (IllegalArgumentException e) {
126: throw new IllegalArgumentException("Invalid algorithm");
127: }
128:
129: if (keyLen <= 0 || keyLen > 20480) {
130: throw new IllegalArgumentException("Invalid key length");
131: }
132:
133: if (keyUsage != UserCredentialManager.KEY_USAGE_AUTHENTICATION
134: && keyUsage != UserCredentialManager.KEY_USAGE_NON_REPUDIATION) {
135: throw new IllegalArgumentException("Invalid key usage");
136: }
137:
138: if (!algorithm.equals(UserCredentialManager.ALGORITHM_RSA)) {
139: throw new UserCredentialManagerException(
140: UserCredentialManagerException.SE_NO_KEYS);
141: }
142:
143: int slotCount = APDUManager.getSlotCount();
144:
145: while (true) {
146:
147: for (int i = 0; i < slotCount; i++) {
148:
149: WIMApplication w = WIMApplication
150: .getInstance(classSecurityToken, i,
151: securityElementID, false);
152: if (w == null) {
153: continue;
154: }
155: try {
156: Vector CSRs = loadCSRList();
157: byte[] CSR = w.generateCSR(nameInfo, keyLen,
158: keyUsage, forceKeyGen, CSRs);
159: storeCSRList(CSRs);
160: return CSR;
161: } finally {
162: w.done();
163: }
164: }
165:
166: // WIM application is not found
167:
168: if (securityElementPrompt != null) {
169: try {
170: if (MessageDialog
171: .showMessage(
172: classSecurityToken,
173: Resource
174: .getString(ResourceConstants.JSR177_WIM_NOT_FOUND),
175: securityElementPrompt, true) != -1) {
176: continue;
177: }
178: } catch (InterruptedException e) {
179: }
180: }
181: throw new UserCredentialManagerException(
182: UserCredentialManagerException.SE_NOT_FOUND);
183: }
184: }
185:
186: /**
187: * Adds a user certificate to a certificate store.
188: * @param certDisplayName the user friendly name associated with the
189: * certificate.
190: * @param pkiPath the DER encoded PKIPath containing user
191: * certificate and certificate authority certificates.
192: * @param uri a URI that resolves to a X.509v3 certificate.
193: * @return true if successful
194: * @throws UserCredentialManagerException if an error occurs while
195: * adding a user credential
196: * @throws SecurityException if a PIN is blocked due to an excessive
197: * number of incorrect PIN entries
198: */
199: public static synchronized boolean addCredential(
200: String certDisplayName, byte[] pkiPath, String uri)
201: throws UserCredentialManagerException {
202:
203: // check parameters
204:
205: if (certDisplayName == null
206: || certDisplayName.trim().length() == 0) {
207: throw new IllegalArgumentException("Invalid name");
208: }
209: certDisplayName = certDisplayName.trim();
210:
211: TLV t = null;
212: String info = null;
213:
214: try {
215: t = new TLV(pkiPath, 0).child;
216: TLV v = t;
217: if (v != null) {
218: while (v.next != null) {
219: v = v.next;
220: }
221: info = Certificate.getInfo(v);
222: }
223: } catch (TLVException e) {
224: } // ignored
225:
226: if (info == null) {
227: throw new IllegalArgumentException("Invalid pkiPath");
228: }
229:
230: // ask user
231:
232: try {
233: if (MessageDialog
234: .showMessage(
235: classSecurityToken,
236: Resource
237: .getString(ResourceConstants.AMS_CONFIRMATION),
238: Resource
239: .getString(ResourceConstants.JSR177_CERTIFICATE_STORED)
240: + "\n\n"
241: +
242: // "Label" +
243: Resource
244: .getString(ResourceConstants.JSR177_CERTIFICATE_LABEL)
245: + ": "
246: + certDisplayName
247: + "\n\n"
248: + info + "\n\n", true) == Dialog.CANCELLED) {
249: return false;
250: }
251: } catch (InterruptedException e) {
252: return false;
253: }
254:
255: // save certificates
256:
257: Vector CSRs = loadCSRList();
258:
259: int slotCount = APDUManager.getSlotCount();
260:
261: for (int i = 0; i < slotCount; i++) {
262:
263: WIMApplication w = WIMApplication.getInstance(
264: classSecurityToken, i, null, false);
265:
266: if (w == null) {
267: continue;
268: }
269: try {
270: int result = w.addCredential(certDisplayName, t, CSRs);
271: if (result == WIMApplication.SUCCESS) {
272: storeCSRList(CSRs);
273: return true;
274: }
275: if (result == WIMApplication.CANCEL) {
276: return false;
277: }
278: if (result == WIMApplication.ERROR) {
279: break;
280: }
281: } catch (IllegalArgumentException e) {
282: throw e;
283: } catch (SecurityException e) {
284: throw e;
285: } finally {
286: w.done();
287: }
288: }
289: throw new UserCredentialManagerException(
290: UserCredentialManagerException.CREDENTIAL_NOT_SAVED);
291: }
292:
293: /**
294: * Removes a certificate from a certificate store.
295: * @param certDisplayName the user friendly name associated with the
296: * certificate.
297: * @param issuerAndSerialNumber the DER encoded ASN.1 structure that
298: * contains the certificate issuer and serial number.
299: * @param securityElementID identifies the security element on which
300: * the key resides.
301: * @param securityElementPrompt guides the user to insert the
302: * correct security element if the security element is removable and
303: * not detected.
304: * @return false if operation cancelled
305: * @throws UserCredentialManagerException if
306: * an error occurs while removing the credential
307: * @throws SecurityException if a PIN is blocked due to an excessive
308: * number of incorrect PIN entries
309: */
310: public static boolean removeCredential(String certDisplayName,
311: byte[] issuerAndSerialNumber, String securityElementID,
312: String securityElementPrompt)
313: throws UserCredentialManagerException {
314:
315: if (certDisplayName == null
316: || certDisplayName.trim().length() == 0) {
317: throw new IllegalArgumentException("Invalid name");
318: }
319: certDisplayName = certDisplayName.trim();
320:
321: TLV isn;
322: try {
323: isn = new TLV(issuerAndSerialNumber, 0);
324: /*
325: Compare the name with itself to make sure that it is
326: properly formatted
327: */
328: try {
329: RFC2253Name.compare(isn.child, isn.child);
330: } catch (IllegalArgumentException iae) {
331: throw new IllegalArgumentException(
332: "Invalid issuerAndSerialNumber");
333: }
334: if (isn.child.next == null
335: || isn.child.next.type != TLV.INTEGER_TYPE) {
336: throw new IllegalArgumentException(
337: "Invalid issuerAndSerialNumber");
338: }
339: } catch (TLVException e) {
340: throw new IllegalArgumentException(
341: "Invalid issuerAndSerialNumber");
342: }
343: int slotCount = APDUManager.getSlotCount();
344:
345: while (true) {
346:
347: for (int i = 0; i < slotCount; i++) {
348:
349: WIMApplication w = WIMApplication
350: .getInstance(classSecurityToken, i,
351: securityElementID, false);
352: if (w == null) {
353: continue;
354: }
355: try {
356: int result = w.removeCredential(certDisplayName,
357: isn);
358: if (result == WIMApplication.SUCCESS) {
359: return true;
360: }
361: if (result == WIMApplication.CANCEL) {
362: return false;
363: }
364: throw new UserCredentialManagerException(
365: UserCredentialManagerException.CREDENTIAL_NOT_FOUND);
366: } finally {
367: w.done();
368: }
369: }
370:
371: // WIM application is not found
372:
373: if (securityElementPrompt != null) {
374: try {
375: if (MessageDialog
376: .showMessage(
377: classSecurityToken,
378: Resource
379: .getString(ResourceConstants.JSR177_WIM_NOT_FOUND),
380: securityElementPrompt, true) != -1) {
381: continue;
382: }
383: } catch (InterruptedException e) {
384: }
385: }
386: throw new UserCredentialManagerException(
387: UserCredentialManagerException.SE_NOT_FOUND);
388: }
389: }
390:
391: /**
392: * Generates a signature.
393: * @param action type of signature operation.
394: * @param data data to be signed or null
395: * @param string string to be signed or null
396: * @param options signature format options
397: * @param caNames an array of Strings that contain the distinguished
398: * names of trusted certification authorities.
399: * @param securityElementPrompt guides a user to insert the correct
400: * security element if the security element is removable and not
401: * detected.
402: * @return the DER encoded signature, null if the signature
403: * generation was cancelled by the user before completion
404: * @throws CMSMessageSignatureServiceException if an error occurs
405: * during signature generation
406: * @throws UserCredentialManagerException if key not found
407: * @throws SecurityException if caller does not have permission
408: */
409: public static byte[] sign(int action, byte[] data, String string,
410: int options, String[] caNames, String securityElementPrompt)
411: throws CMSMessageSignatureServiceException,
412: UserCredentialManagerException {
413:
414: // Only CMSMessageSignatureService.sign() is
415: // protected by MIDP permissions
416: if (action == AUTHENTICATE_DATA) {
417: try {
418: MIDletStateHandler.getMidletStateHandler()
419: .getMIDletSuite().checkForPermission(
420: Permissions.SIGN_SERVICE, null);
421: } catch (InterruptedException ie) {
422: throw new SecurityException(
423: "Interrupted while trying to ask the user permission");
424: }
425: }
426:
427: if (action == AUTHENTICATE_DATA ? (data == null || data.length == 0)
428: : (string == null || string.length() == 0)) {
429: // IMPL_NOTE: specification ?
430: throw new IllegalArgumentException("Invalid data");
431: }
432:
433: /*
434: Parse the CA names, toTLV throws IllegalArgumentException
435: if necessary.
436: */
437:
438: TLV[] names = null;
439: if (caNames != null && caNames.length != 0) {
440: names = new TLV[caNames.length];
441: for (int i = 0; i < caNames.length; i++) {
442: names[i] = RFC2253Name.toTLV(caNames[i]);
443: }
444: }
445:
446: // ask user confirmation if necessary
447: if (action != AUTHENTICATE_DATA) {
448: try {
449: if (MessageDialog
450: .showMessage(
451: classSecurityToken,
452: Resource
453: .getString(ResourceConstants.JSR177_CONFIRM_SIGNATURE),
454: Resource
455: .getString(ResourceConstants.JSR177_STRING_TO_SIGN)
456: + string, true) != 1) {
457: return null;
458: }
459: } catch (InterruptedException e) {
460: throw new CMSMessageSignatureServiceException(
461: CMSMessageSignatureServiceException.SE_FAILURE);
462: }
463:
464: data = Utils.stringToBytes(string);
465: }
466:
467: int slotCount = APDUManager.getSlotCount();
468:
469: while (true) {
470:
471: for (int i = 0; i < slotCount; i++) {
472:
473: WIMApplication w = WIMApplication.getInstance(
474: classSecurityToken, i, null, true);
475: if (w == null) {
476: continue;
477: }
478: try {
479: return w.generateSignature(action == SIGN_STRING,
480: data, options, names);
481: } finally {
482: w.done();
483: }
484: }
485: // WIM application is not found
486:
487: if (securityElementPrompt != null) {
488: try {
489: if (MessageDialog
490: .showMessage(
491: classSecurityToken,
492: Resource
493: .getString(ResourceConstants.JSR177_WIM_NOT_FOUND),
494: securityElementPrompt, true) != -1) {
495: continue;
496: }
497: } catch (InterruptedException e) {
498: }
499: }
500: throw new UserCredentialManagerException(
501: UserCredentialManagerException.SE_NOT_FOUND);
502: }
503: }
504:
505: /**
506: * Loads the list of key identifiers for which certificates are
507: * expected.
508: * @return the list
509: */
510: private static Vector loadCSRList() {
511:
512: if (!persistentCSRList()) {
513: if (tmpCSRList == null) {
514: tmpCSRList = new Vector();
515: }
516: return tmpCSRList;
517: }
518:
519: Vector CSRs = new Vector();
520:
521: String storeName = File
522: .getStorageRoot(Constants.INTERNAL_STORAGE_ID)
523: + CSR_ID_FILE;
524: RandomAccessStream storage = new RandomAccessStream(
525: classSecurityToken);
526: DataInputStream dis;
527:
528: try {
529: storage.connect(storeName, Connector.READ);
530: dis = new DataInputStream(storage.openInputStream());
531: } catch (IOException ioe) {
532:
533: try {
534: storage.connect(storeName, Connector.READ_WRITE);
535: DataOutputStream dos = storage.openDataOutputStream();
536: dos.writeInt(0);
537: dos.flush();
538: dos.close();
539: dis = new DataInputStream(storage.openInputStream());
540: } catch (IOException openwe) {
541: return CSRs;
542: }
543: try {
544: storage.disconnect();
545: } catch (IOException e) {
546: } // ignored
547: return CSRs;
548: }
549:
550: try {
551: int count = dis.readInt();
552: while (count-- > 0) {
553: byte[] id = new byte[20];
554: dis.read(id, 0, 20);
555: CSRs.addElement(id);
556: }
557: } catch (IOException e) {
558: } // ignored
559: finally {
560: try {
561: storage.disconnect();
562: } catch (IOException e) {
563: } // ignored
564: }
565: return CSRs;
566: }
567:
568: /**
569: * Stores the list of key identifiers for which certificates are
570: * expected.
571: * @param CSRs the list
572: */
573: private static void storeCSRList(Vector CSRs) {
574:
575: if (!persistentCSRList()) {
576: return;
577: }
578:
579: String storeName = File
580: .getStorageRoot(Constants.INTERNAL_STORAGE_ID)
581: + CSR_ID_FILE;
582: RandomAccessStream storage = new RandomAccessStream(
583: classSecurityToken);
584: DataOutputStream dos;
585:
586: try {
587: storage.connect(storeName, Connector.WRITE);
588: dos = storage.openDataOutputStream();
589: int len = CSRs.size();
590: dos.writeInt(len);
591: for (int i = 0; i < len; i++) {
592: dos.write((byte[]) CSRs.elementAt(i));
593: }
594: dos.flush();
595: dos.close();
596: storage.truncate(4 + len * 20);
597: } catch (IOException openwe) {
598: } // ignored
599: finally {
600: try {
601: storage.disconnect();
602: } catch (IOException e) {
603: } // ignored
604: }
605: }
606:
607: /**
608: * Returns true if the list of generated CSR IDs must be stored
609: * in persistent storage.
610: * @return true if the list of generated CSR IDs must be stored
611: * in persistent storage
612: */
613: private static boolean persistentCSRList() {
614: return "true".equals(Configuration
615: .getProperty("com.sun.satsa.store_csr_list"));
616: }
617: }
|