mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

david_page
17.31.2007 a7a83f2fdcc1647611bf9cf09e75ea434b546b5d
Add support for MAC key entry type. Similar to Cipher key entry; however, caller must maintain key identifier (string), e.g., in backup directory, in order to verify signature. TODO: investigate prefixing MAC signed data with key identifier, and suffixing with signature, for both byte[] and stream. This enhancement will require wrapping the Mac API.
6 files modified
2050 ■■■■■ changed files
opends/src/server/org/opends/server/backends/SchemaBackend.java 22 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackupManager.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 22 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/CryptoManager.java 1665 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 7 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java 314 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -4204,18 +4204,18 @@
    Mac           mac             = null;
    MessageDigest digest          = null;
    String        digestAlgorithm = null;
    String        macAlgorithm    = null;
    String        macKeyID    = null;
    if (hash)
    {
      if (signHash)
      {
        macAlgorithm = cryptoManager.getPreferredMACAlgorithm();
        backupProperties.put(BACKUP_PROPERTY_MAC_ALGORITHM, macAlgorithm);
        try
        {
          mac = cryptoManager.getPreferredMACProvider();
          macKeyID = cryptoManager.getMacEngineKeyEntryID();
          backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
          mac = cryptoManager.getMacEngine(macKeyID);
        }
        catch (Exception e)
        {
@@ -4225,7 +4225,7 @@
          }
          Message message = ERR_SCHEMA_BACKUP_CANNOT_GET_MAC.get(
              macAlgorithm, stackTraceToSingleLineString(e));
              macKeyID, stackTraceToSingleLineString(e));
          throw new DirectoryException(
                         DirectoryServer.getServerErrorResultCode(), message,
                         e);
@@ -4653,9 +4653,9 @@
    Mac mac = null;
    if (signedHash != null)
    {
      String macAlgorithm =
           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_ALGORITHM);
      if (macAlgorithm == null)
      String macKeyID =
           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
      if (macKeyID == null)
      {
        Message message = ERR_SCHEMA_RESTORE_UNKNOWN_MAC.get(backupID);
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
@@ -4664,12 +4664,12 @@
      try
      {
        mac = DirectoryServer.getCryptoManager().getMACProvider(macAlgorithm);
        mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
      }
      catch (Exception e)
      {
        Message message = ERR_SCHEMA_RESTORE_CANNOT_GET_MAC.get(
            backupID, macAlgorithm);
            backupID, macKeyID);
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, e);
      }
opends/src/server/org/opends/server/backends/jeb/BackupManager.java
@@ -168,18 +168,18 @@
    Mac           mac             = null;
    MessageDigest digest          = null;
    String        digestAlgorithm = null;
    String        macAlgorithm    = null;
    String        macKeyID    = null;
    if (hash)
    {
      if (signHash)
      {
        macAlgorithm = cryptoManager.getPreferredMACAlgorithm();
        backupProperties.put(BACKUP_PROPERTY_MAC_ALGORITHM, macAlgorithm);
        try
        {
          mac = cryptoManager.getPreferredMACProvider();
          macKeyID = cryptoManager.getMacEngineKeyEntryID();
          backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
          mac = cryptoManager.getMacEngine(macKeyID);
        }
        catch (Exception e)
        {
@@ -189,7 +189,7 @@
          }
          Message message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
              macAlgorithm, stackTraceToSingleLineString(e));
              macKeyID, stackTraceToSingleLineString(e));
          throw new DirectoryException(
               DirectoryServer.getServerErrorResultCode(), message, e);
        }
@@ -924,15 +924,15 @@
    Mac           mac             = null;
    MessageDigest digest          = null;
    String        digestAlgorithm = null;
    String        macAlgorithm    = null;
    String        macKeyID        = null;
    if (signHash != null)
    {
      macAlgorithm = backupProperties.get(BACKUP_PROPERTY_MAC_ALGORITHM);
      macKeyID = backupProperties.get(BACKUP_PROPERTY_MAC_KEY_ID);
      try
      {
        mac = cryptoManager.getMACProvider(macAlgorithm);
        mac = cryptoManager.getMacEngine(macKeyID);
      }
      catch (Exception e)
      {
@@ -942,7 +942,7 @@
        }
        Message message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
            macAlgorithm, stackTraceToSingleLineString(e));
            macKeyID, stackTraceToSingleLineString(e));
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, e);
      }
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -2693,18 +2693,18 @@
    Mac           mac             = null;
    MessageDigest digest          = null;
    String        digestAlgorithm = null;
    String        macAlgorithm    = null;
    String        macKeyID    = null;
    if (hash)
    {
      if (signHash)
      {
        macAlgorithm = cryptoManager.getPreferredMACAlgorithm();
        backupProperties.put(BACKUP_PROPERTY_MAC_ALGORITHM, macAlgorithm);
        try
        {
          mac = cryptoManager.getPreferredMACProvider();
          macKeyID = cryptoManager.getMacEngineKeyEntryID();
          backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
          mac = cryptoManager.getMacEngine(macKeyID);
        }
        catch (Exception e)
        {
@@ -2714,7 +2714,7 @@
          }
          Message message = ERR_CONFIG_BACKUP_CANNOT_GET_MAC.get(
              macAlgorithm, stackTraceToSingleLineString(e));
              macKeyID, stackTraceToSingleLineString(e));
          throw new DirectoryException(
                         DirectoryServer.getServerErrorResultCode(), message,
                         e);
@@ -3183,9 +3183,9 @@
    Mac mac = null;
    if (signedHash != null)
    {
      String macAlgorithm =
           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_ALGORITHM);
      if (macAlgorithm == null)
      String macKeyID =
           backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
      if (macKeyID == null)
      {
        Message message = ERR_CONFIG_RESTORE_UNKNOWN_MAC.get(backupID);
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
@@ -3194,12 +3194,12 @@
      try
      {
        mac = DirectoryServer.getCryptoManager().getMACProvider(macAlgorithm);
        mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
      }
      catch (Exception e)
      {
        Message message = ERR_CONFIG_RESTORE_CANNOT_GET_MAC.get(
            backupID, macAlgorithm);
            backupID, macKeyID);
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, e);
      }
opends/src/server/org/opends/server/types/CryptoManager.java
@@ -93,21 +93,28 @@
  private static final Random pseudoRandom
          = new Random(secureRandom.nextLong());
  // The map from encryption key ID to SecretKeyEntry (cache).
  private final HashMap<ByteArray, SecretKeyEntry> secretKeyEntryMap
          = new HashMap<ByteArray, SecretKeyEntry>();
  // The preferred message digest algorithm for the Directory Server.
  private final String preferredDigestAlgorithm;
  // The map from encryption key ID to MacKeyEntry (cache).
  private final HashMap<KeyEntryID, MacKeyEntry> macKeyEntryCache
          = new HashMap<KeyEntryID, MacKeyEntry>();
  // The preferred MAC algorithm for the Directory Server.
  private final String preferredMACAlgorithm;
  // The preferred key length for the preferred MAC algorithm.
  private final int preferredMACAlgorithmKeyLengthBits;
  // The map from encryption key ID to CipherKeyEntry (cache).
  private final HashMap<KeyEntryID, CipherKeyEntry>
      cipherKeyEntryCache = new HashMap<KeyEntryID, CipherKeyEntry>();
  // The preferred cipher for the Directory Server.
  private final String preferredCipherTransformation;
  // The preferred key length for the preferred cipher.
  private final int preferredCipherTransformationKeyLength;
  // The preferred message digest algorithm for the Directory Server.
  private final String preferredDigestAlgorithm;
  // The preferred MAC algorithm for the Directory Server.
  private final String preferredMACAlgorithm;
  private final int preferredCipherTransformationKeyLengthBits;
  // The name of the local certificate to use for SSL.
  private final String sslCertNickname;
