/* * A CCNx command line utility. * * Copyright (C) 2008-2012 Palo Alto Research Center, Inc. * * This work is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * This work is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ package org.ccnx.ccn.utils; import java.io.File; import java.io.IOException; import java.security.InvalidKeyException; import java.util.HashMap; import java.util.Set; import java.util.SortedSet; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.KeyManager; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.config.SystemConfiguration; import org.ccnx.ccn.config.UserConfiguration; import org.ccnx.ccn.impl.security.keys.BasicKeyManager; import org.ccnx.ccn.impl.security.keys.NetworkKeyManager; import org.ccnx.ccn.impl.security.keys.RepositoryKeyManager; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.impl.support.Tuple; import org.ccnx.ccn.io.content.PublicKeyObject; import org.ccnx.ccn.profiles.nameenum.EnumeratedNameList; import org.ccnx.ccn.protocol.Component; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.test.Flosser; /** * The standard CCNx mechanisms try to generate a keystore for each user that uses * them. This tool allows you to make keys for additional users, on the command line * or programmatically. It is primarily useful for tests, and for generating credentials * that will be prepared offline and then given to their intended users. * * Creates and loads a set of simulated users. Will store them into * a repository if asked, or to files and then will reload them from there the next time. * * As long as you are careful to create your CCNHandle objects pointing at these * users' keystores, you can create data as any of these users. */ public class CreateUserData { /** * Our users are named, in order, from this list, with 1 attached the first time, and 2 the * second, and so on. This allows them to be enumerated without requiring them to be stored * in a repo. */ public static final String [] USER_NAMES = {"Alice", "Bob", "Carol", "Dave", "Oswald", "Binky", "Spot", "Fred", "Eve", "Harold", "Barack", "Newt", "Allison", "Zed", "Walter", "Gizmo", "Nick", "Michael", "Nathan", "Rebecca", "Diana", "Jim", "Van", "Teresa", "Russ", "Tim", "Sharon", "Jessica", "Elaine", "Mark", "Weasel", "Ralph", "Junior", "Beki", "Darth", "Cauliflower", "Pico", "Eric", "Eric", "Eric", "Erik", "Richard"}; protected HashMap<String, ContentName> _userContentNames = new HashMap<String,ContentName>(); protected HashMap<String, File> _userKeystoreDirectories = new HashMap<String,File>(); protected HashMap<String, BasicKeyManager> _userKeyManagers = new HashMap<String, BasicKeyManager>(); /** * Read/write constructor to write keystores as CCN data. * Makes extra new users if necessary. Expects names to come as above. * Will incur timeouts the first time, as it checks for data first, and will take time * to generate keys. * TODO eventually use this "for real" with real passwords. * @param userKeyStorePrefix * @param userNames list of user names to use, if null uses built-in list * @param userCount * @param storeInRepo * @param password * @throws IOException * @throws ConfigurationException * @throws InvalidKeyException */ public CreateUserData(ContentName userKeyStorePrefix, String [] userNames, int userCount, boolean storeInRepo, char [] password) throws ConfigurationException, IOException, InvalidKeyException { ContentName childName = null; String friendlyName = null; BasicKeyManager userKeyManager = null; if (null == userNames) { userNames = USER_NAMES; } for (int i=0; i < userCount; ++i) { friendlyName = userNames[i % userNames.length]; if (i >= userNames.length) { friendlyName += Integer.toString(1 + i/userNames.length); } childName = new ContentName(userKeyStorePrefix, friendlyName); Log.info("Loading user: " + friendlyName + " from " + childName); if (storeInRepo) { // This only matters the first time through, when we save the user's data. // but it makes no difference in other cases anyway. userKeyManager = new RepositoryKeyManager(friendlyName, childName, null, password); } else { userKeyManager = new NetworkKeyManager(friendlyName, childName, null, password); } userKeyManager.initialize(); _userContentNames.put(friendlyName, childName); _userKeyManagers.put(friendlyName, userKeyManager); } } /** * Backwards compatibility constructor */ public CreateUserData(ContentName userKeyStorePrefix, int userCount, boolean storeInRepo, char [] password) throws ConfigurationException, IOException, InvalidKeyException { this(userKeyStorePrefix, null, userCount, storeInRepo, password); } /** * General read constructor. Expects names to be available in repo, and so enumerable. * i.e. something must be there. Uses NetworkKeyManager to read them out, though. * @throws IOException * @throws ConfigurationException * @throws InvalidKeyException */ public CreateUserData(ContentName userKeystoreDataPrefix, char [] password, CCNHandle handle) throws IOException, ConfigurationException, InvalidKeyException { EnumeratedNameList userDirectory = new EnumeratedNameList(userKeystoreDataPrefix, handle); userDirectory.waitForChildren(); // will block SortedSet<ContentName> availableChildren = userDirectory.getChildren(); if ((null == availableChildren) || (availableChildren.size() == 0)) { Log.warning("No available user keystore data in directory " + userKeystoreDataPrefix + ", giving up."); throw new IOException("No available user keystore data in directory " + userKeystoreDataPrefix + ", giving up."); } String friendlyName; ContentName childName; BasicKeyManager userKeyManager; while (null != availableChildren) { for (ContentName child : availableChildren) { friendlyName = Component.printNative(child.lastComponent()); if (null != getUser(friendlyName)) { Log.info("Already loaded data for user: " + friendlyName + " from name: " + _userContentNames.get(friendlyName)); continue; } childName = new ContentName(userKeystoreDataPrefix, child.lastComponent()); Log.info("Loading user: " + friendlyName + " from " + childName); userKeyManager = new NetworkKeyManager(friendlyName, childName, null, password); userKeyManager.initialize(); _userContentNames.put(friendlyName, childName); _userKeyManagers.put(friendlyName, userKeyManager); } availableChildren = null; if (userDirectory.hasNewData()) { // go around opportunistically availableChildren = userDirectory.getNewData(); } } } /** * Read/write constructor to write keystores as files. * Makes extra new users if necessary. Expects names to come as above. * Will incur timeouts the first time, as it checks for data first, and will take time * to generate keys. * TODO eventually use this "for real" with real passwords. * @param userKeystoreDirectory a directory under which to put each user's information; * segregated into subdirectories by user name, e.g. <userKeystoreDirectory>/<userName>. * @param userNames list of user names to use, if null uses built-in list * @param userCount * @param storeInRepo * @param password * @param handle * @throws IOException * @throws ConfigurationException * @throws IOException * @throws ConfigurationException * @throws InvalidKeyException * @throws InvalidKeyException */ public CreateUserData(File userKeystoreDirectory, String [] userNames, int userCount, char [] password, boolean clearSavedState) throws ConfigurationException, IOException, InvalidKeyException { String friendlyName = null; BasicKeyManager userKeyManager = null; File userDirectory = null; File userKeystoreFile = null; if (!userKeystoreDirectory.exists()) { userKeystoreDirectory.mkdirs(); } else if (!userKeystoreDirectory.isDirectory()) { Log.severe("Specified path {0} must be a directory!", userKeystoreDirectory); throw new IllegalArgumentException("Specified path " + userKeystoreDirectory + " must be a directory!"); } for (int i=0; i < userCount; ++i) { friendlyName = userNames[i % userNames.length]; if (i >= userNames.length) { friendlyName += Integer.toString(1 + i/userNames.length); } userDirectory = new File(userKeystoreDirectory, friendlyName); if (!userDirectory.exists()) { userDirectory.mkdirs(); } userKeystoreFile = new File(userDirectory, UserConfiguration.keystoreFileName()); if (userKeystoreFile.exists()) { Log.info("Loading user: " + friendlyName + " from " + userKeystoreFile.getAbsolutePath()); } else { Log.info("Creating user's: " + friendlyName + " keystore in file " + userKeystoreFile.getAbsolutePath()); } userKeyManager = new BasicKeyManager(friendlyName, userDirectory.getAbsolutePath(), null, null, null, null, password); if (clearSavedState) { userKeyManager.clearSavedConfigurationState(); } userKeyManager.initialize(); _userKeyManagers.put(friendlyName, userKeyManager); _userKeystoreDirectories.put(friendlyName, userDirectory.getAbsoluteFile()); } } public CreateUserData(File userKeystoreDirectory, String [] userNames, int userCount, char [] password) throws ConfigurationException, IOException, InvalidKeyException { this(userKeystoreDirectory, userNames, userCount, password, false); } /** * For writing apps that run "as" a particular user. * @param userKeystoreDirectory This is the path to this particular user's keystore directory, * not the path above it where a bunch of users might have been generated. Assumes keystore * file has default name in that directory. If you give it a path that doesn't exist, it * takes it as a directory and makes a keystore there making the parent directories if necessary. * @return * @throws IOException * @throws ConfigurationException * @throws InvalidKeyException */ public static KeyManager loadKeystoreFile(File userKeystoreFileOrDirectory, String friendlyName, char [] password) throws ConfigurationException, IOException, InvalidKeyException { // Could actually easily generate this... probably should do that instead. if (!userKeystoreFileOrDirectory.exists()) { userKeystoreFileOrDirectory.mkdirs(); } File userKeystoreFile = userKeystoreFileOrDirectory.isDirectory() ? new File(userKeystoreFileOrDirectory, UserConfiguration.keystoreFileName()) : userKeystoreFileOrDirectory; if (userKeystoreFile.exists()) { Log.info("Loading user: from " + userKeystoreFile.getAbsolutePath()); } else { Log.info("Creating user's: keystore in file " + userKeystoreFile.getAbsolutePath()); } KeyManager userKeyManager = new BasicKeyManager(friendlyName, userKeystoreFile.getParent(), null, null, null, null, password); userKeyManager.initialize(); return userKeyManager; } /** * Load a set of user data from an existing generated set of file directories. Don't * force user to know names or count, enumerate them. * @throws IOException * @throws ConfigurationException * @throws InvalidKeyException */ public static CreateUserData readUserDataDirectory(File userDirectory, char [] keystorePassword) throws ConfigurationException, IOException, InvalidKeyException { if (!userDirectory.exists()) { Log.warning("Asked to read data from user directory {0}, but it does not exist!", userDirectory); return null; } if (!userDirectory.isDirectory()) { Log.warning("Asked to read data from user directory {0}, but it isn't a directory!", userDirectory); return null; } // Right now assume everything below here is a directory. String [] children = userDirectory.list(); return new CreateUserData(userDirectory, children, children.length, keystorePassword); } public void closeAll() { for (BasicKeyManager km : _userKeyManagers.values()) { if (null != km) { km.close(); } } } public void publishUserKeysToRepository() throws IOException{ for (String friendlyName: _userKeyManagers.keySet()) { System.out.println(friendlyName); CCNHandle userHandle = getHandleForUser(friendlyName); try { userHandle.keyManager().publishKeyToRepository(); ContentName keyName = userHandle.keyManager().getDefaultKeyNamePrefix(); keyName = keyName.cut(keyName.count()-1); // Won't publish if it's already there. userHandle.keyManager().publishKeyToRepository(keyName, null); } catch (Exception e) { e.printStackTrace(); } } } /** * Publishes self-referential key objects under user namespace prefix. * @param userNamespace * @return * @throws IOException * @throws InvalidKeyException */ public PublicKeyObject [] publishUserKeysToRepository(ContentName userNamespace) throws IOException, InvalidKeyException{ PublicKeyObject [] results = new PublicKeyObject[_userKeyManagers.size()]; int i=0; for (String friendlyName: _userKeyManagers.keySet()) { CCNHandle userHandle = getHandleForUser(friendlyName); ContentName keyName = new ContentName(userNamespace, friendlyName); results[i++] = userHandle.keyManager().publishSelfSignedKeyToRepository( keyName, userHandle.keyManager().getDefaultPublicKey(), userHandle.keyManager().getDefaultKeyID(), SystemConfiguration.getDefaultTimeout()); } return results; } public PublicKeyObject [] publishUserKeysToRepositorySetLocators(ContentName userNamespace) throws InvalidKeyException, IOException { PublicKeyObject [] results = publishUserKeysToRepository(userNamespace); int i=0; for (String friendlyName: _userKeyManagers.keySet()) { CCNHandle userHandle = getHandleForUser(friendlyName); userHandle.keyManager().setKeyLocator(null, results[i].getPublisherKeyLocator()); i++; } return results; } public boolean hasUser(String friendlyName) { return _userKeyManagers.containsKey(friendlyName); } public BasicKeyManager getUser(String friendlyName) { return _userKeyManagers.get(friendlyName); } public File getUserDirectory(String friendlyName) { return _userKeystoreDirectories.get(friendlyName); } public CCNHandle getHandleForUser(String friendlyName) throws IOException { BasicKeyManager km = getUser(friendlyName); if (null == km) return null; return km.handle(); } public Set<String> friendlyNames() { return _userKeyManagers.keySet(); } public int count() { return _userKeyManagers.size(); } /** * Helper method for other programs that want to use TestUserData. Takes an args * array, and an offset int it, at which it expects to find (optionally) * [-as keystoreDirectoryorFilePath [-name friendlyName]]. If the latter refers to * a file, it takes it as the keystore file. If it refers to a directory, it looks * for the default keystore file name under that directory. If the friendly name * argument is given, it uses that as the friendly name, otherwise it uses the last * component of the keystoreDirectoryOrFilePath. It returns a Tuple of a handle * opened under that user, and the count of arguments read, or null if the argument * at offset was not -as. */ public static Tuple<Integer, CCNHandle> handleAs(String [] args, int offset) throws ConfigurationException, IOException, InvalidKeyException { Tuple<Integer, KeyManager> t = keyManagerAs(args, offset); if (null == t) return null; return new Tuple<Integer, CCNHandle>(t.first(), CCNHandle.open(t.second())); } public static Tuple<Integer, KeyManager> keyManagerAs(String [] args, int offset) throws ConfigurationException, IOException, InvalidKeyException { String friendlyName = null; String keystoreFileOrDirectoryPath = null; int argsUsed = 0; if (args.length < offset + 2) return null; // no as if (args.length >= offset+2) { if (!args[offset].equals("-as")) { return null; // caller must print usage() } else { keystoreFileOrDirectoryPath = args[offset+1]; argsUsed += 2; } } if (args.length >= offset+5) { if (args[offset+2].equals("-name")) { friendlyName = args[offset+3]; argsUsed += 2; } } if (null == keystoreFileOrDirectoryPath) return null; // no as File keystoreFileOrDirectory = new File(keystoreFileOrDirectoryPath); if ((null == friendlyName) && ((keystoreFileOrDirectory.exists() && (keystoreFileOrDirectory.isDirectory())) || (!keystoreFileOrDirectory.exists()))) { // if its a directory, or it doesn't exist yet and we're going to make it as one, // use last component as user name friendlyName = keystoreFileOrDirectory.getName(); } Log.info("handleAs: loading data for user {0} from location {1}", friendlyName, keystoreFileOrDirectory); KeyManager manager = CreateUserData.loadKeystoreFile(keystoreFileOrDirectory, friendlyName, UserConfiguration.keystorePassword().toCharArray()); return new Tuple<Integer, KeyManager>(argsUsed, manager); } public static KeyManager keyManagerAs(String keystoreFileOrDirectoryPath, String friendlyName) throws InvalidKeyException, ConfigurationException, IOException { File keystoreFileOrDirectory = new File(keystoreFileOrDirectoryPath); if ((null == friendlyName) && ((keystoreFileOrDirectory.exists() && (keystoreFileOrDirectory.isDirectory())) || (!keystoreFileOrDirectory.exists()))) { // if its a directory, or it doesn't exist yet and we're going to make it as one, // use last component as user name friendlyName = keystoreFileOrDirectory.getName(); } Log.info("keyManagerAs: loading data for user {0} from location {1}", friendlyName, keystoreFileOrDirectory); KeyManager manager = CreateUserData.loadKeystoreFile(keystoreFileOrDirectory, friendlyName, UserConfiguration.keystorePassword().toCharArray()); return manager; } public static void usage() { System.out.println("usage: CreateUserData [[-f <file directory for keystores>] | [-r] <ccn uri for keystores>] [\"comma-separated user names\"] <user count> [<password>] [-p] (-r == use repo, -f == use files)"); } /** * Command-line driver to generate key data. */ public static void main(String [] args) { boolean useRepo = false; File directory = null; ContentName userNamespace = null; boolean publishKeysToRepo = true; Flosser flosser = null; String [] userNames = null; CreateUserData td = null; int arg = 0; if (args.length < 2) { usage(); return; } if (args[arg].equals("-f")) { arg++; if (args.length < 3) { usage(); return; } directory = new File(args[arg++]); } else { if (args[arg].equals("-r")) { arg++; useRepo = true; if (args.length < 3) { usage(); return; } } // Generate and write to repo try { userNamespace = ContentName.fromURI(args[arg]); if (!useRepo) { flosser = new Flosser(userNamespace); } } catch (Exception e) { System.out.println("Exception parsing user namespace " + args[arg]); e.printStackTrace(); return; } arg++; } if ((args.length - arg) >= 2) { String userNamesString = args[arg++]; userNames = userNamesString.split(","); } else userNames = USER_NAMES; int count = Integer.valueOf(args[arg++]); String password = UserConfiguration.keystorePassword(); if (arg < args.length) { password = args[arg++]; } try { if (null != directory) { td = new CreateUserData(directory, userNames, count, password.toCharArray()); if (publishKeysToRepo) { td.publishUserKeysToRepository(); } } else { td = new CreateUserData(userNamespace, userNames, count, useRepo, password.toCharArray()); if (publishKeysToRepo) { td.publishUserKeysToRepository(); } } System.out.println("Generated/retrieved " + td.count() + " user keystores, for users : " + td.friendlyNames()); } catch (Exception e) { System.out.println("Exception generating/reading user data: " + e); e.printStackTrace(); } finally { if (null != flosser) flosser.stop(); } if (null != td) { td.closeAll(); } System.out.println("Finished."); System.exit(0); } }