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

Jean-Noël Rouvignac
20.41.2016 7d8b8ce1db386a7f930aa8680ade1dee876b8994
CryptoManagerImpl: cleanly separated cipher and MAC functions
1 files modified
468 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java 468 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
@@ -113,8 +113,6 @@
import org.opends.server.util.ServerConstants;
import org.opends.server.util.StaticUtils;
import net.jcip.annotations.GuardedBy;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.protocols.internal.InternalClientConnection.*;
@@ -163,13 +161,10 @@
  /** The DN of the local truststore backend. */
  private static DN localTruststoreDN;
  /** The DN of the ADS instance keys container. */
  private static DN instanceKeysDN;
  /** The DN of the ADS secret keys container. */
  private static DN secretKeysDN;
  /** The DN of the ADS servers container. */
  private static DN serversDN;
@@ -190,44 +185,8 @@
   */
  private static final int CIPHERTEXT_PROLOGUE_VERSION = 1 ;
  private final Lock cipherKeyEntryLock = new ReentrantLock();
  /**
   * The map from encryption key ID to CipherKeyEntry (cache). The cache is
   * accessed by methods that request by ID, publish, and import keys.
   * It contains all keys, even compromised, to be able to access old data.
   */
  @GuardedBy("cipherKeyEntryLock")
  private final Map<KeyEntryID, CipherKeyEntry> cipherKeyEntryCache = new ConcurrentHashMap<>();
  /**
   * Keys imported or generated to use for encryption, mapped by transformation/key length.
   * Only one cipher key per transformation/key length (used as a key for the map, for example
   * "AES/CBC/PKCS5Padding/128") is recorded, the last in temporal order to be imported or
   * generated.
   * Cipher keys belonging to this map also belong in the cache Map, they are used as keys for
   * encrypting new data.
   *
   */
  @GuardedBy("cipherKeyEntryLock")
  private final Map<String, CipherKeyEntry> mostRecentCipherKeys = new ConcurrentHashMap<>();
  private final Lock macKeyEntryLock = new ReentrantLock();
  /**
   * The map from encryption key ID to MacKeyEntry (cache). The cache is
   * accessed by methods that request by ID, publish, and import keys.
   * It contains all keys, even compromised, to be able to access old data.
   */
  @GuardedBy("macKeyEntryLock")
  private final Map<KeyEntryID, MacKeyEntry> macKeyEntryCache = new ConcurrentHashMap<>();
  /**
   * Keys imported or generated for MAC operations, mapped by algorithm/key length.
   * Only one MAC key per algorithm/key length (used as a key for the map, for example
   * "HmacSHA1/128") is recorded, the last in temporal order to be imported or
   * generated.
   * MAC keys belonging to this map also belong in the cache Map, they are
   * used for computing new MAC digests.
   */
  @GuardedBy("macKeyEntryLock")
  private final Map<String, MacKeyEntry> mostRecentMacKeys = new ConcurrentHashMap<>();
  private final CipherKeyManager cipherCryptoManager = new CipherKeyManager();
  private final MacKeyManager macCryptoManager = new MacKeyManager();
  /** The preferred key wrapping transformation. */
  private String preferredKeyWrappingTransformation;
@@ -241,13 +200,11 @@
  /** The preferred cipher for the Directory Server. */
  private String preferredCipherTransformation;
  /** The preferred key length for the preferred cipher. */
  private int preferredCipherTransformationKeyLengthBits;
  /** The preferred MAC algorithm for the Directory Server. */
  private String preferredMACAlgorithm;
  /** The preferred key length for the preferred MAC algorithm. */
  private int preferredMACAlgorithmKeyLengthBits;
@@ -257,13 +214,10 @@
  /** The names of the local certificates to use for SSL. */
  private final SortedSet<String> sslCertNicknames;
  /** Whether replication sessions use SSL encryption. */
  private final boolean sslEncryption;
  /** The set of SSL protocols enabled or null for the default set. */
  private final SortedSet<String> sslProtocols;
  /** The set of SSL cipher suites enabled or null for the default set. */
  private final SortedSet<String> sslCipherSuites;
