/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008-2012 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library 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
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.impl.security.keys;
import static org.ccnx.ccn.impl.support.Serial.readObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.config.UserConfiguration;
import org.ccnx.ccn.impl.security.crypto.EncryptedObjectFileHelper;
import org.ccnx.ccn.impl.security.crypto.util.MinimalCertificateGenerator;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.impl.support.Tuple;
import org.ccnx.ccn.io.content.KeyValueSet;
import org.ccnx.ccn.io.content.PublicKeyObject;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.profiles.security.access.AccessControlManager;
import org.ccnx.ccn.protocol.CCNTime;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.KeyLocator;
import org.ccnx.ccn.protocol.KeyLocator.KeyLocatorType;
import org.ccnx.ccn.protocol.MalformedContentNameStringException;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
/**
* This is a basic implementation of key manager, which reads
* its keying information from a Java keystore. If no keystore
* file is specified, it reads keystore information from
* the default keystore file location in the user's home directory
* (under ~/.ccnx).
* BasicKeyManager expects to find at least a default key pair
* under a know alias and password (change to something more sensible).
* If the file does not exist, BasicKeyManager generates a
* public/private key pair and a certificate and stores them to disk
* at the specified location.
* @see KeyManager
*/
public class BasicKeyManager extends KeyManager {
protected String _userName;
protected ContentName _userNamespace; // default location for publishing keys
protected String _defaultAlias;
protected String _keyStoreDirectory;
protected String _keyStoreFileName;
protected String _keyStoreType;
protected String _configurationFileName;
protected String _keyCacheFileName;
protected KeyStoreInfo _keyStoreInfo;
protected PublisherPublicKeyDigest _defaultKeyID;
protected boolean _initialized = false;
private char [] _password = null;
/**
* Cache of public keys, handles key publishing, etc.
*/
protected PublicKeyCache _publicKeyCache = null;
/**
* Cache of private keys, loaded from keystores.
*/
protected SecureKeyCache _privateKeyCache = null;
/**
* Key server, offering up our keys, if we need one.
*/
protected KeyServer _keyServer = null;
/**
* Configuration data
*/
protected KeyValueSet _configurationData = null;
/**
* Handle used by key server and key retrieval.
* This may be null, so always access via handle() if you are going to use it.
*/
protected CCNHandle _handle = null;
/**
* Registry of key locators to use. In essence, these are pointers to our
* primary credential for each key. Unless overridden this is what we use
* for each of our signing keys.
*
* TODO consider adding a second map tracking all the available key locators for
* a given key, to select from them.
*/
protected HashMap<PublisherPublicKeyDigest, KeyLocator> _currentKeyLocators = new HashMap<PublisherPublicKeyDigest, KeyLocator>();
/**
* Access control managers containing our state.
*/
protected Set<AccessControlManager> _acmList = new HashSet<AccessControlManager>();
/**
* Subclass constructor that sets store-independent parameters.
*/
protected BasicKeyManager(String userName, String keyStoreType,
String defaultAlias, char [] password) throws ConfigurationException, IOException {
_password = (null != password) ? password : UserConfiguration.keystorePassword().toCharArray();
_keyStoreType = (null != keyStoreType) ? keyStoreType : UserConfiguration.defaultKeystoreType();
_defaultAlias = ((null != defaultAlias) ? defaultAlias : UserConfiguration.defaultKeyAlias()).toLowerCase();
String defaultUserName = UserConfiguration.userName();
if ((null == userName) || (userName.equals(defaultUserName))) {
_userNamespace = UserConfiguration.userNamespace();
_userName = defaultUserName;
} else {
_userNamespace = UserConfiguration.userNamespace(userName);
_userName = userName;
}
// must call initialize
}
/**
* Constructor
*
* @throws ConfigurationException
* @throws IOException
*/
public BasicKeyManager(String userName, String keyStoreDirectory,
String configurationFileName,
String keyStoreFileName, String keyStoreType,
String defaultAlias, char [] password) throws ConfigurationException, IOException {
this(userName, keyStoreType, defaultAlias, password);
_keyStoreFileName = (null != keyStoreFileName) ?
keyStoreFileName : UserConfiguration.keystoreFileName();
_configurationFileName = (null != configurationFileName) ?
configurationFileName : UserConfiguration.configurationFileName();
// Don't let people override this. Also make "confguration file" a configuration file,
// move cached identity data to an identity file name TODO.
_keyCacheFileName = UserConfiguration.keyCacheFileName();
_keyStoreDirectory = (null != keyStoreDirectory) ? keyStoreDirectory : UserConfiguration.userConfigurationDirectory();
// must call initialize
}
/**
* Default constructor, takes all parameters from defaults in UserCOnfiguration.
* @throws IOException
* @throws ConfigurationException
*/
public BasicKeyManager() throws ConfigurationException, IOException {
this(null, null, null, null, null, null, null);
}
/**
* This initializes and loads the key pair and certificate of the user.
* If a key store file exists,
* reads in the key; otherwise, create a key store file and a key pair.
* Separate this for the usual reasons; so subclasses can get set up before it's called.
* Could make fake base class constructor, and call loadKeyStore in subclass constructors,
* but this wouldn't work past one level, and this allows subclasses to override initialize behavior.
* @throws ConfigurationException
*/
@Override
public synchronized void initialize() throws ConfigurationException, IOException {
if (_initialized)
return;
_publicKeyCache = new PublicKeyCache();
_privateKeyCache = new SecureKeyCache();
_keyStoreInfo = loadKeyStore();// uses _keyRepository and _privateKeyCache
if (!loadValuesFromKeystore(_keyStoreInfo)) {
Log.warning("Cannot process keystore!");
}
// This also loads our cached keys.
if (!loadValuesFromConfiguration(_keyStoreInfo)) {
Log.warning("Cannot process configuration data!");
}
_initialized = true;
// If we haven't been called off, initialize the key server
if (UserConfiguration.publishKeys()) {
initializeKeyServer(handle());
}
}
public synchronized void initializeKeyServer(CCNHandle handle) throws IOException {
if (null != _keyServer) {
return;
}
_keyServer = new KeyServer(handle);
if (UserConfiguration.publishKeys()) {
try {
this.publishKey(getDefaultKeyName(getDefaultKeyID()),
getDefaultPublicKey(),
null,
null,
true);
} catch (InvalidKeyException e) {
// in java 1.6 move to handing exception to constructor
throw new IOException("Default key is invalid! " + e);
}
}
}
public PublicKeyObject serveKey(ContentName keyName, PublicKey keyToPublish,
PublisherPublicKeyDigest signingKeyID,
KeyLocator signingKeyLocator) throws IOException {
// TODO -- make use default handle, just pass in KM to do signing and have KS use separate
// handle for publishing
initializeKeyServer(handle());
return _keyServer.serveKey(keyName, keyToPublish, signingKeyID, signingKeyLocator);
}
@Override
public void respondToKeyRequests(ContentName keyPrefix) throws IOException {
initializeKeyServer(handle());
_keyServer.respondToKeyRequests(keyPrefix);
}
@Override
public boolean initialized() { return _initialized; }
/**
* Close any connections we have to the network. Ideally prepare to
* reopen them when they are next needed.
*/
public synchronized void close() {
if( Log.isLoggable(Log.FAC_KEYS, Level.FINE) )
Log.fine(Log.FAC_KEYS, "BasicKeyManager.close()");
super.close();
if (_handle != null) {
_handle.close();
_handle = null;
}
try {
saveSecureKeyCache();
} catch (Exception e) {
if (Log.isLoggable(Log.FAC_KEYS, Level.WARNING)) {
Log.warning("Exception saving secure key cache: {0}", e);
}
}
}
public synchronized CCNHandle handle() throws IOException {
if (null == _handle)
_handle = CCNHandle.open(this);
return _handle;
}
protected void setPassword(char [] password) {
_password = password;
}
/**
* If a key store file exists, reads in the key;
* otherwise, create a key store file and a key pair.
* @param keyStoreFileName the file containing the keystore, if null
* uses default in user's home directory.
* @throws ConfigurationException
*/
protected KeyStoreInfo loadKeyStore() throws ConfigurationException, IOException {
File keyStoreFile = new File(_keyStoreDirectory, _keyStoreFileName);
KeyStoreInfo keyStoreInfo = null;
if (!keyStoreFile.exists() || (0 == keyStoreFile.length())) {
// If the BC configuration is screwed up, sometimes a 0-length keystore
// gets created. If so, blow it away and make a new one.
Log.info(Log.FAC_KEYS, "Creating new CCN key store..." + keyStoreFile.getAbsolutePath());
keyStoreInfo = createKeyStore();
Log.info(Log.FAC_KEYS, "...created key store. Version: {0} ({1} ms) Last modified: {2}. Will now load normally.",
keyStoreInfo.getVersion(), keyStoreInfo.getVersion().getTime(), keyStoreFile.lastModified());
// For some reason, if we just go from here, sometimes we end up with very slightly
// different stat times on the file. This causes havoc with versioning. So,
// read the file back in from scratch.
keyStoreInfo = null;
}
if (null == keyStoreInfo) {
FileInputStream in = null;
KeyStore keyStore = null;
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "Loading CCN key store from " + keyStoreFile.getAbsolutePath() + "...last modified " + keyStoreFile.lastModified() + "(ms).");
try {
in = new FileInputStream(keyStoreFile);
keyStore = readKeyStore(in);
keyStoreInfo = new KeyStoreInfo(keyStoreFile.toURI().toString(), keyStore, new CCNTime(keyStoreFile.lastModified()));
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "Loaded CCN key store from " + keyStoreFile.getAbsolutePath() + "...version " + keyStoreInfo.getVersion() + " ms: " + keyStoreInfo.getVersion().getTime());
} catch (FileNotFoundException e) {
Log.warning("Cannot open existing key store file: " + _keyStoreFileName);
throw e;
}
}
return keyStoreInfo;
}
/**
* Reads in a user's private/public keys and certificate from a key store
* Must have set _password.
* @param in input stream
* @throws ConfigurationException
*/
protected KeyStore readKeyStore(InputStream in) throws ConfigurationException {
KeyStore keyStore = null;
try {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "Loading CCN key store...");
keyStore = KeyStore.getInstance(_keyStoreType);
keyStore.load(in, _password);
} catch (NoSuchAlgorithmException e) {
Log.warning("Cannot load keystore: " + e);
throw new ConfigurationException("Cannot load default keystore: " + e);
} catch (CertificateException e) {
Log.warning("Cannot load keystore with no certificates.");
throw new ConfigurationException("Cannot load keystore with no certificates.");
} catch (IOException e) {
Log.warning("Cannot open existing key store: " + e);
try {
in.reset();
java.io.FileOutputStream bais = new java.io.FileOutputStream("KeyDump.p12");
try {
byte [] tmp = new byte[2048];
int read = in.read(tmp);
while (read > 0) {
bais.write(tmp, 0, read);
read = in.read(tmp);
}
bais.flush();
} finally {
bais.close();
}
} catch (IOException e1) {
Log.info(Log.FAC_KEYS, "Another exception: " + e1);
}
throw new ConfigurationException(e);
} catch (KeyStoreException e) {
Log.warning("Cannot create instance of preferred key store type: " + _keyStoreType + " " + e.getMessage());
Log.warningStackTrace(e);
throw new ConfigurationException("Cannot create instance of default key store type: " + _keyStoreType + " " + e.getMessage());
} finally {
if (null != in)
try {
in.close();
} catch (IOException e) {
Log.warning("IOException closing key store file after load.");
Log.warningStackTrace(e);
}
}
return keyStore;
}
/**
* Read data from a newly opened, or newly created keystore.
* @param keyStore
* @throws ConfigurationException
*/
protected boolean loadValuesFromKeystore(KeyStoreInfo keyStoreInfo) throws ConfigurationException {
KeyStore.PrivateKeyEntry entry = null;
try {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "Loading key store {0} version {1} version component {2} millis {3}", keyStoreInfo.getKeyStoreURI(), keyStoreInfo.getVersion().toString(),
VersioningProfile.printAsVersionComponent(keyStoreInfo.getVersion()), keyStoreInfo.getVersion().getTime());
// Default alias should be a PrivateKeyEntry
entry = (KeyStore.PrivateKeyEntry)keyStoreInfo.getKeyStore().getEntry(_defaultAlias, new KeyStore.PasswordProtection(_password));
if (null == entry) {
Log.warning("Cannot get default key entry: " + _defaultAlias);
generateConfigurationException("Cannot retrieve default user keystore entry.", null);
}
X509Certificate certificate = (X509Certificate)entry.getCertificate();
if (null == certificate) {
Log.warning("Cannot get certificate for default key entry: " + _defaultAlias);
generateConfigurationException("Cannot retrieve certificate for default user keystore entry.", null);
}
_defaultKeyID = new PublisherPublicKeyDigest(certificate.getPublicKey());
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "Default key ID for user " + _userName + ": " + _defaultKeyID);
_privateKeyCache.loadKeyStore(keyStoreInfo, _password, _publicKeyCache);
} catch (Exception e) {
generateConfigurationException("Cannot retrieve default user keystore entry.", e);
}
return true;
}
/**
* Load values of relevance to a key manager. Most importantly, loads default
* key locator information. If the system parameter UserConfiguration.useKeyConfiguration()
* (settable from an environment variable, a Java property, or programmatically) is false,
* we do not load our saved key locators/identities, or our saved secret key cache.
* @return true if successful, false on error
* @throws ConfigurationException
*/
protected boolean loadValuesFromConfiguration(KeyStoreInfo keyStoreInfo) throws ConfigurationException {
// Load key locator information. Might be in two places -- system property/environment variable,
// or configuration file. Start with just system property, first round just specify
// name, not publisher.
// Starting step -- read a key name (no publisher) key locator just for our default
// key from an environment variable/system property.
String defaultKeyLocatorName = UserConfiguration.defaultKeyLocator();
// Doesn't even support publisher specifications yet.
if (null != defaultKeyLocatorName) {
try {
ContentName locatorName = ContentName.fromNative(defaultKeyLocatorName);
setKeyLocator(getDefaultKeyID(), new KeyLocator(locatorName));
} catch (MalformedContentNameStringException e) {
generateConfigurationException("Cannot parse key locator name {0}!", e);
}
}
// Load values from our configuration file, which should be read in UserConfiguration.
// If useKeyConfiguration() is false, we do not read configured identities or cached secret/private keys.
if (!UserConfiguration.useKeyConfiguration()) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Not loading key manager configuration data in response to user configuration variable.");
}
return true;
}
// Currently have saved data override command line, which might be bad...
// also use that to preconfigure things like keystores and such
// for right now, just as a super-fast trick, use java serialization to get out minmal data necessary
if ((null == _keyStoreDirectory) || (null == _configurationFileName)) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "loadValuesFromConfiguration: No configuration directory/file set, not loading.");
}
return true;
}
File configurationFile = new File(_keyStoreDirectory, _configurationFileName);
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "loadValuesFromConfiguration: attempting to load configuration from {0}", configurationFile.getAbsolutePath());
}
if (configurationFile.exists()) {
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream(configurationFile));
try {
HashMap<PublisherPublicKeyDigest, KeyLocator> savedKeyLocators = readObject(input);
_currentKeyLocators.putAll(savedKeyLocators);
keyStoreInfo.setConfigurationFileURI(configurationFile.toURI().toString());
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Loaded configuration data from file {0}, got {1} key locator values.",
configurationFile.getAbsolutePath(), savedKeyLocators.size());
}
} finally {
input.close();
}
} catch (FileNotFoundException e) {
throw new ConfigurationException("Cannot read configuration file even though it claims to exist: " + configurationFile.getAbsolutePath(), e);
} catch (IOException e) {
throw new ConfigurationException("I/O error reading configuration file: " + configurationFile.getAbsolutePath(), e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("ClassNotFoundException deserializing configuration file: " + configurationFile.getAbsolutePath(), e);
}
} else {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "loadValuesFromConfiguration: configuration file {0} does not exist.", configurationFile.getAbsolutePath());
}
}
return loadSavedSecureKeyCache();
}
public synchronized boolean loadSavedSecureKeyCache() throws ConfigurationException {
// Load values from our configuration file, which should be read in UserConfiguration.
if (!UserConfiguration.saveAndLoadKeyCache()) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Not loading key cache in response to user configuration variable.");
}
return true;
}
// Currently have saved data override command line, which might be bad...
// also use that to preconfigure things like keystores and such
// for right now, just as a super-fast trick, use java serialization to get out minmal data necessary
if ((null == _keyStoreDirectory) || (null == _configurationFileName)) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "loadValuesFromConfiguration: No configuration directory/file set, not loading.");
}
return true;
}
File keyCacheFile = new File(_keyStoreDirectory, _keyCacheFileName);
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "loadSecureKeyCache: attempting to load saved key cache from {0}", keyCacheFile.getAbsolutePath());
}
if (keyCacheFile.exists()) {
try {
// Try to load the key cache.
SecureKeyCache keyCache = EncryptedObjectFileHelper.readEncryptedObject(keyCacheFile, getDefaultSigningKey());
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Loaded saved key cached data from file {0}, got {1} keys values.",
keyCacheFile.getAbsolutePath(), keyCache.size());
keyCache.printContents();
}
// merge key caches
if (null != keyCache) {
keyCache.merge(_privateKeyCache);
_privateKeyCache = keyCache;
}
} catch (FileNotFoundException e) {
Log.warning("Proceeding without cached keys -- cannot read key cache file even though it claims to exist: " + keyCacheFile.getAbsolutePath(), e);
} catch (IOException e) {
Log.warning("Proceeding without cached keys -- I/O error reading key cache file: " + keyCacheFile.getAbsolutePath(), e);
} catch (ClassNotFoundException e) {
Log.warning("Proceeding without cached keys -- ClassNotFoundException deserializing encrypted key cache file: " + keyCacheFile.getAbsolutePath(), e);
}
} else {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "loadSavedSecureKeyCache: key cache file {0} does not exist.", keyCacheFile.getAbsolutePath());
}
}
return true;
}
/**
* As a very initial pass, save key cache as encrypted Java serialization. Later
* we'll clean this up... (And encryption still needs to be hooked up.)
* @throws IOException
* @throws FileNotFoundException
*/
@Override
public void saveSecureKeyCache() throws FileNotFoundException, IOException {
// This prevents us from writing the data out to a file, where it could interact badly with
// user state (e.g. if we're a unit test). It will still be changed in the runtime data,
// allowing unit tests to use it within a single execution.
if (!UserConfiguration.saveAndLoadKeyCache()) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Not saving key manager secure key cache data in response to user configuration variable.");
}
return;
}
getSecureKeyCache().validateForWriting();
File keyCacheFile = new File(_keyStoreDirectory, _keyCacheFileName);
EncryptedObjectFileHelper.writeEncryptedObject(keyCacheFile, getSecureKeyCache(), getDefaultPublicKey());
}
/**
* As a very initial pass, save configuration state as Java serialization. Later
* we'll clean this up...
* @throws IOException
* @throws FileNotFoundException
*/
@Override
public void saveConfigurationState() throws FileNotFoundException, IOException {
// This prevents us from writing the data out to a file, where it could interact badly with
// user state (e.g. if we're a unit test). It will still be changed in the runtime data,
// allowing unit tests to use it within a single execution.
if (!UserConfiguration.useKeyConfiguration()) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Not saving key manager configuration data in response to user configuration variable.");
}
return;
}
File configurationFile = new File(_keyStoreDirectory, _configurationFileName);
// Update configuration data:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(configurationFile));
try {
oos.writeObject(_currentKeyLocators);
} finally {
oos.close();
}
}
/**
* Return a file URI pointing at our configuration data directory.
*/
@Override
public URI getConfigurationDataURI() {
File configurationFile = new File(_keyStoreDirectory, _configurationFileName);
return configurationFile.toURI();
}
/**
* Need a way to clear this programmatically. Call this before initialize(). This
* deletes our saved configuration state, key files and stored key cache.
*/
@Override
public void clearSavedConfigurationState() throws FileNotFoundException, IOException {
File configurationFile = new File(_keyStoreDirectory, _configurationFileName);
if (configurationFile.exists()) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Deleting configuration state file {0}.", configurationFile.getAbsolutePath());
}
if (!configurationFile.delete()) {
Log.warning("Unable to delete configuration state file {0}.", configurationFile.getAbsolutePath());
}
}
clearStoredKeyLocator(null);
File keyCacheFile = new File(_keyStoreDirectory, _keyCacheFileName);
if (keyCacheFile.exists()) {
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Deleting secret/private key cache file {0}.", keyCacheFile.getAbsolutePath());
}
if (!keyCacheFile.delete()) {
Log.warning("Unable to delete secret/private key cache file {0}.", keyCacheFile.getAbsolutePath());
}
}
}
/**
* Generate our key store if we don't have one. Use createKeyStoreWriteStream to determine where
* to put it.
* @throws ConfigurationException
*/
synchronized protected KeyStoreInfo createKeyStore()
throws ConfigurationException, IOException {
Tuple<KeyStoreInfo, OutputStream> streamInfo = createKeyStoreWriteStream();
KeyStore keyStore = createKeyStore(streamInfo.second());
KeyStoreInfo storeInfo = streamInfo.first();
storeInfo.setKeyStore(keyStore);
if (null == storeInfo.getVersion()) {
storeInfo.setVersion(getKeyStoreVersion(streamInfo.second()));
}
return storeInfo;
}
protected CCNTime getKeyStoreVersion(OutputStream out) throws IOException {
// in our case, our output stream should be a file output stream...
if (!(out instanceof FileOutputStream)) {
throw new IOException("Unexpected output stream type in getKeyStoreVersion: " + out.getClass().getName());
}
File keyStoreFile = new File(_keyStoreDirectory, _keyStoreFileName);
if (!keyStoreFile.exists()) {
throw new IOException("KeyStore doesn't exist in getKeyStoreVersion: " + keyStoreFile.getAbsolutePath());
}
return new CCNTime(keyStoreFile.lastModified());
}
/**
* Creates a key store file
* @throws ConfigurationException
*/
protected Tuple<KeyStoreInfo, OutputStream> createKeyStoreWriteStream() throws ConfigurationException,
IOException {
return createKeyStoreWriteStream(_keyStoreDirectory, _keyStoreFileName);
}
/**
* Creates a key store file
* @throws ConfigurationException
*/
protected static Tuple<KeyStoreInfo, OutputStream> createKeyStoreWriteStream(
String keyStoreDirectory, String keyStoreFileName) throws ConfigurationException, IOException {
File keyStoreDir = new File(keyStoreDirectory);
if (!keyStoreDir.exists()) {
if (!keyStoreDir.mkdirs()) {
throw new ConfigurationException("Cannot create keystore directory: " + keyStoreDir.getAbsolutePath(), null);
}
}
// Alas, until 1.6, we can't set permissions on the file or directory...
// TODO DKS when switch to 1.6, add permission settings.
File keyStoreFile = new File(keyStoreDir, keyStoreFileName);
if (keyStoreFile.exists()) {
Log.warning("Key store file {0} already exists (length {1}), overrwriting.", keyStoreFile.getAbsolutePath(), keyStoreFile.length());
}
FileOutputStream out = null;
try {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "Creating FileOutputStream to write keystore to file " + keyStoreFile.getAbsolutePath());
out = new FileOutputStream(keyStoreFile);
} catch (FileNotFoundException e) {
generateConfigurationException("Cannot create keystore file: " + keyStoreFile.getAbsolutePath(), e);
}
KeyStoreInfo storeInfo = new KeyStoreInfo(keyStoreFile.toURI().toString(), null, new CCNTime(keyStoreFile.lastModified()));
return new Tuple<KeyStoreInfo, OutputStream>(storeInfo, out);
}
protected KeyStore createKeyStore(OutputStream keystoreWriteStream)
throws ConfigurationException, IOException {
return createKeyStore(keystoreWriteStream, _keyStoreType, _defaultAlias, _password, _userName);
}
/**
* Generates a key pair and a certificate, and stores them to the key store using the specified
* alias, password, and other information.
* @param keystoreWriteStream The output stream to write the keystore to (file stream, ccn stream, ...)
* @param keyStoreType The keystore type to use. If null, uses UserConfiguration.defaultKeyStoreType()
* @param keyAlias The key alias to use. If null, uses UserConfiguration.defaultKeyAlias(). Note
* that toLower is called on the alias before it is used, as OSes vary in their handling of
* case in keystore aliases (some treat it as significant, some don't).
* @param password The password to use for the key and keystore, if null uses
* UserConfiguration.keystorePassword()
* @param userName The user name to use. If null, uses UserConfiguration.userName().
* @return
* @throws ConfigurationException
* @throws IOException
*/
public static KeyStore createKeyStore(OutputStream keystoreWriteStream,
String keyStoreType, String keyAlias,
char [] password,
String userName) throws ConfigurationException, IOException {
if (null == keyStoreType) {
keyStoreType = UserConfiguration.defaultKeystoreType();
}
if (null == keyAlias) {
keyAlias = UserConfiguration.defaultKeyAlias();
}
keyAlias = keyAlias.toLowerCase();
if (null == password) {
password = UserConfiguration.keystorePassword().toCharArray();
}
if (null == userName) {
userName = UserConfiguration.userName();
}
KeyStore ks = null;
try {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: getting instance of keystore type " + keyStoreType);
ks = KeyStore.getInstance(keyStoreType);
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: loading key store.");
ks.load(null, password);
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: key store loaded.");
} catch (NoSuchAlgorithmException e) {
generateConfigurationException("Cannot load empty default keystore.", e);
} catch (CertificateException e) {
generateConfigurationException("Cannot load empty default keystore with no certificates.", e);
} catch (KeyStoreException e) {
generateConfigurationException("Cannot create instance of default key store type.", e);
} catch (IOException e) {
generateConfigurationException("Cannot initialize instance of default key store type.", e);
}
KeyPairGenerator kpg = null;
try {
kpg = KeyPairGenerator.getInstance(UserConfiguration.defaultKeyAlgorithm());
} catch (NoSuchAlgorithmException e) {
generateConfigurationException("Cannot generate key using default algorithm: " + UserConfiguration.defaultKeyAlgorithm(), e);
}
kpg.initialize(UserConfiguration.defaultKeyLength());
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: generating " + UserConfiguration.defaultKeyLength() + "-bit " + UserConfiguration.defaultKeyAlgorithm() + " key.");
KeyPair userKeyPair = kpg.generateKeyPair();
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: key generated, generating certificate for user " + userName);
// Generate a self-signed certificate.
String subjectDN = "CN=" + userName;
X509Certificate ssCert = null;
try {
ssCert =
MinimalCertificateGenerator.GenerateUserCertificate(userKeyPair, subjectDN,
MinimalCertificateGenerator.MSEC_IN_YEAR);
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: certificate generated.");
} catch (Exception e) {
generateConfigurationException("InvalidKeyException generating user internal certificate.", e);
}
KeyStore.PrivateKeyEntry entry =
new KeyStore.PrivateKeyEntry(userKeyPair.getPrivate(), new X509Certificate[]{ssCert});
try {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: setting private key entry.");
ks.setEntry(keyAlias, entry,
new KeyStore.PasswordProtection(password));
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: storing key store.");
ks.store(keystoreWriteStream, password);
if (Log.isLoggable(Log.FAC_KEYS, Level.FINEST))
Log.finest(Log.FAC_KEYS, "createKeyStore: wrote key store.");
} catch (NoSuchAlgorithmException e) {
generateConfigurationException("Cannot save default keystore.", e);
} catch (CertificateException e) {
generateConfigurationException("Cannot save default keystore with no certificates.", e);
} catch (KeyStoreException e) {
generateConfigurationException("Cannot set private key entry for user default key", e);
} finally {
if (keystoreWriteStream != null) {
try {
keystoreWriteStream.close();
} catch (IOException e) {
Log.warning("IOException closing key store file after load.");
Log.warningStackTrace(e);
}
}
}
return ks;
}
public KeyStoreInfo getKeyStoreInfo() { return _keyStoreInfo; }
/**
* Helper method to turn low-level errors into ConfigurationExceptions
* @param message explanatory message
* @param e original error
* @throws ConfigurationException
*/
static void generateConfigurationException(String message, Exception e) throws ConfigurationException {
if (null == e) {
Log.warning("Throwing ConfigurationException: {0}", message);
throw new ConfigurationException(message);
} else {
Log.warning(message + " " + e.getClass().getName() + ": " + e.getMessage());
Log.warningStackTrace(e);
throw new ConfigurationException(message, e);
}
}
/**
* Get default key id
* @return default key id
*/
@Override
public PublisherPublicKeyDigest getDefaultKeyID() {
return _defaultKeyID;
}
/**
* Get default public key
* @return default public key
*/
@Override
public PublicKey getDefaultPublicKey() {
return _publicKeyCache.getPublicKeyFromCache(getDefaultKeyID());
}
@Override
public ContentName getDefaultKeyNamePrefix() {
ContentName keyDir = new ContentName(_userNamespace, UserConfiguration.defaultKeyNamespaceMarker());
return keyDir;
}
@Override
public ContentName getDefaultKeyName(PublisherPublicKeyDigest keyID) {
if (null == keyID)
keyID = getDefaultKeyID();
return getDefaultKeyName(getDefaultKeyNamePrefix(), keyID, getKeyVersion(keyID));
}
/**
* Get the key locator to use for a given key. If a value has been stored
* by calling setKeyLocator, that value will be used. Such values can
* also be initialized using command-line properties, environment variables,
* or configuration files. Usually it refers to content already published.
* As we don't know where the key might be published, if no value is
* specified, we return a locator of type KEY. We have deprecated the
* previous behavior of trying to look at objects we have published
* containing this key; this does not allow the user enough control over
* what key locator will be used.
* @return key locator
*/
@Override
public KeyLocator getKeyLocator(PublisherPublicKeyDigest keyID) {
if (null == keyID) {
keyID = getDefaultKeyID();
}
KeyLocator keyLocator = getStoredKeyLocator(keyID);
if (null == keyLocator) {
keyLocator = getKeyTypeKeyLocator(keyID);
}
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "getKeyLocator: returning locator {0} for key {1}", keyLocator, keyID);
return keyLocator;
}
@Override
public KeyLocator getStoredKeyLocator(PublisherPublicKeyDigest keyID) {
if (null == keyID) {
keyID = getDefaultKeyID();
}
return _currentKeyLocators.get(keyID);
}
@Override
public boolean haveStoredKeyLocator(PublisherPublicKeyDigest keyID) {
if (null == keyID) {
keyID = getDefaultKeyID();
}
return _currentKeyLocators.containsKey(keyID);
}
@Override
public void clearStoredKeyLocator(PublisherPublicKeyDigest keyID) {
if (null == keyID) {
keyID = getDefaultKeyID();
}
_currentKeyLocators.remove(keyID);
}
/**
* Helper method to get the key locator for one of our signing keys.
*/
@Override
public KeyLocator getKeyLocator(Key signingKey) {
PublisherPublicKeyDigest keyID = _privateKeyCache.getPublicKeyIdentifier(signingKey);
return getKeyLocator(keyID);
}
/**
* Remember the key locator to use for a given key. Use
* this to publish this key in the future if not overridden by method
* calls. If no key locator stored for this key, and no override
* given, compute a KEY type key locator if this key has not been
* published, and the name given to it when published if it has.
* @param publisherKeyID the key whose locator to set
* @param keyLocator the new key locator for this key; overrides any previous value.
* If null, erases previous value and defaults will be used.
*/
public void setKeyLocator(PublisherPublicKeyDigest publisherKeyID, KeyLocator keyLocator) {
if (null == publisherKeyID)
publisherKeyID = getDefaultKeyID();;
_currentKeyLocators.put(publisherKeyID, keyLocator);
}
@Override
public CCNTime getKeyVersion(PublisherPublicKeyDigest keyID) {
return _publicKeyCache.getPublicKeyVersionFromCache(keyID);
}
/**
* Get private key
* @return private key
*/
@Override
public Key getDefaultSigningKey() {
return _privateKeyCache.getPrivateKey(getDefaultKeyID().digest());
}
/**
* Get signing keys
* @return private signing keys
* TODO bug -- currently returns all the private keys in our cache, which includes decryption
* keys. Replace with getPrivateKeys with getMyPrivateKeys once we're sure it won't cause trouble.
*/
@Override
public Key [] getSigningKeys() {
return _privateKeyCache.getPrivateKeys();
}
/**
* Get the public key digests corresponding to our available signing keys
*
*/
@Override
public PublisherPublicKeyDigest [] getAvailableIdentities() {
PrivateKey [] pks = _privateKeyCache.getMyPrivateKeys();
PublisherPublicKeyDigest [] ids = new PublisherPublicKeyDigest[pks.length];
int i=0;
for (PrivateKey pk : pks) {
PublisherPublicKeyDigest ppkd = _privateKeyCache.getPublicKeyIdentifier(pk);
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "KeyManager: have identity {0}", ppkd);
}
ids[i++] = ppkd; // a little dangerous; with low probability could be null, which
// caller might not expect.
}
return ids;
}
/**
* Get private signing key for a publisher.
* If I am the publisher, return signing key;
* otherwise, return null.
* @param publisher publisher public key digest
* @return private signing key or null
*/
@Override
public Key getSigningKey(PublisherPublicKeyDigest publisher) {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINER) )
Log.finer(Log.FAC_KEYS, "getSigningKey: retrieving key: " + publisher);
if (null == publisher)
return null;
return _privateKeyCache.getPrivateKey(publisher.digest());
}
/**
* Get public key for a publisher, given a key locator.
* Times out after timeout amount of time elapsed
* @param publisherID publisher public key digest
* @param keyLocator key locator
* @param timeout timeout value
* @return public key
*/
@Override
public Key getVerificationKey(
PublisherPublicKeyDigest desiredKeyID, KeyLocator keyLocator,
long timeout) throws IOException {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINER))
Log.finer(Log.FAC_KEYS, "getVerificationKey: retrieving key: " + desiredKeyID + " located at: " + keyLocator);
if (null == keyLocator) {
// Presumably this means that the key is a symmetric key
return getSecureKeyCache().getPrivateKey(desiredKeyID.digest());
}
// this will try local caches, the locator itself, and if it
// has to, will go to the network. The result will be stored in the cache.
// All this tells us is that the key matches the publisher. For whether
// or not we should trust it for some reason, we have to get fancy.
return (Key)getPublicKeyCache().getPublicKey(desiredKeyID, keyLocator, timeout, handle());
}
/**
* Get a public key object for this key locator and publisher, if there is one.
* This is less general than the method above, which retrieves keys we have cached
* but which have never been published -- our keys, keys listed explicitly in locators,
* etc.
* @param desiredKeyID
* @param locator
* @param timeout
* @return
* @throws IOException
*/
@Override
public PublicKeyObject getPublicKeyObject(
PublisherPublicKeyDigest desiredKeyID, KeyLocator locator,
long timeout) throws IOException {
if( Log.isLoggable(Log.FAC_KEYS, Level.FINER) )
Log.finer(Log.FAC_KEYS, "getPublicKeyObject: retrieving key: {0} located at: {1}", desiredKeyID, locator);
// this will try local caches, the locator itself, and if it
// has to, will go to the network. The result will be stored in the cache.
// All this tells us is that the key matches the publisher. For whether
// or not we should trust it for some reason, we have to get fancy.
return getPublicKeyCache().getPublicKeyObject(desiredKeyID, locator, timeout, handle());
}
/**
* Attempt to retrieve public key from cache.
* @throws IOException
*/
@Override
public PublicKey getPublicKey(PublisherPublicKeyDigest desiredKeyID) {
return getPublicKeyCache().getPublicKeyFromCache(desiredKeyID);
}
/**
* Get publisher ID
* @param signingKey private signing key
* @return publisher public key digest
*/
@Override
public PublisherPublicKeyDigest getPublisherKeyID(Key signingKey) {
return _privateKeyCache.getPublicKeyIdentifier(signingKey);
}
/**
* Get key repository
* @return key repository
*/
@Override
public PublicKeyCache getPublicKeyCache() {
return _publicKeyCache;
}
@Override
public SecureKeyCache getSecureKeyCache() {
return _privateKeyCache;
}
@Override
public PublicKeyObject publishKey(ContentName keyName,
PublicKey keyToPublish,
PublisherPublicKeyDigest signingKeyID,
KeyLocator signingKeyLocator,
boolean learnKeyLocator) throws InvalidKeyException, IOException {
if (null == keyToPublish) {
keyToPublish = getDefaultPublicKey();
}
PublisherPublicKeyDigest keyDigest = new PublisherPublicKeyDigest(keyToPublish);
if (null == keyName) {
keyName = getDefaultKeyName(keyDigest);
}
PublicKeyObject keyObject =
serveKey(keyName, keyToPublish, signingKeyID, signingKeyLocator);
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "publishKey: published key {0}\n under specified key name {1}\n key locator: {2}",
keyDigest, keyObject.getVersionedName(), keyObject.getPublisherKeyLocator());
if (learnKeyLocator && !haveStoredKeyLocator(keyDigest) && (null != keyObject)) {
// So once we publish self-signed key object, we store a pointer to that
// to use. Don't override any manually specified values.
KeyLocator newKeyLocator = new KeyLocator(keyObject.getVersionedName(), keyObject.getContentPublisher());
setKeyLocator(keyDigest, newKeyLocator);
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO))
Log.info(Log.FAC_KEYS, "publishKey: storing key locator {0} for key {1}", keyDigest, newKeyLocator);
}
return keyObject;
}
@Override
public PublicKeyObject publishDefaultKey(ContentName keyName)
throws IOException, InvalidKeyException {
if (!initialized()) {
throw new IOException("KeyServer: cannot publish keys, have not yet initialized KeyManager!");
}
return publishKey(keyName, getDefaultKeyID(), null, null);
}
/**
* Publish a key at a certain name, ensuring that it is stored in a repository. Will throw an
* exception if no repository available. Usually used to publish our own keys, but can specify
* any key known to our key cache.
* @param keyName Name under which to publish the key. Currently added under existing version, or version
* included in keyName.
* @param keyToPublish can be null, in which case we publish our own default public key.
* @param handle the handle to use for network requests
* @throws InvalidKeyException
* @throws IOException
*/
@Override
public PublicKeyObject publishKeyToRepository(ContentName keyName,
PublisherPublicKeyDigest keyToPublish,
long timeToWaitForPreexisting) throws InvalidKeyException,
IOException {
if (null == keyToPublish) {
keyToPublish = getDefaultKeyID();
}
PublicKey theKey = getPublicKeyCache().getPublicKeyFromCache(keyToPublish);
if (null == theKey) {
throw new InvalidKeyException("Key " + keyToPublish + " not available in cache, cannot publish!");
}
if (null == keyName) {
KeyLocator locator = getKeyLocator(keyToPublish);
if (locator.type() != KeyLocatorType.NAME) {
// can't get a name from here, pull from the default namespace.
keyName = getDefaultKeyName(keyToPublish);
} else {
keyName = locator.name().name();
}
}
return KeyManager.publishKeyToRepository(keyName, theKey, getDefaultKeyID(),
getDefaultKeyLocator(), timeToWaitForPreexisting,
false, handle());
}
/**
* Publish a key at a certain name, ensuring that it is stored in a repository. Require
* that the key be self-signed. Will throw an
* exception if no repository available. Usually used to publish our own keys, but can specify
* any key known to our key cache.
* @param keyName Name under which to publish the key. Currently added under existing version, or version
* included in keyName.
* @param theKey the public key to publish. Provide both this and keyToPublish to avoid recomputing them
* if the caller has them already; otherwise they'll be retrieved from cache.
* @param keyToPublish can be null, in which case we publish our own default public key.
* @param handle the handle to use for network requests
* @throws InvalidKeyException
* @throws IOException
*/
@Override
public PublicKeyObject publishSelfSignedKeyToRepository(ContentName keyName,
PublicKey theKey,
PublisherPublicKeyDigest keyToPublish,
long timeToWaitForPreexisting) throws InvalidKeyException,
IOException {
if (null == keyToPublish) {
keyToPublish = getDefaultKeyID();
}
if (null == theKey) {
theKey = getPublicKeyCache().getPublicKeyFromCache(keyToPublish);
if (null == theKey) {
throw new InvalidKeyException("Key " + keyToPublish + " not available in cache, cannot publish!");
}
}
return KeyManager.publishKeyToRepository(keyName, theKey, keyToPublish,
KeyManager.SELF_SIGNED_KEY_LOCATOR,
timeToWaitForPreexisting,
false, handle());
}
@Override
public AccessControlManager getAccessControlManagerForName(
ContentName contentName) {
synchronized (_acmList) {
for (AccessControlManager acm : _acmList){
if (acm.inProtectedNamespace(contentName)) {
return acm;
}
}
return null;
}
}
@Override
public void rememberAccessControlManager(AccessControlManager acm) {
synchronized (_acmList) {
_acmList.add(acm);
}
}
}