FileDocCategorySizeDatePackage
MEKeyTool.javaAPI DocphoneME MR2 API (J2ME)40255Wed May 02 18:00:26 BST 2007com.sun.midp.mekeytool

MEKeyTool

public class MEKeyTool extends Object
Manages the initial public keystore needed to bootstrap this MIDP security implementation. It provides both a Java and a command line interface.

The anchor of trust on an ME (mobile equipment) are the public keys loaded on it by the manufacturer, in MIDP implementation this is known as the ME keystore. This tool does for the MIDP implementation what the manufacturer must do for the ME so that trusted MIDP applications can be authenticated.

see
#main(String[])

Fields Summary
private static final String
defaultAppDir
default MIDP application directory, see Utility.c getStorageRoot()
private static final String
defaultKeystoreFilename
default ME keystore filename, see com.sun.midp.Main.java
private static final String[]
AttrLabel
Maps byte codes that follow id-at (0x55 0x04) to corresponding name component tags (e.g. Common Name, or CN, is 0x55, 0x04, 0x03 and Country, or C, is 0x55, 0x04, 0x06). See getName. See X.520 for the OIDs and RFC 1779 for the printable labels. Place holders for unknown labels have a -1 as the first byte.
private static final String
EMAIL_ATTR_LABEL
Email attribute label.
private static final byte[]
EMAIL_ATTR_OID
Email attribute object identifier.
private PublicKeyStoreBuilderBase
keystore
read-writable ME keystore that does not depend on SSL
private int
nextKeyToGet
the state for getFirstKey and getNextKey
Constructors Summary
public MEKeyTool()
Constructs a MEKeyTool with an empty keystore.

        keystore = new PublicKeyStoreBuilderBase();
    
public MEKeyTool(String meKeystoreFilename)
Constructs a MEKeyTool and loads its keystore using a filename.

param
meKeystoreFilename serialized keystore file
exception
FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some other reason cannot be opened for reading.
exception
IOException if the key storage was corrupted


        FileInputStream input;

        input = new FileInputStream(new File(meKeystoreFilename));

        try {
            keystore = new PublicKeyStoreBuilderBase(input);
        } finally {
            input.close();
        }
    
public MEKeyTool(File meKeystoreFile)
Constructs a MEKeyTool and loads its keystore from a file.

param
meKeystoreFile serialized keystore file
exception
FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some other reason cannot be opened for reading.
exception
IOException if the key storage was corrupted


        FileInputStream input;

        input = new FileInputStream(meKeystoreFile);

        try {
            keystore = new PublicKeyStoreBuilderBase(input);
        } finally {
            input.close();
        }
    
public MEKeyTool(InputStream meKeystoreStream)
Constructs a MEKeyTool and loads its keystore from a stream.

param
meKeystoreStream serialized keystore stream
exception
IOException if the key storage was corrupted

        keystore = new PublicKeyStoreBuilderBase(meKeystoreStream);
    
Methods Summary
private static voiddeleteCommand(java.io.File meKeystoreFile, java.lang.String[] args)
Process the command line arguments for the delete command and then delete a public key from a ME keystore. This method assumes the first argument is the delete command and skips it.