@@ -338,8 +292,7 @@
    boolean isAcceptable = true;
    // Requested digest validation.
    String requestedDigestAlgorithm =
         cfg.getDigestAlgorithm();
    String requestedDigestAlgorithm = cfg.getDigestAlgorithm();
    if (! requestedDigestAlgorithm.equals(this.preferredDigestAlgorithm))
    {
      try{
@@ -355,10 +308,8 @@
    }
    // Requested encryption cipher validation.
    String requestedCipherTransformation =
         cfg.getCipherTransformation();
    Integer requestedCipherTransformationKeyLengthBits =
         cfg.getCipherKeyLength();
    String requestedCipherTransformation = cfg.getCipherTransformation();
    Integer requestedCipherTransformationKeyLengthBits = cfg.getCipherKeyLength();
    if (! requestedCipherTransformation.equals(
            this.preferredCipherTransformation) ||
        requestedCipherTransformationKeyLengthBits !=
@@ -371,7 +322,7 @@
      }
      else {
        try {
          CipherKeyEntry.generateKeyEntry(null,
          cipherCryptoManager.generateKeyEntry(
                  requestedCipherTransformation,
                  requestedCipherTransformationKeyLengthBits);
        }
@@ -387,15 +338,13 @@
    // Requested MAC algorithm validation.
    String requestedMACAlgorithm = cfg.getMacAlgorithm();
    Integer requestedMACAlgorithmKeyLengthBits =
         cfg.getMacKeyLength();
    Integer requestedMACAlgorithmKeyLengthBits = cfg.getMacKeyLength();
    if (!requestedMACAlgorithm.equals(this.preferredMACAlgorithm) ||
         requestedMACAlgorithmKeyLengthBits !=
              this.preferredMACAlgorithmKeyLengthBits)
    {
      try {
        MacKeyEntry.generateKeyEntry(
             null,
        macCryptoManager.generateKeyEntry(
             requestedMACAlgorithm,
             requestedMACAlgorithmKeyLengthBits);
      }
@@ -409,8 +358,7 @@
    }
    // Requested secret key wrapping cipher and validation. Validation
    // depends on MAC cipher for secret key.
    String requestedKeyWrappingTransformation
            = cfg.getKeyWrappingTransformation();
    String requestedKeyWrappingTransformation = cfg.getKeyWrappingTransformation();
    if (! requestedKeyWrappingTransformation.equals(
            this.preferredKeyWrappingTransformation)) {
      if (3 != requestedKeyWrappingTransformation.split("/", 0).length) {
@@ -439,7 +387,7 @@
                "lplX1Iq+BrQJAmteiPtwhdZD+EIghe51CaseImjlLlY2ZK8w==";
          final byte[] certificate = Base64.decode(certificateBase64);
          final String keyID = getInstanceKeyID(certificate);
          final SecretKey macKey = MacKeyEntry.generateKeyEntry(null,
          final SecretKey macKey = macCryptoManager.generateKeyEntry(
                  requestedMACAlgorithm,
                  requestedMACAlgorithmKeyLengthBits).getSecretKey();
          encodeSymmetricKeyAttribute(requestedKeyWrappingTransformation,
@@ -1084,7 +1032,7 @@
      // Find the symmetric key value that was wrapped using our instance key.
      SecretKey secretKey = decodeSymmetricKeyAttribute(symmetricKeys);
      if (null != secretKey) {
        CipherKeyEntry.importCipherKeyEntry(this, keyID, transformation,
        cipherCryptoManager.importCipherKeyEntry(keyID, transformation,
                secretKey, keyLengthBits, ivLengthBits, isCompromised);
        return;
      }
@@ -1097,7 +1045,7 @@
                ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE.get(entry.getName()));
      }
      secretKey = decodeSymmetricKeyAttribute(symmetricKey);
      CipherKeyEntry.importCipherKeyEntry(this, keyID, transformation,
      cipherCryptoManager.importCipherKeyEntry(keyID, transformation,
              secretKey, keyLengthBits, ivLengthBits, isCompromised);
      writeValueToEntry(entry, symmetricKey);
@@ -1169,7 +1117,7 @@
      SecretKey secretKey = decodeSymmetricKeyAttribute(symmetricKeys);
      if (secretKey != null)
      {
        MacKeyEntry.importMacKeyEntry(this, keyID, algorithm, secretKey, keyLengthBits, isCompromised);
        macCryptoManager.importMacKeyEntry(keyID, algorithm, secretKey, keyLengthBits, isCompromised);
        return;
      }
@@ -1181,7 +1129,7 @@
             ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE.get(entry.getName()));
      }
      secretKey = decodeSymmetricKeyAttribute(symmetricKey);
      MacKeyEntry.importMacKeyEntry(this, keyID, algorithm, secretKey, keyLengthBits, isCompromised);
      macCryptoManager.importMacKeyEntry(keyID, algorithm, secretKey, keyLengthBits, isCompromised);
      writeValueToEntry(entry, symmetricKey);
    }
@@ -1503,22 +1451,30 @@
    attrs.put(type, Attributes.createAsList(type, value));
  }
  /**
   * 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
  /** Encapsulates cipher-related functions of the {@link CryptoManager}. */
  private final class CipherKeyManager
  {
    /** Ensures atomic updates to both {@link #cipherKeyEntryCache} and {@link #mostRecentCipherKeys}. */
    private final Lock cipherKeyEntryLock = new ReentrantLock();
    /**
     * The map from encryption key ID to CipherKeyEntry (cache). The cache is accessed by methods
     * that request by ID, publish, and import keys. It contains all keys, even compromised, to be
     * able to access old data.
     */
    private final Map<KeyEntryID, CipherKeyEntry> cipherKeyEntryCache = new ConcurrentHashMap<>();
    /**
     * Keys imported or generated to use for encryption, mapped by transformation/key length. Only
     * one cipher key per transformation/key length (used as a key for the map, for example
     * "AES/CBC/PKCS5Padding/128") is recorded, the last in temporal order to be imported or
     * generated. Cipher keys belonging to this map also belong in the cache Map, they are used as
     * keys for encrypting new data.
     */
    private final Map<String, CipherKeyEntry> mostRecentCipherKeys = new ConcurrentHashMap<>();
    /**
     * 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. This argument is required.
     *
@@ -1530,19 +1486,26 @@
     * @throws CryptoManagerException If there is a problem
     * instantiating a Cipher object in order to validate the supplied
     * parameters when creating a new entry.
     *
     * @see MacKeyEntry#getMacKeyEntryOrNull(CryptoManagerImpl, String, int)
     */
    public static CipherKeyEntry generateKeyEntry(
            final CryptoManagerImpl cryptoManager,
            final String transformation,
            final int keyLengthBits)
    private CipherKeyEntry generateAndPublishKeyEntry(final String transformation, final int keyLengthBits)
    throws CryptoManagerException {
      final Map<KeyEntryID, CipherKeyEntry> cache =
          cryptoManager != null ? cryptoManager.cipherKeyEntryCache : null;
      CipherKeyEntry keyEntry = generateKeyEntry(transformation, keyLengthBits);
      CipherKeyEntry keyEntry = new CipherKeyEntry(transformation,
              keyLengthBits);
      /* The key is published to ADS before making it available in the local
         cache with the intention to ensure the key is persisted before use.
         This ordering allows the possibility that data encrypted at another
         instance could arrive at this instance before the key is available in
         the local cache to decode the data. */
      publishKeyEntry(keyEntry);
      cipherKeyEntryCache.put(keyEntry.getKeyID(), keyEntry);
      return keyEntry;
    }
    private CipherKeyEntry generateKeyEntry(final String transformation, final int keyLengthBits)
        throws CryptoManagerException
    {
      CipherKeyEntry keyEntry = new CipherKeyEntry(transformation, keyLengthBits);
      // Validate the key entry. Record the initialization vector length, if any
      final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
@@ -1550,32 +1513,17 @@
      final byte[] iv = cipher.getIV();
      keyEntry.setIVLengthBits(null == iv ? 0 : iv.length * Byte.SIZE);
      if (null != cache) {
        /* The key is published to ADS before making it available in the local
           cache with the intention to ensure the key is persisted before use.
           This ordering allows the possibility that data encrypted at another
           instance could arrive at this instance before the key is available in
           the local cache to decode the data. */
        publishKeyEntry(cryptoManager, keyEntry);
        cache.put(keyEntry.getKeyID(), keyEntry);
      }
      return keyEntry;
    }
    /**
     * Publish a new cipher key by adding an entry into ADS.
     * @param  cryptoManager The CryptoManager instance for which the
     *                       key was generated.
     * @param  keyEntry      The cipher key to be published.
     * @throws CryptoManagerException
     *                       If the key entry could not be added to
     *                       ADS.
     *                       If the key entry could not be added to ADS.
     */
    private static void publishKeyEntry(CryptoManagerImpl cryptoManager,
                                        CipherKeyEntry keyEntry)
         throws CryptoManagerException
    private void publishKeyEntry(CipherKeyEntry keyEntry) throws CryptoManagerException
    {
      // Construct the key entry DN.
      ByteString distinguishedValue =
@@ -1596,7 +1544,7 @@
          String.valueOf(keyEntry.getIVLengthBits()));
      putSingleValueAttribute(userAttrs, attrKeyLength,
          String.valueOf(keyEntry.getKeyLengthBits()));
      userAttrs.put(attrSymmetricKey, buildSymetricKeyAttributes(cryptoManager, keyEntry.getSecretKey()));
      userAttrs.put(attrSymmetricKey, buildSymetricKeyAttributes(keyEntry.getSecretKey()));
      // Create the entry.
      LinkedHashMap<AttributeType, List<Attribute>> opAttrs = new LinkedHashMap<>(0);
@@ -1615,7 +1563,6 @@
     * 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 transformation The cipher transformation for which the key entry was produced.
     * @param secretKey  The cipher key.
@@ -1631,8 +1578,7 @@
     * @throws CryptoManagerException  In case of an error in the
     * parameters used to initialize or validate the key entry.
     */
    public static CipherKeyEntry importCipherKeyEntry(
            final CryptoManagerImpl cryptoManager,
    private CipherKeyEntry importCipherKeyEntry(
            final String keyIDString,
            final String transformation,
            final SecretKey secretKey,
@@ -1646,7 +1592,7 @@
      final KeyEntryID keyID = new KeyEntryID(keyIDString);
      // Check map for existing key entry with the supplied keyID.
      CipherKeyEntry keyEntry = getCipherKeyEntryOrNull(cryptoManager, keyID);
      CipherKeyEntry keyEntry = getCipherKeyEntryOrNull(keyID);
      if (null != keyEntry) {
        // Paranoiac check to ensure exact type match.
        if (!keyEntry.getType().equals(transformation)
@@ -1675,21 +1621,43 @@
      getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
      // Cache new entry
      cryptoManager.cipherKeyEntryLock.lock();
      cipherKeyEntryLock.lock();
      try
      {
        cryptoManager.cipherKeyEntryCache.put(keyEntry.getKeyID(), keyEntry);
        cryptoManager.mostRecentCipherKeys.put(cryptoManager.getKeyFullSpec(transformation, secretKeyLengthBits),
            keyEntry);
        cipherKeyEntryCache.put(keyEntry.getKeyID(), keyEntry);
        mostRecentCipherKeys.put(getKeyFullSpec(transformation, secretKeyLengthBits), keyEntry);
      }
      finally
      {
        cryptoManager.cipherKeyEntryLock.unlock();
        cipherKeyEntryLock.unlock();
      }
      return keyEntry;
    }
    private CipherKeyEntry getCipherKeyEntry(String cipherTransformation, int keyLengthBits)
        throws CryptoManagerException
    {
      final String keyFullSpec = getKeyFullSpec(cipherTransformation, keyLengthBits);
      CipherKeyEntry keyEntry = getCipherKeyEntryOrNull(keyFullSpec);
      if (keyEntry == null) {
        cipherKeyEntryLock.lock();
        try
        {
          keyEntry = getCipherKeyEntryOrNull(keyFullSpec);
          if (keyEntry == null)
          {
            keyEntry = generateAndPublishKeyEntry(cipherTransformation, keyLengthBits);
            mostRecentCipherKeys.put(keyFullSpec, keyEntry);
          }
        }
        finally
        {
          cipherKeyEntryLock.unlock();
        }
      }
      return keyEntry;
    }
    /**
     * Retrieve a CipherKeyEntry from the CipherKeyEntry Map based on
@@ -1699,28 +1667,16 @@
     * specifications is not found. Instead, the ADS monitoring thread
     * is responsible for asynchronous updates to the key map.
     *
     * @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.
     *
     * @param keyFullSpec The key full spec for which the key was produced.
     * @return  The key entry corresponding to the parameters, or
     * {@code null} if no such entry exists or has been compromised
     */
    public static CipherKeyEntry getCipherKeyEntryOrNull(
        final CryptoManagerImpl cryptoManager,
        final String transformation,
        final int keyLengthBits) {
      Reject.ifNull(cryptoManager, transformation);
      Reject.ifFalse(0 < keyLengthBits);
      CipherKeyEntry key = cryptoManager.mostRecentCipherKeys.get(cryptoManager.getKeyFullSpec(transformation,
          keyLengthBits));
    private CipherKeyEntry getCipherKeyEntryOrNull(String keyFullSpec)
    {
      CipherKeyEntry key = mostRecentCipherKeys.get(keyFullSpec);
      return key != null && !key.isCompromised() ? key : null;
    }
    /**
     * Given a key identifier, return the associated cipher key entry
     * from the supplied map. This method would typically be used by
@@ -1737,20 +1693,23 @@
     * the set of instances and querying them. Instead, the caller
     * must retry the operation requesting the decryption.
     *
     * @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, or
     * {@code null} if no such entry exists.
     *
     * @see CryptoManagerImpl.MacKeyEntry
     *  #getMacKeyEntryOrNull(CryptoManagerImpl, String, int)
     */
    public static CipherKeyEntry getCipherKeyEntryOrNull(CryptoManagerImpl cryptoManager, final KeyEntryID keyID) {
      return cryptoManager.cipherKeyEntryCache.get(keyID);
    private CipherKeyEntry getCipherKeyEntryOrNull(final KeyEntryID keyID) {
      return cipherKeyEntryCache.get(keyID);
    }
  }
  /**
   * 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
  {
    /**
     In case a transformation is supplied instead of an algorithm:
     E.g., AES/CBC/PKCS5Padding -> AES.
@@ -1972,24 +1931,30 @@
    return cipher;
  }
  /**
   * 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
  /** Encapsulates MAC-related functions of the {@link CryptoManager}. */
  private final class MacKeyManager
  {
    /** Ensures atomic updates to both {@link #macKeyEntryCache} and {@link #mostRecentMacKeys}. */
    private final Lock macKeyEntryLock = new ReentrantLock();
    /**
     * The map from encryption key ID to MacKeyEntry (cache). The cache is accessed by methods that
     * request by ID, publish, and import keys. It contains all keys, even compromised, to be able
     * to access old data.
     */
    private final Map<KeyEntryID, MacKeyEntry> macKeyEntryCache = new ConcurrentHashMap<>();
    /**
     * Keys imported or generated for MAC operations, mapped by algorithm/key length. Only one MAC
     * key per algorithm/key length (used as a key for the map, for example "HmacSHA1/128") is
     * recorded, the last in temporal order to be imported or generated. MAC keys belonging to this
     * map also belong in the cache Map, they are used for computing new MAC digests.
     */
    private final Map<String, MacKeyEntry> mostRecentMacKeys = new ConcurrentHashMap<>();
    /**
     * 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.
     *
@@ -2001,50 +1966,43 @@
     * @throws CryptoManagerException If there is a problem
     * instantiating a Mac object in order to validate the supplied
     * parameters when creating a new entry.
     *
     * @see CipherKeyEntry#getCipherKeyEntryOrNull(CryptoManagerImpl, String, int)
     */
    public static MacKeyEntry generateKeyEntry(
            final CryptoManagerImpl cryptoManager,
            final String algorithm,
            final int keyLengthBits)
    private MacKeyEntry generateAndPublishKeyEntry(final String algorithm, final int keyLengthBits)
    throws CryptoManagerException {
      Reject.ifNull(algorithm);
      final Map<KeyEntryID, MacKeyEntry> cache =
          cryptoManager != null ? cryptoManager.macKeyEntryCache : null;
      final MacKeyEntry keyEntry = generateKeyEntry(algorithm, keyLengthBits);
      /* The key is published to ADS before making it available in the local
         cache with the intention to ensure the key is persisted before use.
         This ordering allows the possibility that data encrypted at another
         instance could arrive at this instance before the key is available in
         the local cache to decode the data. */
      publishKeyEntry(keyEntry);
      macKeyEntryCache.put(keyEntry.getKeyID(), keyEntry);
      return keyEntry;
    }
    private MacKeyEntry generateKeyEntry(final String algorithm, final int keyLengthBits)
        throws CryptoManagerException
    {
      final MacKeyEntry keyEntry = new MacKeyEntry(algorithm, keyLengthBits);
      // Validate the key entry.
      getMacEngine(keyEntry);
      if (null != cache) {
        /* The key is published to ADS before making it available in the local
           cache with the intention to ensure the key is persisted before use.
           This ordering allows the possibility that data encrypted at another
           instance could arrive at this instance before the key is available in
           the local cache to decode the data. */
        publishKeyEntry(cryptoManager, keyEntry);
        cache.put(keyEntry.getKeyID(), keyEntry);
      }
      return keyEntry;
    }
    /**
     * Publish a new mac key by adding an entry into ADS.
     * @param  cryptoManager The CryptoManager instance for which the
     *                       key was generated.
     * @param  keyEntry      The mac key to be published.
     * @throws CryptoManagerException
     *                       If the key entry could not be added to
     *                       ADS.
     *                       If the key entry could not be added to ADS.
     */
    private static void publishKeyEntry(CryptoManagerImpl cryptoManager,
                                        MacKeyEntry keyEntry)
         throws CryptoManagerException
    private void publishKeyEntry(MacKeyEntry keyEntry) throws CryptoManagerException
    {
      // Construct the key entry DN.
      ByteString distinguishedValue =
@@ -2062,7 +2020,7 @@
      userAttrs.put(attrKeyID, Attributes.createAsList(attrKeyID, distinguishedValue));
      putSingleValueAttribute(userAttrs, attrMacAlgorithm, keyEntry.getType());
      putSingleValueAttribute(userAttrs, attrKeyLength, String.valueOf(keyEntry.getKeyLengthBits()));
      userAttrs.put(attrSymmetricKey, buildSymetricKeyAttributes(cryptoManager, keyEntry.getSecretKey()));
      userAttrs.put(attrSymmetricKey, buildSymetricKeyAttributes(keyEntry.getSecretKey()));
      // Create the entry.
      LinkedHashMap<AttributeType, List<Attribute>> opAttrs = new LinkedHashMap<>(0);
@@ -2081,7 +2039,6 @@
     * 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 name of the MAC algorithm for which the
     * key entry is to be produced.
@@ -2094,8 +2051,7 @@
     * @throws CryptoManagerException  In case of an error in the
     * parameters used to initialize or validate the key entry.
     */
    public static MacKeyEntry importMacKeyEntry(
            final CryptoManagerImpl cryptoManager,
    private MacKeyEntry importMacKeyEntry(
            final String keyIDString,
            final String algorithm,
            final SecretKey secretKey,
@@ -2107,7 +2063,7 @@
      final KeyEntryID keyID = new KeyEntryID(keyIDString);
      // Check map for existing key entry with the supplied keyID.
      MacKeyEntry keyEntry = getMacKeyEntryOrNull(cryptoManager, keyID);
      MacKeyEntry keyEntry = getMacKeyEntryOrNull(keyID);
      if (null != keyEntry) {
        // Paranoiac check to ensure exact type match.
        if (! (keyEntry.getType().equals(algorithm)
@@ -2131,15 +2087,15 @@
      getMacEngine(keyEntry);
      // Cache new entry
      cryptoManager.macKeyEntryLock.lock();
      macKeyEntryLock.lock();
      try
      {
        cryptoManager.macKeyEntryCache.put(keyEntry.getKeyID(), keyEntry);
        cryptoManager.mostRecentMacKeys.put(cryptoManager.getKeyFullSpec(algorithm, secretKeyLengthBits), keyEntry);
        macKeyEntryCache.put(keyEntry.getKeyID(), keyEntry);
        mostRecentMacKeys.put(getKeyFullSpec(algorithm, secretKeyLengthBits), keyEntry);
      }
      finally
      {
        cryptoManager.macKeyEntryLock.unlock();
        macKeyEntryLock.unlock();
      }
      return keyEntry;
    }
@@ -2153,26 +2109,15 @@
     * specifications is not found. Instead, the ADS monitoring thread
     * is responsible for asynchronous updates to the key map.
     *
     * @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.
     *
     * @param keyFullSpec The key full spec for which the key was produced.
     * @return  The key entry corresponding to the parameters, or
     * {@code null} if no such entry exists or has been compromised
     */
    public static MacKeyEntry getMacKeyEntryOrNull(
        final CryptoManagerImpl cryptoManager,
        final String algorithm,
        final int keyLengthBits) {
      Reject.ifNull(cryptoManager, algorithm);
      Reject.ifFalse(0 < keyLengthBits);
      MacKeyEntry key =cryptoManager.mostRecentMacKeys.get(cryptoManager.getKeyFullSpec(algorithm, keyLengthBits));
    private MacKeyEntry getMacKeyEntryOrNull(String keyFullSpec) {
      MacKeyEntry key = mostRecentMacKeys.get(keyFullSpec);
      return key != null && !key.isCompromised() ? key : null;
    }
    /**
     * Given a key identifier, return the associated cipher key entry
     * from the supplied map. This method would typically be used by
@@ -2189,20 +2134,50 @@
     * the set of instances and querying them. Instead, the caller
     * must retry the operation requesting the decryption.
     *
     * @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, or
     * {@code null} if no such entry exists.
     *
     * @see CryptoManagerImpl.MacKeyEntry
     *     #getMacKeyEntryOrNull(CryptoManagerImpl, String, int)
     */
    public static MacKeyEntry getMacKeyEntryOrNull(final CryptoManagerImpl cryptoManager, final KeyEntryID keyID) {
      return cryptoManager.macKeyEntryCache.get(keyID);
    private MacKeyEntry getMacKeyEntryOrNull(final KeyEntryID keyID)
    {
      return macKeyEntryCache.get(keyID);
    }
    private String getMacEngineKeyEntryID(final String macAlgorithm, final int keyLengthBits)
        throws CryptoManagerException
    {
      final String keyFullSpec = getKeyFullSpec(macAlgorithm, keyLengthBits);
      MacKeyEntry keyEntry = getMacKeyEntryOrNull(keyFullSpec);
      if (keyEntry == null)
      {
        macKeyEntryLock.lock();
        try
        {
          keyEntry = getMacKeyEntryOrNull(keyFullSpec);
          if (keyEntry == null)
          {
            keyEntry = generateAndPublishKeyEntry(macAlgorithm, keyLengthBits);
            mostRecentMacKeys.put(keyFullSpec, keyEntry);
          }
        }
        finally
        {
          macKeyEntryLock.unlock();
        }
      }
      return keyEntry.getKeyID().toString();
    }
  }
  /**
   * 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
  {
    /**
     * Construct an instance of {@code MacKeyEntry} using the
     * specified parameters. This constructor would typically be used
@@ -2266,10 +2241,9 @@
    private final String fType;
  }
  private static List<Attribute> buildSymetricKeyAttributes(CryptoManagerImpl cryptoManager, SecretKey secretKey)
      throws CryptoManagerException
  private List<Attribute> buildSymetricKeyAttributes(SecretKey secretKey) throws CryptoManagerException
  {
    Map<String, byte[]> trustedCerts = cryptoManager.getTrustedCertificates();
    Map<String, byte[]> trustedCerts = getTrustedCertificates();
    // Need to add our own instance certificate.
    byte[] instanceKeyCertificate = CryptoManagerImpl.getInstanceKeyCertificateFromLocalTruststore();
@@ -2278,8 +2252,7 @@
    AttributeBuilder builder = new AttributeBuilder(attrSymmetricKey);
    for (Map.Entry<String, byte[]> mapEntry : trustedCerts.entrySet())
    {
      String symmetricKey =
          cryptoManager.encodeSymmetricKeyAttribute(mapEntry.getKey(), mapEntry.getValue(), secretKey);
      String symmetricKey = encodeSymmetricKeyAttribute(mapEntry.getKey(), mapEntry.getValue(), secretKey);
      builder.add(symmetricKey);
    }
    return builder.toAttributeList();
@@ -2397,35 +2370,13 @@
  public String getMacEngineKeyEntryID(final String macAlgorithm,
                                       final int keyLengthBits)
         throws CryptoManagerException {
    Reject.ifNull(macAlgorithm);
    MacKeyEntry keyEntry = MacKeyEntry.getMacKeyEntryOrNull(this, macAlgorithm, keyLengthBits);
    if (keyEntry == null)
    {
      macKeyEntryLock.lock();
      try
      {
        keyEntry = MacKeyEntry.getMacKeyEntryOrNull(this, macAlgorithm, keyLengthBits);
        if (keyEntry == null)
        {
          keyEntry = MacKeyEntry.generateKeyEntry(this, macAlgorithm, keyLengthBits);
          mostRecentMacKeys.put(getKeyFullSpec(macAlgorithm, keyLengthBits), keyEntry);
        }
      }
      finally
      {
        macKeyEntryLock.unlock();
      }
    }
    return keyEntry.getKeyID().toString();
    return macCryptoManager.getMacEngineKeyEntryID(macAlgorithm, keyLengthBits);
  }
  @Override
  public Mac getMacEngine(String keyEntryID)
          throws CryptoManagerException
  public Mac getMacEngine(String keyEntryID) throws CryptoManagerException
  {
    final MacKeyEntry keyEntry = MacKeyEntry.getMacKeyEntryOrNull(this, new KeyEntryID(keyEntryID));
    final MacKeyEntry keyEntry = macCryptoManager.getMacKeyEntryOrNull(new KeyEntryID(keyEntryID));
    return keyEntry != null ? getMacEngine(keyEntry) : null;
  }
@@ -2445,7 +2396,7 @@
  {
    Reject.ifNull(cipherTransformation, data);
    CipherKeyEntry keyEntry = getCipherKeyEntry(cipherTransformation, keyLengthBits);
    CipherKeyEntry keyEntry = cipherCryptoManager.getCipherKeyEntry(cipherTransformation, keyLengthBits);
    final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
    final byte[] iv = cipher.getIV();
@@ -2482,7 +2433,7 @@
  {
    Reject.ifNull(cipherTransformation, outputStream);
    CipherKeyEntry keyEntry = getCipherKeyEntry(cipherTransformation, keyLengthBits);
    CipherKeyEntry keyEntry = cipherCryptoManager.getCipherKeyEntry(cipherTransformation, keyLengthBits);
    final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
    final byte[] keyID = keyEntry.getKeyID().getByteValue();
@@ -2506,34 +2457,23 @@
  @Override
  public void ensureCipherKeyIsAvailable(String cipherTransformation, int cipherKeyLength) throws CryptoManagerException
  {
    getCipherKeyEntry(cipherTransformation, cipherKeyLength);
    cipherCryptoManager.getCipherKeyEntry(cipherTransformation, cipherKeyLength);
  }
  private CipherKeyEntry getCipherKeyEntry(String cipherTransformation, int keyLengthBits) throws CryptoManagerException
  /**
   * Returns the key full spec for the provided algorithm name and key length.
   *
   * @param transformation
   *          The cipher transformation for which the key was produced.
   * @param keyLengthBits
   *          The cipher key length in bits.
   * @return The key full spec for the provided algorithm name and key length
   */
  private String getKeyFullSpec(final String transformation, final int keyLengthBits)
  {
    CipherKeyEntry keyEntry = CipherKeyEntry.getCipherKeyEntryOrNull(this, cipherTransformation, keyLengthBits);
    if (keyEntry == null) {
      cipherKeyEntryLock.lock();
      try
      {
        keyEntry = CipherKeyEntry.getCipherKeyEntryOrNull(this, cipherTransformation, keyLengthBits);
        if (keyEntry == null)
        {
          keyEntry = CipherKeyEntry.generateKeyEntry(this, cipherTransformation, keyLengthBits);
          mostRecentCipherKeys.put(getKeyFullSpec(cipherTransformation, keyLengthBits), keyEntry);
        }
      }
      finally
      {
        cipherKeyEntryLock.unlock();
      }
    }
    return keyEntry;
  }
  private String getKeyFullSpec(String transformation, int keyLength)
  {
    return transformation + "/" + keyLength;
    Reject.ifNull(transformation);
    Reject.ifFalse(0 < keyLengthBits);
    return transformation + "/" + keyLengthBits;
  }
  @Override
@@ -2580,7 +2520,7 @@
                   ex.getMessage()), ex);
    }
    CipherKeyEntry keyEntry = CipherKeyEntry.getCipherKeyEntryOrNull(this, keyID);
    CipherKeyEntry keyEntry = cipherCryptoManager.getCipherKeyEntryOrNull(keyID);
    if (null == keyEntry) {
      throw new CryptoManagerException(
              ERR_CRYPTOMGR_DECRYPT_UNKNOWN_KEY_IDENTIFIER.get());
@@ -2645,7 +2585,7 @@
           ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get(
                   "stream underflow"));
      }
      keyEntry = CipherKeyEntry.getCipherKeyEntryOrNull(this, new KeyEntryID(keyID));
      keyEntry = cipherCryptoManager.getCipherKeyEntryOrNull(new KeyEntryID(keyID));
      if (null == keyEntry) {
        throw new CryptoManagerException(
                ERR_CRYPTOMGR_DECRYPT_UNKNOWN_KEY_IDENTIFIER.get());