Index: modules/tools/src/main/java/org/apache/harmony/tools/keytool/ArgumentsParser.java =================================================================== --- modules/tools/src/main/java/org/apache/harmony/tools/keytool/ArgumentsParser.java (revision 415257) +++ modules/tools/src/main/java/org/apache/harmony/tools/keytool/ArgumentsParser.java (working copy) @@ -346,6 +346,5 @@ */ static void getAdditionalParameters(KeytoolParameters param) { // TODO - throw new RuntimeException("The method is not implemented yet."); } } Index: modules/tools/src/main/java/org/apache/harmony/tools/keytool/Main.java =================================================================== --- modules/tools/src/main/java/org/apache/harmony/tools/keytool/Main.java (revision 415257) +++ modules/tools/src/main/java/org/apache/harmony/tools/keytool/Main.java (working copy) @@ -16,7 +16,6 @@ package org.apache.harmony.tools.keytool; - /** * The main class that bundles command line parsing, interaction with the user * and work with keys and certificates. @@ -35,6 +34,21 @@ case EXPORT: CertExporter.exportCert(param); break; + case LIST: + KeyStoreCertPrinter.list(param); + break; + case KEYCLONE: + EntryManager.keyClone(param); + break; + case DELETE: + EntryManager.delete(param); + break; + case STOREPASSWD: + KeyStoreLoaderSaver.storePasswd(param); + break; + case KEYPASSWD: + EntryManager.keyPasswd(param); + break; // TODO: calls for other options. } } @@ -68,7 +82,7 @@ KeyStoreLoaderSaver.loadStore(param); // prompt for additional parameters if some of the expected // ones have not been specified. - ArgumentsParser.getAdditionalParameters(param); + //ArgumentsParser.getAdditionalParameters(param); } // print the warning if store password is not set Index: modules/tools/src/main/java/org/apache/harmony/tools/keytool/KeyStoreCertPrinter.java =================================================================== --- modules/tools/src/main/java/org/apache/harmony/tools/keytool/KeyStoreCertPrinter.java (revision 0) +++ modules/tools/src/main/java/org/apache/harmony/tools/keytool/KeyStoreCertPrinter.java (revision 0) @@ -0,0 +1,338 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.harmony.tools.keytool; + +import java.io.UnsupportedEncodingException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Enumeration; + +import org.apache.harmony.luni.util.Base64; + +/** + * Class for printing to stdout the contents of the keystore or a single + * certificate. The certificate can be in the keystore (list(..) method) or not + * (printCert(..) method). + */ +public class KeyStoreCertPrinter { + + /** + * Prints the contents of the entry associated with the alias given in + * param. If no alias is specified, the contents of the entire keystore are + * printed. + * + * @param param + * @throws KeyStoreException + * @throws CertificateEncodingException + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws UnsupportedEncodingException + * @throws UnrecoverableKeyException + */ + static void list(KeytoolParameters param) throws KeyStoreException, + CertificateEncodingException, NoSuchAlgorithmException, + NoSuchProviderException, UnsupportedEncodingException, + UnrecoverableKeyException { + Enumeration aliases; + KeyStore keyStore = param.getKeyStore(); + String alias = param.getAlias(); + + if (alias != null) { + // if the alias is specified, make a single-element + // enumeration of it + aliases = Collections.enumeration(Collections.singleton(alias)); + } else {// if the alias is not given, + // get all aliases + aliases = keyStore.aliases(); + // print the keystore info + System.out.println("Type of keystore: " + keyStore.getType()); + System.out.println("Keystore provider name: " + + keyStore.getProvider().getName()); + System.out.println("\nThe keystore contains " + keyStore.size() + + " entries \n"); + } + + String provider = param.getProvider(); + + while (aliases.hasMoreElements()) { + String currentAlias = (String) aliases.nextElement(); + String creationDate = keyStore.getCreationDate(currentAlias) + .toString(); + + // true if the keystore entry is a TrustedCertificateEntry + boolean trustedEntry = false; + // true if the keystore entry is a SecretKeyEntry + boolean secretKeyEntry = false; + + // get the type of the entry to print it out + String entryType = "Key entry"; + if (keyStore.entryInstanceOf(currentAlias, + KeyStore.TrustedCertificateEntry.class)) { + entryType = "Trusted certificate entry"; + trustedEntry = true; + } else if (keyStore.entryInstanceOf(currentAlias, + KeyStore.PrivateKeyEntry.class)) { + entryType = "Private key entry"; + } else if (keyStore.entryInstanceOf(currentAlias, + KeyStore.SecretKeyEntry.class)) { + entryType = "Secret key entry"; + secretKeyEntry = true; + } + + // get the certificate associated with the currentAlias + X509Certificate x509cert = ((X509Certificate) keyStore + .getCertificate(currentAlias)); + + // if -v or -rfc options are specified + if (param.isVerbose() || param.isRfc()) { + // print detailed info about the _entry_ + System.out.println("Alias name: " + currentAlias); + System.out.println("Date of creation: " + creationDate); + System.out.println("Type of the entry: " + entryType); + + if (!secretKeyEntry) { + Certificate[] certChain = keyStore + .getCertificateChain(currentAlias); + + if (!trustedEntry) { + System.out.println("Certificate chain length: " + + certChain.length); + } + + // if -v option was given, print the detailed info about + // the certificate + if (param.isVerbose()) { + // print out the first certificate + System.out.println("Certificate[1]:"); + printX509CertDetailed(x509cert, provider); + if (!trustedEntry) { + for (int i = 1; i < certChain.length; i++) { + System.out.println("Certificate[" + (i + 1) + + "]:"); + printX509CertDetailed( + (X509Certificate) certChain[i], + provider); + } + } + } + // if -rfc option is given, print the certificate in Base64 + // printable format + else { + // print out the first certificate + System.out.println("Certificate[1]:"); + System.out.println("-----BEGIN CERTIFICATE-----"); + System.out.println(Base64.encode(x509cert.getEncoded(), + "ISO-8859-1")); + System.out.println("-----END CERTIFICATE-----"); + + if (!trustedEntry) { + for (int i = 1; i < certChain.length; i++) { + System.out.println("Certificate[" + (i + 1) + + "]:"); + System.out + .println("-----BEGIN CERTIFICATE-----"); + System.out.println(Base64.encode(certChain[i] + .getEncoded(), "ISO-8859-1")); + System.out.println("-----END CERTIFICATE-----"); + } + } + } + } else { + // if the key is explicitly asked to be printed + // by setting the alias, print it out, otherwise - do + // nothing. + if (alias != null) { + // TODO: ask for password if not set, when read from + // stdin is OK. + char[] keyPass; + if ((keyPass = param.getKeyPass()) != null) { + Key key = keyStore.getKey(currentAlias, keyPass); + System.out.println("Algorithm: " + + key.getAlgorithm() + "\nFormat: " + + key.getFormat()); + System.out.println("Key: " + + formatBytes(key.getEncoded())); + } else { + System.out.println("If you want to print the key, " + + "please set the entry password using " + + "\"-keypass\" option"); + } + + } + } + System.out.println("\n*******************************" + + "*******************************\n"); + + } else {// if neither -v nor -rfc options specified + String commaSpc = ", "; + System.out.print(currentAlias + commaSpc + creationDate + + commaSpc + entryType); + + if (!secretKeyEntry) { + System.out.print(commaSpc + "\nCertificate fingerprint (MD5): "); + printMD(x509cert.getEncoded(), "MD5", provider); + } else { + // If the key is explicitly asked to be printed + // by setting the alias, print it out, otherwise - do + // nothing. + System.out.println(); + if (alias != null) { + char[] keyPass; + if ((keyPass = param.getKeyPass()) != null) { + Key key = keyStore.getKey(currentAlias, keyPass); + System.out.println(key.getAlgorithm() + ", " + + key.getFormat() + ", " + + formatBytes(key.getEncoded())); + } else { + System.out.println("If you want to print the key, " + + "please set the entry password using " + + "\"-keypass\" option"); + } + } + } + } + } + + } + + /** + * Prints the detailed description of a certificate in a human-readable + * format: its owner and issuer, serial number, validity period and + * fingerprints. providerName is needed to generate MessageDigest. If it is + * null, a default one is used. + * + * @param x509cert + * @param providerName + * @throws CertificateEncodingException + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + */ + static void printX509CertDetailed(X509Certificate x509cert, + String providerName) throws CertificateEncodingException, + NoSuchAlgorithmException, NoSuchProviderException { + System.out.println("Owner: " + x509cert.getSubjectX500Principal()); + System.out.println("Issuer: " + x509cert.getIssuerX500Principal()); + System.out.println("Serial number: " + + Integer.toHexString(x509cert.getSerialNumber().intValue())); + System.out.println("Validity period \n\t from: " + + x509cert.getNotBefore() + "\n\t until: " + + x509cert.getNotAfter()); + + // print certificate fingerprints (MD5 and SHA1). + byte[] encodedCert; + try { + encodedCert = x509cert.getEncoded(); + } catch (CertificateEncodingException e) { + throw new CertificateEncodingException( + "Failed to encode the certificate", e); + } + + String strMD5 = "MD5"; + String strSHA1 = "SHA1"; + + System.out.print("Certificate fingerprints: " + "\n\t " + strMD5 + + ": "); + printMD(encodedCert, strMD5, providerName); + + System.out.print("\t " + strSHA1 + ": "); + printMD(encodedCert, strSHA1, providerName); + } + + // Prints out the message digest of the encoding using the given algorithm + // and provider. Provider can be null. + private static void printMD(byte[] encoding, String mdAlgorithm, + String providerName) throws NoSuchAlgorithmException, + NoSuchProviderException { + byte[] digest; + // if provider name is given, use it when getting + // an instance of MessageDigest. + try { + if (providerName != null) { + digest = MessageDigest.getInstance(mdAlgorithm, providerName) + .digest(encoding); + } else { + digest = MessageDigest.getInstance(mdAlgorithm) + .digest(encoding); + } + } catch (NoSuchAlgorithmException e) { + throw new NoSuchAlgorithmException("The algorithm " + mdAlgorithm + + " is not found in the environment.", e); + } catch (NoSuchProviderException e) { + throw (NoSuchProviderException) new NoSuchProviderException( + "The provider " + providerName + + " is not found in the environment.").initCause(e); + } + + // print out in the way: "0A:1B:C3:D4:..." + System.out.println(formatBytes(digest)); + } + + /** + * Reads an X.509 certificate from the file specified in param and prints it + * in a human-readable format. If param.getFileName() returns null, the + * certificate is read from the standard input. The input data is awaited + * for some time. If the data is not entered, an exception is thrown. + * + * @param param + */ + static void printCert(KeytoolParameters param) { + // TODO + throw new RuntimeException("The method is not implemented yet."); + } + + // Formats byte array as a String looking like "0A:1B:C3:D4:....:E5". + private static String formatBytes(byte[] bytes) { + int i; + // The method is expected to format mostly message digest results and + // åhe length of the String repesenting a SHA1 digest printed in + // the way: "0A:1B:C3:D4:....:E5" is the biggest and is 59. + StringBuffer buffer = new StringBuffer(59); + int length; + String currentByte; + for (i = 0; i < bytes.length - 1; i++) { + // TODO: change when String.format(..) method is implemented. + // buffer.append(String.format("%02X", bytes[i]) + ":"); + // / + currentByte = Integer.toHexString(bytes[i]).toUpperCase(); + if ((length = currentByte.length()) > 1) { + buffer.append(currentByte.substring(length - 2) + ":"); + } else { + buffer.append("0" + currentByte + ":"); + } + // / + } + // The last byte doesn't need ":" after it ("...:E5:6F") + // TODO: change in the same way to (String.format(..)) + currentByte = Integer.toHexString(bytes[i]).toUpperCase(); + if ((length = currentByte.length()) > 1) { + buffer.append(currentByte.substring(length - 2)); + } else { + buffer.append("0" + currentByte); + } + return new String(buffer); + } +} + Index: modules/tools/src/main/java/org/apache/harmony/tools/keytool/EntryManager.java =================================================================== --- modules/tools/src/main/java/org/apache/harmony/tools/keytool/EntryManager.java (revision 0) +++ modules/tools/src/main/java/org/apache/harmony/tools/keytool/EntryManager.java (revision 0) @@ -0,0 +1,105 @@ +/* + * Copyright 2006 The Apache Software Foundation or its licensors, as applicable + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.harmony.tools.keytool; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; + +/** + * Class for managing keystore entries - cloning, deleting, changing entry + * password. + */ +public class EntryManager { + /** + * Copies the private key and the certificate chain from the keystore entry + * identifiad by given alias into a newly created one with given destination + * alias. alias and destination alias are specified in param. + * + * @param param + * @throws UnrecoverableKeyException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeytoolException + */ + static void keyClone(KeytoolParameters param) throws KeyStoreException, + NoSuchAlgorithmException, UnrecoverableKeyException, + KeytoolException { + KeyStore keyStore = param.getKeyStore(); + String alias = param.getAlias(); + Key srcKey; + try { + srcKey = keyStore.getKey(alias, param.getKeyPass()); + } catch (NoSuchAlgorithmException e) { + throw new NoSuchAlgorithmException( + "Cannot find the algorithm to recover the key. ", e); + } + // if the entry is a not a KeyEntry + if (srcKey == null) { + throw new KeytoolException("The entry <" + alias + "> has no key."); + } + Certificate[] certChain = keyStore + .getCertificateChain(param.getAlias()); + keyStore.setKeyEntry(param.getDestAlias(), srcKey, + param.getNewPasswd(), certChain); + param.setNeedSaveKS(true); + } + + /** + * Removes from the keystore the entry associated with alias. + * + * @param param + * @throws KeyStoreException + */ + static void delete(KeytoolParameters param) throws KeyStoreException { + param.getKeyStore().deleteEntry(param.getAlias()); + param.setNeedSaveKS(true); + } + + /** + * Changes the key password to the new one. + * + * @param param + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + * @throws UnrecoverableKeyException + */ + static void keyPasswd(KeytoolParameters param) throws KeyStoreException, + NoSuchAlgorithmException, UnrecoverableKeyException { + KeyStore keyStore = param.getKeyStore(); + String alias = param.getAlias(); + Key key; + Certificate[] chain; + try { + key = keyStore.getKey(alias, param.getKeyPass()); + chain = keyStore.getCertificateChain(alias); + } catch (NoSuchAlgorithmException e) { + throw new NoSuchAlgorithmException( + "Cannot find the algorithm to recover the key. ", e); + } + + keyStore.deleteEntry(alias); + keyStore.setKeyEntry(alias, key, param.getNewPasswd(), chain); + param.setKeyPass(param.getNewPasswd()); + param.setNeedSaveKS(true); + } + +} + Index: modules/tools/src/main/java/org/apache/harmony/tools/keytool/KeyStoreLoaderSaver.java =================================================================== --- modules/tools/src/main/java/org/apache/harmony/tools/keytool/KeyStoreLoaderSaver.java (revision 415257) +++ modules/tools/src/main/java/org/apache/harmony/tools/keytool/KeyStoreLoaderSaver.java (working copy) @@ -19,6 +19,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -37,8 +38,9 @@ * the password is not set in param, the integrity is not checked. If the * path to the store is not defined an empty keystore is created. * - * @param param - KeytoolParameters object which is used to get path to the - * store and password to unlock it or check its integrity. + * @param param - + * KeytoolParameters object which is used to get path to the + * store and password to unlock it or check its integrity. * @throws NoSuchAlgorithmException * @throws CertificateException * @throws FileNotFoundException @@ -147,11 +149,36 @@ /** * Saves the keystore to the file and protects its integrity with password. * - * @param param + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + * @throws CertificateException + * @throws IOException */ - static void saveStore(KeytoolParameters param) { - // TODO - throw new RuntimeException("The method is not implemented yet."); + static void saveStore(KeytoolParameters param) throws KeyStoreException, + NoSuchAlgorithmException, CertificateException, IOException { + // TODO: store not only to a file? + // if the path to the store is not set, use the default value + if (param.getStorePath() == null) { + param.setStorePath(KeytoolParameters.defaultKeystorePath); + } + File ksFile = new File(param.getStorePath()); + // the file will be created if and only if one with the same name + // doesn't exist + ksFile.createNewFile(); + FileOutputStream fos = new FileOutputStream(param.getStorePath()); + try { + param.getKeyStore().store(fos, param.getStorePass()); + } catch (NoSuchAlgorithmException e) { + throw new NoSuchAlgorithmException( + "Failed to find the algorithm to check the keystore integrity", + e); + } catch (CertificateException e) { + throw new CertificateException( + "Failed to save a certificate to the keystore. ", e); + } catch (IOException e) { + throw (IOException) new IOException("Failed to save the keystore. ") + .initCause(e); + } } /** @@ -160,7 +187,7 @@ * @param param */ static void storePasswd(KeytoolParameters param) { - // TODO - throw new RuntimeException("The method is not implemented yet."); + param.setStorePass(param.getNewPasswd()); } + }