/* * 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.crypto; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.ShortBufferException; import javax.crypto.spec.SecretKeySpec; import org.ccnx.ccn.impl.encoding.XMLEncodable; import org.ccnx.ccn.impl.security.crypto.ContentKeys.ContentInfo; import org.ccnx.ccn.impl.security.crypto.ContentKeys.KeyAndIV; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentEncodingException; import org.ccnx.ccn.protocol.ContentName; /** * This class takes a master symmetric key, and derives from it a key * and initialization vector to be used to encrypt a specific content object. * * This simplifies key management by allowing the same master key to be * used for families of related content (e.g. all the versions of a file, * all of the intrinsic metadata associated with that file, etc.), without * having to manage an additional separately wrapped master key. It allows * hierarchically delegated access control (i.e. when the children of a node * inherit the permissions associated with that node) to proceed without * requiring additional keys. It also acts to prevent errors, by limiting * the risk of IV/counter reuse, both by distributing encryption across * many derived keys, and deriving the IV/counter seeds automatically for * programmers. * * NOTE: This is a low-level cryptographic API. * This class is used internally by CCN's built in access control prototype, * and may be used for key derivation in general, for people building other * encryption models for CCN. However, if you don't * already know what "key derivation" means, you should NOT be using it -- * there is probably a different API that more closely fits your needs. * * For each block, we compute the PRF using key K over * (counter || label || 0x00 || context || L) * where L is the desired output length in bits. * * We encode the counter and L both in 32 bit fields. The counter is initialized * to 1. * * The KDF implemented herein is from NIST special publication 108-008, * and is described in more detail in the CCN documentation. */ public class KeyDerivationFunction { /** * MAC algorithm used as a PRF. * We use HMAC-SHA256 as our primary PRF, because it is the most commonly * implemented acceptable option. CMAC would be preferable, * but is not universally available yet. */ protected static final String MAC_ALGORITHM = "HmacSHA256"; /** * Default parameterization of the KDF for standard algorithm type. This is the * routine that will be typically used by code that does not want to override * default algorithms. * @param masterKeyBytes the source key from which to derive a subkey * @param label a text label for additional parameterization, if desired * @param contentName name of the specific object to derive a key for, including the version (but not including * segment information). * @param publisher for this particular set of objects * @throws InvalidKeyException * @throws ContentEncodingException */ public static final KeyAndIV DeriveKeysForObject( String keyAlgorithm, byte [] masterKeyBytes, ContentInfo contentInfo) throws InvalidKeyException, ContentEncodingException { KeyAndIV keyAndIV = DeriveKeysForObject(keyAlgorithm, masterKeyBytes, ContentKeys.DEFAULT_KEY_LENGTH*DataUtils.BITS_PER_BYTE, StaticContentKeys.IV_MASTER_LENGTH*DataUtils.BITS_PER_BYTE, contentInfo); return keyAndIV; } /** * Derive a key and IV for a particular object. Requested bit lengths must * be divisible by BITS_PER_BYTE. * @param masterKeyBytes master key to derive a new key from * @param keyBitLength bit length of key to derive * @param ivBitLength bit length of iv to derive * @param label a text label to allow derivation of multiple key types from a single * source key/path pair * @param contentName name to derive a key for * @param publisher publisher whose version of contentName we want to derive for * @return returns an {key, iv/counter seed} pair suitable for use in segment encryption * @throws InvalidKeyException * @throws ContentEncodingException */ public static final KeyAndIV DeriveKeysForObject( String keyAlgorithm, byte [] masterKeyBytes, int keyBitLength, int ivBitLength, ContentInfo contentInfo) throws InvalidKeyException, ContentEncodingException { byte [] key = new byte[keyBitLength/DataUtils.BITS_PER_BYTE]; byte [] iv = new byte[ivBitLength/DataUtils.BITS_PER_BYTE]; byte [] keyandiv = DeriveKeyForObjectOrNode(masterKeyBytes, keyBitLength + ivBitLength, contentInfo); System.arraycopy(keyandiv, 0, key, 0, keyBitLength/DataUtils.BITS_PER_BYTE); System.arraycopy(keyandiv, keyBitLength/DataUtils.BITS_PER_BYTE, iv, 0, ivBitLength/DataUtils.BITS_PER_BYTE); return new KeyAndIV(keyAlgorithm, key, iv); } /** * Used to derive keys for nodes in a name hierarchy. The key must be independent of * publisher, as it is used to derive keys for intermediate nodes. As this is used as * input to another key derivation call, no IV is derived. * @param parentNodeKeyBytes the initial source key to derive further keys from * @param label a text label to allow derivation of multiple key types from a single * source key/path pair * @param nodeName the name of the node to derive a key for * @throws InvalidKeyException * @throws ContentEncodingException */ public static final byte [] DeriveKeyForNode( byte [] parentNodeKeyBytes, String label, ContentName nodeName) throws InvalidKeyException, ContentEncodingException { return DeriveKeyForNode(parentNodeKeyBytes, ContentKeys.DEFAULT_KEY_LENGTH*DataUtils.BITS_PER_BYTE, label, nodeName); } /** * Hierarchically derive keys for a child node, given an ancestor key. Why not do this in * one step, with no intervening keys? This way we can delegate/install backlinks to keys * in the middle of the hierarchy and things continue to work. * @param ancestorNodeName the node with whom ancestorNodeKey is associated. * @param ancestorNodeKey the key associated with that ancestor node * @param label a text label to allow derivation of multiple key types from a single * source key/path pair * @param nodeName the name of the node to derive a key for * @throws InvalidKeyException * @throws ContentEncodingException */ public static final byte [] DeriveKeyForNode( ContentName ancestorNodeName, byte [] ancestorNodeKey, String label, ContentName nodeName) throws InvalidKeyException, ContentEncodingException { if ((null == ancestorNodeName) || (null == ancestorNodeKey) || (null == nodeName)) { throw new IllegalArgumentException("Names and keys cannot be null!"); } if (!ancestorNodeName.isPrefixOf(nodeName)) { throw new IllegalArgumentException("Ancestor node name must be prefix of node name!"); } if (ancestorNodeName.equals(nodeName)) { Log.info("We're at the correct node already, will return the original node key."); } ContentName descendantNodeName = ancestorNodeName; byte [] descendantNodeKey = ancestorNodeKey; while (!descendantNodeName.equals(nodeName)) { descendantNodeName = nodeName.cut(descendantNodeName.count() + 1); descendantNodeKey = DeriveKeyForNode(descendantNodeKey, label, descendantNodeName); } return descendantNodeKey; } public static final byte [] DeriveKeyForNode(byte [] parentNodeKeyBytes, int keyLengthInBits, String label, ContentName nodeName) throws InvalidKeyException, ContentEncodingException { return DeriveKeyForObjectOrNode(parentNodeKeyBytes, ContentKeys.DEFAULT_KEY_LENGTH*DataUtils.BITS_PER_BYTE, new ContentInfo(nodeName, null, label)); } /** * Derive a key for a particular object. Requested bit lengths must * be divisible by BITS_PER_BYTE. * @param masterKeyBytes master key to derive a new key from * @param outputLengthInBits bit length of key to derive * @param label a text label to allow derivation of multiple key types from a single * source key/path pair * @param contentName name to derive a key for * @param publisher publisher whose version of contentName we want to derive for * @return returns a key for this object * @throws InvalidKeyException * @throws ContentEncodingException */ public static final byte [] DeriveKeyForObjectOrNode( byte [] masterKeyBytes, int outputLengthInBits, ContentInfo contentInfo) throws InvalidKeyException, ContentEncodingException { if (null == contentInfo.getContentName()) { throw new IllegalArgumentException("Content name cannot be null!"); } return DeriveKey(masterKeyBytes, outputLengthInBits, contentInfo.getLabel(), ((null != contentInfo.getPublisher()) ? new XMLEncodable[]{contentInfo.getContentName(), contentInfo.getPublisher()} : new XMLEncodable[]{contentInfo.getContentName()})); } /** * Core key derivation mechanism. * @param masterKeyBytes master key to derive a new key from * @param outputLengthInBits bit length of key to derive * @param label a text label to allow derivation of multiple key types from a single * source key/path pair * @param contextObjects objects to add into the KDF as context. Usually at least the name * of the node, also possibly the publisher. * @return the derived key * @throws InvalidKeyException * @throws ContentEncodingException */ public static final byte [] DeriveKey(byte [] masterKeyBytes, int outputLengthInBits, String label, XMLEncodable [] contextObjects) throws InvalidKeyException, ContentEncodingException { if ((null == masterKeyBytes) || (masterKeyBytes.length == 0)) { throw new IllegalArgumentException("Master key bytes cannot be null or empty!"); } // DKS deep debugging boolean allzeros = true; for (byte b : masterKeyBytes) { if (b != 0) { allzeros = false; break; } } if (allzeros) { Log.warning("Warning: DeriveKey called with all 0's key of length " + masterKeyBytes.length); } Mac hmac; try { hmac = Mac.getInstance("HmacSHA256"); } catch (NoSuchAlgorithmException e1) { Log.severe("No HMAC-SHA256 available! Serious configuration issue!"); throw new RuntimeException("No HMAC-SHA256 available! Serious configuration issue!"); } hmac.init(new SecretKeySpec(masterKeyBytes, hmac.getAlgorithm())); // Precompute data used from block to block. byte [] Lbytes = new byte[] { (byte)(outputLengthInBits>>24), (byte)(outputLengthInBits>>16), (byte)(outputLengthInBits>>DataUtils.BITS_PER_BYTE), (byte)outputLengthInBits }; byte [][] contextBytes = new byte[((null == contextObjects) ? 0 : contextObjects.length)][]; for (int j = 0; j < contextBytes.length; ++j) { contextBytes[j] = contextObjects[j].encode(); } int outputLengthInBytes = (outputLengthInBits + DataUtils.BITS_PER_BYTE - 1) / DataUtils.BITS_PER_BYTE; byte [] outputBytes = new byte[outputLengthInBytes]; // Number of rounds int macLength = hmac.getMacLength(); int n = (outputLengthInBytes + macLength - 1) / macLength; if (n < 1) { Log.warning("Unexpected: 0 block key derivation: want " + outputLengthInBits + " bits (" + outputLengthInBytes + " bytes)."); } // Run the HMAC for enough blocks to get the keying material we need. for (int i=1; i <= n; ++i) { try { // 32-bit representation of i hmac.update(new byte[] { (byte)(i>>24), (byte)(i>>16), (byte)(i>>8), (byte)i}); // UTF-8 representation of label, including null terminator, or "" if empty if (null == label) { label = DataUtils.EMPTY; } hmac.update(DataUtils.getBytesFromUTF8String(label)); // a 0 byte hmac.update((byte)0x00); // encoded context objects for (int k=0; k < contextBytes.length; ++k) { hmac.update(contextBytes[k]); } // 32-bit representation of L hmac.update(Lbytes); if (i < n) { hmac.doFinal(outputBytes, (i-1)*macLength); } else { byte [] finalBlock = hmac.doFinal(); System.arraycopy(finalBlock, 0, outputBytes, (i-1)*macLength, outputBytes.length - (i-1)*macLength); } } catch (IllegalStateException ex) { Log.severe("Unexpected IllegalStateException in DeriveKey: hmac should have been initialized!"); Log.warningStackTrace(ex); throw new RuntimeException("Unexpected IllegalStateException in DeriveKey: hmac should have been initialized!"); } catch (ShortBufferException sx) { Log.severe("Unexpected ShortBufferException in DeriveKey: buffer should be sufficient!"); Log.warningStackTrace(sx); throw new RuntimeException("Unexpected ShortBufferException in DeriveKey: buffer should be sufficient!"); } } return outputBytes; } }