@@ -142,10 +149,11 @@
  {
    // TODO -- Get the defaults from the configuration rather than
    // hard-coding them.
    preferredCipherTransformation = "AES/CBC/PKCS5Padding";
    preferredCipherTransformationKeyLengthBits = 128;
    preferredDigestAlgorithm = "SHA-1";
    preferredMACAlgorithm    = "HmacSHA1";
    preferredCipherTransformation = "AES/CBC/PKCS5Padding";
    preferredCipherTransformationKeyLength = 128;
    preferredMACAlgorithmKeyLengthBits = 128;
    sslCertNickname = cfg.getSSLCertNickname();
    sslEncryption   = cfg.isSSLEncryption();
@@ -173,11 +181,9 @@
    try
    {
      // TODO: wrap like encryption
      byte[] keyValue = new byte[16];
      secureRandom.nextBytes(keyValue);
      Mac mac = Mac.getInstance(preferredMACAlgorithm);
      mac.init(new SecretKeySpec(keyValue, preferredMACAlgorithm));
      MacKeyEntry.generateKeyEntry(null,
              preferredMACAlgorithm,
              preferredMACAlgorithmKeyLengthBits);
    }
    catch (Exception e)
    {
@@ -194,9 +200,9 @@
    try
    {
      SecretKeyEntry.generateKeyEntry(null,
      CipherKeyEntry.generateKeyEntry(null,
              preferredCipherTransformation,
              preferredCipherTransformationKeyLength);
              preferredCipherTransformationKeyLengthBits);
    }
    catch (Exception e)
    {
@@ -214,57 +220,6 @@
  /**
   *  Converts a UUID string into a compact byte[16] representation.
   *
   *  @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.
   */
  private static byte[] uuidToBytes(String uuidString)
          throws CryptoManagerException
  {
    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 instance into a compact byte[16] representation.
   *
   *  @param  uuid A UUID instance.
   *
   *  @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;
  }
  /**
   * Retrieves the name of the preferred message digest algorithm.
   *
   * @return  The name of the preferred message digest algorithm
@@ -444,188 +399,134 @@
  /**
   * Retrieves the name of the preferred MAC algorithm.
   * For the current preferred MAC algorithm and key length, return
   * the identifier of the corresponding key entry. Note: the result
   * (key identifier) might change across invocations, due to either
   * of the perferred parameters changing, or because the original
   * key was marked compromised and a replacement key generated.
   *
   * @return  The name of the preferred MAC algorithm
   * @return A String representation of the identifier of a key entry
   * corresponding to the preferred MAC algorithm and key length.
   *
   * @throws CryptoManagerException In case one or more of the key
   * parameters is invalid, or there is a problem instantiating the
   * key entry in case it does not already exist.
   */
  public String getPreferredMACAlgorithm()
  public String getMacEngineKeyEntryID()
          throws CryptoManagerException
  {
    return preferredMACAlgorithm;
    return getMacEngineKeyEntryID(preferredMACAlgorithm,
            preferredMACAlgorithmKeyLengthBits);
  }
  /**
   * Retrieves a MAC provider using the preferred algorithm.
   * For the specified MAC algorithm and key length, return
   * the identifier of the corresponding key entry. Note: the result
   * (key identifier) might change across invocations, due to either
   * of the perferred parameters changing, or because the original
   * key was marked compromised and a replacement key generated.
   *
   * @return  A MAC provider using the preferred algorithm.
   * @param  macAlgorithm  The algorithm to use for the MAC engine.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   * @param  keyLengthBits  The key length in bits to use with the
   *         specified algorithm.
   *
   * @throws  InvalidKeyException  If the provided key is not
   *                               appropriate for use with the
   *                               requested MAC algorithm.
   * @return A String representation of the identifier of a key entry
   * corresponding to the specified MAC algorithm and key length.
   *
   * @throws CryptoManagerException In case one or more of the key
   * parameters is invalid, or there is a problem instantiating the
   * key entry in case it does not already exist.
   */
  public Mac getPreferredMACProvider()
         throws NoSuchAlgorithmException, InvalidKeyException
  {
    return getMACProvider(getPreferredMACAlgorithm());
  public String getMacEngineKeyEntryID(final String macAlgorithm,
                                       final int keyLengthBits)
         throws CryptoManagerException {
    Validator.ensureNotNull(macAlgorithm);
    MacKeyEntry keyEntry = MacKeyEntry.getKeyEntry(this, macAlgorithm,
                                                   keyLengthBits);
    if (null == keyEntry) {
      keyEntry = MacKeyEntry.generateKeyEntry(this, macAlgorithm,
                                              keyLengthBits);
    }
    return keyEntry.getKeyID().getStringValue();
  }
  /**
   * For the specified key entry identifier, instantiate a MAC engine.
   *
   * @param keyEntryID The identifier of the key entry containing the
   * desired MAC algorithm name and key length.
   *
   * @return The MAC engine instantiated with the parameters from the
   * referenced key entry, or null if no such entry exists.
   *
   * @throws CryptoManagerException  In case the key entry identifier
   * is invalid or there is a problem instatiating the MAC engine from
   * the parameters in the referenced key entry.
   */
  public Mac getMacEngine(String keyEntryID)
          throws CryptoManagerException
  {
    MacKeyEntry keyEntry;
    try {
      keyEntry = MacKeyEntry.getKeyEntry(this,
              new KeyEntryID(keyEntryID));
    }
    catch (IllegalArgumentException ex) {
      throw new CryptoManagerException(
              // TODO: i18n
              Message.raw("MAC key entry identifier \"%s\" is not" +
                      " a valid UUID.", keyEntryID), ex);
    }
    if (null == keyEntry) return null;
    return getMacEngine(keyEntry);
  }
  /**
   * Retrieves a MAC provider using the specified algorithm.
   * This method produces an initialized MAC engine based on the
   * supplied MacKeyEntry's state.
   *
   * @param  macAlgorithm  The algorithm to use for the MAC provider.
   * @param keyEntry The MacKeyEntry specifying the Mac properties.
   *
   * @return  A MAC provider using the specified algorithm.
   * @return  An initialized Mac object.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   *
   * @throws  InvalidKeyException  If the provided key is not
   *                               appropriate for use with the
   *                               requested MAC algorithm.
   * @throws CryptoManagerException  In case there was a error
   * instantiating the Mac object.
   */
  public Mac getMACProvider(String macAlgorithm)
         throws NoSuchAlgorithmException, InvalidKeyException
  private static Mac getMacEngine(MacKeyEntry keyEntry)
          throws CryptoManagerException
  {
    Mac mac = Mac.getInstance(macAlgorithm);
    byte[] keyValue = new byte[16];
    secureRandom.nextBytes(keyValue);
    mac.init(new SecretKeySpec(keyValue, macAlgorithm));
    Mac mac;
    try {
      mac = Mac.getInstance(keyEntry.getType());
    }
    catch (NoSuchAlgorithmException ex){
      throw new CryptoManagerException(
              // TODO: i18n
              Message.raw("Invalid MAC algorithm specified: + %s",
                      keyEntry.getType()), ex);
    }
    try {
      mac.init(keyEntry.getKeySpec());
    }
    catch (InvalidKeyException ex) {
      throw new CryptoManagerException(
              // TODO: i18n
              Message.raw("Invalid key specification supplied to" +
                      " Mac object initialization"), ex);
    }
    return mac;
  }
  /**
   * Retrieves a byte array containing a MAC based on the provided
   * data, using the preferred MAC algorithm.
   *
   * @param  data  The data for which to generate the MAC.
   *
   * @return  A byte array containing the generated MAC.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   */
  public byte[] mac(byte[] data)
         throws NoSuchAlgorithmException
  {
    return Mac.getInstance(preferredMACAlgorithm).doFinal(data);
  }
  /**
   * Retrieves a byte array containing a MAC based on the provided
   * data, using the requested MAC algorithm.
   *
   * @param  macAlgorithm  The algorithm to use for the MAC.
   * @param  data          The data for which to generate the MAC.
   *
   * @return  A byte array containing the generated MAC.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   */
  public byte[] mac(String macAlgorithm, byte[] data)
         throws NoSuchAlgorithmException
  {
    return Mac.getInstance(macAlgorithm).doFinal(data);
  }
  /**
   * Retrieves a byte array containing a MAC based on the data read
   * from the provided input stream, using the preferred MAC
   * algorithm.  Data will be read until the end of the stream is
   * reached.
   *
   * @param  inputStream  The input stream from which the data is to
   *                      be read.
   *
   * @return  A byte array containing the generated MAC.
   *
   * @throws  IOException  If a problem occurs while reading data from
   *                       the provided stream.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   */
  public byte[] mac(InputStream inputStream)
         throws IOException, NoSuchAlgorithmException
  {
    Mac mac = Mac.getInstance(preferredMACAlgorithm);
    byte[] buffer = new byte[8192];
    while (true)
    {
      int bytesRead = inputStream.read(buffer);
      if (bytesRead < 0)
      {
        break;
      }
      mac.update(buffer, 0, bytesRead);
    }
    return mac.doFinal();
  }
  /**
   * Retrieves a byte array containing a MAC based on the data read
   * from the provided input stream, using the preferred MAC
   * algorithm.  Data will be read until the end of the stream is
   * reached.
   *
   * @param  macAlgorithm  The algorithm to use for the MAC.
   * @param  inputStream   The input stream from which the data is to
   *                       be read.
   *
   * @return  A byte array containing the generated MAC.
   *
   * @throws  IOException  If a problem occurs while reading data from
   *                       the provided stream.
   *
   * @throws  NoSuchAlgorithmException  If the requested algorithm is
   *                                    not supported or is
   *                                    unavailable.
   */
  public byte[] mac(String macAlgorithm, InputStream inputStream)
         throws IOException, NoSuchAlgorithmException
  {
    Mac mac = Mac.getInstance(macAlgorithm);
    byte[] buffer = new byte[8192];
    while (true)
    {
      int bytesRead = inputStream.read(buffer);
      if (bytesRead < 0)
      {
        break;
      }
      mac.update(buffer, 0, bytesRead);
    }
    return mac.doFinal();
  }
  /**
   * Retrieves the name of the preferred cipher algorithm.
   *
@@ -638,376 +539,8 @@
  /**
   * This class corresponds to the encryption key entry in ADS. It is
   * used in the local cache of key entries that have been requested
   * by CryptoManager clients.
   */
  private static class SecretKeyEntry
  {
    /**
     * This method generates a key according to the key parameters,
     * and creates a key entry and registers it in the supplied map.
     *
     * @param map  The SecretKeyEntry Map in which the key is to be
     * stored. Pass null as the argument to this parameter in order to
     * validate a proposed cipher transformation and key length.
     *
     * @param transformation  The cipher transformation for which the
     * key is to be produced.
     *
     * @param keyLengthBits  The cipher key length in bits.
     *
     * @return The key entry corresponding to the parameters.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     */
    public static SecretKeyEntry generateKeyEntry(
            final Map<ByteArray, SecretKeyEntry> map,
            final String transformation,
            final int keyLengthBits)
    throws CryptoManagerException {
      SecretKeyEntry keyEntry;
      // check for existing uncompromised key.
      if (null != map) {
        keyEntry = getKeyEntry(map, transformation, keyLengthBits);
        if (null != keyEntry) {
          return keyEntry;
        }
      }
      // generate a new key
      if (0 != keyLengthBits % Byte.SIZE) {
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("keyLengthBits argument must be evenly"
                        + " divisible by %d.", Byte.SIZE));
      }
      // In case a transformation is supplied instead of an algorithm:
      // E.g., AES/CBC/PKCS5Padding -> AES
      final int separatorIndex = transformation.indexOf('/');
      final String keyAlgorithm = (0 < separatorIndex)
              ? transformation.substring(0, separatorIndex)
              : transformation;
      KeyGenerator keyGen;
      try {
        keyGen = KeyGenerator.getInstance(keyAlgorithm);
      }
      catch (NoSuchAlgorithmException ex) {
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("Unable to produce key generator from" +
                        " cipher transformation argument %s",
                        transformation), ex);
      }
      keyGen.init(keyLengthBits, secureRandom);
      final byte[] key = keyGen.generateKey().getEncoded();
      // generate the key entry
      final byte[] keyID = uuidToBytes(UUID.randomUUID());
      keyEntry = new SecretKeyEntry(keyID, transformation,
              keyAlgorithm, key, /* compute IV length */ -1, false);
      // Validate the entry. Pass the keyAlgorithm for the cipher
      // transformation.
      final Cipher cipher
              = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
      final byte[] iv = cipher.getIV();
      keyEntry.setIVLengthBits(
              (null == iv) ? 0 : iv.length * Byte.SIZE);
      if (null != map) {
        map.put(new ByteArray(keyID), keyEntry);
        // TODO: publish key to ADS. (mark key "blocked" in map
        // until registered?)
      }
      return keyEntry;
    }
    /**
     * Initializes a secret key entry from the supplied parameters,
     * validates it, and registers it in the supplied map. The
     * anticipated use of this method is to import a key entry from
     * ADS.
     *
     * @param map  The secret key map.
     *
     * @param keyID  The key identifier.
     *
     * @param transformation  The cipher transformation for which the
     * key entry was produced.
     *
     * @param keyAlgorithm  The cipher algorithm for which the key was
     * produced.
     *
     * @param key  The cipher key.
     *
     * @param ivLengthBits  The length of the initialization vector,
     * which will be zero in the case of any stream cipher algorithm,
     * or any block cipher algorithm for which the transformation mode
     * does not use an initialization vector.
     *
     * @param isCompromised  Mark the key as compromised, so that it
     * will not subsequently be used for encryption. The key must be
     * maintained in order to decrypt existing ciphertext.
     *
     * @return  The key entry, if one was successfully produced.
     *
     * @throws CryptoManagerException  In case of an error in the
     * parameters used to initialize or validate the key entry.
     */
    public static SecretKeyEntry setKeyEntry(
            final Map<ByteArray, SecretKeyEntry> map,
            final byte[] keyID,
            final String transformation,
            final String keyAlgorithm,
            final byte[] key,
            final int ivLengthBits,
            final boolean isCompromised)
            throws CryptoManagerException {
      Validator.ensureNotNull(keyID, transformation, keyAlgorithm,
              key);
      Validator.ensureTrue(16 == keyID.length);
      Validator.ensureTrue(0 <= ivLengthBits);
      // Check map for existing key entry with the supplied keyID.
      SecretKeyEntry keyEntry = getKeyEntry(map, keyID);
      if (null != keyEntry) {
        // TODO: compare keyEntry with supplied parameters to ensure
        // equal.
        return keyEntry;
      }
      // Instantiate new entry.
      keyEntry = new SecretKeyEntry(keyID, transformation,
              keyAlgorithm, key, ivLengthBits, isCompromised);
      // Validate the entry. Pass the keyAlgorithm for the cipher
      // transformation.
      byte[] iv = null;
      if (0 < ivLengthBits) {
        iv = new byte[ivLengthBits * Byte.SIZE];
        pseudoRandom.nextBytes(iv);
      }
      getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
      map.put(new ByteArray(keyID), keyEntry);
      return keyEntry;
    }
    /**
     * Retrieve a SecretKeyEntry from the SecretKeyEntry Map based on
     * cipher algorithm name and key length.
     *
     * @param map  The SecretKeyEntry Map in which the key is stored.
     *
     * @param transformation  The cipher algorithm for which the key
     * was produced.
     *
     * @param keyLengthBits  The cipher key length in bits.
     *
     * @return  The key entry corresponding to the parameters, or null
     * if no such entry exists.
     */
    public static SecretKeyEntry getKeyEntry(
            final Map<ByteArray, SecretKeyEntry> map,
            final String transformation,
            final int keyLengthBits) {
      Validator.ensureNotNull(map, transformation);
      Validator.ensureTrue(0 < keyLengthBits);
      SecretKeyEntry keyEntry = null;
      // search for an existing key that satisfies the request
      for (Map.Entry<ByteArray, SecretKeyEntry> i: map.entrySet()) {
        SecretKeyEntry entry = i.getValue();
        if (! entry.fIsCompromised
                && entry.getTransformation().equals(transformation)
                && entry.fKeyLengthBits == keyLengthBits) {
          assert Arrays.equals(i.getKey().array(), entry.fKeyID);
          keyEntry = entry;
          break;
        }
      }
      // TODO: if (null == keyEntry) Does ADS monitoring thread keep
      // map updated with keys produced at other sites? Otherwise,
      // search ADS for suitable key.
      // TODO: if (null == keyEntry) consider generating key here.
      if (null != keyEntry) {
        Validator.ensureTrue(0 <= keyEntry.getIVLength(),
                "SecretKeyEntry initialization is not complete.");
      }
      return keyEntry;
    }
    /**
     * Given a key identifier, return the associated secret key entry
     * from the supplied map. This method would typically be used by
     * a decryption routine.
     *
     * @param map  The local cache of key entries.
     *
     * @param keyID  The key identifier.
     *
     * @return  The key entry associated with the key identifier.
     */
    public static SecretKeyEntry getKeyEntry(
            Map<ByteArray, SecretKeyEntry> map, byte[] keyID) {
      return map.get(new ByteArray(keyID));
      /* TODO: Does ADS monitorying thread keep map updated with keys
         produced at other sites? If not, fetch from ADS and update
         map (assuming a legitimate key ID, the key should exist in
         ADS because this routine is called for decryption). */
    }
    /**
     * Construct an instance of SecretKeyEntry using the specified
     * parameters. This constructor would typically be used for key
     * entries imported from ADS, for which the full set of paramters
     * is known, and for a new key entry, for which the initialization
     * vector length might not yet be known, but which must be set
     * before use.
     *
     * @param keyID  The unique identifier of this cipher
     * transformation/key pair.
     *
     * @param transformation  The secret-key cipher transformation for
     * which the key entry is to be produced.
     *
     * @param keyAlgorithm  The secret key cipher algorithm for which
     * the key was produced.
     *
     * @param key  The cipher key.
     *
     * @param ivLengthBits  The length in bits of a mandatory
     * initialization vector or 0 if none is required. Set this
     * parameter to -1 when generating a new encryption key and this
     * method will attempt to compute the proper value by first using
     * the cipher block size and then, if the cipher block size is
     * non-zero, using 0 (i.e., no initialization vector).
     *
     * @param isCompromised If the key
     *
     * @throws  CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     */
    private SecretKeyEntry( final byte[] keyID,
                            final String transformation,
                            final String keyAlgorithm,
                            final byte[] key,
                            final int ivLengthBits,
                            final boolean isCompromised)
            throws CryptoManagerException {
      Validator.ensureNotNull(keyID, transformation, key);
      Validator.ensureTrue(16 == keyID.length); // FIXME: const for id
      // copy arguments
      this.fKeyID = new byte[keyID.length];
      System.arraycopy(keyID, 0, this.fKeyID, 0, keyID.length);
      this.fTransformation = new String(transformation);
      this.fKeySpec = new SecretKeySpec(key, keyAlgorithm);
      this.fKeyLengthBits = key.length * Byte.SIZE;
      this.fIVLengthBits = ivLengthBits;
      this.fIsCompromised = isCompromised;
    }
    /**
     * The cipher transformation for which the key entry was created.
     *
     * @return The cipher transformation.
     */
    public String getTransformation() {
      return fTransformation;
    }
    /**
     * The unique identifier of this cipher transformation/key pair.
     *
     * @return The unique identifier of this cipher transformation/key
     * pair.
     */
    public byte[] getKeyID() {
      return fKeyID;
    }
    /**
     * The secret key spec containing the secret key.
     *
     * @return The secret key spec containing the secret key.
     */
    public SecretKeySpec getKeySpec() {
      return fKeySpec;
    }
    /**
     * The initialization vector length in bits: 0 is a stream cipher
     * or a block cipher that does not use an IV (e.g., ECB); or a
     * positive integer, typically the block size of the cipher.
     * <p>
     * This method returns -1 if the object initialization has not
     * been completed.
     *
     * @return The initialization vector length.
     */
    public int getIVLength() {
      return fIVLengthBits;
    }
    /**
     * Set the algorithm/key pair's required initialization vector
     * length in bits. Typically, this will be the cipher's block
     * size, or 0 for a stream cipher or a block cipher mode that does
     * not use an initialization vector (e.g., ECB).
     *
     * @param ivLengthBits The initiazliation vector length in bits.
     */
    private void setIVLengthBits(int ivLengthBits) {
      Validator.ensureTrue(-1 == fIVLengthBits && 0 <= ivLengthBits);
      fIVLengthBits = ivLengthBits;
    }
    /**
     * Mark a key entry as compromised. The entry will no longer be
     * eligible for use as an encryption key.
     */
    public void setIsCompromised() {
      // TODO: called from ADS monitoring thread. Lock entry?
      fIsCompromised = true;
    }
    // state
    private final byte[] fKeyID;
    private final String fTransformation;
    private final SecretKeySpec fKeySpec;
    private final int fKeyLengthBits;
    private int fIVLengthBits;
    private boolean fIsCompromised = false;
  }
  /**
   * This method produces and initialized Cipher based on this
   * SecretKeyEntry's state and the method parameters.
   * This method produces an initialized Cipher based on the supplied
   * CipherKeyEntry's state.
   *
   * @param keyEntry  The secret key entry containing the cipher
   * transformation and secret key for which to instantiate
@@ -1026,7 +559,7 @@
   * include NoSuchAlgorithmException, NoSuchPaddingException,
   * InvalidKeyException, and InvalidAlgorithmParameterException.
   */
  private static Cipher getCipher(final SecretKeyEntry keyEntry,
  private static Cipher getCipher(final CipherKeyEntry keyEntry,
                                  final int mode,
                                  final byte[] initializationVector)
          throws CryptoManagerException {
@@ -1034,30 +567,30 @@
            || Cipher.DECRYPT_MODE == mode);
    Validator.ensureTrue(Cipher.ENCRYPT_MODE != mode
            || null == initializationVector);
    Validator.ensureTrue(-1 != keyEntry.getIVLength()
    Validator.ensureTrue(-1 != keyEntry.getIVLengthBits()
            || Cipher.ENCRYPT_MODE == mode);
    Validator.ensureTrue(null == initializationVector
            || initializationVector.length * Byte.SIZE
                                          == keyEntry.getIVLength());
                                       == keyEntry.getIVLengthBits());
    Cipher cipher;
    try {
      cipher = Cipher.getInstance(keyEntry.getTransformation());
      cipher = Cipher.getInstance(keyEntry.getType());
    }
    catch (GeneralSecurityException ex) {
      // NoSuchAlgorithmException, NoSuchPaddingException
      throw new CryptoManagerException(
              // TODO: i18n
              Message.raw("Invalid cipher transformation specified"
                      + " %s.", keyEntry.getTransformation()), ex);
              Message.raw("Invalid Cipher transformation specified:"
                      + " %s.", keyEntry.getType()), ex);
    }
    try {
      if (0 < keyEntry.getIVLength()) {
      if (0 < keyEntry.getIVLengthBits()) {
          byte[] iv;
          if (Cipher.ENCRYPT_MODE == mode
                  && null == initializationVector) {
            iv = new byte[keyEntry.getIVLength() / Byte.SIZE];
            iv = new byte[keyEntry.getIVLengthBits() / Byte.SIZE];
            pseudoRandom.nextBytes(iv);
          }
          else {
@@ -1100,7 +633,7 @@
         throws GeneralSecurityException, CryptoManagerException
  {
    return encrypt(preferredCipherTransformation,
                   preferredCipherTransformationKeyLength, data);
            preferredCipherTransformationKeyLengthBits, data);
  }
@@ -1133,17 +666,17 @@
  {
    Validator.ensureNotNull(cipherTransformation, data);
    SecretKeyEntry keyEntry = SecretKeyEntry.getKeyEntry(
            secretKeyEntryMap, cipherTransformation, keyLengthBits);
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
            this, cipherTransformation, keyLengthBits);
    if (null == keyEntry) {
      keyEntry = SecretKeyEntry.generateKeyEntry(secretKeyEntryMap,
      keyEntry = CipherKeyEntry.generateKeyEntry(this,
              cipherTransformation, keyLengthBits);
    }
    final Cipher cipher
            = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID();
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
    final byte[] iv = cipher.getIV();
    final int prologueLength
            = keyID.length + ((null == iv) ? 0 : iv.length);
@@ -1175,7 +708,7 @@
          OutputStream outputStream) throws CryptoManagerException
  {
    return getCipherOutputStream(preferredCipherTransformation,
                preferredCipherTransformationKeyLength, outputStream);
            preferredCipherTransformationKeyLengthBits, outputStream);
  }
@@ -1205,16 +738,16 @@
  {
    Validator.ensureNotNull(cipherTransformation, outputStream);
    SecretKeyEntry keyEntry = SecretKeyEntry.getKeyEntry(
            secretKeyEntryMap, cipherTransformation, keyLengthBits);
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
            this, cipherTransformation, keyLengthBits);
    if (null == keyEntry) {
      keyEntry = SecretKeyEntry.generateKeyEntry(secretKeyEntryMap,
      keyEntry = CipherKeyEntry.generateKeyEntry(this,
              cipherTransformation, keyLengthBits);
    }
    final Cipher cipher
            = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID();
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
    try {
      outputStream.write(keyID);
      if (null != cipher.getIV()) {
@@ -1253,9 +786,12 @@
         throws GeneralSecurityException,
                CryptoManagerException
  {
    final byte[] keyID = new byte[16]; //FIXME: key length constant
    KeyEntryID keyID;
    try {
      System.arraycopy(data, 0, keyID, 0, keyID.length);
      final byte[] keyIDBytes
              = new byte[KeyEntryID.getByteValueLength()];
      System.arraycopy(data, 0, keyIDBytes, 0, keyIDBytes.length);
      keyID = new KeyEntryID(keyIDBytes);
    }
    catch (Exception ex) {
      throw new CryptoManagerException(
@@ -1265,20 +801,20 @@
                              " data prologue."), ex);
    }
    SecretKeyEntry keyEntry
            = SecretKeyEntry.getKeyEntry(secretKeyEntryMap, keyID);
    CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(this, keyID);
    if (null == keyEntry) {
      throw new CryptoManagerException(
              // TODO: i18N
              Message.raw("Invalid key identifier in data" +
                      " prologue."));
              Message.raw("Invalid or unknown key identifier in" +
                      " data prologue."));
    }
    byte[] iv = null;
    if (0 < keyEntry.getIVLength()) {
      iv = new byte[keyEntry.getIVLength()/Byte.SIZE];
    if (0 < keyEntry.getIVLengthBits()) {
      iv = new byte[keyEntry.getIVLengthBits()/Byte.SIZE];
      try {
        System.arraycopy(data, keyID.length, iv, 0, iv.length);
        System.arraycopy(data, KeyEntryID.getByteValueLength(), iv, 0,
                iv.length);
      }
      catch (Exception ex) {
        throw new CryptoManagerException(
@@ -1288,10 +824,10 @@
      }
    }
    final Cipher cipher
            = getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
    final int prologueLength
            = keyID.length + ((null == iv) ? 0 : iv.length);
    final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE,
            iv);
    final int prologueLength = KeyEntryID.getByteValueLength()
                                     + ((null == iv) ? 0 : iv.length);
    return cipher.doFinal(data, prologueLength,
                          data.length - prologueLength);
  }
@@ -1314,10 +850,10 @@
  public CipherInputStream getCipherInputStream(
          InputStream inputStream) throws CryptoManagerException
  {
    SecretKeyEntry keyEntry;
    CipherKeyEntry keyEntry;
    byte[] iv = null;
    try {
      final byte[] keyID = new byte[16]; //FIXME: key length constant
      final byte[] keyID = new byte[KeyEntryID.getByteValueLength()];
      if (keyID.length != inputStream.read(keyID)){
        throw new CryptoManagerException(
                // TODO: i18n
@@ -1325,15 +861,16 @@
                        " identifier from data prologue."));
      }
      keyEntry = SecretKeyEntry.getKeyEntry(secretKeyEntryMap, keyID);
      keyEntry = CipherKeyEntry.getKeyEntry(this,
              new KeyEntryID(keyID));
      if (null == keyEntry) {
        throw new CryptoManagerException(
                // TODO: i18N
             Message.raw("Invalid key identifier in data prologue."));
      }
      if (0 < keyEntry.getIVLength()) {
        iv = new byte[keyEntry.getIVLength() / Byte.SIZE];
      if (0 < keyEntry.getIVLengthBits()) {
        iv = new byte[keyEntry.getIVLengthBits() / Byte.SIZE];
        if (iv.length != inputStream.read(iv)) {
          throw new CryptoManagerException(
                  // TODO: i18n
@@ -1571,6 +1108,868 @@
  }
  /**
   * This class implements a utility interface to the unique
   * identifier corresponding to a cryptographic key. For each key
   * stored in an entry in ADS, the key identifier is the naming
   * attribute of the entry. The external binary representation of the
   * key entry identifier is compact, because it is typically stored
   * as a prefix of encrypted data.
   */
  private static class KeyEntryID
  {
    /**
     *  Constructs a KeyEntryID using a new unique identifier.
     */
    public KeyEntryID() {
      fValue = UUID.randomUUID();
    }
    /**
     * Construct a {@code KeyEntryID} from its {@code byte[]}
     * representation.
     *
     * @param keyEntryID The {@code byte[]} representation of a
     * {@code KeyEntryID}.
     */
    public KeyEntryID(final byte[] keyEntryID) {
      Validator.ensureTrue(getByteValueLength() == keyEntryID.length);
      long hiBytes = 0;
      long loBytes = 0;
      for (int i = 0; i < 8; ++i) {
        hiBytes = (hiBytes << 8) | (keyEntryID[i] & 0xff);
        loBytes = (loBytes << 8) | (keyEntryID[8 + i] & 0xff);
      }
      fValue = new UUID(hiBytes, loBytes);
    }
    /**
     * Constructs a {@code KeyEntryID} from its {@code String}
     * representation.
     *
     * @param  keyEntryID The {@code String} reprentation of a
     * {@code KeyEntryID}.
     *
     * @throws  CryptoManagerException  If the argument does
     * not conform to the {@code KeyEntryID} string syntax.
     */
    public KeyEntryID(final String keyEntryID)
            throws CryptoManagerException {
      try {
        fValue = UUID.fromString(keyEntryID);
      }
      catch (Exception ex) {
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("Key entry identifier \"%s\" has" +
                        " invalid syntax.", keyEntryID), ex);
      }
    }
    /**
     * Copy constructor.
     *
     * @param keyEntryID  The {@code KeyEntryID} to copy.
     */
    public KeyEntryID(final KeyEntryID keyEntryID) {
      fValue = new UUID(keyEntryID.fValue.getMostSignificantBits(),
                        keyEntryID.fValue.getLeastSignificantBits());
    }
    /**
     * Returns the compact {@code byte[]} representation of this
     * {@code KeyEntryID}.
     * @return The compact {@code byte[]} representation of this
     * {@code KeyEntryID
     */
    public byte[] getByteValue(){
      final byte[] uuidBytes = new byte[16];
      long hiBytes = fValue.getMostSignificantBits();
      long loBytes = fValue.getLeastSignificantBits();
      for (int i = 7; i >= 0; --i) {
        uuidBytes[i] = (byte)hiBytes;
        hiBytes >>>= 8;
        uuidBytes[8 + i] = (byte)loBytes;
        loBytes >>>= 8;
      }
      return uuidBytes;
    }
    /**
     * Returns the {@code String} representation of this
     * {@code KeyEntryID}.
     * @return The {@code String} representation of this
     * {@code KeyEntryID}.
     */
    public String getStringValue() {
      return fValue.toString();
    }
    /**
     * Returns the length of the compact {@code byte[]} representation
     * of a {@code KeyEntryID}.
     *
     * @return The length of the compact {@code byte[]} representation
     * of a {@code KeyEntryID}.
     */
    public static int getByteValueLength() {
      return 16;
    }
    /**
     * Compares this object to the specified object. The result is
     * true if and only if the argument is not null, is of type
     * {@code KeyEntryID}, and has the same value (i.e., the
     * {@code String} and {@code byte[]} representations are
     * identical).
     *
     * @param obj The object to which to compare this instance.
     *
     * @return {@code true} if the objects are the same, {@code false}
     * otherwise.
     */
    public boolean equals(final Object obj){
      return obj instanceof KeyEntryID
              && fValue.equals(((KeyEntryID) obj).fValue);
    }
    /**
     * Returns a hash code for this {@code KeyEntryID}.
     *
     * @return a hash code value for this {@code KeyEntryID}.
     */
    public int hashCode() {
      return fValue.hashCode();
    }
    // state
    private final UUID fValue;
  }
  /**
   * This class corresponds to the secret key portion if a secret
   * key entry in ADS.
   */
  private static class SecretKeyEntry
  {
    /**
     * Construct an instance of {@code SecretKeyEntry} using the
     * specified parameters. This constructor is used for key
     * generation.
     *
     * @param algorithm  The name of the secret key algorithm for
     * which the key entry is to be produced.
     *
     * @param keyLengthBits  The length of the requested key in bits.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating the key generator.
     */
    public SecretKeyEntry(String algorithm, int keyLengthBits)
    throws CryptoManagerException {
      KeyGenerator keyGen;
      try {
        keyGen = KeyGenerator.getInstance(algorithm);
      }
      catch (NoSuchAlgorithmException ex) {
        throw new CryptoManagerException(
                // TODO: i18n
                Message.raw("Unable to produce key generator using" +
                        " key algorithm argument %s",
                        algorithm), ex);
      }
      keyGen.init(keyLengthBits, secureRandom);
      final byte[] key = keyGen.generateKey().getEncoded();
      this.fKeyID = new KeyEntryID();
      this.fKeySpec = new SecretKeySpec(key, algorithm);
      this.fKeyLengthBits = key.length * Byte.SIZE;
      this.fIsCompromised = false;
    }
    /**
     * Construct an instance of {@code SecretKeyEntry} using the
     * specified parameters. This constructor would typically be used
     * for key entries imported from ADS, for which the full set of
     * paramters is known.
     *
     * @param keyID  The unique identifier of this algorithm/key pair.
     *
     * @param algorithm  The name of the secret key algorithm for
     * which the key entry is to be produced.
     *
     * @param key  The secret key.
     *
     * @param isCompromised {@code false} if the key may be used
     * for operations on new data, or {@code true} if the key is being
     * retained only for use in validation.
     */
    public SecretKeyEntry(final KeyEntryID keyID,
                          final String algorithm,
                          final byte[] key,
                          final boolean isCompromised) {
      // copy arguments
      this.fKeyID = new KeyEntryID(keyID);
      this.fKeySpec = new SecretKeySpec(key, algorithm);
      this.fKeyLengthBits = key.length * Byte.SIZE;
      this.fIsCompromised = isCompromised;
    }
    /**
     * The unique identifier of this algorithm/key pair.
     *
     * @return The unique identifier of this algorithm/key pair.
     */
    public KeyEntryID getKeyID() {
      return fKeyID;
    }
    /**
     * The secret key spec containing the secret key.
     *
     * @return The secret key spec containing the secret key.
     */
    public SecretKeySpec getKeySpec() {
      return fKeySpec;
    }
    /**
     * Mark a key entry as compromised. The entry will no longer be
     * eligible for use as an encryption key.
     */
    public void setIsCompromised() {
      // TODO: called from ADS monitoring thread. Lock entry?
      fIsCompromised = true;
    }
    /**
     * Returns the length of the secret key in bits.
     * @return the length of the secret key in bits.
     */
    public int getKeyLengthBits() {
      return fKeyLengthBits;
    }
    /**
     * Returns the status of the key.
     * @return  {@code false} if the key may be used for operations on
     * new data, or {@code true} if the key is being retained only for
     * use in validation.
     */
    public boolean isCompromised() {
      return fIsCompromised;
    }
    // state
    private final KeyEntryID fKeyID;
    private final SecretKeySpec fKeySpec;
    private final int fKeyLengthBits;
    private boolean fIsCompromised = false;
  }
  /**
   * This class corresponds to the cipher key entry in ADS. It is
   * used in the local cache of key entries that have been requested
   * by CryptoManager clients.
   */
  private static class CipherKeyEntry extends SecretKeyEntry
  {
    /**
     * This method generates a key according to the key parameters,
     * and creates a key entry and registers it in the supplied map.
     *
     * @param  cryptoManager The CryptoManager instance for which the
     * key is to be generated. Pass {@code null} as the argument to
     * this parameter in order to validate a proposed cipher
     * transformation and key length without publishing the key.
     *
     * @param transformation  The cipher transformation for which the
     * key is to be produced.
     *
     * @param keyLengthBits  The cipher key length in bits.
     *
     * @return The key entry corresponding to the parameters.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     *
     * @see CipherKeyEntry#getKeyEntry(CryptoManager, String, int)
     */
    public static CipherKeyEntry generateKeyEntry(
            final CryptoManager cryptoManager,
            final String transformation,
            final int keyLengthBits)
    throws CryptoManagerException {
      final Map<KeyEntryID, CipherKeyEntry> map
              = (null == cryptoManager)
              ? null : cryptoManager.cipherKeyEntryCache;
      CipherKeyEntry keyEntry = new CipherKeyEntry(transformation,
              keyLengthBits);
      // Validate the key entry.
      final Cipher cipher
              = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
      final byte[] iv = cipher.getIV();
      keyEntry.setIVLengthBits(
              (null == iv) ? 0 : iv.length * Byte.SIZE);
      if (null != map) {
        map.put(keyEntry.getKeyID(), keyEntry);
        // TODO: publish key in ADS. (mark key "blocked" in map
        // until registered? OTOH, Key should be in local map prior to
        // publication, since data could arrive from a remote OpenDS
        // instance encrypted with the key any time after publication.
        // OTOH, the key should be published in ADS before any use,
        // since that is the persistent shared secret key repository.)
      }
      return keyEntry;
    }
    /**
     * Initializes a secret key entry from the supplied parameters,
     * validates it, and registers it in the supplied map. The
     * anticipated use of this method is to import a key entry from
     * ADS.
     *
     * @param cryptoManager  The CryptoManager instance.
     *
     * @param keyIDBytes  The key identifier.
     *
     * @param transformation  The cipher transformation for which the
     * key entry was produced.
     *
     * @param keyAlgorithm  The cipher algorithm for which the key was
     * produced.
     *
     * @param key  The cipher key.
     *
     * @param ivLengthBits  The length of the initialization vector,
     * which will be zero in the case of any stream cipher algorithm,
     * any block cipher algorithm for which the transformation mode
     * does not use an initialization vector, and any HMAC algorithm.
     *
     * @param isCompromised  Mark the key as compromised, so that it
     * will not subsequently be used for encryption. The key entry
     * must be maintained in order to decrypt existing ciphertext.
     *
     * @return  The key entry, if one was successfully produced.
     *
     * @throws CryptoManagerException  In case of an error in the
     * parameters used to initialize or validate the key entry.
     */
    public static CipherKeyEntry importCipherKeyEntry(
            final CryptoManager cryptoManager,
            final byte[] keyIDBytes,
            final String transformation,
            final String keyAlgorithm,
            final byte[] key,
            final int ivLengthBits,
            final boolean isCompromised)
            throws CryptoManagerException {
      Validator.ensureNotNull(keyIDBytes, transformation,
              keyAlgorithm, key);
      Validator.ensureTrue(0 <= ivLengthBits);
      final KeyEntryID keyID = new KeyEntryID(keyIDBytes);
      // Check map for existing key entry with the supplied keyID.
      CipherKeyEntry keyEntry = getKeyEntry(cryptoManager, keyID);
      if (null != keyEntry) {
        // TODO: compare keyEntry with supplied parameters to ensure
        // equal.
        return keyEntry;
      }
      // Instantiate new entry.
      keyEntry = new CipherKeyEntry(keyID, transformation,
              keyAlgorithm, key, ivLengthBits, isCompromised);
      // Validate new entry.
      byte[] iv = null;
      if (0 < ivLengthBits) {
        iv = new byte[ivLengthBits * Byte.SIZE];
        pseudoRandom.nextBytes(iv);
      }
      getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
      // Cache new entry.
      cryptoManager.cipherKeyEntryCache.put(keyEntry.getKeyID(),
              keyEntry);
      return keyEntry;
    }
    /**
     * Retrieve a CipherKeyEntry from the CipherKeyEntry Map based on
     * the algorithm name and key length.
     *
     * @param cryptoManager  The CryptoManager instance with which the
     * key entry is associated.
     *
     * @param transformation  The cipher transformation for which the
     * key was produced.
     *
     * @param keyLengthBits  The cipher key length in bits.
     *
     * @return  The key entry corresponding to the parameters, or null
     * if no such entry exists.
     */
    public static CipherKeyEntry getKeyEntry(
            final CryptoManager cryptoManager,
            final String transformation,
            final int keyLengthBits) {
      Validator.ensureNotNull(cryptoManager, transformation);
      Validator.ensureTrue(0 < keyLengthBits);
      CipherKeyEntry keyEntry = null;
      // search for an existing key that satisfies the request
      for (Map.Entry<KeyEntryID, CipherKeyEntry> i
              : cryptoManager.cipherKeyEntryCache.entrySet()) {
        CipherKeyEntry entry = i.getValue();
        if (! entry.isCompromised()
                && entry.getType().equals(transformation)
                && entry.getKeyLengthBits() == keyLengthBits) {
          keyEntry = entry;
          break;
        }
      }
      // TODO: if (null == keyEntry) Does ADS monitoring thread keep
      // map updated with keys produced at other sites? Otherwise,
      // search ADS for suitable key.
      // TODO: if (null == keyEntry) consider generating key here.
      return keyEntry;
    }
    /**
     * Given a key identifier, return the associated cipher key entry
     * from the supplied map. This method would typically be used by
     * a decryption routine.
     *
     * @param cryptoManager  The CryptoManager instance with which the
     * key entry is associated.
     *
     * @param keyID  The key identifier.
     *
     * @return  The key entry associated with the key identifier.
     */
    public static CipherKeyEntry getKeyEntry(
            CryptoManager cryptoManager,
            final KeyEntryID keyID) {
      return cryptoManager.cipherKeyEntryCache.get(keyID);
      /* TODO: Does ADS monitorying thread keep map updated with keys
         produced at other sites? If not, fetch from ADS and update
         map (assuming a legitimate key ID, the key should exist in
         ADS because this routine is called for decryption). */
    }
    /**
     In case a transformation is supplied instead of an algorithm:
     E.g., AES/CBC/PKCS5Padding -> AES.
     @param transformation The cipher transformation from which to
     extract the cipher algorithm.
     @return  The algorithm prefix of the Cipher transformation. If
     the transformation is supplied as an algorithm-only (no mode or
     padding), return the transformation as-is.
     */
    private static String keyAlgorithmFromTransformation(
            String transformation){
    final int separatorIndex = transformation.indexOf('/');
      return (0 < separatorIndex)
              ? transformation.substring(0, separatorIndex)
              : transformation;
    }
    /**
     * Construct an instance of {@code CipherKeyEntry} using the
     * specified parameters. This constructor would typically be used
     * for key generation.
     *
     * @param transformation  The name of the Cipher transformation
     * for which the key entry is to be produced.
     *
     * @param keyLengthBits  The length of the requested key in bits.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating the key generator.
     */
    private CipherKeyEntry(final String transformation,
                           final int keyLengthBits)
            throws CryptoManagerException {
      // Generate a new key.
      super(keyAlgorithmFromTransformation(transformation),
              keyLengthBits);
      // copy arguments.
      this.fType = new String(transformation);
      this.fIVLengthBits = -1; /* compute IV length */
    }
    /**
     * Construct an instance of CipherKeyEntry using the specified
     * parameters. This constructor would typically be used for key
     * entries imported from ADS, for which the full set of paramters
     * is known, and for a newly generated key entry, for which the
     * initialization vector length might not yet be known, but which
     * must be set prior to using the key.
     *
     * @param keyID  The unique identifier of this cipher
     * transformation/key pair.
     *
     * @param transformation  The name of the secret-key cipher
     * transformation for which the key entry is to be produced.
     *
     * @param keyAlgorithm  The name of the secret key cipher
     * algorithm for which the key was produced.
     *
     * @param key  The cipher key.
     *
     * @param ivLengthBits  The length in bits of a mandatory
     * initialization vector or 0 if none is required. Set this
     * parameter to -1 when generating a new encryption key and this
     * method will attempt to compute the proper value by first using
     * the cipher block size and then, if the cipher block size is
     * non-zero, using 0 (i.e., no initialization vector).
     *
     * @param isCompromised {@code false} if the key may be used
     * for encryption, or {@code true} if the key is being retained
     * only for use in decrypting existing data.
     *
     * @throws  CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     */
    private CipherKeyEntry(final KeyEntryID keyID,
                           final String transformation,
                           final String keyAlgorithm,
                           final byte[] key,
                           final int ivLengthBits,
                           final boolean isCompromised)
            throws CryptoManagerException {
      super(keyID, keyAlgorithm, key, isCompromised);
      // copy arguments
      this.fType = new String(transformation);
      this.fIVLengthBits = ivLengthBits;
    }
    /**
     * The cipher transformation for which the key entry was created.
     *
     * @return The cipher transformation.
     */
    public String getType() {
      return fType;
    }
    /**
     * Set the algorithm/key pair's required initialization vector
     * length in bits. Typically, this will be the cipher's block
     * size, or 0 for a stream cipher or a block cipher mode that does
     * not use an initialization vector (e.g., ECB).
     *
     * @param ivLengthBits The initiazliation vector length in bits.
     */
    private void setIVLengthBits(int ivLengthBits) {
      Validator.ensureTrue(-1 == fIVLengthBits && 0 <= ivLengthBits);
      fIVLengthBits = ivLengthBits;
    }
    /**
     * The initialization vector length in bits: 0 is a stream cipher
     * or a block cipher that does not use an IV (e.g., ECB); or a
     * positive integer, typically the block size of the cipher.
     * <p>
     * This method returns -1 if the object initialization has not
     * been completed.
     *
     * @return The initialization vector length.
     */
    public int getIVLengthBits() {
      return fIVLengthBits;
    }
    // state
    private final String fType;
    private int fIVLengthBits = -1;
  }
  /**
   * This class corresponds to the MAC key entry in ADS. It is
   * used in the local cache of key entries that have been requested
   * by CryptoManager clients.
   */
  private static class MacKeyEntry extends SecretKeyEntry
  {
    /**
     * This method generates a key according to the key parameters,
     * creates a key entry, and optionally registers it in the
     * supplied CryptoManager context.
     *
     * @param  cryptoManager The CryptoManager instance for which the
     * key is to be generated. Pass {@code null} as the argument to
     * this parameter in order to validate a proposed MAC algorithm
     * and key length, but not publish the key entry.
     *
     * @param algorithm  The MAC algorithm for which the
     * key is to be produced. This argument is required.
     *
     * @param keyLengthBits  The MAC key length in bits. The argument
     * must be a positive integer evenly divisible by the value
     * Byte.SIZE.
     *
     * @return The key entry corresponding to the parameters.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating a Mac object in order to validate the supplied
     * parameters when creating a new entry.
     *
     * @see MacKeyEntry#getKeyEntry(CryptoManager, String, int)
     */
    public static MacKeyEntry generateKeyEntry(
            final CryptoManager cryptoManager,
            final String algorithm,
            final int keyLengthBits)
    throws CryptoManagerException {
      Validator.ensureNotNull(algorithm);
      final Map<KeyEntryID, MacKeyEntry> map = (null == cryptoManager)
              ? null : cryptoManager.macKeyEntryCache;
      final MacKeyEntry keyEntry = new MacKeyEntry(algorithm,
              keyLengthBits);
      // Validate the key entry.
      getMacEngine(keyEntry);
      if (null != map) {
        map.put(keyEntry.getKeyID(), keyEntry);
        // TODO: publish key in ADS. (mark key "blocked" in map
        // until registered? OTOH, Key should be in local map prior to
        // publication, since data could arrive from a remote OpenDS
        // instance encrypted with the key any time after publication.
        // OTOH, the key should be published in ADS before any use,
        // since that is the persistent shared secret key repository.)
      }
      return keyEntry;
    }
    /**
     * Initializes a secret key entry from the supplied parameters,
     * validates it, and registers it in the supplied map. The
     * anticipated use of this method is to import a key entry from
     * ADS.
     *
     * @param cryptoManager  The CryptoManager instance.
     *
     * @param keyIDString  The key identifier.
     *
     * @param algorithm  The algorithm for which the key entry was
     * produced.
     *
     * @param key  The cipher key.
     *
     * @param isCompromised  Mark the key as compromised, so that it
     * will not subsequently be used for new data. The key entry
     * must be maintained in order to verify existing signatures.
     *
     * @return  The key entry, if one was successfully produced.
     *
     * @throws CryptoManagerException  In case of an error in the
     * parameters used to initialize or validate the key entry.
     */
    public static MacKeyEntry importMacKeyEntry(
            final CryptoManager cryptoManager,
            final String keyIDString,
            final String algorithm,
            final byte[] key,
            final boolean isCompromised)
            throws CryptoManagerException {
      Validator.ensureNotNull(keyIDString, algorithm, key);
      final KeyEntryID keyID = new KeyEntryID(keyIDString);
      // Check map for existing key entry with the supplied keyID.
      MacKeyEntry keyEntry = getKeyEntry(cryptoManager, keyID);
      if (null != keyEntry) {
        // TODO: compare keyEntry with supplied parameters to ensure
        // equal.
        return keyEntry;
      }
      // Instantiate new entry.
      keyEntry = new MacKeyEntry(keyID, algorithm, key,
              isCompromised);
      // Validate new entry.
      getMacEngine(keyEntry);
      // Cache new entry.
      cryptoManager.macKeyEntryCache.put(keyEntry.getKeyID(),
              keyEntry);
      return keyEntry;
    }
    /**
     * Retrieve a MacKeyEntry from the MacKeyEntry Map based on
     * the algorithm name and key length.
     *
     * @param cryptoManager  The CryptoManager instance with which the
     * key entry is associated.
     *
     * @param algorithm  The MAC algorithm for which the key was
     * produced.
     *
     * @param keyLengthBits  The MAC key length in bits.
     *
     * @return  The key entry corresponding to the parameters, or null
     * if no such entry exists.
     */
    public static MacKeyEntry getKeyEntry(
            final CryptoManager cryptoManager,
            final String algorithm,
            final int keyLengthBits) {
      Validator.ensureNotNull(cryptoManager, algorithm);
      Validator.ensureTrue(0 < keyLengthBits);
      MacKeyEntry keyEntry = null;
      // search for an existing key that satisfies the request
      for (Map.Entry<KeyEntryID, MacKeyEntry> i
              : cryptoManager.macKeyEntryCache.entrySet()) {
        MacKeyEntry entry = i.getValue();
        if (! entry.isCompromised()
                && entry.getType().equals(algorithm)
                && entry.getKeyLengthBits() == keyLengthBits) {
          keyEntry = entry;
          break;
        }
      }
      // TODO: if (null == keyEntry) Does ADS monitoring thread keep
      // map updated with keys produced at other sites? Otherwise,
      // search ADS for suitable key.
      // TODO: if (null == keyEntry) consider generating key here.
      return keyEntry;
    }
    /**
     * Given a key identifier, return the associated cipher key entry
     * from the supplied map. This method would typically be used by
     * a decryption routine.
     *
     * @param cryptoManager  The CryptoManager instance with which the
     * key entry is associated.
     *
     * @param keyID  The key identifier.
     *
     * @return  The key entry associated with the key identifier.
     */
    public static MacKeyEntry getKeyEntry(
            final CryptoManager cryptoManager,
            final KeyEntryID keyID) {
      return cryptoManager.macKeyEntryCache.get(keyID);
      /* TODO: Does ADS monitorying thread keep map updated with keys
         produced at other sites? If not, fetch from ADS and update
         map (assuming a legitimate key ID, the key should exist in
         ADS because this routine is called for decryption). */
    }
    /**
     * Construct an instance of {@code MacKeyEntry} using the
     * specified parameters. This constructor would typically be used
     * for key generation.
     *
     * @param algorithm  The name of the MAC algorithm for which the
     * key entry is to be produced.
     *
     * @param keyLengthBits  The length of the requested key in bits.
     *
     * @throws CryptoManagerException If there is a problem
     * instantiating the key generator.
     */
    private MacKeyEntry(final String algorithm,
                        final int keyLengthBits)
            throws CryptoManagerException {
      // Generate a new key.
      super(algorithm, keyLengthBits);
      // copy arguments
      this.fType = new String(algorithm);
    }
    /**
     * Construct an instance of MacKeyEntry using the specified
     * parameters. This constructor would typically be used for key
     * entries imported from ADS, for which the full set of paramters
     * is known.
     *
     * @param keyID  The unique identifier of this MAC algorithm/key
     * pair.
     *
     * @param algorithm  The name of the MAC algorithm for which the
     * key entry is to be produced.
     *
     * @param key  The MAC key.
     *
     * @param isCompromised {@code false} if the key may be used
     * for signing, or {@code true} if the key is being retained only
     * for use in signature verification.
     */
    private MacKeyEntry(final KeyEntryID keyID,
                        final String algorithm,
                        final byte[] key,
                        final boolean isCompromised) {
      super(keyID, algorithm, key, isCompromised);
      // copy arguments
      this.fType = new String(algorithm);
    }
    /**
     * The algorithm for which the key entry was created.
     *
     * @return The algorithm.
     */
    public String getType() {
      return fType;
    }
    // state
    private final String fType;
  }
  /**
   * 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.
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2235,10 +2235,11 @@
  /**
   * The name of the backup property that holds the name of the MAC algorithm
   * used to generate the signed hash of a backup.
   * The name of the backup property that holds the identifer of the key entry
   * that contains the MAC algorithm and shared secret key used to generate
   * the signed hash of a backup.
   */
  public static final String BACKUP_PROPERTY_MAC_ALGORITHM = "mac_algorithm";
  public static final String BACKUP_PROPERTY_MAC_KEY_ID = "mac_key_id";
opends/tests/unit-tests-testng/src/server/org/opends/server/types/CryptoManagerTestCase.java
@@ -40,10 +40,16 @@
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.LinkedList;
import java.util.Arrays;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.annotations.DataProvider;
import javax.crypto.Mac;
/**
 This class tests the CryptoManager.
@@ -69,30 +75,127 @@
  }
  @Test
  public void testMacSuccess()
          throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String text = "1234";
    final String macKeyID = cm.getMacEngineKeyEntryID();
    final Mac signingMac = cm.getMacEngine(macKeyID);
    final byte[] signedHash = signingMac.doFinal(text.getBytes());
    final Mac validatingMac = cm.getMacEngine(macKeyID);
    final byte[] calculatedSignature = validatingMac.doFinal(text.getBytes());
    assertTrue(Arrays.equals(calculatedSignature, signedHash));
  }
  /**
   Tests a simple encryption-decryption cycle.
   Cipher parameters
   */
  private class CipherParameters {
    private final String fAlgorithm;
    private final String fMode;
    private final String fPadding;
    private final int fKeyLength;
    private final int fIVLength;
    public CipherParameters(final String algorithm, final String mode,
                            final String padding, final int keyLength,
                            final int ivLength) {
      fAlgorithm = algorithm;
      fMode = mode;
      fPadding = padding;
      fKeyLength = keyLength;
      fIVLength = ivLength;
    }
    public String getTransformation() {
      if (null == fAlgorithm) return null; // default
      return (null == fMode)
              ? new String(fAlgorithm)
              : (new StringBuilder(fAlgorithm)).append("/").append(fMode)
                .append("/").append(fPadding).toString();
    }
    public int getKeyLength() {
      return fKeyLength;
    }
    public int getIVLength() {
      return fIVLength;
    }
  }
  /**
   Cipher parameter data set.
   @return The set of Cipher parameters with which to test.
   */
  @DataProvider(name = "cipherParametersData")
  public Object[][] cipherParametersData() {
    List<CipherParameters> paramList = new LinkedList<CipherParameters>();
    // default (preferred) AES/CBC/PKCS5Padding 128bit key.
    paramList.add(new CipherParameters(null, null, null, 128, 128));
    // custom
    paramList.add(new CipherParameters("Blowfish", "CFB", "NoPadding", 128, 64));
    paramList.add(new CipherParameters("RC4", null, null, 104, 0));
    paramList.add(new CipherParameters("DES", "CFB", "NoPadding", 56, 56));
    paramList.add(new CipherParameters("DESede", "ECB", "PKCS5Padding", 168, 56));
    Object[][] cipherParameters = new Object[paramList.size()][1];
    for (int i=0; i < paramList.size(); i++)
    {
      cipherParameters[i] = new Object[] { paramList.get(i) };
    }
    return cipherParameters;
  }
  /**
   Tests a simple encryption-decryption cycle using the supplied cipher
   parameters.
   @param cp  Cipher parameters to use for this test iteration.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testEncryptDecryptSuccess() throws Exception {
  @Test(dataProvider = "cipherParametersData")
  public void testEncryptDecryptSuccess(CipherParameters cp)
          throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt(secretMessage.getBytes());
    final byte[] cipherText = (null == cp.getTransformation())
            ? cm.encrypt(secretMessage.getBytes()) // default
            : cm.encrypt(cp.getTransformation(), cp.getKeyLength(),
                         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.
   Tests a simple cipher stream encryption-decryption cycle using the supplied
   cipher parameters.
   @param cp  Cipher parameters to use for this test iteration.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testCipherEncryptDecryptSuccess() throws Exception {
  @Test(dataProvider = "cipherParametersData")
  public void testStreamEncryptDecryptSuccess(CipherParameters cp)
          throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "56789";
@@ -101,200 +204,9 @@
    tempFile.deleteOnExit();
    OutputStream os = new FileOutputStream(tempFile);
    os = cm.getCipherOutputStream(os);
    os.write(secretMessage.getBytes());
    os.close();
    // TODO: check tempfile for plaintext.
    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...
  /**
   Tests a simple encryption-decryption cycle.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testEncryptDecryptSuccessX() throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt("Blowfish/CFB/NoPadding", 128,
            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 testCipherEncryptDecryptSuccessX() 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("Blowfish/CFB/NoPadding", 128, os);
    os.write(secretMessage.getBytes());
    os.close();
    // TODO: check tempfile for plaintext.
    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);
  }
  /**
   Tests a simple encryption-decryption cycle.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testEncryptDecryptSuccessY() throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt("RC4", 104,
            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 testCipherEncryptDecryptSuccessY() 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("RC4", 104, os);
    os.write(secretMessage.getBytes());
    os.close();
    // TODO: check tempfile for plaintext.
    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);
  }
  /**
   Tests a simple encryption-decryption cycle.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testEncryptDecryptSuccessZ() throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt("DES/CFB/NoPadding", 56,
            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 testCipherEncryptDecryptSuccessZ() 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("DES/CFB/NoPadding", 56, os);
    os.write(secretMessage.getBytes());
    os.close();
    // TODO: check tempfile for plaintext.
    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);
  }
  /**
   Tests a simple encryption-decryption cycle.
   @throws Exception If an exceptional condition arises.
   */
  @Test
  public void testEncryptDecryptSuccessZZ() throws Exception {
    final CryptoManager cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "1234";
    final byte[] cipherText = cm.encrypt("DESede/ECB/PKCS5Padding", 168,
            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 testCipherEncryptDecryptSuccessZZ() 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("DESede/ECB/PKCS5Padding", 168, os);
    os = (null == cp.getTransformation())
            ? cm.getCipherOutputStream(os) // default
            : cm.getCipherOutputStream(cp.getTransformation(), cp.getKeyLength(), os);
    os.write(secretMessage.getBytes());
    os.close();