/* * Part of the CCNx Java Library. * * Copyright (C) 2009, 2010 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.profiles.security.access; import java.io.IOException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.config.SystemConfiguration; import org.ccnx.ccn.impl.CCNFlowControl.SaveType; import org.ccnx.ccn.impl.security.crypto.ContentKeys; import org.ccnx.ccn.impl.security.crypto.KDFContentKeys; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.ErrorStateException; import org.ccnx.ccn.io.content.ContentDecodingException; import org.ccnx.ccn.io.content.ContentEncodingException; import org.ccnx.ccn.io.content.ContentGoneException; import org.ccnx.ccn.io.content.ContentNotReadyException; import org.ccnx.ccn.io.content.WrappedKey; import org.ccnx.ccn.io.content.WrappedKey.WrappedKeyObject; import org.ccnx.ccn.profiles.SegmentationProfile; import org.ccnx.ccn.profiles.namespace.NamespaceManager; import org.ccnx.ccn.profiles.security.access.AccessControlPolicyMarker.AccessControlPolicyMarkerObject; import org.ccnx.ccn.profiles.security.access.group.GroupAccessControlManager; import org.ccnx.ccn.profiles.security.access.group.NodeKey; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.MalformedContentNameStringException; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; import org.ccnx.ccn.protocol.SignedInfo.ContentType; /** * Abstract class containing core functionality we expect to be common across all access control * schemes. Key functionality provided: * * * maintain static cache of AccessControlManager instances, one per controlled namespace; and * provide code to load ACMs (by retrieving a policy marker from a given nametree root and loading * the type of ACM specified in that policy marker), and to look them up * * * whenever any content is to be written using a CCNOutputStream (or subclass) or network object, * keysForOutput is called to determine whether there is an access control manger in force for that * content's namespace, and to retrieve the appropriate content encryption keys to protect it using * that ACM instance. * * * when a piece of encrypted content is read, keysForInput is used to retrieve the keys necessary to * decrypt it using the loaded ACM instance for that namespace (TODO could load ACMs automatically * in this case); according to the access control scheme supported by that ACM instance * * * isProtectedContent determines whether a given piece of content that is about to be written should * be encrypted or not. The default policy exempts content of type KEY, LINK, and access control * metadata (data used to control access control) from encryption. (TODO need a special encrypted * LINK type - ELNK). Subclasses can add to this list of exemptions (by calling super.isProtectedContent * to determine their superclass' exemptions and then adding their own. They should not override * the superclass' request to exempt something, otherwise things may break (in other words, they can * leave unencrypted more content than the superclass suggests, but probably should not encrypt * content the superclass says should not be encrypted). * * * data key handling -- content streams are encrypted using nonce keys; access control is used to * protect those nonce keys. Basic nonce key handling (creation, content encryption) is implemented * in this class, protection of nonce keys is left abstract for subclassess to implement. * */ public abstract class AccessControlManager { /** * Track available access control profiles. */ protected static Map<ContentName, Class<? extends AccessControlManager>> _accessControlManagerTypes = new TreeMap<ContentName, Class<? extends AccessControlManager>>(); static { try { registerAccessControlManagerType(ContentName.fromNative(GroupAccessControlManager.PROFILE_NAME_STRING), GroupAccessControlManager.class); } catch (MalformedContentNameStringException e) { throw new RuntimeException("Cannot parse built-in profile name: " + GroupAccessControlManager.PROFILE_NAME_STRING); } } public static synchronized void registerAccessControlManagerType(ContentName profileName, Class<? extends AccessControlManager> acmClazz) { _accessControlManagerTypes.put(profileName, acmClazz); } /** * Default data key length in bytes. No real reason this can't be bumped up to 32. It * acts as the seed for a KDF, not an encryption key. */ public static final int DEFAULT_DATA_KEY_LENGTH = 16; /** * The keys we're wrapping are really seeds for a KDF, not keys in their own right. * Eventually we'll use CMAC, so call them AES... */ public static final String DEFAULT_DATA_KEY_ALGORITHM = "AES"; public static final String DATA_KEY_LABEL = "Data Key"; protected ContentName _namespace; protected CCNHandle _handle; protected SecureRandom _random = new SecureRandom(); protected AccessControlPolicyMarkerObject _policy; /** * Make an AccessControlManager of a particular type given stored policy information. * @param policyInformation * @param handle * @return * @throws ContentNotReadyException * @throws ContentGoneException * @throws ErrorStateException * @throws InstantiationException * @throws IllegalAccessException * @throws ConfigurationException * @throws IOException */ public static AccessControlManager createAccessControlManager(AccessControlPolicyMarkerObject policyInformation, CCNHandle handle) throws ContentNotReadyException, ContentGoneException, ErrorStateException, InstantiationException, IllegalAccessException, IOException { Class<? extends AccessControlManager> acmClazz = null; synchronized(NamespaceManager.class) { acmClazz = _accessControlManagerTypes.get(policyInformation.policy().profileName()); } if (null != acmClazz) { AccessControlManager acm = (AccessControlManager)acmClazz.newInstance(); acm.initialize(policyInformation, handle); return acm; } return null; } public static AccessControlManager createAccessControlManager(ContentName accessControlPolicyName, CCNHandle handle) throws ContentNotReadyException, ContentGoneException, ErrorStateException, ConfigurationException, InstantiationException, IllegalAccessException, IOException { AccessControlPolicyMarkerObject policyInformation = new AccessControlPolicyMarkerObject(accessControlPolicyName, handle); if (!policyInformation.available()) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) { Log.info(Log.FAC_ACCESSCONTROL, "Cannot find an access control policy at {0}, returning null manager.", accessControlPolicyName); } return null; } return createAccessControlManager(policyInformation, handle); } /** * Subclasses should implement a default constructor and set themselves up with an * initialize method. */ public AccessControlManager() {} public abstract boolean initialize(AccessControlPolicyMarkerObject policyInformation, CCNHandle handle) throws IOException; /** * Labels for deriving various types of keys. * @return */ public String dataKeyLabel() { return DATA_KEY_LABEL; } public CCNHandle handle() { return _handle; } public AccessControlPolicyMarkerObject policy() { return _policy; } // if subclass set it in initialize() public boolean inProtectedNamespace(ContentName content) { return NamespaceManager.inProtectedNamespace(_namespace, content); } public ContentName getNamespaceRoot() { return _namespace; } /** * Used by content reader to retrieve the keys necessary to decrypt this content. * Delegates to specific subclasses to retrieve data key using retrieveWrappedDataKey, * and then if key used to encrypt data key isn't * in cache, delegates retrieving the unwrapping key * to subclasses using getDataKeyUnwrappingKey. Provides a default implementation * of retrieveDataKey. * To turn the result of this into a key for decrypting content, * follow the steps in the comments to #generateAndStoreDataKey(ContentName). * @param dataNodeName * @return * @throws IOException * @throws ContentDecodingException * @throws InvalidKeyException * @throws NoSuchAlgorithmException */ public Key getDataKey(ContentName dataNodeName) throws ContentDecodingException, IOException, InvalidKeyException, NoSuchAlgorithmException { // Let subclasses change data key storage conventions. WrappedKeyObject wdko = retrieveWrappedDataKey(dataNodeName); if (null == wdko) { return null; } if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) { Log.finer(Log.FAC_ACCESSCONTROL, "getDataKey: data key is wrapped by key {0} stored at {1}, attempting to retrieve.", DataUtils.printHexBytes(wdko.wrappedKey().wrappingKeyIdentifier()), wdko.wrappedKey().wrappingKeyName()); } Key dataKey = null; Key wrappingKey = null; if (_handle.keyManager().getSecureKeyCache().containsKey(wdko.wrappedKey().wrappingKeyIdentifier())) { Key cachedKey = _handle.keyManager().getSecureKeyCache().getKey(wdko.wrappedKey().wrappingKeyIdentifier()); if (null == cachedKey) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.WARNING)) { Log.warning(Log.FAC_ACCESSCONTROL, "Thought we had key {0} in cache, but cannot retrieve it! Data node: {1}.", DataUtils.printHexBytes(wdko.wrappedKey().wrappingKeyIdentifier()), dataNodeName); } // fall through, try subclass retrieval } else { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINE)) { Log.fine(Log.FAC_ACCESSCONTROL, "Unwrapping key for data node {0} with cached key whose id is {1}.", dataNodeName, DataUtils.printHexBytes(wdko.wrappedKey().wrappingKeyIdentifier())); } // The cached key is not actually the key we want. We need to hand it to our access // control manager to do any key prep. wrappingKey = getDataKeyWrappingKey(dataNodeName, wdko.wrappedKey().wrappingKeyName(), cachedKey); } } // Could simplify to remove cache-retry logic. if (null == wrappingKey) { // No dice. Try subclass-specific retrieval. if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) { Log.info(Log.FAC_ACCESSCONTROL, "getDataKey: key {0} not in cache, getting data key wrapping key for data node {1} with wrapped key {2}", DataUtils.printHexBytes(wdko.wrappedKey().wrappingKeyIdentifier()), dataNodeName, wdko); } wrappingKey = getDataKeyWrappingKey(dataNodeName, wdko); } if (null != wrappingKey) { dataKey = wdko.wrappedKey().unwrapKey(wrappingKey); if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINE)) { Log.fine(Log.FAC_ACCESSCONTROL, "getDataKey: unwrapped data key {0}", DataUtils.printHexBytes(WrappedKey.wrappingKeyIdentifier(dataKey))); } return dataKey; } return null; } protected abstract Key getDataKeyWrappingKey(ContentName dataNodeName, WrappedKeyObject wrappedDataKeyObject) throws InvalidKeyException, ContentNotReadyException, ContentGoneException, ContentEncodingException, ContentDecodingException, IOException, NoSuchAlgorithmException; protected WrappedKeyObject retrieveWrappedDataKey(ContentName dataNodeName) throws ContentDecodingException, ContentGoneException, ContentNotReadyException, IOException { WrappedKeyObject wdko = new WrappedKeyObject(AccessControlProfile.dataKeyName(dataNodeName), handle()); if (null == wdko.wrappedKey()) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.WARNING)) { Log.warning(Log.FAC_ACCESSCONTROL, "Could not retrieve data key for node {0}", dataNodeName); } return null; } return wdko; } /** * Find the key to use to wrap a data key at this node. This requires * the current effective node key, and wrapping this data key in it. If the * current node key is dirty, this causes a new one to be generated. * If data at the current node is public, this returns null. Does not check * to see whether content is excluded from encryption (e.g. by being access * control data). * @param dataNodeName the node for which to find a data key wrapping key * @param publisher in case output key retrieval needs to be specialized by publisher * @return if null, the data is to be unencrypted. (Alteratively, could * return a NodeKey that indicates public.) * @param newRandomDataKey * @throws AccessDeniedException if we don't have rights to retrieve key. * @throws InvalidKeyException * @throws ContentEncodingException * @throws IOException * @throws NoSuchAlgorithmException */ public abstract NodeKey getDataKeyWrappingKey(ContentName dataNodeName, PublisherPublicKeyDigest publisher) throws AccessDeniedException, InvalidKeyException, ContentEncodingException, IOException, NoSuchAlgorithmException; /** * Get the data key wrapping key if we happened to have cached a copy of the decryption key. * @param dataNodeName * @param wrappedDataKeyObject * @param cachedWrappingKey * @return * @throws ContentEncodingException * @throws InvalidKeyException */ public abstract Key getDataKeyWrappingKey(ContentName dataNodeName, ContentName wrappingKeyName, Key cachedWrappingKey) throws InvalidKeyException, ContentEncodingException; /** * Wrap a data key in a given node key and store it. * @param dataNodeName * @param dataKey * @param wrappingKey * @throws InvalidKeyException * @throws ContentEncodingException * @throws IOException */ public void storeDataKey(ContentName dataNodeName, Key dataKey, NodeKey wrappingKey) throws InvalidKeyException, ContentEncodingException, IOException { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) { Log.info(Log.FAC_ACCESSCONTROL, "storeDataKey: Wrapping data key {0} for node {1} with wrappingKey for node {2} " + " derived from stored node key for node {3}", DataUtils.printHexBytes(WrappedKey.wrappingKeyIdentifier(dataKey)), dataNodeName, wrappingKey.nodeName(), wrappingKey.storedNodeKeyName() ); Log.info(Log.FAC_ACCESSCONTROL, "storeDataKey: stored node key has key id {0}, derived key has id {1}", DataUtils.printHexBytes(wrappingKey.storedNodeKeyID()), DataUtils.printHexBytes(WrappedKey.wrappingKeyIdentifier(wrappingKey.nodeKey()))); } // TODO another case where we're wrapping in an effective node key but labeling it with // the stored node key information. This will work except if we interpose an ACL in the meantime -- // we may not have the information necessary to figure out how to decrypt. WrappedKey wrappedDataKey = WrappedKey.wrapKey(dataKey, null, dataKeyLabel(), wrappingKey.nodeKey()); wrappedDataKey.setWrappingKeyIdentifier(wrappingKey.storedNodeKeyID()); wrappedDataKey.setWrappingKeyName(wrappingKey.storedNodeKeyName()); storeKeyContent(AccessControlProfile.dataKeyName(dataNodeName), wrappedDataKey); } /** * Generate a random data key. **/ public Key generateDataKey(ContentName dataNodeName) { // Generate new random data key of appropriate length byte [] dataKeyBytes = new byte[DEFAULT_DATA_KEY_LENGTH]; _random.nextBytes(dataKeyBytes); Key dataKey = new SecretKeySpec(dataKeyBytes, DEFAULT_DATA_KEY_ALGORITHM); return dataKey; } /** * Actual output functions. * @param dataNodeName -- the content node for whom this is the data key. * @param wrappedDataKey * @throws IOException * @throws ContentEncodingException */ protected void storeKeyContent(ContentName dataNodeName, WrappedKey wrappedKey) throws ContentEncodingException, IOException { WrappedKeyObject wko = new WrappedKeyObject(AccessControlProfile.dataKeyName(dataNodeName), wrappedKey, SaveType.REPOSITORY, handle()); wko.save(); } /** * Given the name of a content stream, this function verifies that access is allowed and returns the * keys required to decrypt the stream. * @param dataNodeName The name of the stream, including version component, but excluding * segment component. * @param publisher the publisher to get keys for, if it matters * @return Returns the keys ready to be used for en/decryption, or null if the content is not encrypted. * @throws IOException * @throws InvalidKeyException * @throws AccessDeniedException * @throws NoSuchAlgorithmException */ public ContentKeys getContentKeys(ContentName dataNodeName, PublisherPublicKeyDigest publisher) throws InvalidKeyException, AccessDeniedException, IOException, NoSuchAlgorithmException { if (SegmentationProfile.isSegment(dataNodeName)) { dataNodeName = SegmentationProfile.segmentRoot(dataNodeName); } Key dataKey = getDataKey(dataNodeName); if (null == dataKey) return null; return getDefaultAlgorithmContentKeys(dataKey); } public static ContentKeys getDefaultAlgorithmContentKeys(Key dataKey) throws InvalidKeyException { try { // TODO - figure out where algorithm spec lives return new KDFContentKeys(ContentKeys.DEFAULT_CIPHER_ALGORITHM, dataKey.getEncoded(), DATA_KEY_LABEL); } catch (NoSuchAlgorithmException e) { String err = "Unexpected NoSuchAlgorithmException for default algorithm we have already used!"; Log.severe(err); throw new InvalidKeyException(err, e); } catch (NoSuchPaddingException e) { String err = "Unexpected NoSuchPaddingException for default algorithm we have already used!"; Log.severe(err); throw new InvalidKeyException(err, e); } } /** * Called when a stream containing encrypted content is opened for reading, * to determine if the name is under a root ACL, and * if so find or create an AccessControlManager, and get keys for access. * * @param name name of the stream to be opened, without the segment number * @param publisher the publisher of the stream to open, in case that matters for key retrieva * @param library CCN Library instance to use for any network operations. * @return If the stream is under access control then keys to decrypt the data are returned if it's * encrypted. If the stream is not under access control (no Root ACL block can be found) then null is * returned. * @throws IOException if a problem happens getting keys. */ public static ContentKeys keysForInput(ContentName name, PublisherPublicKeyDigest publisher, CCNHandle handle) throws IOException { AccessControlManager acm; try { acm = findACM(name, handle); if (acm != null) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) { Log.info(Log.FAC_ACCESSCONTROL, "keysForInput: retrieving key for data node {0}", name); } return acm.getContentKeys(name, publisher); } } catch (InvalidKeyException e) { // TODO use 1.6 constuctors that take nested exceptions when can move off 1.5 Log.logException("InvalidKeyException in keysForInput", e); throw new IOException(e.getClass().getName() + ": Opening stream for input: " + e.getMessage()); } catch (NoSuchAlgorithmException e) { // TODO use 1.6 constuctors that take nested exceptions when can move off 1.5 Log.logException("NoSuchAlgorithmException in keysForInput", e); throw new IOException(e.getClass().getName() + ": Opening stream for input: " + e.getMessage()); } return null; } /** * Get keys to encrypt content as its' written, if that content is to be protected. * Called by the CCNOutputStream subclasses to get encryption keys for specific content * segments. * @param name * @param publisher * @param type the type of content to be written. Mostly used to determine whether * content is protected, but could also be used to specialize keys. * @param handle * @return * @throws IOException */ public static ContentKeys keysForOutput(ContentName name, PublisherPublicKeyDigest publisher, ContentType contentType, CCNHandle handle) throws IOException { if (SystemConfiguration.disableAccessControl()) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINEST)) { Log.finest(Log.FAC_ACCESSCONTROL, "Access control disabled, not searching for keys for {0}.", name); } return null; } AccessControlManager acm; try { acm = findACM(name, handle); if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) { Log.info(Log.FAC_ACCESSCONTROL, "keysForOutput: found an acm: {0}", acm); } if ((acm != null) && (acm.isProtectedContent(name, publisher, contentType, handle))) { // First we need to figure out whether this content is public or unprotected... if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.INFO)) { Log.info(Log.FAC_ACCESSCONTROL, "keysForOutput: found ACM, protected content, generating new data key for data node {0}", name); } NodeKey dataKeyWrappingKey = acm.getDataKeyWrappingKey(name, publisher); if (null == dataKeyWrappingKey) { // if content is public -- either null or a special value would work return null; // no keys } Key dataKey = acm.generateDataKey(name); if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) { Log.finer(Log.FAC_ACCESSCONTROL, "keysForOutput: content {0} publisher {1} data key {2} wrapping key {3}", name, publisher, DataUtils.printHexBytes(dataKey.getEncoded()), dataKeyWrappingKey); } acm.storeDataKey(name, dataKey, dataKeyWrappingKey); return getDefaultAlgorithmContentKeys(dataKey); } } catch (InvalidKeyException e) { // TODO use 1.6 constuctors that take nested exceptions when can move off 1.5 Log.logException("InvalidKeyException in keysForInput", e); throw new IOException(e.getClass().getName() + ": Opening stream for input: " + e.getMessage()); } catch (NoSuchAlgorithmException e) { // TODO use 1.6 constuctors that take nested exceptions when can move off 1.5 Log.logException("NoSuchAlgorithmException in keysForInput", e); throw new IOException(e.getClass().getName() + ": Opening stream for input: " + e.getMessage()); } return null; } /** * Find an ACM that controls access to content in a given namespace. Only looks * in the cache of already-loaded AccessControlManagers; it doesn't load additional * ACMs or search for policy objects. This is called by keysForOutput *every* time * content is written. It can't do extensive search. Use loadAccessControlManagerForNamespace * to load an ACM for a namespace if policy specifies to use one. * @param name * @param handle * @return null if namespace is not under access control, or an ACM to perform * operations on the name if it is. * @throws IOException */ public static AccessControlManager findACM(ContentName name, CCNHandle handle) throws IOException { // See if we already have an AccessControlManager covering this namespace AccessControlManager acm = handle.keyManager().getAccessControlManagerForName(name); if (null != acm) { return acm; } return null; } /** * Given a name prefix, search for policy markers along that name path and load corresponding * access control managers. * TODO handle multiple policy points * TODO maybe handle policies overlapping in namespace * @param namespace * @param handle * @return The ACM we find if one already existed, or the new one we created, if configured; * if no AC configured for this namespace, return null; * @throws ConfigurationException * @throws ContentNotReadyException * @throws ContentGoneException * @throws ErrorStateException * @throws IOException */ public static AccessControlManager loadAccessControlManagerForNamespace(ContentName namespace, CCNHandle handle) throws ContentNotReadyException, ContentGoneException, ErrorStateException, IOException { // Make sure we haven't already loaded it. AccessControlManager acm = findACM(namespace, handle); if (null != acm) { return acm; } // See if we have an access control policy, and if so make an access control manager for it. ContentName policyNamespace = NamespaceManager.findPolicyControlledNamespace(namespace, handle); if (null == policyNamespace) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) { Log.finer(Log.FAC_ACCESSCONTROL, "No policy controlling name: {0}", namespace); } return null; } // TODO cache nonexistence of access control policy in policy namespace. Here or in NSM? AccessControlPolicyMarkerObject ro = new AccessControlPolicyMarkerObject(AccessControlProfile.getAccessControlPolicyName(policyNamespace), handle); if (!ro.available()) { if (Log.isLoggable(Log.FAC_ACCESSCONTROL, Level.FINER)) { Log.finer(Log.FAC_ACCESSCONTROL, "No access control policy in policy namespace: {0}", policyNamespace); } // TODO add to negative cache return null; } try { acm = AccessControlManager.createAccessControlManager(ro, handle); handle.keyManager().rememberAccessControlManager(acm); return acm; } catch (InstantiationException e) { Log.severe("InstantiationException attempting to create access control manager: " + e.getMessage()); Log.warningStackTrace(e); throw new ErrorStateException("InstantiationException attempting to create access control manager: " + e.getMessage(), e); } catch (IllegalAccessException e) { Log.severe("IllegalAccessException attempting to create access control manager: " + e.getMessage()); Log.warningStackTrace(e); throw new ErrorStateException("IllegalAccessException attempting to create access control manager: " + e.getMessage(), e); } } /** * Allow AccessControlManagers to specify some content is not to be protected; for example, * access control lists are not themselves encrypted. * TODO: should headers be exempt from encryption? */ public boolean isProtectedContent(ContentName name, PublisherPublicKeyDigest publisher, ContentType contentType, CCNHandle hande) { if (!inProtectedNamespace(name)) { return false; } // Don't encrypt KEYs or LINKs for now. if ((ContentType.KEY == contentType) || (ContentType.LINK == contentType)) { return false; } if (AccessControlProfile.isAccessName(name)) { // Don't encrypt the access control metadata itself, or we couldn't get the // keys to decrypt the other stuff. return false; } if (AccessControlProfile.isAccessControlPolicyName(name)) { // don't encrypt the access control policy metadata. return false; } return true; } /** * Each access control manager subclass should shut down any ongoing network operations. * We don't own our handle, so can't close that. But any outstanding interests should be * canceled; filters should be unregistered, and so on. */ public void shutdown() {} }