From 1b0a51f76a8d3d1f6df978e50ca8f869dbac48dc Mon Sep 17 00:00:00 2001
From: david_page <david_page@localhost>
Date: Wed, 05 Sep 2007 17:58:40 +0000
Subject: [PATCH] Update CryptoManager secret key encryption public API:
---
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackupManager.java | 21 -
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java | 118 +++++++++
opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java | 18 -
opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java | 19 -
opendj-sdk/opends/src/server/org/opends/server/types/CryptoManager.java | 539 +++++++++++++++++++++++++++++++++-----------
5 files changed, 535 insertions(+), 180 deletions(-)
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
index f4b8e02..2b2a457 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -51,9 +51,6 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
import javax.crypto.Mac;
import org.opends.server.api.AlertGenerator;
@@ -4314,13 +4311,12 @@
// output stream.
if (encrypt)
{
- String cipherAlgorithm = cryptoManager.getPreferredCipherAlgorithm();
+ String cipherAlgorithm = cryptoManager.getPreferredCipherTransformation();
backupProperties.put(BACKUP_PROPERTY_CIPHER_ALGORITHM, cipherAlgorithm);
-
- Cipher cipher;
try
{
- cipher = cryptoManager.getPreferredCipher(Cipher.ENCRYPT_MODE);
+ outputStream
+ = cryptoManager.getCipherOutputStream(outputStream);
}
catch (Exception e)
{
@@ -4334,8 +4330,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- outputStream = new CipherOutputStream(outputStream, cipher);
}
@@ -4710,11 +4704,10 @@
message);
}
- Cipher cipher;
try
{
- cipher = DirectoryServer.getCryptoManager().getCipher(cipherAlgorithm,
- Cipher.DECRYPT_MODE);
+ inputStream = DirectoryServer.getCryptoManager()
+ .getCipherInputStream(inputStream);
}
catch (Exception e)
{
@@ -4723,8 +4716,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- inputStream = new CipherInputStream(inputStream, cipher);
}
// Now wrap the resulting input stream in a zip stream so that we can read
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackupManager.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackupManager.java
index 6456125..c2eca97 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackupManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackupManager.java
@@ -37,9 +37,6 @@
import org.opends.server.types.DirectoryException;
import org.opends.server.types.RestoreConfig;
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
import javax.crypto.Mac;
import java.io.BufferedReader;
import java.io.File;
@@ -345,13 +342,13 @@
// output stream.
if (encrypt)
{
- String cipherAlgorithm = cryptoManager.getPreferredCipherAlgorithm();
+ String cipherAlgorithm = cryptoManager.getPreferredCipherTransformation();
backupProperties.put(BACKUP_PROPERTY_CIPHER_ALGORITHM, cipherAlgorithm);
- Cipher cipher;
try
{
- cipher = cryptoManager.getPreferredCipher(Cipher.ENCRYPT_MODE);
+ outputStream
+ = cryptoManager.getCipherOutputStream(outputStream);
}
catch (Exception e)
{
@@ -365,8 +362,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- outputStream = new CipherOutputStream(outputStream, cipher);
}
@@ -983,10 +978,9 @@
String cipherAlgorithm =
backupProperties.get(BACKUP_PROPERTY_CIPHER_ALGORITHM);
- Cipher cipher;
try
{
- cipher = cryptoManager.getCipher(cipherAlgorithm, Cipher.DECRYPT_MODE);
+ inputStream = cryptoManager.getCipherInputStream(inputStream);
}
catch (Exception e)
{
@@ -1000,8 +994,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- inputStream = new CipherInputStream(inputStream, cipher);
}
@@ -1318,10 +1310,9 @@
String cipherAlgorithm =
backupProperties.get(BACKUP_PROPERTY_CIPHER_ALGORITHM);
- Cipher cipher;
try
{
- cipher = cryptoManager.getCipher(cipherAlgorithm, Cipher.DECRYPT_MODE);
+ inputStream = cryptoManager.getCipherInputStream(inputStream);
}
catch (Exception e)
{
@@ -1335,8 +1326,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- inputStream = new CipherInputStream(inputStream, cipher);
}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index 228105f..9d0822c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -54,9 +54,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
import javax.crypto.Mac;
import org.opends.messages.Message;
@@ -2823,13 +2820,13 @@
// output stream.
if (encrypt)
{
- String cipherAlgorithm = cryptoManager.getPreferredCipherAlgorithm();
+ String cipherAlgorithm = cryptoManager.getPreferredCipherTransformation();
backupProperties.put(BACKUP_PROPERTY_CIPHER_ALGORITHM, cipherAlgorithm);
- Cipher cipher;
try
{
- cipher = cryptoManager.getPreferredCipher(Cipher.ENCRYPT_MODE);
+ outputStream
+ = cryptoManager.getCipherOutputStream(outputStream);
}
catch (Exception e)
{
@@ -2843,8 +2840,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- outputStream = new CipherOutputStream(outputStream, cipher);
}
@@ -3259,11 +3254,10 @@
message);
}
- Cipher cipher;
try
{
- cipher = DirectoryServer.getCryptoManager().getCipher(cipherAlgorithm,
- Cipher.DECRYPT_MODE);
+ inputStream = DirectoryServer.getCryptoManager()
+ .getCipherInputStream(inputStream);
}
catch (Exception e)
{
@@ -3272,8 +3266,6 @@
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message, e);
}
-
- inputStream = new CipherInputStream(inputStream, cipher);
}
// Now wrap the resulting input stream in a zip stream so that we can read
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/CryptoManager.java b/opendj-sdk/opends/src/server/org/opends/server/types/CryptoManager.java
index f29db43..1b966bb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/CryptoManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/CryptoManager.java
@@ -25,26 +25,24 @@
* Portions Copyright 2006-2007 Sun Microsystems, Inc.
*/
package org.opends.server.types;
+
import org.opends.messages.Message;
import static org.opends.messages.CoreMessages.*;
-
import java.io.InputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.*;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
-import java.util.SortedSet;
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
+import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManager;
@@ -55,14 +53,16 @@
import org.opends.server.config.ConfigException;
import org.opends.server.config.ConfigConstants;
+import org.opends.server.admin.std.server.CryptoManagerCfg;
+import org.opends.server.api.Backend;
+import org.opends.server.backends.TrustStoreBackend;
+import org.opends.server.core.DirectoryServer;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
import static org.opends.server.util.StaticUtils.*;
+import org.opends.server.util.Validator;
import org.opends.server.util.SelectableCertificateKeyManager;
-import org.opends.server.api.Backend;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.backends.TrustStoreBackend;
-import org.opends.server.admin.std.server.CryptoManagerCfg;
/**
* This class provides the interface to the Directory Server
@@ -86,33 +86,44 @@
*/
private static final DebugTracer TRACER = getTracer();
+ // The secure random number generator used for key generation,
+ // initialization vector PRNG seed...
+ private final SecureRandom secureRandom = new SecureRandom();
+ // The random number generator used for initialization vector
+ // production.
+ private final Random pseudoRandom
+ = new Random(secureRandom.nextLong());
+ // The map from encryption key ID to cipher algorithm name.
+ private final HashMap<ByteString, String> cipherTransformationMap
+ = new HashMap<ByteString, String>();
- // The default secret key that we will use for encryption and
- // decryption.
- private SecretKey secretKey;
+ // The map from encryption key ID to cipher key.
+ private final HashMap<ByteString, byte[]> secretKeyMap
+ = new HashMap<ByteString, byte[]>();
// The preferred cipher for the Directory Server.
- private String preferredCipher;
+ private final String preferredCipherTransformation;
// The preferred message digest algorithm for the Directory Server.
- private String preferredDigestAlgorithm;
+ private final String preferredDigestAlgorithm;
// The preferred MAC algorithm for the Directory Server.
- private String preferredMACAlgorithm;
+ private final String preferredMACAlgorithm;
// The name of the local certificate to use for SSL.
- private String sslCertNickname;
+ private final String sslCertNickname;
// Whether replication sessions use SSL encryption.
- private boolean sslEncryption;
+ private final boolean sslEncryption;
// The set of SSL protocols enabled or null for the default set.
- private SortedSet<String> sslProtocols;
+ private final SortedSet<String> sslProtocols;
// The set of SSL cipher suites enabled or null for the default set.
- private SortedSet<String> sslCipherSuites;
+ private final SortedSet<String> sslCipherSuites;
+
/**
* Creates a new instance of this crypto manager object from a given
@@ -136,13 +147,12 @@
// hard-coding them.
preferredDigestAlgorithm = "SHA-1";
preferredMACAlgorithm = "HmacSHA1";
- preferredCipher = "AES/CBC/PKCS5Padding";
+ preferredCipherTransformation = "AES/CBC/PKCS5Padding";
-
- // FIXME -- Use a much more secure way of constructing the secret
- // key.
- secretKey = new SecretKeySpec(new byte[16], "AES");
-
+ sslCertNickname = cfg.getSSLCertNickname();
+ sslEncryption = cfg.isSSLEncryption();
+ sslProtocols = cfg.getSSLProtocols();
+ sslCipherSuites = cfg.getSSLCipherSuites();
// Make sure that we can create instances of the preferred digest,
// MAC, and cipher algorithms.
@@ -163,10 +173,12 @@
getExceptionMessage(e).toString()), e);
}
+ byte[] keyValue = new byte[16];
+ secureRandom.nextBytes(keyValue);
try
{
Mac mac = Mac.getInstance(preferredMACAlgorithm);
- mac.init(secretKey);
+ mac.init(new SecretKeySpec(keyValue, preferredMACAlgorithm));
}
catch (Exception e)
{
@@ -183,8 +195,12 @@
try
{
- Cipher cipher = Cipher.getInstance(preferredCipher);
- cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ Cipher cipher
+ = Cipher.getInstance(preferredCipherTransformation);
+ cipher.init(Cipher.ENCRYPT_MODE,
+ new SecretKeySpec(keyValue,
+ preferredCipherTransformation.substring(0,
+ preferredCipherTransformation.indexOf('/'))));
}
catch (Exception e)
{
@@ -198,25 +214,59 @@
Message.raw("Can't get preferred cipher: " +
getExceptionMessage(e).toString()), e);
}
-
- sslCertNickname = cfg.getSSLCertNickname();
- sslEncryption = cfg.isSSLEncryption();
- sslProtocols = cfg.getSSLProtocols();
- sslCipherSuites = cfg.getSSLCipherSuites();
}
/**
- * Retrieves an instance of a secure random number generator.
+ * Converts a UUID string into a compact byte[16] representation.
*
- * @return An instance of a secure random number generator.
+ * @param uuidString A string reprentation of a UUID.
+ *
+ * @return A new byte[16] containing the binary representation of
+ * the UUID.
+ *
+ * @throws CryptoManagerException If the uuidString argument does
+ * not conform to the UUID string syntax.
*/
- public SecureRandom getSecureRandom()
+ private static byte[] uuidToBytes(String uuidString)
+ throws CryptoManagerException
{
- // FIXME -- Is this threadsafe? Can we share a single instance
- // among all threads?
- return new SecureRandom();
+ UUID uuid;
+ try {
+ uuid = UUID.fromString(uuidString);
+ }
+ catch (Exception ex) {
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw("Invalid string representation of a UUID."),
+ ex);
+ }
+ return uuidToBytes(uuid);
+ }
+
+
+
+ /**
+ * Converts a UUID string into a compact byte[16] representation.
+ *
+ * @param uuid A UUID.
+ *
+ * @return A new byte[16] containing the binary representation of
+ * the UUID.
+ */
+ private static byte[] uuidToBytes(UUID uuid)
+ {
+ final byte[] uuidBytes = new byte[16];
+ long hiBytes = uuid.getMostSignificantBits();
+ long loBytes = uuid.getLeastSignificantBits();
+ for (int i = 7; i >= 0; --i) {
+ uuidBytes[i] = (byte)hiBytes;
+ hiBytes >>>= 8;
+ uuidBytes[8 + i] = (byte)loBytes;
+ loBytes >>>= 8;
+ }
+ return uuidBytes;
}
@@ -429,10 +479,7 @@
public Mac getPreferredMACProvider()
throws NoSuchAlgorithmException, InvalidKeyException
{
- Mac mac = Mac.getInstance(preferredMACAlgorithm);
- mac.init(secretKey);
-
- return mac;
+ return getMACProvider(getPreferredMACAlgorithm());
}
@@ -456,7 +503,9 @@
throws NoSuchAlgorithmException, InvalidKeyException
{
Mac mac = Mac.getInstance(macAlgorithm);
- mac.init(secretKey);
+ byte[] keyValue = new byte[16];
+ secureRandom.nextBytes(keyValue);
+ mac.init(new SecretKeySpec(keyValue, macAlgorithm));
return mac;
}
@@ -590,22 +639,24 @@
*
* @return The name of the preferred cipher algorithm
*/
- public String getPreferredCipherAlgorithm()
+ public String getPreferredCipherTransformation()
{
- return preferredCipher;
+ return preferredCipherTransformation;
}
/**
- * Retrieves a cipher using the preferred algorithm and the
- * specified cipher mode.
+ * Retrieves an encryption mode cipher using the specified
+ * algorithm.
*
- * @param cipherMode The cipher mode that indicates how the cipher
- * will be used (e.g., encryption, decryption,
- * wrapping, unwrapping).
+ * @param cipherTransformation The algorithm/mode/padding to use
+ * for the cipher.
*
- * @return A cipher using the preferred algorithm.
+ * @param keyIdentifier The key identifier assigned to the cipher
+ * key used by the cipher.
+ *
+ * @return An encryption mode cipher using the preferred algorithm.
*
* @throws NoSuchAlgorithmException If the requested algorithm is
* not supported or is
@@ -622,34 +673,73 @@
* @throws InvalidAlgorithmParameterException
* If an internal problem occurs as a result of the
* initialization vector used.
+ *
+ * @throws CryptoManagerException If there is a problem managing
+ * the encryption key.
*/
- public Cipher getPreferredCipher(int cipherMode)
+ private Cipher getEncryptionCipher(String cipherTransformation,
+ byte[] keyIdentifier)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException,
- InvalidAlgorithmParameterException
+ InvalidAlgorithmParameterException,
+ CryptoManagerException
{
- Cipher cipher = Cipher.getInstance(preferredCipher);
+ Validator.ensureNotNull(cipherTransformation);
- // FIXME -- This needs to be more secure.
- IvParameterSpec iv = new IvParameterSpec(new byte[16]);
+ byte[] secretKey;
+ byte[] keyID = null;
+ for (Map.Entry<ByteString, String> cipherEntry
+ : cipherTransformationMap.entrySet()) {
+ if (cipherEntry.getValue().equals(cipherTransformation)) {
+ keyID = cipherEntry.getKey().value();
+ break;
+ }
+ }
+ if (null == keyID) {
+ secretKey = new byte[16]; // FIXME: not all keys are 128-bits
+ secureRandom.nextBytes(secretKey);
+ keyID = uuidToBytes(UUID.randomUUID());
+ final ByteString keyString = new ASN1OctetString(keyID);
+ secretKeyMap.put(keyString, secretKey);
+ cipherTransformationMap.put(keyString, cipherTransformation);
+ }
+ else {
+ secretKey = secretKeyMap.get(new ASN1OctetString(keyID));
+ }
- cipher.init(cipherMode, secretKey, iv);
+ final Cipher cipher = Cipher.getInstance(cipherTransformation);
+ final String cipherAlgorithm = cipherTransformation.substring(0,
+ cipherTransformation.indexOf('/'));
+ final byte[] iv = new byte[16];// FIXME: always keylength?
+ pseudoRandom.nextBytes(iv);
+ cipher.init(Cipher.ENCRYPT_MODE,
+ new SecretKeySpec(secretKey, cipherAlgorithm),
+ new IvParameterSpec(iv));
+ try {
+ System.arraycopy(keyID, 0, keyIdentifier, 0, keyID.length);
+ }
+ catch (Exception ex) {
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw("Error copying key identifier."),
+ ex);
+ }
return cipher;
}
/**
- * Retrieves a cipher using the preferred algorithm and the
- * specified cipher mode.
+ * Retrieves a decryption mode cipher using the algorithm and
+ * encryption key specified by the key identifier and the supplied
+ * initialization vector.
*
- * @param cipherAlgorithm The algorithm to use for the cipher.
- * @param cipherMode The cipher mode that indicates how the
- * cipher will be used (e.g., encryption,
- * decryption, wrapping, unwrapping).
+ * @param keyID The ID of the key to be used for decryption.
+ * @param initializationVector The initialization vector to be used
+ * for decryption.
*
- * @return A cipher using the preferred algorithm.
+ * @return A decryption mode cipher using the specified parameters.
*
* @throws NoSuchAlgorithmException If the requested algorithm is
* not supported or is
@@ -666,18 +756,35 @@
* @throws InvalidAlgorithmParameterException
* If an internal problem occurs as a result of the
* initialization vector used.
+ *
+ * @throws CryptoManagerException If an invalid keyID is supplied.
*/
- public Cipher getCipher(String cipherAlgorithm, int cipherMode)
+ private Cipher getDecryptionCipher(byte[] keyID,
+ byte[] initializationVector)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException,
- InvalidAlgorithmParameterException
+ InvalidAlgorithmParameterException,
+ CryptoManagerException
{
- Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+ Validator.ensureNotNull(keyID, initializationVector);
- // FIXME -- This needs to be more secure.
- IvParameterSpec iv = new IvParameterSpec(new byte[16]);
+ final ByteString keyString = new ASN1OctetString(keyID);
+ final String cipherTransformation
+ = cipherTransformationMap.get(keyString);
+ final byte[] secretKey = secretKeyMap.get(keyString);
+ if (null == cipherTransformation || null == secretKey) {
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw("Invalid encryption key identifier %s.",
+ keyString));
+ }
- cipher.init(cipherMode, secretKey, iv);
+ final Cipher cipher = Cipher.getInstance(cipherTransformation);
+ final String cipherAlgorithm = cipherTransformation.substring(0,
+ cipherTransformation.indexOf('/'));
+ cipher.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(secretKey, cipherAlgorithm),
+ new IvParameterSpec(initializationVector));
return cipher;
}
@@ -686,7 +793,7 @@
/**
* Encrypts the data in the provided byte array using the preferred
- * cipher.
+ * cipher transformation.
*
* @param data The data to be encrypted.
*
@@ -696,23 +803,129 @@
* @throws GeneralSecurityException If a problem occurs while
* attempting to encrypt the
* data.
+ *
+ * @throws CryptoManagerException If a problem occurs managing the
+ * encryption key.
*/
public byte[] encrypt(byte[] data)
- throws GeneralSecurityException
+ throws GeneralSecurityException,
+ CryptoManagerException
{
- Cipher cipher = Cipher.getInstance(preferredCipher);
-
- // FIXME -- This needs to be more secure.
- IvParameterSpec iv = new IvParameterSpec(new byte[16]);
-
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
- return cipher.doFinal(data);
+ return encrypt(getPreferredCipherTransformation(), data);
}
/**
- * Decrypts the data in the provided byte array using the preferred
+ * Encrypts the data in the provided byte array using the requested
+ * cipher algorithm.
+ *
+ * @param cipherTransformation The algorithm to use to encrypt the
+ * data.
+ * @param data The data to be encrypted.
+ *
+ * @return A byte array containing the encrypted representation of
+ * the provided data.
+ *
+ * @throws GeneralSecurityException If a problem occurs while
+ * attempting to encrypt the
+ * data.
+ *
+ * @throws CryptoManagerException If a problem occurs managing the
+ * encryption key.
+ */
+ public byte[] encrypt(String cipherTransformation, byte[] data)
+ throws GeneralSecurityException, CryptoManagerException
+ {
+ Validator.ensureNotNull(cipherTransformation, data);
+
+ final byte[] keyID = new byte[16];// FIXME: key id length constant
+ final Cipher cipher
+ = getEncryptionCipher(cipherTransformation, keyID);
+ final byte[] iv = cipher.getIV();
+ final int prologueLength = keyID.length + iv.length;
+ final int dataLength = cipher.getOutputSize(data.length);
+ final byte[] cipherText = new byte[prologueLength + dataLength];
+ System.arraycopy(keyID, 0, cipherText, 0, keyID.length);
+ System.arraycopy(iv, 0, cipherText, keyID.length, iv.length);
+ System.arraycopy(cipher.doFinal(data), 0, cipherText,
+ prologueLength, dataLength);
+ return cipherText;
+ }
+
+
+
+ /**
+ * Encrypts the data in the provided byte array using the preferred
+ * cipher algorithm.
+ *
+ * @param outputStream The output stream to be wrapped by the
+ * returned output stream.
+ *
+ * @return A byte array containing the encrypted representation of
+ * the provided data.
+ *
+ * @throws GeneralSecurityException If a problem occurs while
+ * attempting to encrypt the
+ * data.
+ *
+ * @throws CryptoManagerException If a problem occurs managing the
+ * encryption key.
+ */
+ public CipherOutputStream getCipherOutputStream(
+ OutputStream outputStream)
+ throws GeneralSecurityException, CryptoManagerException
+ {
+ return getCipherOutputStream(getPreferredCipherTransformation(),
+ outputStream);
+ }
+
+
+
+ /**
+ * Encrypts the data in the provided output stream using the
+ * requested cipher transformation.
+ *
+ * @param cipherTransformation The algorithm to use to encrypt the
+ * data.
+ * @param outputStream The output stream to be wrapped by the
+ * returned output stream.
+ *
+ * @return A byte array containing the encrypted representation of
+ * the provided data.
+ *
+ * @throws GeneralSecurityException If a problem occurs while
+ * attempting to encrypt the
+ * data.
+ *
+ * @throws CryptoManagerException If a problem occurs managing the
+ * encryption key.
+ */
+ public CipherOutputStream getCipherOutputStream(
+ String cipherTransformation, OutputStream outputStream)
+ throws GeneralSecurityException, CryptoManagerException
+ {
+ final byte[] keyID = new byte[16];// FIXME: key id length constant
+ final Cipher cipher
+ = getEncryptionCipher(cipherTransformation, keyID);
+ try {
+ outputStream.write(keyID);
+ outputStream.write(cipher.getIV());
+ }
+ catch (IOException ioe) {
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw("Exception when writing CryptoManager prologue."),
+ ioe);
+ }
+ return new CipherOutputStream(outputStream, cipher);
+ }
+
+
+
+ /**
+ * Decrypts the data in the provided byte array using cipher
+ * specified by the key identifier prologue to the data.
* cipher.
*
* @param data The data to be decrypted.
@@ -723,79 +936,95 @@
* @throws GeneralSecurityException If a problem occurs while
* attempting to decrypt the
* data.
+ *
+ * @throws CryptoManagerException If a problem occurs reading the
+ * key identifier or initialization vector from the data
+ * prologue.
*/
public byte[] decrypt(byte[] data)
- throws GeneralSecurityException
+ throws GeneralSecurityException,
+ CryptoManagerException
{
- Cipher cipher = Cipher.getInstance(preferredCipher);
+ final byte[] keyID = new byte[16];
+ final byte[] iv = new byte[16]; // FIXME: always 128 bits?
+ final int prologueLength = keyID.length + iv.length;
+ try {
+ System.arraycopy(data, 0, keyID, 0, keyID.length);
+ System.arraycopy(data, keyID.length, iv, 0, iv.length);
+ }
+ catch (Exception ex) {
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw("Exception when reading CryptoManager prologue."),
+ ex);
+ }
- // FIXME -- This needs to be more secure.
- IvParameterSpec iv = new IvParameterSpec(new byte[16]);
-
- cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
- return cipher.doFinal(data);
+ Cipher cipher = getDecryptionCipher(keyID, iv);
+ return cipher.doFinal(data, prologueLength,
+ data.length - prologueLength);
}
/**
- * Encrypts the data in the provided byte array using the preferred
- * cipher.
+ * Returns a CipherInputStream instantiated with a cipher
+ * corresponding to the key identifier prologue to the data.
*
- * @param cipherAlgorithm The algorithm to use to encrypt the
- * data.
- * @param data The data to be encrypted.
+ * @param inputStream The input stream be wrapped with the
+ * CipherInputStream.
*
- * @return A byte array containing the encrypted representation of
- * the provided data.
+ * @return The CiperInputStream instantiated as specified.
*
- * @throws GeneralSecurityException If a problem occurs while
- * attempting to encrypt the
- * data.
+ * @throws NoSuchAlgorithmException If the requested algorithm is
+ * not supported or is
+ * unavailable.
+ *
+ * @throws NoSuchPaddingException If the requested padding
+ * mechanism is not supported or is
+ * unavailable.
+ *
+ * @throws InvalidKeyException If the provided key is not
+ * appropriate for use with the
+ * requested cipher algorithm.
+ *
+ * @throws InvalidAlgorithmParameterException
+ * If an internal problem occurs as a result of the
+ * initialization vector used.
+ *
+ * @throws CryptoManagerException If there is a problem reading the
+ * key ID or initialization vector from the input stream.
*/
- public byte[] encrypt(String cipherAlgorithm, byte[] data)
- throws GeneralSecurityException
+ public CipherInputStream getCipherInputStream(
+ InputStream inputStream)
+ throws NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidKeyException,
+ InvalidAlgorithmParameterException,
+ CryptoManagerException
{
- Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+ byte[] keyID = new byte[16];
+ byte[] iv = new byte[16]; // FIXME: not all ivs are key length
+ try {
+ if (keyID.length != inputStream.read(keyID)
+ || iv.length != inputStream.read(iv)){
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw(
+ "Stream underflow when reading CryptoManager prologue."));
+ }
+ }
+ catch (IOException ioe) {
+ throw new CryptoManagerException(
+ // TODO: i18n
+ Message.raw(
+ "IO exception when reading CryptoManager prologue."),
+ ioe);
+ }
- // FIXME -- This needs to be more secure.
- IvParameterSpec iv = new IvParameterSpec(new byte[16]);
-
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
- return cipher.doFinal(data);
+ return new CipherInputStream(inputStream,
+ getDecryptionCipher(keyID, iv));
}
-
- /**
- * Decrypts the data in the provided byte array using the requested
- * cipher.
- *
- * @param cipherAlgorithm The algorithm to use to decrypt the
- * data.
- * @param data The data to be decrypted.
- *
- * @return A byte array containing the cleartext representation of
- * the provided data.
- *
- * @throws GeneralSecurityException If a problem occurs while
- * attempting to decrypt the
- * data.
- */
- public byte[] decrypt(String cipherAlgorithm, byte[] data)
- throws GeneralSecurityException
- {
- Cipher cipher = Cipher.getInstance(cipherAlgorithm);
-
- // FIXME -- This needs to be more secure.
- IvParameterSpec iv = new IvParameterSpec(new byte[16]);
-
- cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
- return cipher.doFinal(data);
- }
-
-
-
/**
* Attempts to compress the data in the provided source array into
* the given destination array. If the compressed data will fit
@@ -1010,5 +1239,41 @@
{
return sslCipherSuites;
}
+
+ /**
+ * This class defines an exception that is thrown in the case of
+ * problems with encryption key managagment, and is a wrapper for a
+ * variety of other cipher related exceptions.
+ */
+ public static class CryptoManagerException extends OpenDsException
+ {
+ /**
+ * The serial version identifier required to satisfy the compiler
+ * because this class extends <CODE>java.lang.Exception</CODE>,
+ * which implements the <CODE>java.io.Serializable</CODE>
+ * interface. This value was generated using the
+ * <CODE>serialver</CODE> command-line utility included with the
+ * Java SDK.
+ */
+ static final long serialVersionUID = -5890763923778143774L;
+
+ /**
+ * Creates an exception with the given message.
+ * @param message the message message.
+ */
+ public CryptoManagerException(Message message) {
+ super(message);
+ }
+
+ /**
+ * Creates an exception with the given message and underlying
+ * cause.
+ * @param message The message message.
+ * @param cause The underlying cause.
+ */
+ public CryptoManagerException(Message message, Exception cause) {
+ super(message, cause);
+ }
+ }
}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
new file mode 100644
index 0000000..26bff26
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
@@ -0,0 +1,118 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.opends.server.TestCaseUtils;
+
+import org.opends.server.core.DirectoryServer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ This class tests the CryptoManager.
+ */
+public class CryptoManagerTestCase extends TypesTestCase
+{
+ /**
+ Setup..
+ @throws Exception If an unexpected problem occurs.
+ */
+ @BeforeClass()
+ public void setUp()
+ throws Exception {
+ TestCaseUtils.startServer();
+ }
+
+ /**
+ Cleanup.
+ @throws Exception If an exceptional condition arises.
+ */
+ @AfterClass()
+ public void CleanUp() throws Exception {
+
+ }
+
+ /**
+ Tests a simple encryption-decryption cycle.
+
+ @throws Exception If an exceptional condition arises.
+ */
+ @Test
+ public void testEncryptDecryptSuccess() throws Exception {
+ final CryptoManager cm = DirectoryServer.getCryptoManager();
+ final String secretMessage = "1234";
+
+ final byte[] cipherText = cm.encrypt(secretMessage.getBytes());
+ assertEquals(-1, (new String(cipherText)).indexOf(secretMessage));
+
+ final byte[] plainText = cm.decrypt(cipherText);
+ assertEquals((new String(plainText)), secretMessage);
+ }
+
+ /**
+ Tests a simple cipher stream encryption-decryption cycle.
+
+ @throws Exception If an exceptional condition arises.
+ */
+ @Test
+ public void testCipherEncryptDecryptSuccess() throws Exception {
+ final CryptoManager cm = DirectoryServer.getCryptoManager();
+ final String secretMessage = "56789";
+
+ final File tempFile
+ = File.createTempFile(cm.getClass().getName(), null);
+ tempFile.deleteOnExit();
+
+ OutputStream os = new FileOutputStream(tempFile);
+ os = cm.getCipherOutputStream(os);
+ os.write(secretMessage.getBytes());
+ os.close();
+
+ InputStream is = new FileInputStream(tempFile);
+ is = cm.getCipherInputStream(is);
+ byte[] plainText = new byte[secretMessage.getBytes().length];
+ assertEquals(is.read(plainText), secretMessage.getBytes().length);
+ assertEquals(is.read(), -1);
+ is.close();
+ assertEquals(new String(plainText), secretMessage);
+ }
+
+ // TODO: other-than-preferred cipher algorithms, failure cases...
+}
--
Gitblit v1.10.0