0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.midp.mekeytool;
0028:
0029: import java.util.*;
0030: import java.io.*;
0031:
0032: import java.security.*;
0033: import java.security.cert.*;
0034: import java.security.interfaces.RSAPublicKey;
0035: import java.math.BigInteger;
0036:
0037: import com.sun.midp.publickeystore.*;
0038:
0039: /**
0040: * Manages the initial public keystore needed to bootstrap this MIDP
0041: * security implementation. It provides both a Java and a command line interface.
0042: * <p>
0043: * The anchor of trust on an ME (mobile equipment) are the public keys
0044: * loaded on it by the manufacturer, in MIDP implementation this is known
0045: * as the <i>ME keystore</i>. This tool does for the MIDP implementation
0046: * what the manufacturer must do for the ME so that trusted MIDP
0047: * applications can be authenticated.
0048: * @see #main(String[])
0049: */
0050: public class MEKeyTool {
0051: /** default MIDP application directory, see Utility.c getStorageRoot() */
0052: private final static String defaultAppDir = "appdb";
0053:
0054: /** default ME keystore filename, see com.sun.midp.Main.java */
0055: private final static String defaultKeystoreFilename = "_main.ks";
0056:
0057: /**
0058: * Maps byte codes that follow id-at (0x55 0x04) to corresponding name
0059: * component tags (e.g. Common Name, or CN, is 0x55, 0x04, 0x03 and
0060: * Country, or C, is 0x55, 0x04, 0x06). See getName. See X.520 for
0061: * the OIDs and RFC 1779 for the printable labels. Place holders for
0062: * unknown labels have a -1 as the first byte.
0063: */
0064: private static final String[] AttrLabel = { null, null, null, "CN", // Common name: id-at 3
0065: "SN", // Surname: id-at 4
0066: null, "C", // Country: id-at 6
0067: "L", // Locality: id-at 7
0068: "ST", // State or province: id-at 8
0069: "STREET", // Street address: id-at 9
0070: "O", // Organization: id-at 10
0071: "OU", // Organization unit: id-at 11
0072: };
0073:
0074: /** Email attribute label. */
0075: private static final String EMAIL_ATTR_LABEL = "EmailAddress";
0076:
0077: /** Email attribute object identifier. */
0078: private static final byte[] EMAIL_ATTR_OID = { (byte) 0x2a,
0079: (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7,
0080: (byte) 0x0d, (byte) 0x01, (byte) 0x09, (byte) 0x01 };
0081:
0082: /** read-writable ME keystore that does not depend on SSL */
0083: private PublicKeyStoreBuilderBase keystore;
0084:
0085: /** the state for getFirstKey and getNextKey */
0086: private int nextKeyToGet;
0087:
0088: /**
0089: * Performs the command specified in the first argument.
0090: * <p>
0091: * Exits with a 0 status if the command was successful.
0092: * Exits and prints out an error message with a -1 status if the command
0093: * failed.</p>
0094: * <p><pre>
0095: *MEKeyTool supports the following commands:
0096: *
0097: * no args - same has -help
0098: * -import - import a public key from a JCE keystore
0099: * into a ME keystore
0100: * -delete - delete a key from a ME keystore
0101: * -help - print a usage summary
0102: * -list - list the owner and validity period of each
0103: * key in a ME keystore
0104: *
0105: *Parameters for (commands):
0106: *
0107: * -MEkeystore <filename of the ME keystore> (optional for all)
0108: * -keystore <filename of the JCA keystore> (optional import)
0109: * -storepass <password for the JCA keystore> (optional import)
0110: * -alias <short string ID of a key in a JCA keystore> (import)
0111: * -domain <security domain of the ME key> (optional import)
0112: * -owner <name of the owner of a ME key> (delete)
0113: * -number <key number starting a 1 of a ME key> (delete)
0114: *
0115: *Defaults:
0116: *
0117: * -MEkeystore appdir/main.ks
0118: * -keystore <user's home dir>/.keystore
0119: * -domain untrusted
0120: * </pre>
0121: * @param args command line arguments
0122: */
0123: public static void main(String[] args) {
0124: File meKeystoreFile = null;
0125:
0126: if (args.length == 0) {
0127: System.out.println("\n Error: No command given");
0128: displayUsage();
0129: System.exit(-1);
0130: }
0131:
0132: if (args[0].equals("-help")) {
0133: // user just needs help with the arguments
0134: displayUsage();
0135: System.exit(0);
0136: }
0137:
0138: // start with the default keystore file
0139: meKeystoreFile = new File(defaultAppDir,
0140: defaultKeystoreFilename);
0141:
0142: try {
0143: if (args[0].equals("-import")) {
0144: importCommand(meKeystoreFile, args);
0145: System.exit(0);
0146: }
0147:
0148: if (args[0].equals("-delete")) {
0149: deleteCommand(meKeystoreFile, args);
0150: System.exit(0);
0151: }
0152:
0153: if (args[0].equals("-list")) {
0154: listCommand(meKeystoreFile, args);
0155: System.exit(0);
0156: }
0157:
0158: throw new UsageException(" Invalid command: " + args[0]);
0159: } catch (Exception e) {
0160: System.out.println("\n Error: " + e.getMessage());
0161:
0162: if (e instanceof UsageException) {
0163: displayUsage();
0164: }
0165:
0166: System.exit(-1);
0167: }
0168: }
0169:
0170: /**
0171: * Display the usage text to standard output.
0172: */
0173: private static void displayUsage() {
0174: System.out
0175: .println("\n MEKeyTool argument combinations:\n\n"
0176: + " -help\n"
0177: + " -import [-MEkeystore <filename>] "
0178: + "[-keystore <filename>]\n"
0179: + " [-storepass <password>] -alias <key alias> "
0180: + "[-domain <domain>]\n"
0181: + " -list [-MEkeystore <filename>]\n"
0182: + " -delete [-MEkeystore <filename>]\n"
0183: + " (-owner <owner name> | -number <key number>)\n"
0184: + "\n"
0185: + " The default for -MEkeystore is \"appdb/_main.ks\".\n"
0186: + " The default for -keystore is \"$HOME/.keystore\".\n");
0187: }
0188:
0189: /**
0190: * Process the command line arguments for the import command and
0191: * then imports a public key from a JCA keystore to ME keystore.
0192: * This method assumes the first argument is the import command
0193: * and skips it.
0194: * @param meKeystoreFile ME keystore abstract file name
0195: * @param args command line arguments
0196: * @exception Exception if an unrecoverable error occurs
0197: */
0198: private static void importCommand(File meKeystoreFile, String[] args)
0199: throws Exception {
0200: String jcaKeystoreFilename = null;
0201: String keystorePassword = null;
0202: String alias = null;
0203: String domain = "identified";
0204: MEKeyTool keyTool;
0205:
0206: for (int i = 1; i < args.length; i++) {
0207: try {
0208: if (args[i].equals("-MEkeystore")) {
0209: i++;
0210: meKeystoreFile = new File(args[i]);
0211: } else if (args[i].equals("-keystore")) {
0212: i++;
0213: jcaKeystoreFilename = args[i];
0214: } else if (args[i].equals("-storepass")) {
0215: i++;
0216: keystorePassword = args[i];
0217: } else if (args[i].equals("-alias")) {
0218: i++;
0219: alias = args[i];
0220: } else if (args[i].equals("-domain")) {
0221: i++;
0222: domain = args[i];
0223: } else {
0224: throw new UsageException(
0225: "Invalid argument for import command: "
0226: + args[i]);
0227: }
0228: } catch (ArrayIndexOutOfBoundsException e) {
0229: throw new UsageException("Missing value for "
0230: + args[--i]);
0231: }
0232: }
0233:
0234: if (jcaKeystoreFilename == null) {
0235: jcaKeystoreFilename = System.getProperty("user.home")
0236: + File.separator + ".keystore";
0237: }
0238:
0239: if (alias == null) {
0240: throw new Exception("J2SE key alias was not given");
0241: }
0242:
0243: try {
0244: keyTool = new MEKeyTool(meKeystoreFile);
0245: } catch (FileNotFoundException fnfe) {
0246: keyTool = new MEKeyTool();
0247: }
0248:
0249: keyTool.importKeyFromJcaKeystore(jcaKeystoreFilename,
0250: keystorePassword, alias, domain);
0251: keyTool.saveKeystore(meKeystoreFile);
0252: }
0253:
0254: /**
0255: * Process the command line arguments for the delete command and
0256: * then delete a public key from a ME keystore.
0257: * This method assumes the first argument is the delete command
0258: * and skips it.
0259: * @param meKeystoreFile ME keystore abstract file name
0260: * @param args command line arguments
0261: * @exception Exception if an unrecoverable error occurs
0262: */
0263: private static void deleteCommand(File meKeystoreFile, String[] args)
0264: throws Exception {
0265: String owner = null;
0266: int keyNumber = -1;
0267: boolean keyNumberGiven = false;
0268: MEKeyTool keyTool;
0269:
0270: for (int i = 1; i < args.length; i++) {
0271: try {
0272: if (args[i].equals("-MEkeystore")) {
0273: i++;
0274: meKeystoreFile = new File(args[i]);
0275: } else if (args[i].equals("-owner")) {
0276: i++;
0277: owner = args[i];
0278: } else if (args[i].equals("-number")) {
0279: keyNumberGiven = true;
0280: i++;
0281: try {
0282: keyNumber = Integer.parseInt(args[i]);
0283: } catch (NumberFormatException e) {
0284: throw new UsageException(
0285: "Invalid number for the -number argument: "
0286: + args[i]);
0287: }
0288: } else {
0289: throw new UsageException(
0290: "Invalid argument for the delete command: "
0291: + args[i]);
0292: }
0293: } catch (ArrayIndexOutOfBoundsException e) {
0294: throw new UsageException("Missing value for "
0295: + args[--i]);
0296: }
0297: }
0298:
0299: if (owner == null && !keyNumberGiven) {
0300: throw new UsageException(
0301: "Neither key -owner or -number was not given");
0302: }
0303:
0304: if (owner != null && keyNumberGiven) {
0305: throw new UsageException(
0306: "-owner and -number cannot be used " + "together");
0307: }
0308:
0309: keyTool = new MEKeyTool(meKeystoreFile);
0310:
0311: if (owner != null) {
0312: if (!keyTool.deleteKey(owner)) {
0313: throw new UsageException("Key not found for: " + owner);
0314: }
0315: } else {
0316: try {
0317: keyTool.deleteKey(keyNumber - 1);
0318: } catch (ArrayIndexOutOfBoundsException e) {
0319: throw new UsageException(
0320: "Invalid number for the -number "
0321: + "delete option: " + keyNumber);
0322: }
0323: }
0324:
0325: keyTool.saveKeystore(meKeystoreFile);
0326: }
0327:
0328: /**
0329: * Process the command line arguments for the list command and
0330: * then list the public keys of a ME keystore.
0331: * This method assumes the first argument is the list command
0332: * and skips it.
0333: * @param meKeystoreFile ME keystore abstract file name
0334: * @param args command line arguments
0335: * @exception Exception if an unrecoverable error occurs
0336: */
0337: private static void listCommand(File meKeystoreFile, String[] args)
0338: throws Exception {
0339: MEKeyTool keyTool;
0340: PublicKeyInfo key;
0341:
0342: for (int i = 1; i < args.length; i++) {
0343: try {
0344: if (args[i].equals("-MEkeystore")) {
0345: i++;
0346: meKeystoreFile = new File(args[i]);
0347: } else {
0348: throw new UsageException(
0349: "Invalid argument for the list "
0350: + "command: " + args[i]);
0351: }
0352: } catch (ArrayIndexOutOfBoundsException e) {
0353: throw new UsageException("Missing value for "
0354: + args[--i]);
0355: }
0356: }
0357:
0358: keyTool = new MEKeyTool(meKeystoreFile);
0359: key = keyTool.getFirstKey();
0360: for (int i = 1; key != null; i++) {
0361: System.out.println("Key " + Integer.toString(i));
0362: System.out.println(formatKeyInfo(key));
0363: key = keyTool.getNextKey();
0364: }
0365:
0366: System.out.println("");
0367: }
0368:
0369: /**
0370: * Constructs a MEKeyTool with an empty keystore.
0371: */
0372: public MEKeyTool() {
0373: keystore = new PublicKeyStoreBuilderBase();
0374: }
0375:
0376: /**
0377: * Constructs a MEKeyTool and loads its keystore using a filename.
0378: * @param meKeystoreFilename serialized keystore file
0379: * @exception FileNotFoundException if the file does not exist, is a
0380: * directory rather than a regular file, or for some other reason
0381: * cannot be opened for reading.
0382: * @exception IOException if the key storage was corrupted
0383: */
0384: public MEKeyTool(String meKeystoreFilename)
0385: throws FileNotFoundException, IOException {
0386:
0387: FileInputStream input;
0388:
0389: input = new FileInputStream(new File(meKeystoreFilename));
0390:
0391: try {
0392: keystore = new PublicKeyStoreBuilderBase(input);
0393: } finally {
0394: input.close();
0395: }
0396: };
0397:
0398: /**
0399: * Constructs a MEKeyTool and loads its keystore from a file.
0400: * @param meKeystoreFile serialized keystore file
0401: * @exception FileNotFoundException if the file does not exist, is a
0402: * directory rather than a regular file, or for some other reason
0403: * cannot be opened for reading.
0404: * @exception IOException if the key storage was corrupted
0405: */
0406: public MEKeyTool(File meKeystoreFile) throws FileNotFoundException,
0407: IOException {
0408:
0409: FileInputStream input;
0410:
0411: input = new FileInputStream(meKeystoreFile);
0412:
0413: try {
0414: keystore = new PublicKeyStoreBuilderBase(input);
0415: } finally {
0416: input.close();
0417: }
0418: };
0419:
0420: /**
0421: * Constructs a MEKeyTool and loads its keystore from a stream.
0422: * @param meKeystoreStream serialized keystore stream
0423: * @exception IOException if the key storage was corrupted
0424: */
0425: public MEKeyTool(InputStream meKeystoreStream) throws IOException {
0426: keystore = new PublicKeyStoreBuilderBase(meKeystoreStream);
0427: };
0428:
0429: /**
0430: * Copies a key from a Standard Edition keystore into the ME keystore.
0431: * @param jcakeystoreFilename name of the serialized keystore
0432: * @param keystorePassword password to unlock the keystore
0433: * @param alias the ID of the key in the SE keystore
0434: * @param domain security domain of any application authorized
0435: * with the corresponding private key
0436: */
0437: public void importKeyFromJcaKeystore(String jcakeystoreFilename,
0438: String keystorePassword, String alias, String domain)
0439: throws IOException, GeneralSecurityException {
0440: FileInputStream keystoreStream;
0441: KeyStore jcaKeystore;
0442:
0443: // Load the keystore
0444: keystoreStream = new FileInputStream(new File(
0445: jcakeystoreFilename));
0446:
0447: try {
0448: jcaKeystore = KeyStore.getInstance(KeyStore
0449: .getDefaultType());
0450:
0451: if (keystorePassword == null) {
0452: jcaKeystore.load(keystoreStream, null);
0453: } else {
0454: jcaKeystore.load(keystoreStream, keystorePassword
0455: .toCharArray());
0456: }
0457: } finally {
0458: keystoreStream.close();
0459: }
0460:
0461: importKeyFromJcaKeystore(jcaKeystore, alias, domain);
0462: }
0463:
0464: /**
0465: * Copies a key from a Standard Edition keystore into the ME keystore.
0466: * @param jcaKeystore loaded JCA keystore
0467: * @param alias the ID of the key in the SE keystore
0468: * @param domain security domain of any application authorized
0469: * with the corresponding private key
0470: */
0471: public void importKeyFromJcaKeystore(KeyStore jcaKeystore,
0472: String alias, String domain) throws IOException,
0473: GeneralSecurityException {
0474: X509Certificate cert;
0475: byte[] der;
0476: TLV tbsCert;
0477: TLV subjectName;
0478: RSAPublicKey rsaKey;
0479: String owner;
0480: long notBefore;
0481: long notAfter;
0482: byte[] rawModulus;
0483: int i;
0484: int keyLen;
0485: byte[] modulus;
0486: byte[] exponent;
0487: Vector keys;
0488:
0489: // get the cert from the keystore
0490: try {
0491: cert = (X509Certificate) jcaKeystore.getCertificate(alias);
0492: } catch (ClassCastException cce) {
0493: throw new CertificateException("Certificate not X.509 type");
0494: }
0495:
0496: if (cert == null) {
0497: throw new CertificateException("Certificate not found");
0498: }
0499:
0500: /*
0501: * J2SE reorders the attributes when building a printable name
0502: * so we must build a printable name on our own.
0503: */
0504:
0505: /*
0506: * TBSCertificate ::= SEQUENCE {
0507: * version [0] EXPLICIT Version DEFAULT v1,
0508: * serialNumber CertificateSerialNumber,
0509: * signature AlgorithmIdentifier,
0510: * issuer Name,
0511: * validity Validity,
0512: * subject Name,
0513: * subjectPublicKeyInfo SubjectPublicKeyInfo,
0514: * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
0515: * -- If present, version shall be v2 or v3
0516: * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
0517: * -- If present, version shall be v2 or v3
0518: * extensions [3] EXPLICIT Extensions OPTIONAL
0519: * -- If present, version shall be v3
0520: * }
0521: */
0522: der = cert.getTBSCertificate();
0523: tbsCert = new TLV(der, 0);
0524:
0525: // walk down the tree of TLVs to find the subject name
0526: try {
0527: // Top level a is Sequence, drop down to the first child
0528: subjectName = tbsCert.child;
0529:
0530: // skip the version if present.
0531: if (subjectName.type == TLV.VERSION_TYPE) {
0532: subjectName = subjectName.next;
0533: }
0534:
0535: // skip the serial number
0536: subjectName = subjectName.next;
0537:
0538: // skip the signature alg. id.
0539: subjectName = subjectName.next;
0540:
0541: // skip the issuer
0542: subjectName = subjectName.next;
0543:
0544: // skip the validity
0545: subjectName = subjectName.next;
0546:
0547: owner = parseDN(der, subjectName);
0548: } catch (NullPointerException e) {
0549: throw new CertificateException("TBSCertificate corrupt 1");
0550: } catch (IndexOutOfBoundsException e) {
0551: throw new CertificateException("TBSCertificate corrupt 2");
0552: }
0553:
0554: notBefore = cert.getNotBefore().getTime();
0555: notAfter = cert.getNotAfter().getTime();
0556:
0557: // get the key from the cert
0558: try {
0559: rsaKey = (RSAPublicKey) cert.getPublicKey();
0560: } catch (ClassCastException cce) {
0561: throw new RuntimeException(
0562: "Key in certificate is not an RSA key");
0563: }
0564:
0565: // get the key parameters from the key
0566: rawModulus = rsaKey.getModulus().toByteArray();
0567:
0568: /*
0569: * the modulus is given as the minimum positive integer,
0570: * will not padded to the bit size of the key, or may have a extra
0571: * pad to make it positive. SSL expects the key to be signature
0572: * bit size. but we cannot get that from the key, so we should
0573: * remove any zero pad bytes and then pad out to a multiple of 8 bytes
0574: */
0575: for (i = 0; i < rawModulus.length && rawModulus[i] == 0; i++)
0576: ;
0577:
0578: keyLen = rawModulus.length - i;
0579: keyLen = (keyLen + 7) / 8 * 8;
0580: modulus = new byte[keyLen];
0581:
0582: int k, j;
0583: for (k = rawModulus.length - 1, j = keyLen - 1; k >= 0
0584: && j >= 0; k--, j--) {
0585: modulus[j] = rawModulus[k];
0586: }
0587:
0588: exponent = rsaKey.getPublicExponent().toByteArray();
0589:
0590: // add the key
0591: keys = keystore.findKeys(owner);
0592: if (keys != null) {
0593: boolean duplicateKey = false;
0594:
0595: for (int n = 0; !duplicateKey && n < keys.size(); n++) {
0596: PublicKeyInfo key = (PublicKeyInfo) keys.elementAt(n);
0597:
0598: if (key.getOwner().equals(owner)) {
0599: byte[] temp = key.getModulus();
0600:
0601: if (modulus.length == temp.length) {
0602: duplicateKey = true;
0603: for (int m = 0; j < modulus.length
0604: && m < temp.length; m++) {
0605: if (modulus[m] != temp[m]) {
0606: duplicateKey = false;
0607: break;
0608: }
0609: }
0610: }
0611: }
0612: }
0613:
0614: if (duplicateKey) {
0615: throw new CertificateException(
0616: "Owner already has this key in the ME keystore");
0617: }
0618: }
0619:
0620: keystore.addKey(new PublicKeyInfo(owner, notBefore, notAfter,
0621: modulus, exponent, domain));
0622: }
0623:
0624: /**
0625: * Deletes the first public key matching the owner's distinguished name.
0626: * @param owner name of the key's owner
0627: * @return true, if the key was deleted, else false
0628: */
0629: public boolean deleteKey(String owner) {
0630: PublicKeyInfo key;
0631:
0632: for (int i = 0; i < keystore.numberOfKeys(); i++) {
0633: key = keystore.getKey(i);
0634: if (key.getOwner().equals(owner)) {
0635: keystore.deleteKey(i);
0636: return true;
0637: }
0638: }
0639:
0640: return false;
0641: };
0642:
0643: /**
0644: * Deletes a key by key number, 0 being the first public key.
0645: *
0646: * @param number number of the key
0647: *
0648: * @exception ArrayIndexOutOfBoundsException if an invalid number was
0649: * given.
0650: */
0651: public void deleteKey(int number) {
0652: keystore.deleteKey(number);
0653: };
0654:
0655: /**
0656: * Gets the first key in the keystore.
0657: * @return all the information related to the first key
0658: */
0659: protected PublicKeyInfo getFirstKey() {
0660: nextKeyToGet = 0;
0661: return getNextKey();
0662: };
0663:
0664: /**
0665: * Gets the next key after the previous one returned by
0666: * {@link #getFirstKey} or this method. If getFirstKey is not called
0667: * before the first call to this method, null will be returned.
0668: * @return all the information related to the next key, or null if
0669: * there are no more keys
0670: */
0671: protected PublicKeyInfo getNextKey() {
0672: PublicKeyInfo key;
0673:
0674: try {
0675: key = keystore.getKey(nextKeyToGet);
0676: } catch (ArrayIndexOutOfBoundsException e) {
0677: return null;
0678: }
0679:
0680: nextKeyToGet++;
0681:
0682: return key;
0683: };
0684:
0685: /**
0686: * Saves the keystore to a file.
0687: * @param meKeystoreFile serialized keystore file
0688: */
0689: public void saveKeystore(File meKeystoreFile) throws IOException {
0690: FileOutputStream output;
0691:
0692: output = new FileOutputStream(meKeystoreFile);
0693:
0694: keystore.serialize(output);
0695: output.close();
0696: }
0697:
0698: /**
0699: * Gets the read-write keystore this tool is manipulating.
0700: * For advanced users.
0701: * @return read-write keystore
0702: */
0703: public PublicKeyStoreBuilderBase getKeystore() {
0704: return keystore;
0705: }
0706:
0707: /**
0708: * Creates a string representation of a key that is displayed to a
0709: * user during a list command. The string does not include the modulus
0710: * and exponent.
0711: * @param keyInfo key to display
0712: * @return printable representation of the key
0713: */
0714: public static String formatKeyInfo(PublicKeyInfo keyInfo) {
0715: return " Owner: " + keyInfo.getOwner() + "\n Valid from "
0716: + (new Date(keyInfo.getNotBefore())).toString()
0717: + " to " + (new Date(keyInfo.getNotAfter())).toString()
0718: + "\n Security Domain: " + keyInfo.getDomain()
0719: + "\n Enabled: " + keyInfo.isEnabled();
0720: };
0721:
0722: /**
0723: * Parses a DER TLV tree into a printable distinguished name.
0724: *
0725: * @param buffer DER buffer
0726: * @param dn sequence of TLV nodes.
0727: *
0728: * @return printable name.
0729: *
0730: * @exception NullPointerException if the name is corrupt
0731: * @exception IndexOutOfBoundsException if the name is corrupt
0732: */
0733: private String parseDN(byte[] buffer, TLV dn) {
0734: TLV attribute;
0735: TLV type;
0736: TLV value;
0737: StringBuffer name = new StringBuffer(256);
0738:
0739: /*
0740: * Name ::= CHOICE { RDNSequence } # CHOICE does not encoded
0741: *
0742: * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
0743: *
0744: * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
0745: *
0746: * AttributeTypeAndValue ::= SEQUENCE {
0747: * type AttributeType,
0748: * value AttributeValue }
0749: *
0750: * AttributeType ::= OBJECT IDENTIFIER
0751: *
0752: * AttributeValue ::= ANY DEFINED BY AttributeType
0753: *
0754: * basically this means that each attribute value is 3 levels down
0755: */
0756:
0757: // sequence drop down a level
0758: attribute = dn.child;
0759:
0760: while (attribute != null) {
0761: if (attribute != dn.child) {
0762: name.append(";");
0763: }
0764:
0765: /*
0766: * we do not handle relative distinguished names yet
0767: * which should not be used by CAs anyway
0768: * so only take the first element of the sequence
0769: */
0770:
0771: type = attribute.child.child;
0772:
0773: /*
0774: * At this point we tag the name component, e.g. C= or hex
0775: * if unknown.
0776: */
0777: if ((type.length == 3)
0778: && (buffer[type.valueOffset] == 0x55)
0779: && (buffer[type.valueOffset + 1] == 0x04)) {
0780: // begins with id-at, so try to see if we have a label
0781: int temp = buffer[type.valueOffset + 2] & 0xFF;
0782: if ((temp < AttrLabel.length)
0783: && (AttrLabel[temp] != null)) {
0784: name.append(AttrLabel[temp]);
0785: } else {
0786: name.append(TLV.hexEncode(buffer, type.valueOffset,
0787: type.length, -1));
0788: }
0789: } else if (TLV.byteMatch(buffer, type.valueOffset,
0790: type.length, EMAIL_ATTR_OID, 0,
0791: EMAIL_ATTR_OID.length)) {
0792: name.append(EMAIL_ATTR_LABEL);
0793: } else {
0794: name.append(TLV.hexEncode(buffer, type.valueOffset,
0795: type.length, -1));
0796: }
0797:
0798: name.append("=");
0799:
0800: value = attribute.child.child.next;
0801: if (value.type == TLV.PRINTSTR_TYPE
0802: || value.type == TLV.TELETEXSTR_TYPE
0803: || value.type == TLV.UTF8STR_TYPE
0804: || value.type == TLV.IA5STR_TYPE
0805: || value.type == TLV.UNIVSTR_TYPE) {
0806: try {
0807: name.append(new String(buffer, value.valueOffset,
0808: value.length, "UTF-8"));
0809: } catch (UnsupportedEncodingException e) {
0810: throw new RuntimeException(e.toString());
0811: }
0812: } else {
0813: name.append(TLV.hexEncode(buffer, value.valueOffset,
0814: value.length, -1));
0815: }
0816:
0817: attribute = attribute.next;
0818: }
0819:
0820: return name.toString();
0821: }
0822: }
0823:
0824: /**
0825: * Used to represent each Type, Length, Value structure in a DER buffer.
0826: */
0827: class TLV {
0828: /** ASN context specific flag used in types (0x80). */
0829: static final int CONTEXT = 0x80;
0830: /** ASN constructed flag used in types (0x20). */
0831: static final int CONSTRUCTED = 0x20;
0832: /** ASN constructed flag used in types (0x20). */
0833: static final int EXPLICIT = CONSTRUCTED;
0834:
0835: /** ANY_STRING type used as a place holder. [UNIVERSAL 0] */
0836: static final int ANY_STRING_TYPE = 0x00; // our own impl
0837:
0838: /** ASN BOOLEAN type used in certificate parsing. [UNIVERSAL 1] */
0839: static final int BOOLEAN_TYPE = 1;
0840: /** ASN INTEGER type used in certificate parsing. [UNIVERSAL 2] */
0841: static final int INTEGER_TYPE = 2;
0842: /** ASN BIT STRING type used in certificate parsing. [UNIVERSAL 3] */
0843: static final int BITSTRING_TYPE = 3;
0844: /** ASN OCTET STRING type used in certificate parsing. [UNIVERSAL 4] */
0845: static final int OCTETSTR_TYPE = 4;
0846: /** ASN NULL type used in certificate parsing. [UNIVERSAL 5] */
0847: static final int NULL_TYPE = 5;
0848: /** ASN OBJECT ID type used in certificate parsing. [UNIVERSAL 6] */
0849: static final int OID_TYPE = 6;
0850: /** ASN UTF8String type used in certificate parsing. [UNIVERSAL 12] */
0851: static final int UTF8STR_TYPE = 12;
0852: /**
0853: * ASN SEQUENCE type used in certificate parsing.
0854: * [UNIVERSAL CONSTRUCTED 16]
0855: */
0856: static final int SEQUENCE_TYPE = CONSTRUCTED + 16;
0857: /**
0858: * ASN SET type used in certificate parsing.
0859: * [UNIVERSAL CONSTRUCTED 17]
0860: */
0861: static final int SET_TYPE = CONSTRUCTED + 17;
0862: /** ASN PrintableString type used in certificate parsing. [UNIVERSAL 19] */
0863: static final int PRINTSTR_TYPE = 19;
0864: /** ASN TELETEX STRING type used in certificate parsing. [UNIVERSAL 20] */
0865: static final int TELETEXSTR_TYPE = 20;
0866: /** ASN IA5 STRING type used in certificate parsing. [UNIVERSAL 22] */
0867: static final int IA5STR_TYPE = 22; // Used for EmailAddress
0868: /** ASN UCT time type used in certificate parsing [UNIVERSAL 23] */
0869: static final int UCT_TIME_TYPE = 23;
0870: /**
0871: * ASN Generalized time type used in certificate parsing.
0872: * [UNIVERSAL 24]
0873: */
0874: static final int GEN_TIME_TYPE = 24;
0875: /**
0876: * ASN UniversalString type used in certificate parsing.
0877: * [UNIVERSAL 28].
0878: */
0879: static final int UNIVSTR_TYPE = 28;
0880: /** ASN BIT STRING type used in certificate parsing. [UNIVERSAL 30] */
0881: static final int BMPSTR_TYPE = 30;
0882:
0883: /**
0884: * Context specific explicit type for certificate version.
0885: * [CONTEXT EXPLICIT 0]
0886: */
0887: static final int VERSION_TYPE = CONTEXT + EXPLICIT + 0;
0888: /**
0889: * Context specific explicit type for certificate extensions.
0890: * [CONTEXT EXPLICIT 3]
0891: */
0892: static final int EXTENSIONS_TYPE = CONTEXT + EXPLICIT + 3;
0893:
0894: /**
0895: * Checks if two byte arrays match.
0896: * <P />
0897: * @param a first byte array
0898: * @param aOff starting offset for comparison within a
0899: * @param aLen number of bytes of a to be compared
0900: * @param b second byte array
0901: * @param bOff starting offset for comparison within b
0902: * @param bLen number of bytes of b to be compared
0903: * @return true if aLen == bLen and the sequence of aLen bytes in a
0904: * starting at
0905: * aOff matches those in b starting at bOff, false otherwise
0906: */
0907: static boolean byteMatch(byte[] a, int aOff, int aLen, byte[] b,
0908: int bOff, int bLen) {
0909: if ((aLen != bLen) || (a.length < aOff + aLen)
0910: || (b.length < bOff + bLen)) {
0911: return false;
0912: }
0913:
0914: for (int i = 0; i < aLen; i++) {
0915: if (a[i + aOff] != b[i + bOff]) {
0916: return false;
0917: }
0918: }
0919:
0920: return true;
0921: }
0922:
0923: /**
0924: * Converts a subsequence of bytes into a printable OID,
0925: * a string of decimal digits, each separated by a ".".
0926: *
0927: * @param buffer byte array containing the bytes to be converted
0928: * @param offset starting offset of the byte subsequence inside b
0929: * @param length number of bytes to be converted
0930: *
0931: * @return printable OID
0932: */
0933: static String OIDtoString(byte[] buffer, int offset, int length) {
0934: StringBuffer result;
0935: int end;
0936: int t;
0937: int x;
0938: int y;
0939:
0940: if (length == 0) {
0941: return "";
0942: }
0943:
0944: result = new StringBuffer(40);
0945:
0946: end = offset + length;
0947:
0948: /*
0949: * first byte (t) always represents the first 2 values (x, y).
0950: * t = (x * 40) + y;
0951: */
0952: t = buffer[offset++] & 0xff;
0953: x = t / 40;
0954: y = t - (x * 40);
0955:
0956: result.append(x);
0957: result.append('.');
0958: result.append(y);
0959:
0960: x = 0;
0961: while (offset < end) {
0962: // 7 bit per byte, bit 8 = 0 means the end of a value
0963: x = x << 7;
0964:
0965: t = buffer[offset++];
0966: if (t >= 0) {
0967: x += t;
0968: result.append('.');
0969: result.append(x);
0970: x = 0;
0971: } else {
0972: x += t & 0x7f;
0973: }
0974: }
0975:
0976: return result.toString();
0977: }
0978:
0979: /** Hexadecimal digits. */
0980: static char[] hc = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
0981: '9', 'A', 'B', 'C', 'D', 'E', 'F' };
0982:
0983: /**
0984: * Converts a subsequence of bytes in a byte array into a
0985: * corresponding string of hexadecimal digits, each separated by a ":".
0986: *
0987: * @param b byte array containing the bytes to be converted
0988: * @param off starting offset of the byte subsequence inside b
0989: * @param len number of bytes to be converted
0990: * @param max print a single "+" instead of the bytes after max,
0991: * -1 for no max.
0992: * @return a string of corresponding hexadecimal digits or
0993: * an error string
0994: */
0995: static String hexEncode(byte[] b, int off, int len, int max) {
0996: char[] r;
0997: int v;
0998: int i;
0999: int j;
1000:
1001: if ((b == null) || (len == 0)) {
1002: return "";
1003: }
1004:
1005: if ((off < 0) || (len < 0)) {
1006: throw new ArrayIndexOutOfBoundsException();
1007: }
1008:
1009: r = new char[len * 3];
1010:
1011: for (i = 0, j = 0;;) {
1012: v = b[off + i] & 0xff;
1013: r[j++] = hc[v >>> 4];
1014: r[j++] = hc[v & 0x0f];
1015:
1016: i++;
1017: if (i >= len) {
1018: break;
1019: }
1020:
1021: if (i == max) {
1022: r[j++] = ' ';
1023: r[j++] = '+';
1024: break;
1025: }
1026:
1027: r[j++] = ':';
1028: }
1029:
1030: return (new String(r, 0, j));
1031: }
1032:
1033: /** Raw DER type. */
1034: int type;
1035: /** Number of bytes that make up the value. */
1036: int length;
1037: /** Offset of the value. */
1038: int valueOffset;
1039: /** Non-null for constructed types, the first child TLV. */
1040: TLV child;
1041: /** The next TLV in the parent sequence. */
1042: TLV next;
1043:
1044: /**
1045: * Construct a TLV structure, recursing down for constructed types.
1046: *
1047: * @param buffer DER buffer
1048: * @param offset where to start parsing
1049: *
1050: * @exception IndexOutOfBoundException if the DER is corrupt
1051: */
1052: TLV(byte[] buffer, int offset) {
1053: boolean constructed;
1054: int size;
1055:
1056: type = buffer[offset++] & 0xff;
1057:
1058: // recurse for constructed types, bit 6 = 1
1059: constructed = (type & 0x20) == 0x20;
1060:
1061: if ((type & 0x1f) == 0x1f) {
1062: // multi byte type, 7 bits per byte, only last byte bit 8 as zero
1063: type = 0;
1064: for (;;) {
1065: int temp = buffer[offset++];
1066: type = type << 7;
1067: if (temp >= 0) {
1068: type += temp;
1069: break;
1070: }
1071:
1072: // strip off bit 8
1073: temp = temp & 0x7f;
1074: type += temp;
1075: }
1076:
1077: }
1078:
1079: size = buffer[offset++] & 0xff;
1080: if (size >= 128) {
1081: int sizeLen = size - 128;
1082:
1083: // NOTE: for now, all sizes must fit int two bytes
1084: if (sizeLen > 2) {
1085: throw new RuntimeException("TLV size to large");
1086: }
1087:
1088: size = 0;
1089: while (sizeLen > 0) {
1090: size = (size << 8) + (buffer[offset++] & 0xff);
1091: sizeLen--;
1092: }
1093: }
1094:
1095: length = size;
1096: valueOffset = offset;
1097:
1098: if (constructed) {
1099: int end;
1100: TLV temp;
1101:
1102: end = offset + length;
1103:
1104: child = new TLV(buffer, offset);
1105: temp = child;
1106: for (;;) {
1107: offset = temp.valueOffset + temp.length;
1108: if (offset >= end) {
1109: break;
1110: }
1111:
1112: temp.next = new TLV(buffer, offset);
1113: temp = temp.next;
1114: }
1115: }
1116: }
1117:
1118: /**
1119: * Print the a TLV structure, recursing down for constructed types.
1120: *
1121: * @param buffer DER buffer
1122: */
1123: void print(byte[] buffer) {
1124: print(buffer, 0);
1125: }
1126:
1127: /**
1128: * Print the a TLV structure, recursing down for constructed types.
1129: *
1130: * @param buffer DER buffer
1131: * @param level what level this TLV is at
1132: */
1133: private void print(byte[] buffer, int level) {
1134: for (int i = 0; i < level; i++) {
1135: System.out.print(' ');
1136: }
1137:
1138: if (child == null) {
1139: System.out.print("Type: 0x" + Integer.toHexString(type)
1140: + " length: " + length + " value: ");
1141: if (type == PRINTSTR_TYPE || type == TELETEXSTR_TYPE
1142: || type == UTF8STR_TYPE || type == IA5STR_TYPE
1143: || type == UNIVSTR_TYPE) {
1144: try {
1145: System.out.print(new String(buffer, valueOffset,
1146: length, "UTF-8"));
1147: } catch (UnsupportedEncodingException e) {
1148: // ignore
1149: }
1150: } else if (type == OID_TYPE) {
1151: System.out.print(OIDtoString(buffer, valueOffset,
1152: length));
1153: } else {
1154: System.out.print(hexEncode(buffer, valueOffset, length,
1155: 14));
1156: }
1157:
1158: System.out.println("");
1159: } else {
1160: if (type == SET_TYPE) {
1161: System.out.println("Set:");
1162: } else if (type == VERSION_TYPE) {
1163: System.out.println("Version (explicit):");
1164: } else if (type == EXTENSIONS_TYPE) {
1165: System.out.println("Extensions (explicit):");
1166: } else {
1167: System.out.println("Sequence:");
1168: }
1169:
1170: child.print(buffer, level + 1);
1171: }
1172:
1173: if (next != null) {
1174: next.print(buffer, level);
1175: }
1176: }
1177: }
1178:
1179: /**
1180: * This exception is used to signal a usage error.
1181: */
1182: class UsageException extends Exception {
1183:
1184: /**
1185: * Constructs a UsageException.
1186: */
1187: UsageException() {
1188: }
1189:
1190: /**
1191: * Constructs a UsageException.
1192: *
1193: * @param message exception message
1194: */
1195: UsageException(String message) {
1196: super(message);
1197: }
1198: }
|