/*
* 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.io.content;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import org.bouncycastle.util.Arrays;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.security.keys.SecureKeyCache;
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.io.content.Link.LinkObject;
import org.ccnx.ccn.io.content.WrappedKey.WrappedKeyObject;
import org.ccnx.ccn.profiles.VersionMissingException;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.profiles.nameenum.EnumeratedNameList;
import org.ccnx.ccn.profiles.security.KeyProfile;
import org.ccnx.ccn.profiles.security.access.AccessDeniedException;
import org.ccnx.ccn.profiles.security.access.group.NodeKey;
import org.ccnx.ccn.protocol.Component;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.PublisherID;
/**
* A key directory holds a key (secret or private), distributed to entities
* (represented by public keys), by a set of key blocks each of which
* wrapping that key under different target keys. If the key to be distributed
* is a private key, it is first wrapped under a nonce key, and that nonce
* key is stored encrypted under the keys of the receiving entitites.
*
* Essentially a KeyDirectory is a software wrapper for managing a set of content
* stored in CCNx (writing and reading portions of that content); that content
* consists of a set of key blocks used to give one key to a number of target
* entities.
*
* Key blocks
* are implemented as a set of wrapped key objects
* all stored in one directory. Wrapped key objects are typically short
* and only need one segment. The directory the keys are stored in
* is prefixed by a version, to allow the contents to evolve. In addition
* some potential supporting information pointing to previous
* or subsequent versions of this key is kept. A particular wrapped key
* entry's name would look like:
*
* <pre><keyname>/#version/xxx/s0</pre>
* <br>Where xxx is the identifier of the wrapped key.
*
* Our model is that higher-level function may use this interface
* to try many ways to get a given key. Some will work (access is
* allowed), some may not -- the latter does not mean that the
* principal doesn't have access, just that the principal doesn't
* have access by this route. So for the moment, we return null
* when we don't conclusively know that this principal doesn't
* have access to this data somehow, rather than throwing
* AccessDeniedException.
*/
public class KeyDirectory extends EnumeratedNameList {
public static final Component SUPERSEDED_MARKER = new Component("SupersededBy");
public static final Component PREVIOUS_KEY = new Component("PreviousKey");
public static final Component GROUP_PUBLIC_KEY = new Component("Key");
public static final Component GROUP_PRIVATE_KEY = new Component("PrivateKey");
protected static final Comparator<byte[]> byteArrayComparator = new ByteArrayCompare();
protected CCNHandle _handle;
protected boolean cacheHit; // true if one of the unwrapping keys is in our cache
/**
* The set _keyIDs contains the digests of the (public) wrapping keys
* of the wrapped key objects stored in the KeyDirectory.
*/
TreeSet<byte []> _keyIDs = new TreeSet<byte []>(byteArrayComparator);
final ReadWriteLock _keyIDLock = new ReentrantReadWriteLock();
/**
* The set _otherNames records the presence of superseded keys, previous keys,
* group private keys, etc.
*/
TreeSet<byte []> _otherNames = new TreeSet<byte []>(byteArrayComparator);
final ReadWriteLock _otherNamesLock = new ReentrantReadWriteLock();
/**
* Directory name should be versioned, else we pull the latest version; start
* enumeration.
* @param manager the access control manager.
* @param directoryName the root of the KeyDirectory.
* @param handle
* @throws IOException
*/
public KeyDirectory(ContentName directoryName, CCNHandle handle)
throws IOException {
this(directoryName, true, handle);
}
/**
* Directory name should be versioned, else we pull the latest version.
* @param directoryName the root of the KeyDirectory.
* @param handle
* @throws IOException
*/
public KeyDirectory(ContentName directoryName, boolean enumerate, CCNHandle handle)
throws IOException {
super(directoryName, false, handle);
_handle = handle;
cacheHit = false;
initialize(enumerate);
}
/**
* We don't start enumerating until we get here.
* @throws IOException
*/
protected void initialize(boolean startEnumerating) throws IOException {
if (!VersioningProfile.hasTerminalVersion(_namePrefix)) {
ContentObject latestVersionObject =
VersioningProfile.getLatestVersion(_namePrefix,
null, SystemConfiguration.MAX_TIMEOUT, _enumerator.handle().defaultVerifier(), _enumerator.handle());
if (null == latestVersionObject) {
throw new IOException("Cannot find content for any version of " + _namePrefix + "!");
}
ContentName versionedNamePrefix =
latestVersionObject.name().subname(0, _namePrefix.count() + 1);
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINE)) {
Log.fine(Log.FAC_ACCESSCONTROL, "KeyDirectory: initialize: {0} is the latest version found for {1}.", versionedNamePrefix, _namePrefix);
}
_namePrefix = versionedNamePrefix;
}
// We don't register prefix in constructor anymore; don't start enumerating till we finish
// initialize. Note that if you subclass KeyDirectory, will need to override initialize().
if (startEnumerating) {
synchronized(_childLock) {
_enumerator.registerPrefix(_namePrefix);
}
}
}
/**
* Called each time new data comes in, gets to parse it and load processed
* arrays.
*/
@Override
protected void processNewChildren(SortedSet<ContentName> newChildren) {
for (ContentName childName : newChildren) {
// currently encapsulated in single-component ContentNames
byte [] wkChildName = childName.lastComponent();
processNewChild(wkChildName);
}
}
protected void processNewChild(byte[] wkChildName) {
if (KeyProfile.isKeyNameComponent(wkChildName)) {
byte[] keyid = KeyProfile.getKeyIDFromNameComponent(wkChildName);
try{
_keyIDLock.writeLock().lock();
_keyIDs.add(keyid);
} finally {
_keyIDLock.writeLock().unlock();
}
if (_handle.keyManager().getSecureKeyCache().containsKey(keyid)) cacheHit = true;
} else {
try{
_otherNamesLock.writeLock().lock();
_otherNames.add(wkChildName);
}finally{
_otherNamesLock.writeLock().unlock();
}
}
}
@Override
public boolean hasResult() {
// For now, we only report a result if we have an unwrapping key that is in our key cache.
// TODO: also consider reporting results if we have an unwrapping key for a group that we know we're a member of.
return cacheHit;
}
/**
* Return a copy to avoid synchronization problems.
* @throws ContentNotReadyException
*
*/
public TreeSet<byte []> getCopyOfWrappingKeyIDs() throws ContentNotReadyException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
TreeSet<byte []> copy = new TreeSet<byte []>(byteArrayComparator);
try {
_keyIDLock.readLock().lock();
for (byte[] elt: _keyIDs) copy.add(elt);
} finally {
_keyIDLock.readLock().unlock();
}
return copy;
}
/**
* Returns a copy to avoid synchronization problems
* @throws ContentNotReadyException
*/
public TreeSet<byte []> getCopyOfOtherNames() throws ContentNotReadyException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
TreeSet<byte []> copy = new TreeSet<byte []>(byteArrayComparator);
try {
_otherNamesLock.readLock().lock();
for (byte[] elt: _otherNames) copy.add(elt);
} finally {
_otherNamesLock.readLock().unlock();
}
return copy;
}
/**
* Returns the wrapped key object corresponding to a public key specified by its digest.
* Up to caller to decide when this is reasonable to call; should call available() on result.
* @param keyID the digest of the specified public key.
* @return the corresponding wrapped key object.
* @throws ContentDecodingException
* @throws IOException
*/
public WrappedKeyObject getWrappedKeyForKeyID(byte [] keyID) throws ContentDecodingException, IOException {
ContentName wrappedKeyName = getWrappedKeyNameForKeyID(keyID);
return getWrappedKey(wrappedKeyName);
}
/**
* Returns the wrapped key name for a public key specified by its digest.
* @param keyID the digest of the public key.
* @return the corresponding wrapped key name.
*/
public ContentName getWrappedKeyNameForKeyID(byte [] keyID) {
return KeyProfile.keyName(_namePrefix, keyID);
}
/**
* Checks for the existence of a superseded block.
* @throws ContentNotReadyException
*/
public boolean hasSupersededBlock() throws ContentNotReadyException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
boolean b = false;
try{
_otherNamesLock.readLock().lock();
b = _otherNames.contains(SUPERSEDED_MARKER.getComponent());
} finally {
_otherNamesLock.readLock().unlock();
}
return b;
}
public ContentName getSupersededBlockName() {
return getSupersededBlockNameForKey(_namePrefix);
}
public static ContentName getSupersededBlockNameForKey(ContentName versionedKeyName) {
return new ContentName(versionedKeyName, SUPERSEDED_MARKER);
}
/**
* We have several choices for how to represent superseded and previous keys.
* Ignoring for now the case where we might have to have more than one per key directory
* (e.g. if we represent removal of several interposed ACLs), we could have the
* wrapped key block stored in the superseded block location, and the previous
* key block be a link, or the previous key block be a wrapped key and the superseded
* location be a link. Or we could store wrapped key blocks in both places. Because
* the wrapped key blocks can contain the name of the key that wrapped them (but
* not the key being wrapped), they are in essence a pointer forward to the replacing
* key. So, the superseded block, if it contains a wrapped key, is both a key and a link.
* If the block was stored at the previous key, it would not be both a key and a link,
* as its wrapping key is indicated by where it is. So it should indeed be a link --
* except in the case of an interposed ACL, where there is nothing to link to;
* and it instead stores a wrapped key block containing the effective node key that
* was the previous key.
* This method checks for the existence of a superseded block.
* @return
* @throws ContentNotReadyException
* @throws ContentDecodingException
* @throws IOException
*/
public WrappedKeyObject getSupersededWrappedKey() throws ContentDecodingException, ContentNotReadyException, IOException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
if (!hasSupersededBlock())
return null;
return getWrappedKey(getSupersededBlockName());
}
/**
* Returns the wrapped key object corresponding to the specified wrapped key name. We know
* there is only one version of this object, so avoid getLatestVersion.
* @param wrappedKeyName
* @return
* @throws IOException
* @throws ContentDecodingException
*/
public WrappedKeyObject getWrappedKey(ContentName wrappedKeyName) throws ContentDecodingException, IOException {
WrappedKeyObject wrappedKey = null;
if (VersioningProfile.hasTerminalVersion(wrappedKeyName)) {
wrappedKey = new WrappedKeyObject(wrappedKeyName, _handle);
if (!wrappedKey.available()) { // for some reason we timed out, try again.
wrappedKey.update();
}
} else {
wrappedKey = new WrappedKeyObject(wrappedKeyName, null, null, _handle);
wrappedKey.updateAny();
if (!wrappedKey.available()) { // for some reason we timed out, try again.
wrappedKey.updateAny();
}
}
return wrappedKey;
}
/**
* Checks for the existence of a previous key block
* @throws ContentNotReadyException
*/
public boolean hasPreviousKeyBlock() throws ContentNotReadyException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
boolean b;
try{
_otherNamesLock.readLock().lock();
b = _otherNames.contains(PREVIOUS_KEY.getComponent());
}finally{
_otherNamesLock.readLock().unlock();
}
return b;
}
public ContentName getPreviousKeyBlockName() {
return getPreviousKeyBlockName(_namePrefix);
}
public static ContentName getPreviousKeyBlockName(ContentName keyDirectoryName) {
return new ContentName(keyDirectoryName, PREVIOUS_KEY);
}
/**
* Returns a link to the previous key.
* Previous key might be a link, if we're a simple newer version, or it might
* be a wrapped key, if we're an interposed node key.
* DKS TODO
* @return
* @throws IOException
* @throws ContentNotReadyException
* @throws ContentDecodingException
*/
public Link getPreviousKey(long timeout) throws ContentNotReadyException, IOException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
if (!hasPreviousKeyBlock())
return null;
LinkObject previousKey = new LinkObject(getPreviousKeyBlockName(), _handle);
previousKey.waitForData(timeout);
if (!previousKey.available()) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Unexpected: no previous key link at {0}", getPreviousKeyBlockName());
}
return null;
}
return previousKey.link();
}
/**
* We store a private key as a single block wrapped under a nonce key, which is
* then wrapped under the public keys of various principals. The WrappedKey structure
* would allow us to do this (wrap private in public) in a single object, with
* an inline nonce key, but this option is more efficient.
* Checks for the existence of a private key block
* @return
* @throws ContentNotReadyException
*/
public boolean hasPrivateKeyBlock() throws ContentNotReadyException {
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
boolean b;
try{
_otherNamesLock.readLock().lock();
b = _otherNames.contains(GROUP_PRIVATE_KEY.getComponent());
}finally{
_otherNamesLock.readLock().unlock();
}
return b;
}
public ContentName getPrivateKeyBlockName() {
return new ContentName(_namePrefix, GROUP_PRIVATE_KEY);
}
/**
* Returns the private key object, if one exists as a wrapped key object. Does
* not check to see if we have a private key block; simply sends a request for it
* (saves the requirement to do enumeration). Callers should check available() on the
* result to see if we actually got one. In general, callers will know whether
* one should exist or not. hasPrivateKeyBlock can be used to test (after enumeration)
* whether one exists if you don't know.
* @return
* @throws IOException
* @throws ContentGoneException
* @throws ContentDecodingException
*/
protected WrappedKeyObject getPrivateKeyObject() throws ContentGoneException, IOException {
return new WrappedKey.WrappedKeyObject(getPrivateKeyBlockName(), _handle);
}
/**
* Unwrap and return the key wrapped in a wrapping key specified by its digest.
* Find a copy of the key block in this directory that we can unwrap (either the private
* key wrapping key block or a wrapped raw symmetric key). Chase superseding keys if
* we have to. This mechanism should be generic, and should work for node keys
* as well as private key wrapping keys in directories following this structure.
* @return
* @throws InvalidKeyException
* @throws ContentDecodingException
* @throws IOException
* @throws NoSuchAlgorithmException
*/
public Key getUnwrappedKey(byte [] expectedKeyID)
throws InvalidKeyException, ContentDecodingException, IOException, NoSuchAlgorithmException {
byte[] retrievedKeyID;
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINEST)) {
Log.finest(Log.FAC_ACCESSCONTROL, "getUnwrappedKey({0})", DataUtils.printHexBytes(expectedKeyID));
}
Key unwrappedKey = findUnwrappedKey(expectedKeyID);
if (null != unwrappedKey) {
_handle.keyManager().getSecureKeyCache().addKey(getName(), unwrappedKey);
if (null != expectedKeyID) {
retrievedKeyID = NodeKey.generateKeyID(unwrappedKey);
if (!Arrays.areEqual(expectedKeyID, retrievedKeyID)) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.WARNING)) {
Log.warning(Log.FAC_ACCESSCONTROL, "Retrieved and decrypted wrapped key, but it was the wrong key. We wanted " +
DataUtils.printBytes(expectedKeyID) + ", we got " + DataUtils.printBytes(retrievedKeyID));
}
}
}
}
return unwrappedKey;
}
protected Key findUnwrappedKey(byte[] expectedKeyID) throws IOException,
ContentNotReadyException, InvalidKeyException,
ContentDecodingException, NoSuchAlgorithmException {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) {
if (expectedKeyID == null) {
Log.finer(Log.FAC_ACCESSCONTROL, "KeyDirectory.findUnwrappedKey: at {0} unwrapping key without expectedKeyID", this._namePrefix);
}
else {
Log.finer(Log.FAC_ACCESSCONTROL, "KeyDirectory.findUnwrappedKey: at {0} unwrapping key with expectedKeyID {1} ",
this._namePrefix,
DataUtils.printHexBytes(expectedKeyID));
}
}
Key unwrappedKey = null;
// Do we have the unwrapped key in our cache?
// First, look up the desired keyID in the cache.
// If it's not in the cache, look up the desired key by name
SecureKeyCache skc = _handle.keyManager().getSecureKeyCache();
if ((null != expectedKeyID) && (skc.containsKey(expectedKeyID))) {
unwrappedKey = skc.getKey(expectedKeyID);
Log.info(Log.FAC_ACCESSCONTROL, "KeyDirectory getUnwrappedKey: found desired unwrapped keyID in our cache.");
}
if ((null == unwrappedKey) && (skc.containsKey(getName()))) {
unwrappedKey = skc.getKey(skc.getKeyID(getName()));
Log.info(Log.FAC_ACCESSCONTROL, "KeyDirectory getUnwrappedKey: found desired unwrapped key name in our cache.");
}
if (null == unwrappedKey) {
// If we've never enumerated, now might be the time.
if (!hasEnumerated()) {
startEnumerating();
}
waitForNoUpdatesOrResult(SystemConfiguration.getDefaultTimeout());
// Only test here if we didn't get it via the cache.
if (!hasChildren()) {
throw new ContentNotReadyException("Need to call waitForData(); assuming directory known to be non-empty!");
}
// Do we have one of the wrapping keys already in our cache?
unwrappedKey = unwrapKeyViaCache();
if (null == unwrappedKey) {
// Not in cache. Is it superseded?
if (hasSupersededBlock()) {
unwrappedKey = unwrapKeyViaSupersededKey();
}
}
}
return unwrappedKey;
}
/**
* Fast path -- once we have an idea which of our keys will unwrap this key,
* get it. Can be called after enumeration, or if we have a guess of what key to
* use.
* @param keyIDOfCachedKeytoUse
* @return
* @throws IOException
* @throws ContentDecodingException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
protected Key unwrapKeyViaCache(byte [] keyIDOfCachedKeytoUse) throws ContentDecodingException, IOException, InvalidKeyException, NoSuchAlgorithmException {
WrappedKeyObject wko = getWrappedKeyForKeyID(keyIDOfCachedKeytoUse);
if ((null == wko) || (!wko.available()) || (null == wko.wrappedKey())) {
return null;
}
return wko.wrappedKey().unwrapKey(_handle.keyManager().getSecureKeyCache().getKey(keyIDOfCachedKeytoUse));
}
protected Key unwrapKeyViaCache() throws InvalidKeyException, ContentDecodingException, IOException, NoSuchAlgorithmException {
Key unwrappedKey = null;
try {
_keyIDLock.readLock().lock();
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "KeyDirectory getUnwrappedKey: the directory has {0} wrapping keys.", _keyIDs.size());
}
for (byte [] keyid : _keyIDs) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL,
"KeyDirectory getUnwrappedKey: the KD secret key is wrapped under a key whose id is {0}, name component {1}",
DataUtils.printHexBytes(keyid), KeyProfile.keyIDToNameComponentAsString(keyid));
}
if (_handle.keyManager().getSecureKeyCache().containsKey(keyid)) {
// We have it, pull the block, unwrap the node key.
unwrappedKey = unwrapKeyViaCache(keyid);
}
}
} finally {
_keyIDLock.readLock().unlock();
}
return unwrappedKey;
}
protected Key unwrapKeyViaSupersededKey() throws InvalidKeyException, ContentDecodingException, IOException, NoSuchAlgorithmException {
Key unwrappedKey = null;
// OK, is the superseding key just a newer version of this key? If it is, roll
// forward to the latest version and work back from there.
WrappedKeyObject supersededKeyBlock = getSupersededWrappedKey();
if (null != supersededKeyBlock) {
// We could just walk up superseding key hierarchy, and then walk back with
// decrypted keys. Or we could attempt to jump all the way to the end and then
// walk back. Not sure there is a significant win in doing the latter, so start
// with the former... have to touch intervening versions in both cases.
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Attempting to retrieve key {0} by retrieving superseding key {1}",
getName(), supersededKeyBlock.wrappedKey().wrappingKeyName());
}
Key unwrappedSupersedingKey = null;
KeyDirectory supersedingKeyDirectory = null;
try {
supersedingKeyDirectory = factory(supersededKeyBlock.wrappedKey().wrappingKeyName());
supersedingKeyDirectory.waitForNoUpdates(SystemConfiguration.SHORT_TIMEOUT);
// This wraps the key we actually want.
unwrappedSupersedingKey = supersedingKeyDirectory.getUnwrappedKey(supersededKeyBlock.wrappedKey().wrappingKeyIdentifier());
} finally {
if (null != supersedingKeyDirectory)
supersedingKeyDirectory.stopEnumerating();
}
if (null != unwrappedSupersedingKey) {
_handle.keyManager().getSecureKeyCache().addKey(supersedingKeyDirectory.getName(), unwrappedSupersedingKey);
unwrappedKey = supersededKeyBlock.wrappedKey().unwrapKey(unwrappedSupersedingKey);
} else {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Unable to retrieve superseding key {0}", supersededKeyBlock.wrappedKey().wrappingKeyName());
}
}
}
return unwrappedKey;
}
protected KeyDirectory factory(ContentName name) throws IOException {
return new KeyDirectory(name, _handle);
}
/**
* @return true if the private key is in the secure key cache.
*/
public boolean isPrivateKeyInCache() {
return _handle.keyManager().getSecureKeyCache().containsKey(getPrivateKeyBlockName());
}
@SuppressWarnings("serial")
public static class NoPrivateKeyException extends IOException {}
/**
* Returns the private key stored in the KeyDirectory.
* The private key is wrapped in a wrapping key, which is itself wrapped.
* So the unwrapping proceeds in two steps.
* First, we unwrap the wrapping key for the private key.
* Then, we unwrap the private key itself.
* Relies on the caller, who presumably knows the public key, to add the result to the
* cache.
* @return
* @throws AccessDeniedException
* @throws ContentGoneException
* @throws ContentNotReadyException
* @throws InvalidKeyException
* @throws ContentDecodingException
* @throws IOException
* @throws NoSuchAlgorithmException
*/
public Key getPrivateKey()
throws AccessDeniedException, InvalidKeyException,
ContentNotReadyException, ContentGoneException, ContentDecodingException,
IOException, NoSuchAlgorithmException {
// is the private key already in the cache?
SecureKeyCache skc = _handle.keyManager().getSecureKeyCache();
if (skc.containsKey(getPrivateKeyBlockName())) {
Log.info(Log.FAC_ACCESSCONTROL, "KeyDirectory getPrivateKey: found private key in cache.");
return skc.getPrivateKey(getPrivateKeyBlockName());
}
// Skip checking enumeration results. Assume we know we have one or not. Just
// as fast to do the get as to enumerate and then do the get.
WrappedKeyObject wko = getPrivateKeyObject();
if ((null == wko) || (!wko.available()) || (null == wko.wrappedKey())) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Cannot retrieve wrapped private key for {0}", getPrivateKeyBlockName());
}
throw new NoPrivateKeyException();
}
// This will pull from the cache if it's in our cache, and otherwise
// will start enumerating if necessary.
// This throws AccessDeniedException...
Key wrappingKey = getUnwrappedKey(wko.wrappedKey().wrappingKeyIdentifier());
if (null == wrappingKey) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Cannot get key to unwrap private key {0}", getPrivateKeyBlockName());
}
throw new AccessDeniedException("Cannot get key to unwrap private key " + getPrivateKeyBlockName());
}
Key unwrappedPrivateKey = wko.wrappedKey().unwrapKey(wrappingKey);
if (!(unwrappedPrivateKey instanceof PrivateKey)) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Unwrapped private key is not an instance of PrivateKey! Its an {0}", unwrappedPrivateKey.getClass().getName());
}
} else {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) {
Log.info(Log.FAC_ACCESSCONTROL, "Unwrapped private key is a private key, in fact it's a {0}", unwrappedPrivateKey.getClass().getName());
}
}
return unwrappedPrivateKey;
}
/**
* Writes a wrapped key block to the repository.
* Eventually aggregate signing and repo stream operations at the very
* least across writing paired objects and links, preferably across larger
* swaths of data.
* @param secretKeyToWrap either a node key, a data key, or a private key wrapping key
* @param publicKeyName the name of the public key.
* @param publicKey the public key.
* @throws ContentEncodingException
* @throws IOException
* @throws InvalidKeyException
* @throws VersionMissingException
* @throws VersionMissingException
*/
public WrappedKeyObject addWrappedKeyBlock(Key secretKeyToWrap,
ContentName publicKeyName, PublicKey publicKey)
throws ContentEncodingException, IOException, InvalidKeyException, VersionMissingException {
WrappedKey wrappedKey = WrappedKey.wrapKey(secretKeyToWrap, null, null, publicKey);
wrappedKey.setWrappingKeyIdentifier(publicKey);
wrappedKey.setWrappingKeyName(publicKeyName);
WrappedKeyObject wko =
new WrappedKeyObject(getWrappedKeyNameForKeyID(WrappedKey.wrappingKeyIdentifier(publicKey)),
wrappedKey,SaveType.REPOSITORY, _handle);
wko.save();
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) {
Log.finer(Log.FAC_ACCESSCONTROL, "KeyDirectory addWrappedKeyBlock: wrapped secret key {0} under public key named {1} whose id is {2} for key directory {3}",
DataUtils.printHexBytes(secretKeyToWrap.getEncoded()), publicKeyName, DataUtils.printHexBytes(publicKey.getEncoded()), this._namePrefix);
}
return wko;
}
/**
* Writes a private key block to the repository.
* @param privateKey the private key.
* @param privateKeyWrappingKey the wrapping key used to wrap the private key.
* @throws ContentEncodingException
* @throws IOException
* @throws InvalidKeyException
*/
public void addPrivateKeyBlock(PrivateKey privateKey, Key privateKeyWrappingKey)
throws ContentEncodingException, IOException, InvalidKeyException {
WrappedKey wrappedKey = WrappedKey.wrapKey(privateKey, null, null, privateKeyWrappingKey);
wrappedKey.setWrappingKeyIdentifier(privateKeyWrappingKey);
WrappedKeyObject wko = new WrappedKeyObject(getPrivateKeyBlockName(), wrappedKey, SaveType.REPOSITORY, _handle);
wko.save();
}
/**
* Add a superseded-by block to our key directory.
* @param oldPrivateKeyWrappingKey
* @param supersedingKeyName
* @param newPrivateKeyWrappingKey
* @throws ContentEncodingException
* @throws IOException
* @throws InvalidKeyException
*/
public void addSupersededByBlock(Key oldPrivateKeyWrappingKey,
ContentName storedSupersedingKeyName, byte [] storedSupersedingKeyID, Key newPrivateKeyWrappingKey)
throws InvalidKeyException, ContentEncodingException, IOException {
addSupersededByBlock(_namePrefix, oldPrivateKeyWrappingKey,
storedSupersedingKeyName, storedSupersedingKeyID, newPrivateKeyWrappingKey, _handle);
}
/**
* Add a superseded-by block to another node key, where we may have only its name, not its enumeration.
* Use as a static method to add our own superseded-by blocks as well.
* @throws ContentEncodingException
* @throws IOException
* @throws InvalidKeyException
*/
public static void addSupersededByBlock(ContentName oldKeyVersionedNameToAddBlockTo, Key oldKeyToBeSuperseded,
ContentName storedSupersedingKeyName, byte [] storedSupersedingKeyID,
Key supersedingKey, CCNHandle handle)
throws ContentEncodingException, IOException, InvalidKeyException {
WrappedKey wrappedKey = WrappedKey.wrapKey(oldKeyToBeSuperseded, null, null, supersedingKey);
wrappedKey.setWrappingKeyIdentifier(
((null == storedSupersedingKeyID) ? WrappedKey.wrappingKeyIdentifier(supersedingKey) :
storedSupersedingKeyID));
wrappedKey.setWrappingKeyName(storedSupersedingKeyName);
WrappedKeyObject wko = new WrappedKeyObject(getSupersededBlockNameForKey(oldKeyVersionedNameToAddBlockTo),
wrappedKey, SaveType.REPOSITORY, handle);
wko.save();
}
/**
* Writes a link to a previous key to the repository.
* @param previousKey
* @param previousKeyPublisher
* @throws ContentEncodingException
* @throws IOException
*/
public void addPreviousKeyLink(ContentName previousKey, PublisherID previousKeyPublisher)
throws ContentEncodingException, IOException {
if (hasPreviousKeyBlock()) {
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.WARNING)) {
Log.warning(Log.FAC_ACCESSCONTROL, "Unexpected, already have previous key block : {0}", getPreviousKeyBlockName());
}
}
LinkAuthenticator la = (null != previousKeyPublisher) ? new LinkAuthenticator(previousKeyPublisher) : null;
LinkObject pklo = new LinkObject(getPreviousKeyBlockName(), new Link(previousKey,la), SaveType.REPOSITORY, _handle);
pklo.save();
}
/**
* Writes a previous key block to the repository.
* @param oldPrivateKeyWrappingKey
* @param supersedingKeyName
* @param newPrivateKeyWrappingKey
* @throws InvalidKeyException
* @throws ContentEncodingException
* @throws IOException
*/
public void addPreviousKeyBlock(Key oldPrivateKeyWrappingKey,
ContentName supersedingKeyName, Key newPrivateKeyWrappingKey)
throws InvalidKeyException, ContentEncodingException, IOException {
// DKS TODO -- do we need in the case of deletion of ACLs to allow for multiple previous key blocks simultaneously?
// Then need to add previous key id to previous key block name.
WrappedKey wrappedKey = WrappedKey.wrapKey(oldPrivateKeyWrappingKey, null, null, newPrivateKeyWrappingKey);
wrappedKey.setWrappingKeyIdentifier(newPrivateKeyWrappingKey);
wrappedKey.setWrappingKeyName(supersedingKeyName);
WrappedKeyObject wko = new WrappedKeyObject(getPreviousKeyBlockName(), wrappedKey,SaveType.REPOSITORY, _handle);
wko.save();
if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) {
Log.finer(Log.FAC_ACCESSCONTROL, "KeyDirectory addPreviousKeyBlock: old wrapping key is {0} and superseding key name is {1} and new wrapping key is {2}.",
DataUtils.printHexBytes(oldPrivateKeyWrappingKey.getEncoded()),
supersedingKeyName,
DataUtils.printHexBytes(newPrivateKeyWrappingKey.getEncoded())
);
}
}
}