param
meKeystoreFile ME keystore abstract file name
param
args command line arguments
exception
Exception if an unrecoverable error occurs

        String owner = null;
        int keyNumber = -1;
        boolean keyNumberGiven = false;
        MEKeyTool keyTool;

        for (int i = 1; i < args.length; i++) {
            try {
                if (args[i].equals("-MEkeystore")) {
                    i++;
                    meKeystoreFile = new File(args[i]); 
                } else if (args[i].equals("-owner")) {
                    i++;
                    owner = args[i];
                } else if (args[i].equals("-number")) {
                    keyNumberGiven = true;
                    i++;
                    try {
                        keyNumber = Integer.parseInt(args[i]);
                    } catch (NumberFormatException e) {
                        throw new UsageException(
                            "Invalid number for the -number argument: " +
                            args[i]);
                    }
                } else {
                    throw new UsageException(
                        "Invalid argument for the delete command: " + args[i]);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new UsageException("Missing value for " + args[--i]);
            }
        }

        if (owner == null && !keyNumberGiven) {
            throw new UsageException(
                "Neither key -owner or -number was not given");
        }

        if (owner != null && keyNumberGiven) {
            throw new UsageException("-owner and -number cannot be used " +
                "together");
        }

        keyTool = new MEKeyTool(meKeystoreFile);

        if (owner != null) {
            if (!keyTool.deleteKey(owner)) {
                throw new UsageException("Key not found for: " + owner);
            }
        } else {
            try {
                keyTool.deleteKey(keyNumber - 1);
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new UsageException("Invalid number for the -number " +
                                    "delete option: " + keyNumber);
            }                
        }

        keyTool.saveKeystore(meKeystoreFile);
    
public booleandeleteKey(java.lang.String owner)
Deletes the first public key matching the owner's distinguished name.

param
owner name of the key's owner
return
true, if the key was deleted, else false

        PublicKeyInfo key;

        for (int i = 0; i < keystore.numberOfKeys(); i++) {
            key = keystore.getKey(i);
            if (key.getOwner().equals(owner)) {
                keystore.deleteKey(i);
                return true;
            }
        }

        return false;
    
public voiddeleteKey(int number)
Deletes a key by key number, 0 being the first public key.

param
number number of the key
exception
ArrayIndexOutOfBoundsException if an invalid number was given.

        keystore.deleteKey(number);
    
private static voiddisplayUsage()
Display the usage text to standard output.

        System.out.println("\n  MEKeyTool argument combinations:\n\n" +
            "    -help\n" +
            "    -import [-MEkeystore <filename>] " +
            "[-keystore <filename>]\n" +
            "            [-storepass <password>] -alias <key alias> " +
            "[-domain <domain>]\n" +
            "    -list [-MEkeystore <filename>]\n" +
            "    -delete [-MEkeystore <filename>]\n" +
            "            (-owner <owner name> | -number <key number>)\n" +
            "\n" +
            "  The default for -MEkeystore is \"appdb/_main.ks\".\n" +
            "  The default for -keystore is \"$HOME/.keystore\".\n");
    
public static java.lang.StringformatKeyInfo(PublicKeyInfo keyInfo)
Creates a string representation of a key that is displayed to a user during a list command. The string does not include the modulus and exponent.

param
keyInfo key to display
return
printable representation of the key

        return "  Owner: " + keyInfo.getOwner() +
            "\n  Valid from " +
            (new Date(keyInfo.getNotBefore())).toString() +
            " to " + (new Date(keyInfo.getNotAfter())).toString() +
            "\n  Security Domain: " + keyInfo.getDomain() +
            "\n  Enabled: " + keyInfo.isEnabled();
    
protected PublicKeyInfogetFirstKey()
Gets the first key in the keystore.

return
all the information related to the first key

        nextKeyToGet = 0;
        return getNextKey();
    
public PublicKeyStoreBuilderBasegetKeystore()
Gets the read-write keystore this tool is manipulating. For advanced users.

return
read-write keystore

        return keystore;
    
protected PublicKeyInfogetNextKey()
Gets the next key after the previous one returned by {@link #getFirstKey} or this method. If getFirstKey is not called before the first call to this method, null will be returned.

return
all the information related to the next key, or null if there are no more keys

        PublicKeyInfo key;

        try {
            key = keystore.getKey(nextKeyToGet);
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }

        nextKeyToGet++;

        return key;
    
private static voidimportCommand(java.io.File meKeystoreFile, java.lang.String[] args)
Process the command line arguments for the import command and then imports a public key from a JCA keystore to ME keystore. This method assumes the first argument is the import command and skips it.

param
meKeystoreFile ME keystore abstract file name
param
args command line arguments
exception
Exception if an unrecoverable error occurs

        String jcaKeystoreFilename = null;
        String keystorePassword = null;
        String alias = null;
        String domain = "identified";
        MEKeyTool keyTool;

        for (int i = 1; i < args.length; i++) {
            try {
                if (args[i].equals("-MEkeystore")) {
                    i++;
                    meKeystoreFile = new File(args[i]); 
                } else if (args[i].equals("-keystore")) {
                    i++;
                    jcaKeystoreFilename = args[i]; 
                } else if (args[i].equals("-storepass")) {
                    i++;
                    keystorePassword = args[i]; 
                } else if (args[i].equals("-alias")) {
                    i++;
                    alias = args[i];
                } else if (args[i].equals("-domain")) {
                    i++;
                    domain = args[i];
                } else {
                    throw new UsageException(
                        "Invalid argument for import command: " + args[i]);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new UsageException("Missing value for " + args[--i]);
            }
        }

        if (jcaKeystoreFilename == null) {
            jcaKeystoreFilename = System.getProperty("user.home") +
                                  File.separator + ".keystore";
        }
        
        if (alias == null) {
            throw new Exception("J2SE key alias was not given");
        }

        try {
            keyTool = new MEKeyTool(meKeystoreFile);
        } catch (FileNotFoundException fnfe) {
            keyTool = new MEKeyTool();
        }

        keyTool.importKeyFromJcaKeystore(jcaKeystoreFilename,
                                      keystorePassword,
                                      alias, domain);
        keyTool.saveKeystore(meKeystoreFile);
    
public voidimportKeyFromJcaKeystore(java.lang.String jcakeystoreFilename, java.lang.String keystorePassword, java.lang.String alias, java.lang.String domain)
Copies a key from a Standard Edition keystore into the ME keystore.

param
jcakeystoreFilename name of the serialized keystore
param
keystorePassword password to unlock the keystore
param
alias the ID of the key in the SE keystore
param
domain security domain of any application authorized with the corresponding private key

        FileInputStream keystoreStream;
        KeyStore jcaKeystore;

         // Load the keystore
        keystoreStream = new FileInputStream(new File(jcakeystoreFilename));

        try {
            jcaKeystore = KeyStore.getInstance(KeyStore.getDefaultType());

            if (keystorePassword == null) {
                jcaKeystore.load(keystoreStream, null);
            } else {
                jcaKeystore.load(keystoreStream,
                                 keystorePassword.toCharArray());
            }
        } finally {
            keystoreStream.close();
        }

        importKeyFromJcaKeystore(jcaKeystore,
                                 alias,
                                 domain);
    
public voidimportKeyFromJcaKeystore(java.security.KeyStore jcaKeystore, java.lang.String alias, java.lang.String domain)
Copies a key from a Standard Edition keystore into the ME keystore.

param
jcaKeystore loaded JCA keystore
param
alias the ID of the key in the SE keystore
param
domain security domain of any application authorized with the corresponding private key

        X509Certificate cert;
        byte[] der;
        TLV tbsCert;
        TLV subjectName;
        RSAPublicKey rsaKey;
        String owner;
        long notBefore;
        long notAfter;
        byte[] rawModulus;
        int i;
        int keyLen;
        byte[] modulus;
        byte[] exponent;
        Vector keys;

        // get the cert from the keystore
        try {
            cert = (X509Certificate)jcaKeystore.getCertificate(alias);
        } catch (ClassCastException cce) {
            throw new CertificateException("Certificate not X.509 type");
        }

        if (cert == null) {
            throw new CertificateException("Certificate not found");
        }

        /*
         * J2SE reorders the attributes when building a printable name
         * so we must build a printable name on our own.
         */

        /*
         * TBSCertificate  ::=  SEQUENCE  {
         *   version         [0]  EXPLICIT Version DEFAULT v1,
         *   serialNumber         CertificateSerialNumber,
         *   signature            AlgorithmIdentifier,
         *   issuer               Name,
         *   validity             Validity,
         *   subject              Name,
         *   subjectPublicKeyInfo SubjectPublicKeyInfo,
         *   issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
         *                        -- If present, version shall be v2 or v3
         *   subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
         *                        -- If present, version shall be v2 or v3
         *   extensions      [3]  EXPLICIT Extensions OPTIONAL
         *                        -- If present, version shall be v3
         * }
         */
        der = cert.getTBSCertificate();
        tbsCert = new TLV(der, 0);

        // walk down the tree of TLVs to find the subject name
        try {
            // Top level a is Sequence, drop down to the first child
            subjectName = tbsCert.child;

            // skip the version if present.
            if (subjectName.type == TLV.VERSION_TYPE) {
                subjectName = subjectName.next;
            }

            // skip the serial number
            subjectName = subjectName.next;
            
            // skip the signature alg. id.
            subjectName = subjectName.next;
            
            // skip the issuer
            subjectName = subjectName.next;
            
            // skip the validity
            subjectName = subjectName.next;

            owner = parseDN(der, subjectName);
        } catch (NullPointerException e) {
            throw new CertificateException("TBSCertificate corrupt 1");
        } catch (IndexOutOfBoundsException e) {
            throw new CertificateException("TBSCertificate corrupt 2");
        }

        notBefore = cert.getNotBefore().getTime();
        notAfter = cert.getNotAfter().getTime();

        // get the key from the cert
        try {
            rsaKey = (RSAPublicKey)cert.getPublicKey();
        } catch (ClassCastException cce) {
            throw new RuntimeException("Key in certificate is not an RSA key");
        }

        // get the key parameters from the key
        rawModulus = rsaKey.getModulus().toByteArray();

        /*
         * the modulus is given as the minimum positive integer,
         * will not padded to the bit size of the key, or may have a extra
         * pad to make it positive. SSL expects the key to be signature
         * bit size. but we cannot get that from the key, so we should
         * remove any zero pad bytes and then pad out to a multiple of 8 bytes
         */
        for (i = 0; i < rawModulus.length && rawModulus[i] == 0; i++);

        keyLen = rawModulus.length - i;
        keyLen = (keyLen + 7) / 8 * 8;
        modulus = new byte[keyLen];

        int k, j;
        for (k = rawModulus.length - 1, j = keyLen - 1;
                 k >= 0 && j >= 0; k--, j--) {
            modulus[j] = rawModulus[k];
        }

        exponent = rsaKey.getPublicExponent().toByteArray();

        // add the key
        keys = keystore.findKeys(owner);
        if (keys != null) {
            boolean duplicateKey = false;

            for (int n = 0; !duplicateKey && n < keys.size(); n++) {
                PublicKeyInfo key = (PublicKeyInfo)keys.elementAt(n);

                if (key.getOwner().equals(owner)) {
                    byte[] temp = key.getModulus();

                    if (modulus.length == temp.length) {
                        duplicateKey = true;
                        for (int m = 0; j < modulus.length && m < temp.length;
                                 m++) {
                            if (modulus[m] != temp[m]) {
                                duplicateKey = false;
                                break;
                            }
                        }
                    }
                }
            }
                
            if (duplicateKey) {
                throw new CertificateException(
                    "Owner already has this key in the ME keystore");
            }
        }
                 
        keystore.addKey(new PublicKeyInfo(owner, notBefore, notAfter,
                                          modulus, exponent, domain));
    
private static voidlistCommand(java.io.File meKeystoreFile, java.lang.String[] args)
Process the command line arguments for the list command and then list the public keys of a ME keystore. This method assumes the first argument is the list command and skips it.

param
meKeystoreFile ME keystore abstract file name
param
args command line arguments
exception
Exception if an unrecoverable error occurs

        MEKeyTool keyTool;
        PublicKeyInfo key;

        for (int i = 1; i < args.length; i++) {
            try {
                if (args[i].equals("-MEkeystore")) {
                    i++;
                    meKeystoreFile = new File(args[i]); 
                } else {
                    throw new UsageException("Invalid argument for the list " +
                                             "command: " + args[i]);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new UsageException("Missing value for " + args[--i]);
            }
        }

        keyTool = new MEKeyTool(meKeystoreFile);
        key = keyTool.getFirstKey();
        for (int i = 1; key != null; i++) {
            System.out.println("Key " + Integer.toString(i));
            System.out.println(formatKeyInfo(key));
            key = keyTool.getNextKey();
        }

        System.out.println("");
    
public static voidmain(java.lang.String[] args)
Performs the command specified in the first argument.

Exits with a 0 status if the command was successful. Exits and prints out an error message with a -1 status if the command failed.

MEKeyTool supports the following commands:

no args - same has -help
-import - import a public key from a JCE keystore
into a ME keystore
-delete - delete a key from a ME keystore
-help - print a usage summary
-list - list the owner and validity period of each
key in a ME keystore

Parameters for (commands):

-MEkeystore <filename of the ME keystore> (optional for all)
-keystore <filename of the JCA keystore> (optional import)
-storepass <password for the JCA keystore> (optional import)
-alias <short string ID of a key in a JCA keystore> (import)
-domain <security domain of the ME key> (optional import)
-owner <name of the owner of a ME key> (delete)
-number <key number starting a 1 of a ME key> (delete)

Defaults:

-MEkeystore appdir/main.ks
-keystore <user's home dir>/.keystore
-domain untrusted

param
args command line arguments


                                                                                                                                                                                                                                                                  
         
        File meKeystoreFile = null;

        if (args.length == 0) {
            System.out.println("\n  Error: No command given");
            displayUsage();
            System.exit(-1);
        }

        if (args[0].equals("-help")) {
            // user just needs help with the arguments
            displayUsage();
            System.exit(0);
        }


        // start with the default keystore file
        meKeystoreFile = new File(defaultAppDir, defaultKeystoreFilename);

        try {
            if (args[0].equals("-import")) {
                importCommand(meKeystoreFile, args);
                System.exit(0);
            }
            
            if (args[0].equals("-delete")) {
                deleteCommand(meKeystoreFile, args);
                System.exit(0);
            }

            if (args[0].equals("-list")) {
                listCommand(meKeystoreFile, args);
                System.exit(0);
            }

            throw new UsageException("  Invalid command: " + args[0]);
        } catch (Exception e) {
            System.out.println("\n  Error: " + e.getMessage());

            if (e instanceof UsageException) {
                displayUsage();
            }

            System.exit(-1);
        }
    
private java.lang.StringparseDN(byte[] buffer, com.sun.midp.mekeytool.TLV dn)
Parses a DER TLV tree into a printable distinguished name.

param
buffer DER buffer
param
dn sequence of TLV nodes.
return
printable name.
exception
NullPointerException if the name is corrupt
exception
IndexOutOfBoundsException if the name is corrupt

        TLV attribute;
        TLV type;
        TLV value;
        StringBuffer name = new StringBuffer(256);

        /*
         * Name ::= CHOICE { RDNSequence } # CHOICE does not encoded
         *
         * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
         *
         * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
         *
         *   AttributeTypeAndValue ::= SEQUENCE {
         *     type     AttributeType,
         *     value    AttributeValue }
         *
         * AttributeType ::= OBJECT IDENTIFIER
         *
         * AttributeValue ::= ANY DEFINED BY AttributeType
         *
         * basically this means that each attribute value is 3 levels down
         */

        // sequence drop down a level
        attribute = dn.child;
        
        while (attribute != null) {
            if (attribute != dn.child) {
                name.append(";");
            }

            /*
             * we do not handle relative distinguished names yet
             * which should not be used by CAs anyway
             * so only take the first element of the sequence
             */

            type = attribute.child.child;

            /*
             * At this point we tag the name component, e.g. C= or hex
             * if unknown.
             */
            if ((type.length == 3) && (buffer[type.valueOffset] == 0x55) &&
                    (buffer[type.valueOffset + 1] == 0x04)) {
                // begins with id-at, so try to see if we have a label
                int temp = buffer[type.valueOffset + 2] & 0xFF;
                if ((temp < AttrLabel.length) &&
                        (AttrLabel[temp] != null)) {
                    name.append(AttrLabel[temp]);
                } else {
                    name.append(TLV.hexEncode(buffer, type.valueOffset,
                                              type.length, -1));
                }
            } else if (TLV.byteMatch(buffer, type.valueOffset,
                                     type.length, EMAIL_ATTR_OID,
                                     0, EMAIL_ATTR_OID.length)) {
                name.append(EMAIL_ATTR_LABEL);
            } else {
                name.append(TLV.hexEncode(buffer, type.valueOffset,
                                          type.length, -1));
            }

            name.append("=");

            value = attribute.child.child.next;
            if (value.type == TLV.PRINTSTR_TYPE ||
                    value.type == TLV.TELETEXSTR_TYPE ||
                    value.type == TLV.UTF8STR_TYPE ||
                    value.type == TLV.IA5STR_TYPE ||
                    value.type == TLV.UNIVSTR_TYPE) {
                try {
                    name.append(new String(buffer, value.valueOffset,
                                           value.length, "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e.toString());
                }
            } else {
                name.append(TLV.hexEncode(buffer, value.valueOffset,
                                          value.length, -1));
            }

            attribute = attribute.next;
        }

        return name.toString();
    
public voidsaveKeystore(java.io.File meKeystoreFile)
Saves the keystore to a file.

param
meKeystoreFile serialized keystore file

        FileOutputStream output;

        output = new FileOutputStream(meKeystoreFile);

        keystore.serialize(output);
        output.close();