/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009, 2010, 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.crypto;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.impl.security.crypto.util.CryptoConstants;
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;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
/**
* ContentKeys is a container class holding a key and optional IV or counter value,
* plus an algorithm specifier. It is used to carry the state necessary to perform
* symmetric encryption of content. To do so, it requires a function that maps from
* a key set to the keying data to be used to encrypt/decrypt a specific block of
* content (see getSegmentEncryptionCipher and getSegmentDecryptionCipher), which may,
* either use this key material directly or use a key derivation function to obtain
* subkeys specific to each segment.
*
*/
public abstract class ContentKeys implements Cloneable {
public static final String DEFAULT_KEY_ALGORITHM = CryptoConstants.AES_ALGORITHM;
public static final String DEFAULT_CIPHER_ALGORITHM = CryptoConstants.AES_CTR_MODE;
public static final int DEFAULT_KEY_LENGTH = 16; // bytes, 128 bits (do NOT increase for AES,
// security of AES-192 and AES-256 actually
// more suspect than AES-128
/**
* A simple source of key derivation material.
*/
private static SecureRandom _random;
protected String _encryptionAlgorithm;
protected KeyAndIV _masterKeyAndIVCtr;
/**
* Not used in this class, but available to subclasses.
*/
protected HashMap<ContentInfo, KeyAndIV> _keyCache;
public static class KeyAndIV {
private SecretKeySpec _key;
private byte [] _iv;
public KeyAndIV(String algorithm, byte [] key, byte [] iv) {
_key = new SecretKeySpec(key, algorithm);
_iv = iv;
}
public KeyAndIV(Key key, byte [] iv) {
_key = new SecretKeySpec(key.getEncoded(), key.getAlgorithm());
_iv = iv;
}
public SecretKeySpec getKey() { return _key; }
public byte [] getIV() { return _iv; }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(_iv);
result = prime * result + ((_key == null) ? 0 : _key.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
KeyAndIV other = (KeyAndIV) obj;
if (!Arrays.equals(_iv, other._iv))
return false;
if (_key == null) {
if (other._key != null)
return false;
} else if (!_key.equals(other._key))
return false;
return true;
}
@Override
public String toString() {
return "Key: " + DataUtils.printHexBytes(_key.getEncoded()) + " IV: " + DataUtils.printHexBytes(_iv);
}
}
public static class ContentInfo {
private ContentName _contentName;
private PublisherPublicKeyDigest _publisher;
private String _label;
public ContentInfo(ContentName contentName, PublisherPublicKeyDigest publisher, String label) {
_contentName = contentName;
_publisher = publisher;
_label = label;
}
public ContentName getContentName() { return _contentName; }
public PublisherPublicKeyDigest getPublisher() { return _publisher; }
public String getLabel() { return _label; }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((_contentName == null) ? 0 : _contentName.hashCode());
result = prime * result
+ ((_label == null) ? 0 : _label.hashCode());
result = prime * result
+ ((_publisher == null) ? 0 : _publisher.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ContentInfo other = (ContentInfo) obj;
if (_contentName == null) {
if (other._contentName != null)
return false;
} else if (!_contentName.equals(other._contentName))
return false;
if (_label == null) {
if (other._label != null)
return false;
} else if (!_label.equals(other._label))
return false;
if (_publisher == null) {
if (other._publisher != null)
return false;
} else if (!_publisher.equals(other._publisher))
return false;
return true;
}
}
private ContentKeys(String encryptionAlgorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
if (null != encryptionAlgorithm) {
Cipher.getInstance(encryptionAlgorithm, KeyManager.PROVIDER);
_encryptionAlgorithm = encryptionAlgorithm;
} else {
_encryptionAlgorithm = DEFAULT_CIPHER_ALGORITHM;
}
}
protected ContentKeys(String encryptionAlgorithm, byte [] masterEncryptionKey, byte [] masterIVCtr) throws NoSuchAlgorithmException, NoSuchPaddingException {
this(encryptionAlgorithm);
Log.finer("ContentKeys: initializing key for algorithm {0}, master key {1}, iv/ctr {2}", encryptionAlgorithm,
DataUtils.printHexBytes(masterEncryptionKey), DataUtils.printHexBytes(masterIVCtr));
this._masterKeyAndIVCtr = new KeyAndIV(_encryptionAlgorithm.substring(0, _encryptionAlgorithm.indexOf('/')), masterEncryptionKey, masterIVCtr);
}
protected ContentKeys(String encryptionAlgorithm, Key masterEncryptionKey, byte [] masterIVCtr) throws NoSuchAlgorithmException, NoSuchPaddingException {
this(encryptionAlgorithm);
this._masterKeyAndIVCtr = new KeyAndIV(masterEncryptionKey, masterIVCtr);
}
public ContentKeys(ContentKeys other) {
}
/**
* Get the full algorithm specification, including mode and padding.
* @return
*/
protected String getEncryptionAlgorithm() { return _encryptionAlgorithm; }
/**
* Get the simple algorithm specification for the algorithm used by the key (e.g. "AES").
* @return
*/
protected String getKeyAlgorithm() { return _masterKeyAndIVCtr.getKey().getAlgorithm(); }
protected static synchronized SecureRandom getRandom() {
// see http://www.cigital.com/justiceleague/2009/08/14/proper-use-of-javas-securerandom/
// also Fedora seems to have screwed up the built in PRNG provider, slowing thing down dramatically
if (null != _random)
return _random;
try {
_random = SecureRandom.getInstance("SHA1PRNG", KeyManager.PROVIDER);
} catch (NoSuchAlgorithmException e) {
Log.warning("Cannot find random number generation algorithm SHA1PRNG: " + e.getMessage());
_random = new SecureRandom();
}
if (null == _random) {
Log.severe("ERROR: Cannot create secure random number generator!");
}
return _random;
}
/**
* Put this here temporarily. It will disappear as soon as we get the rest of the CBC code
* in place.
* Test if this is using the default encryption algorithm.
* A number of users of ContentKeys only support using the default algorithm, and use this to verify.
* @throws UnsupportedOperationException if the algorithm for this object is not the default.
*/
public void requireDefaultAlgorithm() {
// For now we only support the default algorithm.
if (!_encryptionAlgorithm.equals(KDFContentKeys.DEFAULT_CIPHER_ALGORITHM)) {
String err = "Right now the only encryption algorithm we support is: " +
KDFContentKeys.DEFAULT_CIPHER_ALGORITHM + ", " + _encryptionAlgorithm +
" will come later.";
Log.severe(err);
throw new UnsupportedOperationException(err);
}
}
/**
* @return The base algorithm used in the encryption algorithm specified for this
* ContentKeys. For example, if the encryptionAlgorithm is "AES/CTR/NoPadding",
* the base algorithm is AES.
*/
public String getBaseAlgorithm() {
if (_encryptionAlgorithm.contains("/")) {
return _encryptionAlgorithm.substring(0, _encryptionAlgorithm.indexOf("/"));
}
return _encryptionAlgorithm;
}
/**
* Create a cipher for the encryption algorithm used by this ContentKeys
* @return the cipher
*/
public Cipher getCipher() {
// We have tried a dummy call to Cipher.getInstance on construction of this ContentKeys - so
// further "NoSuch" exceptions should not happen here.
try {
return Cipher.getInstance(_encryptionAlgorithm, KeyManager.PROVIDER);
} catch (NoSuchAlgorithmException e) {
String err = "Unexpected NoSuchAlgorithmException for an algorithm we have already used!";
Log.severe(err);
throw new RuntimeException(err, e);
} catch (NoSuchPaddingException e) {
String err = "Unexpected NoSuchPaddingException for an algorithm we have already used!";
Log.severe(err);
throw new RuntimeException(err, e);
}
}
/**
* Make an encrypting or decrypting Cipher to be used in making a CipherStream to
* wrap CCN data.
* @throws ContentEncodingException
*/
public Cipher getSegmentEncryptionCipher(ContentName contentName, PublisherPublicKeyDigest publisher, long segmentNumber)
throws InvalidKeyException, InvalidAlgorithmParameterException, ContentEncodingException {
return getSegmentCipher(contentName, publisher, segmentNumber, true);
}
/**
* Create a decryption cipher for the specified segment.
* @param segmentNumber the segment to decrypt
* @return the Cipher
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws ContentEncodingException
* @see getSegmentEncryptionCipher(long)
*/
public Cipher getSegmentDecryptionCipher(ContentName contentName, PublisherPublicKeyDigest publisher, long segmentNumber)
throws InvalidKeyException, InvalidAlgorithmParameterException, ContentEncodingException {
return getSegmentCipher(contentName, publisher, segmentNumber, false);
}
/**
* Generate a segment encryption or decryption cipher using these ContentKeys
* to encrypt or decrypt a particular segment.
* @param segmentNumber segment to encrypt/decrypt
* @param encryption true for encryption, false for decryption
* @return the Cipher
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws ContentEncodingException
* @see getSegmentEncryptionCipher(long)
*/
protected abstract Cipher getSegmentCipher(ContentName contentName, PublisherPublicKeyDigest publisher, long segmentNumber, boolean encryption)
throws InvalidKeyException, InvalidAlgorithmParameterException, ContentEncodingException;
/**
* Helper methods to let subclasses cache derived key information that might be
* expensive to re-derive.
*/
protected synchronized boolean hasCachedKeyInformation(ContentInfo contentInfo) {
if (null == _keyCache) {
return false;
}
if (null == contentInfo) {
Log.info("Unexpected: content info is null!");
return false;
}
return (null != getCachedKeyInformation(contentInfo));
}
protected synchronized void addCachedKeyInformation(ContentInfo contentInfo, KeyAndIV keyAndIV) {
if (null == _keyCache) {
_keyCache = new HashMap<ContentInfo, KeyAndIV>();
}
_keyCache.put(contentInfo, keyAndIV);
}
protected synchronized KeyAndIV getCachedKeyInformation(ContentInfo contentInfo) {
if (null == _keyCache) {
return null;
}
return _keyCache.get(contentInfo);
}
/**
* Converts a segment number to a byte array representation (big-endian).
* @param segmentNumber the segment number to convert
* @return the byte array representation of segmentNumber
*/
public static byte [] segmentNumberToByteArray(long segmentNumber) {
byte [] ba = new byte[KDFContentKeys.SEGMENT_NUMBER_LENGTH];
// Is this the fastest way to do this?
byte [] bv = BigInteger.valueOf(segmentNumber).toByteArray();
System.arraycopy(bv, 0, ba, KDFContentKeys.SEGMENT_NUMBER_LENGTH-bv.length, bv.length);
return ba;
}
public ContentKeys clone() {
try {
ContentKeys ck = (ContentKeys)super.clone();
// probably should clone
ck._encryptionAlgorithm = this._encryptionAlgorithm;
ck._masterKeyAndIVCtr = this._masterKeyAndIVCtr;
return ck;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
public Key getMasterKey() {
return _masterKeyAndIVCtr.getKey();
}
public byte [] getMasterIVCtr() {
return _masterKeyAndIVCtr.getIV();
}
}