/* * Part of the CCNx Java Library. * * Copyright (C) 2008-2013 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 java.io.Serializable; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableEntryException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Comparator; import java.util.Enumeration; import java.util.TreeMap; import java.util.logging.Level; import javax.crypto.SecretKey; import org.ccnx.ccn.KeyManager; import org.ccnx.ccn.impl.security.crypto.CCNDigestHelper; import org.ccnx.ccn.impl.support.ByteArrayCompare; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; /** * A container for our private keys and other secret key * material that we have retrieved (e.g. from access control). * * TODO: finish mechanism that saves the key cache between runs. */ public class SecureKeyCache implements Serializable { /** * */ private static final long serialVersionUID = 2652940059623137734L; public static String privateKeyFormat = "PKCS#8"; static Comparator<byte[]> byteArrayComparator = new ByteArrayCompare(); /** Map the digest of a key to the key. */ private TreeMap<byte [], Key> _keyMap = new TreeMap<byte [], Key>(byteArrayComparator); /** Map the digest of a public key to <I>my</I> corresponding private key. */ private TreeMap<byte [], PrivateKey> _myKeyMap = new TreeMap<byte [], PrivateKey>(byteArrayComparator); /** Map the digest of a public key to the corresponding private key */ private TreeMap<byte [], PrivateKey> _privateKeyMap = new TreeMap<byte [], PrivateKey>(byteArrayComparator); /** Map the digest of a secret key to the corresponding key. * TODO - do we need to keep the secretKeyMap & privateKeyMap separate? */ private TreeMap<byte[], SecretKey> _secretKeyMap = new TreeMap<byte[], SecretKey>(byteArrayComparator); private TreeMap<byte [], byte []> _privateKeyIdentifierMap = new TreeMap<byte [], byte[]>(byteArrayComparator); /** Map the name of a key to its digest */ private TreeMap<ContentName, byte []> _nameKeyMap = new TreeMap<ContentName, byte []>(); public SecureKeyCache() { } /** * Constructor that loads keys from a KeyManager * @param keyManagerToLoadFrom the key manager * TODO bug -- should merge key caches, not just load signing keys. */ public SecureKeyCache(KeyManager keyManagerToLoadFrom) { Key [] pks = keyManagerToLoadFrom.getSigningKeys(); for (Key pk : pks) { PublisherPublicKeyDigest ppkd = keyManagerToLoadFrom.getPublisherKeyID(pk); Log.info("KeyCache: loading signing key {0}", ppkd); addMySigningKey(ppkd.digest(), pk); } } /** * Load the private keys from a KeyStore. * @param keystore * @throws KeyStoreException */ public void loadKeyStore(KeyStoreInfo keyStoreInfo, char [] password, PublicKeyCache publicKeyCache) throws KeyStoreException { Enumeration<String> aliases = keyStoreInfo.getKeyStore().aliases(); String alias; KeyStore.PrivateKeyEntry entry = null; KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(password); while (aliases.hasMoreElements()) { alias = aliases.nextElement(); if (keyStoreInfo.getKeyStore().isKeyEntry(alias)) { try { entry = (KeyStore.PrivateKeyEntry)keyStoreInfo.getKeyStore().getEntry(alias, passwordProtection); } catch (NoSuchAlgorithmException e) { throw new KeyStoreException("Unexpected NoSuchAlgorithm retrieving key for alias : " + alias, e); } catch (UnrecoverableEntryException e) { throw new KeyStoreException("Unexpected UnrecoverableEntryException retrieving key for alias : " + alias, e); } if (null == entry) { Log.warning("Cannot get private key entry for alias: " + alias); } else { PrivateKey pk = entry.getPrivateKey(); if (null != pk) { X509Certificate certificate = (X509Certificate)entry.getCertificate(); if (null != certificate) { PublisherPublicKeyDigest ppkd = new PublisherPublicKeyDigest(certificate.getPublicKey()); Log.info("KeyCache: loading signing key {0}, remembering public key in public key cache.", ppkd); addMySigningKey(ppkd.digest(), pk); publicKeyCache.remember(certificate, keyStoreInfo.getVersion()); } else { Log.warning("Private key for alias: " + alias + " has no certificate entry. No way to get public key. Not caching."); } } else { Log.warning("Cannot retrieve private key for key entry alias " + alias); } } } } } /** * Retrieve a key specified by its digest * To restrict access to keys, store key cache in a private variable, and don't * allow references to it from untrusted code. * @param desiredKeyIdentifier the digest * @return the key */ public Key getKey(byte [] desiredKeyIdentifier) { Key theKey = _keyMap.get(desiredKeyIdentifier); if (null == theKey) { theKey = _privateKeyMap.get(desiredKeyIdentifier); } if (null == theKey) { theKey = _myKeyMap.get(desiredKeyIdentifier); } return theKey; } /** * Retrieve a key specified by its name. */ public Key getKey(ContentName desiredKeyName) { byte [] keyID = _nameKeyMap.get(desiredKeyName); if (null != keyID) { return getKey(keyID); } return null; } /** * Try both in one call. */ public Key getKey(ContentName desiredKeyName, byte [] desiredKeyID) { Key targetKey = null; if (null != desiredKeyID) { targetKey = getKey(desiredKeyID); } if ((null == targetKey) && (null != desiredKeyName)) { targetKey = getKey(desiredKeyName); } return targetKey; } /** * Checks whether we have a record of a key specified by its digest, or in the case * of a private key, the digest of the corresponding public key. * @param keyIdentifier the key digest. * @return */ public boolean containsKey(byte [] keyIdentifier) { if ((_keyMap.containsKey(keyIdentifier)) || (_myKeyMap.containsKey(keyIdentifier)) || (_privateKeyMap.containsKey(keyIdentifier))) { return true; } return false; } /** * As the map from name to content is not unique, this might not give you a * definite answer, and you should still check the digest. * @param keyName * @return */ public boolean containsKey(ContentName keyName) { if (_nameKeyMap.containsKey(keyName)) return true; return false; } /** * Get the key ID associated with a name, if we have one. Currently store * keys under versioned names -- might be nice to effectively search * over versions of a key... This can be used to look up the key, allowing * the caller to be sure they have the right key. */ public byte [] getKeyID(ContentName versionedName) { return _nameKeyMap.get(versionedName); } /** * Returns the private key corresponding to a key identified by its digest. * To restrict access to keys, store key cache in a private variable, and don't * allow references to it from untrusted code. * @param desiredPublicKeyIdentifier the digest of the public key. * @return the corresponding private key. */ public Key getPrivateKey(byte [] desiredPublicKeyIdentifier) { Key key = _myKeyMap.get(desiredPublicKeyIdentifier); if (null == key) { key = _secretKeyMap.get(desiredPublicKeyIdentifier); } if (null == key) { key = _privateKeyMap.get(desiredPublicKeyIdentifier); } return key; } public Key getPrivateKey(ContentName desiredKeyName) { byte [] keyID = _nameKeyMap.get(desiredKeyName); if (null != keyID) { return getPrivateKey(keyID); } return null; } /** * Returns all private keys in cache, loaded from keystore or picked up during operation. */ public PrivateKey [] getPrivateKeys() { ArrayList<PrivateKey> allKeys = new ArrayList<PrivateKey>(); allKeys.addAll(_myKeyMap.values()); allKeys.addAll(_privateKeyMap.values()); PrivateKey [] pkarray = new PrivateKey[allKeys.size()]; return allKeys.toArray(pkarray); } public PrivateKey [] getMyPrivateKeys() { PrivateKey [] pkarray = new PrivateKey[_myKeyMap.size()]; return _myKeyMap.values().toArray(pkarray); } private ContentName getContentName(byte[] ident) { for (ContentName name : _nameKeyMap.keySet()) { if (byteArrayComparator.compare(ident, _nameKeyMap.get(name)) == 0) { return name; } } return null; } /** * Records a private key and the name and digest of the corresponding public key. * @param keyName a name under which to look up the private key * @param publicKeyIdentifier the digest of the public key * @param pk the private key */ public synchronized void addPrivateKey(ContentName keyName, byte [] publicKeyIdentifier, PrivateKey pk) { _privateKeyMap.put(publicKeyIdentifier, pk); _privateKeyIdentifierMap.put(getKeyIdentifier(pk), publicKeyIdentifier); if (null != keyName) { _nameKeyMap.put(keyName, publicKeyIdentifier); Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding private key {0} with name {1}", DataUtils.printHexBytes(publicKeyIdentifier), keyName); } else { Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding private key {0}", DataUtils.printHexBytes(publicKeyIdentifier)); } } /** * Records a secret (symmetric) key and the name and digest of the corresponding identifier * (which should be a "PublisherPublicKeyDigest". * @param keyName a name under which to look up the private key * @param identifier the digest of the public key * @param sk the secret key */ public synchronized void addSecretKey(ContentName keyName, byte [] identifier, SecretKey sk) { _secretKeyMap.put(identifier, sk); _privateKeyIdentifierMap.put(getKeyIdentifier(sk), identifier); if (null != keyName) { _nameKeyMap.put(keyName, identifier); Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding secret key {0} with name {1}", DataUtils.printHexBytes(identifier), keyName); } else { Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding secret key {0}", DataUtils.printHexBytes(identifier)); } } /** * Records one of my private keys and the digest of the corresponding public key. * @param publicKeyIdentifier the digest of the public key. * @param pk the corresponding private key. */ public synchronized void addMySigningKey(byte [] publicKeyIdentifier, Key k) { _privateKeyIdentifierMap.put(getKeyIdentifier(k), publicKeyIdentifier); String alg = k.getFormat(); if (alg.equals("RAW")) _secretKeyMap.put(publicKeyIdentifier, (SecretKey)k); else _myKeyMap.put(publicKeyIdentifier, (PrivateKey)k); Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding my private key {0}", DataUtils.printHexBytes(publicKeyIdentifier)); } /** * Make a record of a key by its name and digest. * @param name the name of the key. * @param key the key. */ public synchronized void addKey(ContentName name, Key key) { byte [] id = getKeyIdentifier(key); _keyMap.put(id, key); if (null != name) { _nameKeyMap.put(name, id); Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding key {0} with name {1} of type {2}", DataUtils.printHexBytes(id), name, key.getClass().getName()); } else { Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: adding key {0} of type {1}", DataUtils.printHexBytes(id), key.getClass().getName()); } } public PublisherPublicKeyDigest getPublicKeyIdentifier(Key pk) { return new PublisherPublicKeyDigest(_privateKeyIdentifierMap.get(getKeyIdentifier(pk))); } /** * Returns the digest of a specified key. * @param key the key. * @return the digest. */ public static byte [] getKeyIdentifier(Key key) { // Works on symmetric and public. return CCNDigestHelper.digest(key.getEncoded()); } /** * Return a total count of keys in this cache. * @return */ public int size() { int count = _keyMap.size(); count += _myKeyMap.size(); count += _privateKeyMap.size(); return count; } /** * Merges the SecureKeyCache with a given SecureKeyCache. The original SecureKeyCache * dominates, i.e. the merged cache will contain the names in the original cache if there * are any conflicts * * @param cache the SecureKeyCache to merge with */ public synchronized void merge(SecureKeyCache cache) { /** _keyMap.putAll(cache._keyMap); _myKeyMap.putAll(cache._myKeyMap); _privateKeyMap.putAll(cache._privateKeyMap); _privateKeyIdentifierMap.putAll(cache._privateKeyIdentifierMap); Collection<byte[]> digests = cache._nameKeyMap.values(); Iterator<byte[]> it = digests.iterator(); while (it.hasNext()) { if (this._nameKeyMap.containsValue(it.next())) { it.remove(); } } _nameKeyMap.putAll(cache._nameKeyMap); */ // check that all my private keys are already in cache for (PrivateKey pkey : cache._myKeyMap.values()) { byte[] identifier = cache.getPublicKeyIdentifier(pkey).digest(); if (!this._myKeyMap.containsKey(identifier)) { this.addMySigningKey(identifier, pkey); } } // check that all my symmetric keys are already in cache for (SecretKey skey : cache._secretKeyMap.values()) { byte[] identifier = cache.getPublicKeyIdentifier(skey).digest(); if (!this._myKeyMap.containsKey(identifier)) { this.addMySigningKey(identifier, skey); } } // check that all other private keys are already in cache for (PrivateKey pkey : cache._privateKeyMap.values()) { byte[] identifier = cache.getPublicKeyIdentifier(pkey).digest(); ContentName name = cache.getContentName(identifier); if (!this._privateKeyMap.containsKey(identifier)) { this.addPrivateKey(name, identifier, pkey); } else { if (this.getContentName(identifier) == null) { _nameKeyMap.put(name, identifier); } } } // check that all symmetric keys are already in cache for (Key key : cache._keyMap.values()) { byte[] identifier = getKeyIdentifier(key); ContentName name = cache.getContentName(identifier); if (!this.containsKey(identifier)) { this.addKey(name, key); } else { if (this.getContentName(identifier) == null) { _nameKeyMap.put(name, identifier); } } } } /** * Debugging utility to print the contents of the secureKeyCache */ public void printContents() { Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: {0} keys in _keyMap ", _keyMap.size()); Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: {0} keys in _myKeyMap ", _myKeyMap.size()); for (byte[] b: _myKeyMap.keySet()) { Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: myKeyMap contains key with hash {0}", DataUtils.printHexBytes(b)); } Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: {0} keys in _privateKeyMap ", _privateKeyMap.size()); for (ContentName cn: _nameKeyMap.keySet()) { Log.info(Log.FAC_ACCESSCONTROL, "SecureKeyCache: _nameKeyMap contains a key with name {0} and hash {1}", cn, DataUtils.printHexBytes(_nameKeyMap.get(cn))); } Log.info(Log.FAC_ACCESSCONTROL, "Dumping _keyMap"); for (byte [] keyHash : _keyMap.keySet()) { Log.info(Log.FAC_ACCESSCONTROL, " KeyID: {0}", DataUtils.printHexBytes(keyHash)); } Log.info(Log.FAC_ACCESSCONTROL, "Dumping _myKeyMap"); for (byte [] keyHash : _myKeyMap.keySet()) { Log.info(Log.FAC_ACCESSCONTROL, " KeyID: {0}", DataUtils.printHexBytes(keyHash)); } Log.info(Log.FAC_ACCESSCONTROL, "Dumping _privateKeyMap"); for (byte [] keyHash : _privateKeyMap.keySet()) { Log.info(Log.FAC_ACCESSCONTROL, " KeyID: {0}", DataUtils.printHexBytes(keyHash)); } } /** * Make sure everything in here is Serializable. * @return */ public boolean validateForWriting() { boolean valid = true; for (Key key : _keyMap.values()) { if (!(key instanceof Serializable)) { if (Log.isLoggable(Log.FAC_KEYS, Level.WARNING)) { Log.warning(Log.FAC_KEYS, "Cannot serialize key of type {0}: {1}", key.getClass().getName(), key); } valid = false; } } for (Key key : _myKeyMap.values()) { if (!(key instanceof Serializable)) { if (Log.isLoggable(Log.FAC_KEYS, Level.WARNING)) { Log.warning(Log.FAC_KEYS, "Cannot serialize key of type {0}: {1}", key.getClass().getName(), key); } valid = false; } } for (Key key : _privateKeyMap.values()) { if (!(key instanceof Serializable)) { if (Log.isLoggable(Log.FAC_KEYS, Level.WARNING)) { Log.warning(Log.FAC_KEYS, "Cannot serialize key of type {0}: {1}", key.getClass().getName(), key); } valid = false; } } return valid; } }