From 1a2cdfb5cf5f89348e8fee7ceeaa699d4aa54cea Mon Sep 17 00:00:00 2001
From: Fabio Pistolesi <fabio.pistolesi@forgerock.com>
Date: Thu, 21 Apr 2016 15:17:15 +0000
Subject: [PATCH] OPENDJ-2616 Support protection of pluggable backend data at rest

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexQueryFactoryImpl.java                     |   33 +
 opendj-server-legacy/src/messages/org/opends/messages/backend.properties                                               |   10 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java                       |    8 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java                              |   31 +
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java                             |    2 
 opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java                                         |    7 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DnKeyFormat.java                               |    2 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java                                 |    2 
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/BackendIndexConfiguration.xml     |   34 +
 opendj-server-legacy/resource/schema/02-config.ldif                                                                    |   12 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java                                     |    4 
 opendj-server-legacy/src/messages/org/opends/messages/core.properties                                                  |    1 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java                            |   85 +++-
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/TestDnKeyFormat.java                           |    9 
 opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequenceReader.java                                            |   13 
 opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java                                     |    6 
 opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoSuite.java                                           |  176 +++++++++
 opendj-server-legacy/src/main/java/org/opends/server/core/ServerContext.java                                           |    8 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java                            |  169 +++++++-
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendStat.java                               |   10 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java                                  |  210 ++++++++---
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml |   91 ++++
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java                               |   12 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java                                |   66 +++
 opendj-server-legacy/src/main/java/org/opends/server/types/CryptoManager.java                                          |   12 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ExportJob.java                                 |   11 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DataConfig.java                                |  108 ++++-
 27 files changed, 948 insertions(+), 184 deletions(-)

diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequenceReader.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequenceReader.java
index 3cb9d89..a9ba54f 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequenceReader.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequenceReader.java
@@ -12,7 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2009 Sun Microsystems, Inc.
- * Portions copyright 2012-2015 ForgeRock AS.
+ * Portions copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.ldap;
 
@@ -506,7 +506,16 @@
         return sequence.toString();
     }
 
-    private InputStream asInputStream() {
+    /**
+     * Returns an {@link InputStream} from the current position in the sequence.
+     * There is only a single {@link InputStream} for a given ByteSequence, so
+     * multiple calls to {@code asInputStream()} will always return the same object.
+     * The returned {@code InputStream} does not support {@code mark()}.
+     * Calling {@code close()} does nothing.
+     *
+     * @return an {@link InputStream} from the current position in the sequence
+     */
+    public InputStream asInputStream() {
         if (inputStream == null) {
             inputStream = new InputStream() {
                 @Override
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/BackendIndexConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/BackendIndexConfiguration.xml
index 78883f2..325c103 100644
--- a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/BackendIndexConfiguration.xml
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/BackendIndexConfiguration.xml
@@ -13,7 +13,7 @@
   information: "Portions Copyright [year] [name of copyright owner]".
 
   Copyright 2007-2009 Sun Microsystems, Inc.
-  Portions copyright 2014 ForgeRock AS.
+  Portions copyright 2014-2016 ForgeRock AS.
   ! -->
 <adm:managed-object name="backend-index" plural-name="backend-indexes"
   package="org.forgerock.opendj.server.config"
@@ -219,4 +219,36 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+  <adm:property name="confidentiality-enabled">
+    <adm:synopsis>
+      Specifies whether contents of the index should be confidential.
+    </adm:synopsis>
+    <adm:description>
+      Setting the flag to true will hash keys for equality type indexes using SHA-1
+      and encrypt the list of entries matching a substring key for substring indexes.
+    </adm:description>
+    <adm:requires-admin-action>
+      <adm:other>
+        <adm:synopsis>
+          If the index for the attribute must be protected for security purposes and values
+          for that attribute already exist in the database, the index must be rebuilt
+          before it will be accurate.
+          The property cannot be set on a backend for which confidentiality is not enabled.
+        </adm:synopsis>
+      </adm:other>
+    </adm:requires-admin-action>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>false</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean/>
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-confidentiality-enabled</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
 </adm:managed-object>
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml
index f32909f..7e8ff02 100644
--- a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/PluggableBackendConfiguration.xml
@@ -12,7 +12,7 @@
   Header, with the fields enclosed by brackets [] replaced by your own identifying
   information: "Portions Copyright [year] [name of copyright owner]".
 
-  Copyright 2014-2015 ForgeRock AS.
+  Copyright 2014-2016 ForgeRock AS.
   ! -->
 <adm:managed-object abstract="true" name="pluggable-backend"
   plural-name="pluggable-backends" package="org.forgerock.opendj.server.config"
@@ -285,4 +285,93 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+  <adm:property name="confidentiality-enabled">
+    <adm:synopsis>
+      Indicates whether the backend should make entries in database files readable only by Directory Server.
+    </adm:synopsis>
+    <adm:description>
+      Confidentiality is achieved by enrypting entries before writing them to the underlying storage.
+      Entry encryption will protect data on disk from unauthorised parties reading the files; for complete
+      protection, also set confidentiality for sensitive attributes indexes.
+      The property cannot be set to false if some of the indexes have confidentiality set to true.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>false</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-confidentiality-enabled</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="cipher-transformation">
+    <adm:synopsis>
+      Specifies the cipher for the directory server.
+      The syntax is "algorithm/mode/padding".
+    </adm:synopsis>
+    <adm:description>
+      The full transformation is required: specifying only an algorithm
+      and allowing the cipher provider to supply the default mode and
+      padding is not supported, because there is no guarantee these
+      default values are the same among different implementations.
+      Some cipher algorithms, including RC4 and ARCFOUR, do not have a
+      mode or padding, and hence must be specified using NONE for the
+      mode field and NoPadding for the padding field. For example,
+      RC4/NONE/NoPadding.
+    </adm:description>
+    <adm:requires-admin-action>
+      <adm:none>
+        <adm:synopsis>
+          Changes to this property take effect immediately but
+          only affect cryptographic operations performed after the
+          change.
+        </adm:synopsis>
+      </adm:none>
+    </adm:requires-admin-action>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>AES/CBC/PKCS5Padding</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-cipher-transformation</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="cipher-key-length">
+    <adm:synopsis>
+      Specifies the key length in bits for the preferred cipher.
+    </adm:synopsis>
+    <adm:requires-admin-action>
+      <adm:none>
+        <adm:synopsis>
+          Changes to this property take effect immediately but
+          only affect cryptographic operations performed after the
+          change.
+        </adm:synopsis>
+      </adm:none>
+    </adm:requires-admin-action>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>128</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:integer />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-cipher-key-length</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
 </adm:managed-object>
diff --git a/opendj-server-legacy/resource/schema/02-config.ldif b/opendj-server-legacy/resource/schema/02-config.ldif
index 865b1ec..6926c4c 100644
--- a/opendj-server-legacy/resource/schema/02-config.ldif
+++ b/opendj-server-legacy/resource/schema/02-config.ldif
@@ -3830,7 +3830,13 @@
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE
   X-ORIGIN 'OpenDJ Directory Server' )
-attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.159
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.157
+  NAME 'ds-cfg-confidentiality-enabled'
+  EQUALITY booleanMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+  SINGLE-VALUE
+  X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.158
   NAME 'ds-task-import-offheap-size'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
@@ -5754,6 +5760,9 @@
         ds-cfg-entries-compressed $
         ds-cfg-compact-encoding $
         ds-cfg-index-filter-analyzer-enabled $
+        ds-cfg-confidentiality-enabled $
+        ds-cfg-cipher-transformation $
+        ds-cfg-cipher-key-length $
         ds-cfg-index-filter-analyzer-max-filters )
   X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.23
@@ -5777,6 +5786,7 @@
          ds-cfg-index-type )
   MAY ( ds-cfg-index-entry-limit $
               ds-cfg-substring-length $
+              ds-cfg-confidentiality-enabled $
               ds-cfg-index-extensible-matching-rule )
   X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.25
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
index 47d3d72..fb8e8b4 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/AttributeIndex.java
@@ -47,6 +47,7 @@
 import org.opends.server.backends.pluggable.spi.WriteableTransaction;
 import org.opends.server.core.DirectoryServer;
 import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.opends.server.crypto.CryptoSuite;
 import org.opends.server.types.*;
 import org.opends.server.util.StaticUtils;
 
@@ -96,6 +97,8 @@
     }
   }
 
+  static final String PROTECTED_INDEX_ID = ":hash";
+
   /** This class implements an attribute indexer for matching rules in a Backend. */
   static final class MatchingRuleIndex extends DefaultIndex
   {
@@ -103,11 +106,13 @@
     private final Indexer indexer;
 
     private MatchingRuleIndex(EntryContainer entryContainer, AttributeType attributeType, State state, Indexer indexer,
-        int indexEntryLimit)
+        int indexEntryLimit, boolean encryptValues, CryptoSuite cryptoSuite)
     {
       super(getIndexName(entryContainer, attributeType, indexer.getIndexID()), state, indexEntryLimit, entryContainer);
       this.attributeType = attributeType;
       this.indexer = indexer;
+      this.encryptValues = encryptValues;
+      this.cryptoSuite = cryptoSuite;
     }
 
     Set<ByteString> indexEntry(Entry entry)
@@ -192,6 +197,45 @@
     }
   }
 
+  /**
+   * Decorates an Indexer so that we can post process key and change index name for
+   * those attributes declared as protected in the configuration.
+   */
+  private static class HashedKeyEqualityIndexer implements Indexer {
+
+    private final Indexer delegate;
+    private CryptoSuite cryptoSuite;
+
+    private HashedKeyEqualityIndexer(Indexer delegate, CryptoSuite cryptoSuite)
+    {
+      this.delegate = delegate;
+      this.cryptoSuite = cryptoSuite;
+    }
+
+    @Override
+    public String getIndexID()
+    {
+      return delegate.getIndexID() + PROTECTED_INDEX_ID;
+    }
+
+    @Override
+    public void createKeys(Schema schema, ByteSequence value, Collection<ByteString> keys) throws DecodeException
+    {
+      Collection<ByteString> hashKeys = new ArrayList<>(1);
+      delegate.createKeys(schema, value, hashKeys);
+      for (ByteString key : hashKeys)
+      {
+        keys.add(cryptoSuite.hash48(key).toByteString());
+      }
+    }
+
+    @Override
+    public String keyToHumanReadableString(ByteSequence key)
+    {
+      return key.toByteString().toHexString();
+    }
+  }
+
   /** The key bytes used for the presence index as a {@link ByteString}. */
   static final ByteString PRESENCE_KEY = ByteString.valueOfUtf8("+");
 
@@ -233,47 +277,76 @@
   private Map<String, MatchingRuleIndex> indexIdToIndexes;
   private IndexingOptions indexingOptions;
   private final State state;
+  private final CryptoSuite cryptoSuite;
 
-  AttributeIndex(BackendIndexCfg config, State state, EntryContainer entryContainer) throws ConfigException
+  AttributeIndex(BackendIndexCfg config, State state, EntryContainer entryContainer, CryptoSuite cryptoSuite)
+      throws ConfigException
   {
     this.entryContainer = entryContainer;
     this.config = config;
     this.state = state;
+    this.cryptoSuite = cryptoSuite;
     this.indexingOptions = new IndexingOptionsImpl(config.getSubstringLength());
-    this.indexIdToIndexes = Collections.unmodifiableMap(buildIndexes(entryContainer, state, config));
+    this.indexIdToIndexes = Collections.unmodifiableMap(buildIndexes(entryContainer, state, config, cryptoSuite));
   }
 
-  private static Map<String, MatchingRuleIndex> buildIndexes(EntryContainer entryContainer, State state,
-      BackendIndexCfg config) throws ConfigException
+  private Map<String, MatchingRuleIndex> buildIndexes(EntryContainer entryContainer, State state,
+      BackendIndexCfg config, CryptoSuite cryptoSuite) throws ConfigException
   {
     final AttributeType attributeType = config.getAttribute();
     final int indexEntryLimit = config.getIndexEntryLimit();
     final IndexingOptions indexingOptions = new IndexingOptionsImpl(config.getSubstringLength());
 
-    Collection<Indexer> indexers = new ArrayList<>();
+    Map<Indexer, Boolean> indexers = new HashMap<>();
     for(IndexType indexType : config.getIndexType()) {
       switch (indexType)
       {
       case PRESENCE:
-        indexers.add(PRESENCE_INDEXER);
+        indexers.put(PRESENCE_INDEXER, false);
         break;
       case EXTENSIBLE:
-        indexers.addAll(
+        indexers.putAll(
             getExtensibleIndexers(config.getAttribute(), config.getIndexExtensibleMatchingRule(), indexingOptions));
         break;
-      case APPROXIMATE:
       case EQUALITY:
-      case ORDERING:
+        indexers.putAll(buildBaseIndexers(config.isConfidentialityEnabled(), false, indexType, attributeType,
+            indexingOptions));
+        break;
       case SUBSTRING:
-        MatchingRule rule = getMatchingRule(indexType, attributeType);
-        throwIfNoMatchingRule(attributeType, indexType, rule);
-        indexers.addAll(rule.createIndexers(indexingOptions));
+        indexers.putAll(buildBaseIndexers(false, config.isConfidentialityEnabled(), indexType, attributeType,
+            indexingOptions));
+        break;
+      case APPROXIMATE:
+      case ORDERING:
+        indexers.putAll(buildBaseIndexers(false, false, indexType, attributeType, indexingOptions));
         break;
       default:
        throw new ConfigException(ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(attributeType, indexType));
       }
     }
-    return buildIndexesForIndexers(entryContainer, attributeType, state, indexEntryLimit, indexers);
+    return buildIndexesForIndexers(entryContainer, attributeType, state, indexEntryLimit, indexers, cryptoSuite);
+  }
+
+  private Map<Indexer, Boolean> buildBaseIndexers(boolean protectIndexKeys, boolean protectIndexValues,
+      IndexType indexType, AttributeType attributeType, IndexingOptions indexingOptions) throws ConfigException
+  {
+    Map<Indexer, Boolean> indexers = new HashMap<>();
+    MatchingRule rule = getMatchingRule(indexType, attributeType);
+    throwIfNoMatchingRule(attributeType, indexType, rule);
+    throwIfProtectKeysAndValues(attributeType, protectIndexKeys, protectIndexValues);
+    Collection<? extends Indexer> ruleIndexers = rule.createIndexers(indexingOptions);
+    for (Indexer indexer: ruleIndexers)
+    {
+      if (protectIndexKeys)
+      {
+        indexers.put(new HashedKeyEqualityIndexer(indexer, cryptoSuite), false);
+      }
+      else
+      {
+        indexers.put(indexer, protectIndexValues);
+      }
+    }
+    return indexers;
   }
 
   private static void throwIfNoMatchingRule(AttributeType attributeType, IndexType indexType, MatchingRule rule)
@@ -285,22 +358,34 @@
     }
   }
 
+  private void throwIfProtectKeysAndValues(AttributeType attributeType, boolean protectKeys, boolean protectValues)
+      throws ConfigException
+  {
+    if (protectKeys && protectValues)
+    {
+      throw new ConfigException(ERR_CONFIG_INDEX_CANNOT_PROTECT_BOTH.get(attributeType));
+    }
+  }
+
   private static Map<String, MatchingRuleIndex> buildIndexesForIndexers(EntryContainer entryContainer,
-      AttributeType attributeType, State state, int indexEntryLimit, Collection<? extends Indexer> indexers)
+      AttributeType attributeType, State state, int indexEntryLimit, Map<Indexer, Boolean> indexers,
+      CryptoSuite cryptoSuite)
   {
     final Map<String, MatchingRuleIndex> indexes = new HashMap<>();
-    for (Indexer indexer : indexers)
+    for (Map.Entry<Indexer, Boolean> indexerEntry : indexers.entrySet())
     {
-      final String indexID = indexer.getIndexID();
+      final String indexID = indexerEntry.getKey().getIndexID();
       if (!indexes.containsKey(indexID))
       {
-        indexes.put(indexID, new MatchingRuleIndex(entryContainer, attributeType, state, indexer, indexEntryLimit));
+        indexes.put(indexID,
+            new MatchingRuleIndex(entryContainer, attributeType, state, indexerEntry.getKey(),
+            indexEntryLimit, indexerEntry.getValue(), cryptoSuite));
       }
     }
     return indexes;
   }
 
-  private static Collection<Indexer> getExtensibleIndexers(AttributeType attributeType, Set<String> extensibleRules,
+  private static Map<Indexer, Boolean> getExtensibleIndexers(AttributeType attributeType, Set<String> extensibleRules,
       IndexingOptions options) throws ConfigException
   {
     IndexType indexType = IndexType.EXTENSIBLE;
@@ -309,12 +394,15 @@
       throw new ConfigException(ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(attributeType, indexType));
     }
 
-    final Collection<Indexer> indexers = new ArrayList<>();
+    final Map<Indexer, Boolean> indexers = new HashMap<>();
     for (final String ruleName : extensibleRules)
     {
       final MatchingRule rule = DirectoryServer.getMatchingRule(toLowerCase(ruleName));
       throwIfNoMatchingRule(attributeType, indexType, rule);
-      indexers.addAll(rule.createIndexers(options));
+      for (Indexer indexer : rule.createIndexers(options))
+      {
+        indexers.put(indexer, false);
+      }
     }
 
     return indexers;
@@ -356,6 +444,11 @@
     return config.getAttribute();
   }
 
+  public CryptoSuite getCryptoSuite()
+  {
+    return cryptoSuite;
+  }
+
   /**
    * Return the indexing options of this AttributeIndex.
    *
@@ -722,13 +815,24 @@
   public synchronized boolean isConfigurationChangeAcceptable(
       BackendIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
   {
-    return isIndexAcceptable(cfg, IndexType.EQUALITY, unacceptableReasons)
+    return isIndexConfidentialityAcceptable(cfg, unacceptableReasons)
+        && isIndexAcceptable(cfg, IndexType.EQUALITY, unacceptableReasons)
         && isIndexAcceptable(cfg, IndexType.SUBSTRING, unacceptableReasons)
         && isIndexAcceptable(cfg, IndexType.ORDERING, unacceptableReasons)
         && isIndexAcceptable(cfg, IndexType.APPROXIMATE, unacceptableReasons)
         && isExtensibleIndexAcceptable(cfg, unacceptableReasons);
   }
 
+  private boolean isIndexConfidentialityAcceptable(BackendIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
+  {
+    if (!entryContainer.isConfidentialityEnabled() && cfg.isConfidentialityEnabled())
+    {
+      unacceptableReasons.add(ERR_CLEARTEXT_BACKEND_FOR_INDEX_CONFIDENTIALITY.get(cfg.getAttribute().getNameOrOID()));
+      return false;
+    }
+    return true;
+  }
+
   private boolean isExtensibleIndexAcceptable(BackendIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
   {
     IndexType indexType = IndexType.EXTENSIBLE;
@@ -781,7 +885,8 @@
     final IndexingOptions newIndexingOptions = new IndexingOptionsImpl(newConfiguration.getSubstringLength());
     try
     {
-      final Map<String, MatchingRuleIndex> newIndexIdToIndexes = buildIndexes(entryContainer, state, newConfiguration);
+      final Map<String, MatchingRuleIndex> newIndexIdToIndexes = buildIndexes(entryContainer, state, newConfiguration,
+          cryptoSuite);
 
       final Map<String, MatchingRuleIndex> removedIndexes = new HashMap<>(indexIdToIndexes);
       removedIndexes.keySet().removeAll(newIndexIdToIndexes.keySet());
@@ -836,7 +941,7 @@
 
       for (Index updatedIndex : updatedIndexes.values())
       {
-        updateIndex(updatedIndex, newConfiguration.getIndexEntryLimit(), ccr);
+        updateIndex(updatedIndex, newConfiguration, ccr);
       }
     }
     catch (Exception e)
@@ -858,14 +963,19 @@
     }
   }
 
-  private static void updateIndex(Index updatedIndex, int newIndexEntryLimit, ConfigChangeResult ccr)
+  private static void updateIndex(Index updatedIndex, BackendIndexCfg newConfig, ConfigChangeResult ccr)
   {
-    if (updatedIndex.setIndexEntryLimit(newIndexEntryLimit))
+    // This index could still be used since a new smaller index size limit doesn't impact validity of the results.
+    if (updatedIndex.setIndexEntryLimit(newConfig.getIndexEntryLimit()))
     {
-      // This index can still be used since index size limit doesn't impact validity of the results.
       ccr.setAdminActionRequired(true);
       ccr.addMessage(NOTE_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(updatedIndex.getName()));
     }
+    if (updatedIndex.setProtected(newConfig.isConfidentialityEnabled()))
+    {
+      ccr.setAdminActionRequired(true);
+      ccr.addMessage(NOTE_CONFIG_INDEX_CONFIDENTIALITY_REQUIRES_REBUILD.get(updatedIndex.getName()));
+    }
   }
 
   private static void deleteIndex(WriteableTransaction txn, EntryContainer entryContainer, Index index)
@@ -897,6 +1007,11 @@
     return true;
   }
 
+  boolean isConfidentialityEnabled()
+  {
+    return config.isConfidentialityEnabled();
+  }
+
   /**
    * Get the tree name prefix for indexes in this attribute index.
    *
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendStat.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendStat.java
index 8ed87c2..11fad6a 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendStat.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendStat.java
@@ -579,13 +579,9 @@
     final String subCommandName = subCommand.getName();
     try
     {
-      DirectoryServer.InitializationBuilder initializationBuilder =
-          new DirectoryServer.InitializationBuilder(configFile.getValue());
-      if (subCommandName.equals(DUMP_INDEX) || subCommandName.equals(SHOW_INDEX_STATUS))
-      {
-        initializationBuilder.requireCryptoServices();
-      }
-      initializationBuilder.initialize();
+      new DirectoryServer.InitializationBuilder(configFile.getValue())
+          .requireCryptoServices()
+          .initialize();
     }
     catch (InitializationException e)
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DataConfig.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DataConfig.java
index 6ed7624..6eee795 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DataConfig.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DataConfig.java
@@ -12,12 +12,13 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions Copyright 2014-2015 ForgeRock AS.
+ * Portions Copyright 2014-2016 ForgeRock AS
  */
 package org.opends.server.backends.pluggable;
 
 import org.forgerock.util.Reject;
 import org.opends.server.api.CompressedSchema;
+import org.opends.server.crypto.CryptoSuite;
 import org.opends.server.types.EntryEncodeConfig;
 
 /**
@@ -26,66 +27,123 @@
  */
 final class DataConfig
 {
+  /**
+   * Builder for a DataConfig with all compression/encryption options.
+   */
+  static final class Builder
+  {
+    private boolean compressed;
+    private boolean encrypted;
+    private boolean compactEncoding;
+    private CompressedSchema compressedSchema;
+    private CryptoSuite cryptoSuite;
+
+    Builder()
+    {
+      // Nothing to do.
+    }
+
+    public Builder encode(boolean enabled)
+    {
+      this.compactEncoding = enabled;
+      return this;
+    }
+
+    public Builder compress(boolean enabled)
+    {
+      this.compressed = enabled;
+      return this;
+    }
+
+    public Builder encrypt(boolean enabled)
+    {
+      this.encrypted = enabled;
+      return this;
+    }
+
+    public Builder schema(CompressedSchema schema)
+    {
+      this.compressedSchema = schema;
+      return this;
+    }
+
+    public Builder cryptoSuite(CryptoSuite cs)
+    {
+      this.cryptoSuite = cs;
+      return this;
+    }
+
+    public DataConfig build()
+    {
+      return new DataConfig(this);
+    }
+  }
   /** Indicates whether data should be compressed before writing to the storage. */
   private final boolean compressed;
 
   /** The configuration to use when encoding entries in the tree. */
   private final EntryEncodeConfig encodeConfig;
 
+  private final boolean encrypted;
+
+  private final CryptoSuite cryptoSuite;
   /**
    * Construct a new DataConfig object with the specified settings.
    *
-   * @param compressed true if data should be compressed, false if not.
-   * @param compactEncoding true if data should be encoded in compact form,
-   * false if not.
-   * @param compressedSchema the compressed schema manager to use.  It must not
-   * be {@code null} if compactEncoding is {@code true}.
+   * @param builder the builder with the configuration
    */
-  DataConfig(boolean compressed, boolean compactEncoding, CompressedSchema compressedSchema)
+  private DataConfig(Builder builder)
   {
-    this.compressed = compressed;
+    this.compressed = builder.compressed;
+    this.encrypted = builder.encrypted;
+    this.cryptoSuite = builder.cryptoSuite;
 
-    if (compressedSchema == null)
+    if (builder.compressedSchema == null)
     {
-      Reject.ifTrue(compactEncoding);
-      this.encodeConfig = new EntryEncodeConfig(false, compactEncoding, false);
+      Reject.ifTrue(builder.compactEncoding);
+      this.encodeConfig = new EntryEncodeConfig(false, builder.compactEncoding, false);
     }
     else
     {
-      this.encodeConfig =
-          new EntryEncodeConfig(false, compactEncoding, compactEncoding, compressedSchema);
+      this.encodeConfig = new EntryEncodeConfig(false, builder.compactEncoding, builder.compactEncoding,
+          builder.compressedSchema);
     }
   }
 
-  /**
-   * Determine whether data should be compressed before writing to the tree.
-   * @return true if data should be compressed, false if not.
-   */
   boolean isCompressed()
   {
     return compressed;
   }
 
-  /**
-   * Get the EntryEncodeConfig object in use by this configuration.
-   * @return the EntryEncodeConfig object in use by this configuration.
-   */
+  boolean isEncrypted()
+  {
+    return encrypted;
+  }
+
   EntryEncodeConfig getEntryEncodeConfig()
   {
     return encodeConfig;
   }
 
-  /**
-   * Get a string representation of this object.
-   * @return A string representation of this object.
-   */
+  CryptoSuite getCryptoSuite()
+  {
+    return cryptoSuite;
+  }
+
   @Override
   public String toString()
   {
     final StringBuilder builder = new StringBuilder();
     builder.append("DataConfig(compressed=");
     builder.append(compressed);
+    builder.append(", encrypted=");
+    builder.append(encrypted);
     builder.append(", ");
+    if (encrypted)
+    {
+      builder.append(cryptoSuite.toString());
+      builder.append(", ");
+    }
     encodeConfig.toString(builder);
     builder.append(")");
     return builder.toString();
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java
index e791e64..f08a36f 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DefaultIndex.java
@@ -12,7 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
- * Portions Copyright 2012-2015 ForgeRock AS.
+ * Portions Copyright 2012-2016 ForgeRock AS.
  */
 package org.opends.server.backends.pluggable;
 
@@ -36,6 +36,7 @@
 import org.opends.server.backends.pluggable.spi.TreeName;
 import org.opends.server.backends.pluggable.spi.UpdateFunction;
 import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+import org.opends.server.crypto.CryptoSuite;
 
 /**
  * Represents an index implemented by a tree in which each key maps to a set of entry IDs. The key
@@ -46,11 +47,14 @@
 {
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
-  /** The limit on the number of entry IDs that may be indexed by one key. */
   private final State state;
   private final EntryContainer entryContainer;
+  /** The limit on the number of entry IDs that may be indexed by one key. */
   private int indexEntryLimit;
+
   private EntryIDSetCodec codec;
+  protected boolean encryptValues;
+  protected CryptoSuite cryptoSuite;
 
   /**
    * A flag to indicate if this index should be trusted to be consistent with the entries tree.
@@ -92,6 +96,10 @@
   {
     final EnumSet<IndexFlag> flags = state.getIndexFlags(txn, getName());
     codec = flags.contains(COMPACTED) ? CODEC_V2 : CODEC_V1;
+    if (encryptValues)
+    {
+      codec = new EntryIDSet.EntryIDSetCodecV3(codec, cryptoSuite);
+    }
     trusted = flags.contains(TRUSTED);
     if (!trusted && entryContainer.getHighestEntryID(txn).longValue() == 0)
     {
@@ -143,6 +151,17 @@
     return codec.encode(entryIDSet);
   }
 
+  // Keeps temporary values during import encrypted even in on-disk buffers.
+  long importDecodeValue(ByteString value)
+  {
+    return encryptValues ? decodeValue(ByteString.empty(), value).iterator().next().longValue() : value.toLong();
+  }
+
+  ByteString importToValue(EntryID entryID)
+  {
+    return encryptValues ? toValue(newDefinedSet(entryID.longValue())) : entryID.toByteString();
+  }
+
   @Override
   public final void update(final WriteableTransaction txn, final ByteString key, final EntryIDSet deletedIDs,
       final EntryIDSet addedIDs) throws StorageRuntimeException
@@ -272,6 +291,14 @@
   }
 
   @Override
+  public boolean setProtected(boolean protectIndex)
+  {
+    final boolean rebuildRequired = this.encryptValues != protectIndex;
+    this.encryptValues = protectIndex;
+    return rebuildRequired;
+  }
+
+  @Override
   public final int getIndexEntryLimit()
   {
     return indexEntryLimit;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DnKeyFormat.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DnKeyFormat.java
index ee576b5..87403d2 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DnKeyFormat.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DnKeyFormat.java
@@ -24,8 +24,6 @@
 /** Handles the disk representation of LDAP data. */
 public class DnKeyFormat
 {
-  /** The format version used by this class to encode and decode a ByteString. */
-  static final byte FORMAT_VERSION = 0x01;
 
   // The following fields have been copied from the DN class in the SDK
   /** RDN separator for normalized byte string of a DN. */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
index 05f2f3a..8e71e8a 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryContainer.java
@@ -88,10 +88,12 @@
 import org.opends.server.core.ModifyDNOperation;
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.SearchOperation;
+import org.opends.server.core.ServerContext;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.Attributes;
 import org.opends.server.types.CanceledOperationException;
 import org.opends.server.types.Control;
+import org.opends.server.crypto.CryptoSuite;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Entry;
 import org.opends.server.types.Modification;
@@ -161,6 +163,10 @@
    */
   private final String treePrefix;
 
+  private final ServerContext serverContext;
+
+  private CryptoSuite cryptoSuite;
+
   /**
    * This class is responsible for managing the configuration for attribute
    * indexes used within this entry container.
@@ -174,7 +180,7 @@
     {
       try
       {
-        new AttributeIndex(cfg, state, EntryContainer.this);
+        newAttributeIndex(cfg);
         return true;
       }
       catch(Exception e)
@@ -190,7 +196,7 @@
       final ConfigChangeResult ccr = new ConfigChangeResult();
       try
       {
-        final AttributeIndex index = new AttributeIndex(cfg, state, EntryContainer.this);
+        final AttributeIndex index = newAttributeIndex(cfg);
         storage.write(new WriteOperation()
         {
           @Override
@@ -338,26 +344,15 @@
   final Lock sharedLock = lock.readLock();
   final Lock exclusiveLock = lock.writeLock();
 
-  /**
-   * Create a new entry container object.
-   *
-   * @param baseDN  The baseDN this entry container will be responsible for
-   *                storing on disk.
-   * @param backendID  ID of the backend that is creating this entry container.
-   *                   It is needed by the Directory Server entry cache methods.
-   * @param config The configuration of the backend.
-   * @param storage The storage for this entryContainer.
-   * @param rootContainer The root container this entry container is in.
-   * @throws ConfigException if a configuration related error occurs.
-   */
-  EntryContainer(DN baseDN, String backendID, PluggableBackendCfg config, Storage storage,
-      RootContainer rootContainer) throws ConfigException
+  EntryContainer(DN baseDN, String backendID, PluggableBackendCfg config, Storage storage, RootContainer rootContainer,
+      ServerContext serverContext) throws ConfigException
   {
     this.backendID = backendID;
     this.baseDN = baseDN;
     this.config = config;
     this.storage = storage;
     this.rootContainer = rootContainer;
+    this.serverContext = serverContext;
     this.treePrefix = baseDN.toNormalizedUrlSafeString();
     this.id2childrenCount = new ID2ChildrenCount(getIndexName(ID2CHILDREN_COUNT_TREE_NAME));
     this.dn2id = new DN2ID(getIndexName(DN2ID_TREE_NAME), baseDN);
@@ -375,6 +370,22 @@
     config.addBackendVLVIndexDeleteListener(vlvIndexCfgManager);
   }
 
+  private AttributeIndex newAttributeIndex(BackendIndexCfg cfg) throws ConfigException
+  {
+    return new AttributeIndex(cfg, state, this, cryptoSuite);
+  }
+
+  private DataConfig newDataConfig(PluggableBackendCfg config)
+  {
+    return new DataConfig.Builder()
+        .compress(config.isEntriesCompressed())
+        .encode(config.isCompactEncoding())
+        .encrypt(config.isConfidentialityEnabled())
+        .cryptoSuite(cryptoSuite)
+        .schema(rootContainer.getCompressedSchema())
+        .build();
+  }
+
   private TreeName getIndexName(String indexId)
   {
     return new TreeName(treePrefix, indexId);
@@ -393,10 +404,9 @@
     boolean shouldCreate = accessMode.isWriteable();
     try
     {
-      DataConfig entryDataConfig = new DataConfig(
-          config.isEntriesCompressed(), config.isCompactEncoding(), rootContainer.getCompressedSchema());
-
-      id2entry = new ID2Entry(getIndexName(ID2ENTRY_TREE_NAME), entryDataConfig);
+      cryptoSuite = serverContext.getCryptoManager().newCryptoSuite(config.getCipherTransformation(),
+          config.getCipherKeyLength());
+      id2entry = new ID2Entry(getIndexName(ID2ENTRY_TREE_NAME), newDataConfig(config));
       id2entry.open(txn, shouldCreate);
       id2childrenCount.open(txn, shouldCreate);
       dn2id.open(txn, shouldCreate);
@@ -407,7 +417,7 @@
       {
         BackendIndexCfg indexCfg = config.getBackendIndex(idx);
 
-        final AttributeIndex index = new AttributeIndex(indexCfg, state, this);
+        final AttributeIndex index = newAttributeIndex(indexCfg);
         index.open(txn, shouldCreate);
         if(!index.isTrusted())
         {
@@ -2341,11 +2351,25 @@
   }
 
   @Override
-  public boolean isConfigurationChangeAcceptable(
-      PluggableBackendCfg cfg, List<LocalizableMessage> unacceptableReasons)
+  public boolean isConfigurationChangeAcceptable(PluggableBackendCfg cfg, List<LocalizableMessage> unacceptableReasons)
   {
-    // This is always true because only all config attributes used
-    // by the entry container should be validated by the admin framework.
+    StringBuilder builder = new StringBuilder();
+    for (AttributeIndex attributeIndex : attrIndexMap.values())
+    {
+      if (attributeIndex.isConfidentialityEnabled() && !cfg.isConfidentialityEnabled())
+      {
+        if (builder.length() > 0)
+        {
+          builder.append(", ");
+        }
+        builder.append(attributeIndex.getAttributeType().getNameOrOID());
+      }
+    }
+    if (builder.length() > 0)
+    {
+      unacceptableReasons.add(ERR_BACKEND_CANNOT_CHANGE_CONFIDENTIALITY.get(getBaseDN(), builder.toString()));
+      return false;
+    }
     return true;
   }
 
@@ -2362,9 +2386,9 @@
         @Override
         public void run(WriteableTransaction txn) throws Exception
         {
-          DataConfig entryDataConfig = new DataConfig(cfg.isEntriesCompressed(),
-              cfg.isCompactEncoding(), rootContainer.getCompressedSchema());
-          id2entry.setDataConfig(entryDataConfig);
+          cryptoSuite.setCipherTransformation(cfg.getCipherTransformation());
+          cryptoSuite.setCipherKeyLength(cfg.getCipherKeyLength());
+          id2entry.setDataConfig(newDataConfig(cfg));
 
           EntryContainer.this.config = cfg;
         }
@@ -2476,6 +2500,11 @@
     return false;
   }
 
+  boolean isConfidentialityEnabled()
+  {
+    return config.isConfidentialityEnabled();
+  }
+
   /**
    * Fetch the base Entry of the EntryContainer.
    * @param searchBaseDN the DN for the base entry
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java
index 62f7ff1..3066154 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/EntryIDSet.java
@@ -19,6 +19,7 @@
 import static org.forgerock.util.Reject.*;
 import static org.opends.server.util.StaticUtils.*;
 
+import java.security.GeneralSecurityException;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
@@ -31,6 +32,8 @@
 import org.forgerock.util.Reject;
 
 import com.forgerock.opendj.util.Iterators;
+import org.opends.server.types.CryptoManagerException;
+import org.opends.server.crypto.CryptoSuite;
 
 /**
  * Represents a set of Entry IDs. It can represent a set where the IDs are not defined, for example when the index entry
@@ -535,6 +538,69 @@
     }
   }
 
+  /**
+   * Decorate a V1 or V2 codec with encryption. When writing EntryIDSets to disk,
+   * prepend two bytes, {0, 1} to mark them as encrypted.
+   * The first is tag zero (unused in other encodings), followed by a byte
+   * indicating version 1 of encryption.
+   */
+  static class EntryIDSetCodecV3 implements EntryIDSetCodec
+  {
+    private static final byte CODEC_V3_TAG = 0x00;
+    private static final byte CODEC_V3_VERSION = 0x01;
+    private final EntryIDSetCodec delegate;
+    private final CryptoSuite cryptoSuite;
+    EntryIDSetCodecV3(EntryIDSetCodec delegate, CryptoSuite cryptoSuite)
+    {
+      this.delegate = delegate;
+      this.cryptoSuite = cryptoSuite;
+    }
+
+    @Override
+    public ByteString encode(EntryIDSet idSet)
+    {
+      ByteString encodedValue = delegate.encode(idSet);
+      ByteStringBuilder builder = new ByteStringBuilder(encodedValue.length());
+      builder.appendByte(CODEC_V3_TAG);
+      builder.appendByte(CODEC_V3_VERSION);
+      try
+      {
+        builder.appendBytes(cryptoSuite.encrypt(encodedValue.toByteArray()));
+        return builder.toByteString();
+      }
+      catch (GeneralSecurityException | CryptoManagerException e)
+      {
+        // Only if the underlying crypto provider has serious problems.
+        throw new IllegalStateException();
+      }
+    }
+
+    @Override
+    public EntryIDSet decode(ByteSequence key, ByteString value)
+    {
+      checkNotNull(value, "value must not be null");
+      if (value.byteAt(0) == CODEC_V3_TAG)
+      {
+        try
+        {
+          return delegate.decode(key,
+              ByteString.wrap(cryptoSuite.decrypt(value.subSequence(2, value.length()).toByteArray())));
+        }
+        catch (GeneralSecurityException | CryptoManagerException e)
+        {
+          // Only if data is completely corrupted.
+          throw new IllegalStateException();
+        }
+      }
+      return delegate.decode(key, value);
+    }
+  }
+
+  static EntryIDSetCodec newEntryIDSetCodecV3(EntryIDSetCodec codec, CryptoSuite cs)
+  {
+    return new EntryIDSetCodecV3(codec, cs);
+  }
+
   static EntryIDSet newUndefinedSet()
   {
     return newUndefinedSetWithKey(NO_KEY);
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ExportJob.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ExportJob.java
index 680a97e..aedf099 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ExportJob.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ExportJob.java
@@ -171,8 +171,8 @@
   private void exportContainer(ReadableTransaction txn, EntryContainer entryContainer)
        throws StorageRuntimeException, IOException, LDIFException
   {
-    Cursor<ByteString, ByteString> cursor = txn.openCursor(entryContainer.getID2Entry().getName());
-    try
+    ID2Entry id2entry = entryContainer.getID2Entry();
+    try (final Cursor<ByteString, ByteString> cursor = txn.openCursor(id2entry.getName()))
     {
       while (cursor.next())
       {
@@ -209,8 +209,7 @@
         Entry entry = null;
         try
         {
-          entry = ID2Entry.entryFromDatabase(value,
-                       entryContainer.getRootContainer().getCompressedSchema());
+          entry = id2entry.entryFromDatabase(value, entryContainer.getRootContainer().getCompressedSchema());
         }
         catch (Exception e)
         {
@@ -235,10 +234,6 @@
         }
       }
     }
-    finally
-    {
-      cursor.close();
-    }
   }
 
   /** This class reports progress of the export job at fixed intervals. */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
index 117c5f5..678bd20 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
@@ -12,10 +12,11 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
- * Portions Copyright 2012-2015 ForgeRock AS.
+ * Portions Copyright 2012-2016 ForgeRock AS.
  */
 package org.opends.server.backends.pluggable;
 
+import static org.forgerock.opendj.ldap.ResultCode.UNWILLING_TO_PERFORM;
 import static org.forgerock.util.Reject.*;
 import static org.forgerock.util.Utils.*;
 import static org.opends.messages.BackendMessages.*;
@@ -23,16 +24,18 @@
 import static org.opends.server.core.DirectoryServer.*;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.zip.DataFormatException;
 import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
 import java.util.zip.InflaterOutputStream;
 
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.io.ASN1;
 import org.forgerock.opendj.io.ASN1Reader;
-import org.forgerock.opendj.io.ASN1Writer;
 import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteSequenceReader;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.forgerock.opendj.ldap.DecodeException;
@@ -45,6 +48,7 @@
 import org.opends.server.backends.pluggable.spi.TreeName;
 import org.opends.server.backends.pluggable.spi.WriteableTransaction;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.CryptoManagerException;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.Entry;
 import org.opends.server.types.LDAPException;
@@ -103,40 +107,87 @@
   /** A cached set of ByteStringBuilder buffers and ASN1Writer used to encode entries. */
   private static final class EntryCodec
   {
+    /**
+     * The format version used encode and decode entries in previous versions.
+     * Not used anymore, kept for compatibility during upgrade.
+     */
+    static final byte FORMAT_VERSION = 0x01;
+
     /** The ASN1 tag for the ByteString type. */
     private static final byte TAG_TREE_ENTRY = 0x60;
     private static final int BUFFER_INIT_SIZE = 512;
+    private static final byte PLAIN_ENTRY = 0x00;
+    private static final byte COMPRESS_ENTRY = 0x01;
+    private static final byte ENCRYPT_ENTRY = 0x02;
+
+    /** The format version for entry encoding. */
+    static final byte FORMAT_VERSION_V2 = 0x02;
 
     private final ByteStringBuilder encodedBuffer = new ByteStringBuilder();
     private final ByteStringBuilder entryBuffer = new ByteStringBuilder();
     private final ByteStringBuilder compressedEntryBuffer = new ByteStringBuilder();
-    private final ASN1Writer writer;
     private final int maxBufferSize;
 
     private EntryCodec()
     {
       this.maxBufferSize = getMaxInternalBufferSize();
-      this.writer = ASN1.getWriter(encodedBuffer, maxBufferSize);
     }
 
     private void release()
     {
-      closeSilently(writer);
       encodedBuffer.clearAndTruncate(maxBufferSize, BUFFER_INIT_SIZE);
       entryBuffer.clearAndTruncate(maxBufferSize, BUFFER_INIT_SIZE);
-      compressedEntryBuffer.clearAndTruncate(maxBufferSize, BUFFER_INIT_SIZE);
     }
 
     private Entry decode(ByteString bytes, CompressedSchema compressedSchema)
         throws DirectoryException, DecodeException, IOException
     {
-      // Get the format version.
-      byte formatVersion = bytes.byteAt(0);
-      if(formatVersion != DnKeyFormat.FORMAT_VERSION)
+      final byte formatVersion = bytes.byteAt(0);
+      switch(formatVersion)
       {
+      case FORMAT_VERSION:
+        return decodeV1(bytes, compressedSchema);
+      case FORMAT_VERSION_V2:
+        return decodeV2(bytes, compressedSchema);
+      default:
         throw DecodeException.error(ERR_INCOMPATIBLE_ENTRY_VERSION.get(formatVersion));
       }
+    }
 
+    /**
+     * Decodes an entry from the old format.
+     * <p>
+     * An entry on disk is ASN1 encoded in this format:
+     *
+     * <pre>
+     * ByteString ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+     *  uncompressedSize      INTEGER,      -- A zero value means not compressed.
+     *  dataBytes             OCTET STRING  -- Optionally compressed encoding of
+     *                                         the data bytes.
+     * }
+     *
+     * ID2EntryValue ::= ByteString
+     *  -- Where dataBytes contains an encoding of DirectoryServerEntry.
+     *
+     * DirectoryServerEntry ::= [APPLICATION 1] IMPLICIT SEQUENCE {
+     *  dn                      LDAPDN,
+     *  objectClasses           SET OF LDAPString,
+     *  userAttributes          AttributeList,
+     *  operationalAttributes   AttributeList
+     * }
+     * </pre>
+     *
+     * @param bytes A byte array containing the encoded tree value.
+     * @param compressedSchema The compressed schema manager to use when decoding.
+     * @return The decoded entry.
+     * @throws DecodeException If the data is not in the expected ASN.1 encoding
+     * format.
+     * @throws DirectoryException If a Directory Server error occurs.
+     * @throws IOException if an error occurs while reading the ASN1 sequence.
+     */
+    private Entry decodeV1(ByteString bytes, CompressedSchema compressedSchema)
+        throws DirectoryException, DecodeException, IOException
+    {
       // Read the ASN1 sequence.
       ASN1Reader reader = ASN1.getReader(bytes.subSequence(1, bytes.length()));
       reader.readStartSequence();
@@ -173,6 +224,71 @@
       }
     }
 
+    /**
+     * Decodes an entry in the new extensible format.
+     * Enties are encoded according to the sequence
+     *   {VERSION_BYTE, FLAG_BYTE, COMPACT_INTEGER_LENGTH, ID2ENTRY_VALUE}
+     * where
+     *
+     * ID2ENTRY_VALUE = encoding of Entry as in decodeV1()
+     * VERSION_BYTE = 0x2
+     * FLAG_BYTE = bit field of OR'ed values indicating post-encoding processing.
+     *     possible meaningful flags are COMPRESS_ENTRY and ENCRYPT_ENTRY.
+     * COMPACT_INTEGER_LENGTH = length of ID2ENTRY_VALUE
+     *
+     * @param bytes A byte array containing the encoded tree value.
+     * @param compressedSchema The compressed schema manager to use when decoding.
+     * @return The decoded entry.
+     * @throws DecodeException If the data is not in the expected ASN.1 encoding
+     * format or a decryption error occurs.
+     * @throws DirectoryException If a Directory Server error occurs.
+     * @throws IOException if an error occurs while reading the ASN1 sequence.
+     */
+    private Entry decodeV2(ByteString bytes, CompressedSchema compressedSchema)
+        throws DirectoryException, DecodeException, IOException
+    {
+      ByteSequenceReader reader = bytes.asReader();
+      // skip version byte
+      reader.position(1);
+      int format = reader.readByte();
+      int encodedEntryLen = reader.readCompactUnsignedInt();
+      try
+      {
+        if (format == PLAIN_ENTRY)
+        {
+          return Entry.decode(reader, compressedSchema);
+        }
+        InputStream is = reader.asInputStream();
+        if ((format & ENCRYPT_ENTRY) == ENCRYPT_ENTRY)
+        {
+          is = getCryptoManager().getCipherInputStream(is);
+        }
+        if ((format & COMPRESS_ENTRY) == COMPRESS_ENTRY)
+        {
+          is = new InflaterInputStream(is);
+        }
+        byte[] data = new byte[encodedEntryLen];
+        int readBytes;
+        int position = 0;
+        int leftToRead = encodedEntryLen;
+        // CipherInputStream does not read more than block size...
+        do
+        {
+          if ((readBytes = is.read(data, position, leftToRead)) == -1 )
+          {
+            throw DecodeException.error(ERR_CANNOT_DECODE_ENTRY.get());
+          }
+          position += readBytes;
+          leftToRead -= readBytes;
+        } while (leftToRead > 0 && readBytes > 0);
+        return Entry.decode(ByteString.wrap(data).asReader(), compressedSchema);
+      }
+      catch (CryptoManagerException cme)
+      {
+        throw DecodeException.error(cme.getMessageObject());
+      }
+    }
+
     private ByteString encode(Entry entry, DataConfig dataConfig) throws DirectoryException
     {
       encodeVolatile(entry, dataConfig);
@@ -181,44 +297,44 @@
 
     private void encodeVolatile(Entry entry, DataConfig dataConfig) throws DirectoryException
     {
-      // Encode the entry for later use.
       entry.encode(entryBuffer, dataConfig.getEntryEncodeConfig());
 
-      // First write the DB format version byte.
-      encodedBuffer.appendByte(DnKeyFormat.FORMAT_VERSION);
-
+      OutputStream os = encodedBuffer.asOutputStream();
       try
       {
-        // Then start the ASN1 sequence.
-        writer.writeStartSequence(TAG_TREE_ENTRY);
-
+        byte[] formatFlags = { FORMAT_VERSION_V2, 0};
+        os.write(formatFlags);
+        encodedBuffer.appendCompactUnsigned(entryBuffer.length());
         if (dataConfig.isCompressed())
         {
-          OutputStream compressor = null;
-          try {
-            compressor = new DeflaterOutputStream(compressedEntryBuffer.asOutputStream());
-            entryBuffer.copyTo(compressor);
-          }
-          finally {
-            closeSilently(compressor);
-          }
-
-          // Compression needed and successful.
-          writer.writeInteger(entryBuffer.length());
-          writer.writeOctetString(compressedEntryBuffer);
+          os = new DeflaterOutputStream(os);
+          formatFlags[1] = COMPRESS_ENTRY;
         }
-        else
+        if (dataConfig.isEncrypted())
         {
-          writer.writeInteger(0);
-          writer.writeOctetString(entryBuffer);
+          os = dataConfig.getCryptoSuite().getCipherOutputStream(os);
+          formatFlags[1] |= ENCRYPT_ENTRY;
         }
+        encodedBuffer.setByte(1, formatFlags[1]);
 
-        writer.writeEndSequence();
+        entryBuffer.copyTo(os);
+        os.flush();
       }
-      catch(IOException ioe)
+      catch(CryptoManagerException | IOException e)
       {
-        // TODO: This should never happen with byte buffer.
-        logger.traceException(ioe);
+        logger.traceException(e);
+        throw new DirectoryException(UNWILLING_TO_PERFORM, ERR_CANNOT_ENCODE_ENTRY.get(e.getLocalizedMessage()));
+      }
+      finally
+      {
+        try
+        {
+          os.close();
+        }
+        catch (IOException ioe)
+        {
+          throw new DirectoryException(UNWILLING_TO_PERFORM, ERR_CANNOT_ENCODE_ENTRY.get(ioe.getLocalizedMessage()));
+        }
       }
     }
   }
@@ -250,26 +366,6 @@
 
   /**
    * Decodes an entry from its tree representation.
-   * <p>
-   * An entry on disk is ASN1 encoded in this format:
-   *
-   * <pre>
-   * ByteString ::= [APPLICATION 0] IMPLICIT SEQUENCE {
-   *  uncompressedSize      INTEGER,      -- A zero value means not compressed.
-   *  dataBytes             OCTET STRING  -- Optionally compressed encoding of
-   *                                         the data bytes.
-   * }
-   *
-   * ID2EntryValue ::= ByteString
-   *  -- Where dataBytes contains an encoding of DirectoryServerEntry.
-   *
-   * DirectoryServerEntry ::= [APPLICATION 1] IMPLICIT SEQUENCE {
-   *  dn                      LDAPDN,
-   *  objectClasses           SET OF LDAPString,
-   *  userAttributes          AttributeList,
-   *  operationalAttributes   AttributeList
-   * }
-   * </pre>
    *
    * @param bytes A byte array containing the encoded tree value.
    * @param compressedSchema The compressed schema manager to use when decoding.
@@ -283,7 +379,7 @@
    * @throws DirectoryException If a Directory Server error occurs.
    * @throws IOException if an error occurs while reading the ASN1 sequence.
    */
-  static Entry entryFromDatabase(ByteString bytes,
+  Entry entryFromDatabase(ByteString bytes,
       CompressedSchema compressedSchema) throws DirectoryException,
       DecodeException, LDAPException, DataFormatException, IOException
   {
@@ -308,7 +404,7 @@
    * @throws  DirectoryException  If a problem occurs while attempting to encode
    *                              the entry.
    */
-  static ByteString entryToDatabase(Entry entry, DataConfig dataConfig) throws DirectoryException
+  ByteString entryToDatabase(Entry entry, DataConfig dataConfig) throws DirectoryException
   {
     EntryCodec codec = acquireEntryCodec();
     try
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java
index f2e9e79..287294e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/Index.java
@@ -12,7 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
- * Portions Copyright 2012-2015 ForgeRock AS.
+ * Portions Copyright 2012-2016 ForgeRock AS.
  */
 package org.opends.server.backends.pluggable;
 
@@ -39,6 +39,8 @@
 
   boolean setIndexEntryLimit(int indexEntryLimit);
 
+  boolean setProtected(boolean protectIndex);
+
   void setTrusted(WriteableTransaction txn, boolean trusted);
 
   void update(WriteableTransaction txn, ByteString key, EntryIDSet deletedIDs, EntryIDSet addedIDs);
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java
index ab2b865..9cc954e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexBuffer.java
@@ -47,7 +47,7 @@
 
     void writeTrustState(WriteableTransaction txn) throws StorageRuntimeException;
 
-    void put(Index index, ByteString key, EntryID entryID);
+    void put(DefaultIndex index, ByteString key, EntryID entryID);
 
     void put(VLVIndex index, ByteString sortKey);
 
@@ -223,7 +223,7 @@
     }
 
     @Override
-    public void put(Index index, ByteString key, EntryID entryID)
+    public void put(DefaultIndex index, ByteString key, EntryID entryID)
     {
       createOrGetBufferedIndexValues(index, key).addEntryID(entryID);
     }
@@ -272,20 +272,18 @@
   {
     private final WriteableTransaction txn;
     private final EntryID expectedEntryID;
-    private final ByteString encodedEntryID;
 
     ImportIndexBuffer(WriteableTransaction txn, EntryID expectedEntryID)
     {
       this.txn = txn;
       this.expectedEntryID = expectedEntryID;
-      this.encodedEntryID = ByteString.valueOfLong(expectedEntryID.longValue());
     }
 
     @Override
-    public void put(Index index, ByteString key, EntryID entryID)
+    public void put(DefaultIndex index, ByteString key, EntryID entryID)
     {
       Reject.ifFalse(this.expectedEntryID.equals(entryID), "Unexpected entryID");
-      txn.put(index.getName(), key, encodedEntryID);
+      txn.put(index.getName(), key, index.importToValue(entryID));
     }
 
     @Override
@@ -370,7 +368,7 @@
     impl.writeTrustState(txn);
   }
 
-  void put(Index index, ByteString key, EntryID entryID)
+  void put(DefaultIndex index, ByteString key, EntryID entryID)
   {
     impl.put(index, key, entryID);
   }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexQueryFactoryImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexQueryFactoryImpl.java
index 2fa1329..fdda820 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexQueryFactoryImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/IndexQueryFactoryImpl.java
@@ -23,10 +23,12 @@
 import java.util.ArrayList;
 import java.util.Collection;
 
+import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.LocalizableMessageBuilder;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
 import org.forgerock.opendj.ldap.spi.IndexingOptions;
@@ -169,19 +171,34 @@
   {
     return new IndexQuery()
       {
+
         @Override
         public EntryIDSet evaluate(LocalizableMessageBuilder debugMessage, StringBuilder indexNameOut)
         {
           // Read the tree and get Record for the key.
           // Select the right index to be used.
-          final Index index = attributeIndex.getNameToIndexes().get(indexID);
+          Index index = attributeIndex.getNameToIndexes().get(indexID);
+          ByteSequence indexKey = key;
           if (index == null)
           {
-            appendDisabledIndexType(debugMessage, indexID, attributeIndex.getAttributeType());
-            return createMatchAllQuery().evaluate(debugMessage, indexNameOut);
+            index = attributeIndex.getNameToIndexes().get(indexID + AttributeIndex.PROTECTED_INDEX_ID);
+            if (index == null)
+            {
+              appendDisabledIndexType(debugMessage, indexID, attributeIndex.getAttributeType());
+              return createMatchAllQuery().evaluate(debugMessage, indexNameOut);
+            }
+            try
+            {
+              indexKey = attributeIndex.getCryptoSuite().hash48(key);
+            }
+            catch (DecodeException de)
+            {
+              appendExceptionError(debugMessage, de.getMessageObject());
+              return createMatchAllQuery().evaluate(debugMessage, indexNameOut);
+            }
           }
 
-          final EntryIDSet entrySet = index.get(txn, key);
+          final EntryIDSet entrySet = index.get(txn, indexKey);
           updateStatsForUndefinedResults(debugMessage, entrySet, index);
           return entrySet;
         }
@@ -367,6 +384,14 @@
       };
   }
 
+  private static void appendExceptionError(LocalizableMessageBuilder debugMessage, LocalizableMessage msg)
+  {
+    if (debugMessage != null)
+    {
+      debugMessage.append(msg);
+    }
+  }
+
   private static void appendDisabledIndexType(LocalizableMessageBuilder debugMessage, String indexID,
       AttributeType attrType)
   {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
index 2d08f3d..5dca49c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
@@ -733,8 +733,8 @@
           Executors.newSingleThreadScheduledExecutor(newThreadFactory(null, PHASE1_REPORTER_THREAD_NAME, true));
       scheduler.scheduleAtFixedRate(new PhaseOneProgressReporter(), 10, 10, TimeUnit.SECONDS);
       final PromiseImpl<Void, Exception> promise = PromiseImpl.create();
-      try (final SequentialCursor<ByteString, ByteString> cursor =
-          importer.openCursor(entryContainer.getID2Entry().getName()))
+      final ID2Entry id2Entry = entryContainer.getID2Entry();
+      try (final SequentialCursor<ByteString, ByteString> cursor = importer.openCursor(id2Entry.getName()))
       {
         while (cursor.next())
         {
@@ -748,7 +748,7 @@
               try
               {
                 entryProcessor.processEntry(entryContainer,
-                    new EntryID(key), ID2Entry.entryFromDatabase(value, schema));
+                    new EntryID(key), id2Entry.entryFromDatabase(value, schema));
                 nbEntriesProcessed.incrementAndGet();
               }
               catch (Exception e)
@@ -3190,7 +3190,7 @@
     {
       if (resultContainer.size() < indexLimit)
       {
-        resultContainer.add(value.toLong());
+        resultContainer.add(index.importDecodeValue(value));
       }
       /*
        * else EntryIDSet is above index entry limits, discard additional values
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java
index e686113..da992a0 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java
@@ -174,7 +174,7 @@
   EntryContainer openEntryContainer(DN baseDN, WriteableTransaction txn, AccessMode accessMode)
       throws StorageRuntimeException, ConfigException
   {
-    EntryContainer ec = new EntryContainer(baseDN, backendId, config, storage, this);
+    EntryContainer ec = new EntryContainer(baseDN, backendId, config, storage, this, serverContext);
     ec.open(txn, accessMode);
     return ec;
   }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
index f9b7be8..d691326 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/VerifyJob.java
@@ -373,7 +373,7 @@
         Entry entry;
         try
         {
-          entry = ID2Entry.entryFromDatabase(value, rootContainer.getCompressedSchema());
+          entry = id2entry.entryFromDatabase(value, rootContainer.getCompressedSchema());
         }
         catch (Exception e)
         {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java b/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
index baf1b19..f932415 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
@@ -143,6 +143,7 @@
 import org.opends.server.types.AcceptRejectWarn;
 import org.opends.server.types.BackupConfig;
 import org.opends.server.types.Control;
+import org.opends.server.types.CryptoManager;
 import org.opends.server.types.DITContentRule;
 import org.opends.server.types.DITStructureRule;
 import org.opends.server.types.DirectoryEnvironmentConfig;
@@ -1027,6 +1028,12 @@
     {
       return directoryServer.loggerConfigManager;
     }
+
+    @Override
+    public CryptoManager getCryptoManager()
+    {
+      return directoryServer.cryptoManager;
+    }
   }
 
   /**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/ServerContext.java b/opendj-server-legacy/src/main/java/org/opends/server/core/ServerContext.java
index 1dc13a3..dbaf199 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/ServerContext.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/ServerContext.java
@@ -20,6 +20,7 @@
 import org.forgerock.opendj.server.config.server.RootCfg;
 import org.opends.server.extensions.DiskSpaceMonitor;
 import org.opends.server.loggers.CommonAudit;
+import org.opends.server.types.CryptoManager;
 import org.opends.server.types.DirectoryEnvironmentConfig;
 import org.opends.server.types.Schema;
 
@@ -118,4 +119,11 @@
    * @return the logger config manager
    */
   LoggerConfigManager getLoggerConfigManager();
+
+  /**
+   * Returns the Crypto Manager for the instance.
+   *
+   * @return the Crypto Manager for the instance
+   */
+  CryptoManager getCryptoManager();
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
index 7a81d6b..8a69589 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
@@ -2750,4 +2750,10 @@
   {
     return sslCipherSuites;
   }
+
+  @Override
+  public CryptoSuite newCryptoSuite(String cipherTransformation, int cipherKeyLength)
+  {
+    return new CryptoSuite(this, cipherTransformation, cipherKeyLength);
+  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoSuite.java b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoSuite.java
new file mode 100644
index 0000000..d6f3e67
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoSuite.java
@@ -0,0 +1,176 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.opends.server.crypto;
+
+import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.opends.server.types.CryptoManager;
+import org.opends.server.types.CryptoManagerException;
+
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+
+import static org.opends.messages.CoreMessages.*;
+
+/** Defines cipher transformation and hash algorithm for cryptographic related operations. */
+public class CryptoSuite
+{
+  private String cipherTransformation;
+  private int cipherKeyLength;
+  private final CryptoManager cryptoManager;
+
+  /**
+   * Declares a new CryptoSuite with provided parameters.
+   * @param cryptoManager the CryptoManager to use for cryptographic operations
+   * @param cipherTransformation the initial cipher transformation
+   * @param cipherKeyLength the initial key length for the cipher
+   */
+  public CryptoSuite(CryptoManager cryptoManager, String cipherTransformation, int cipherKeyLength)
+  {
+    this.cryptoManager = cryptoManager;
+    this.cipherTransformation = cipherTransformation;
+    this.cipherKeyLength = cipherKeyLength;
+  }
+
+  /**
+   * Returns the cipher transformation to use.
+   *
+   * @return the cipher transformation to use
+   */
+  public String getCipherTransformation()
+  {
+    return cipherTransformation;
+  }
+
+  /**
+   * Returns the cipher key length to use.
+   *
+   * @return the cipher key length to use
+   */
+  public int getCipherKeyLength()
+  {
+    return cipherKeyLength;
+  }
+
+  /**
+   * Sets the cipher transformation for the CryptoSuite.
+   *
+   * @param cipherTransformation the new cipher transformation
+   */
+  public void setCipherTransformation(String cipherTransformation)
+  {
+    this.cipherTransformation = cipherTransformation;
+  }
+
+  /**
+   * Sets the key length for the CryptoSuite.
+   *
+   * @param cipherKeyLength the new key length
+   */
+  public void setCipherKeyLength(int cipherKeyLength)
+  {
+    this.cipherKeyLength = cipherKeyLength;
+  }
+
+  /**
+   * Decrypts data using the key specified in the prologue.
+   *
+   * @param data the cipher-text to be decrypted (contains prologue)
+   * @return a byte array with the clear-text
+   * @throws GeneralSecurityException if a problem occurs while decrypting the data
+   * @throws CryptoManagerException if a problem occurs during cipher initialization
+   */
+  public byte[] decrypt(byte[] data) throws GeneralSecurityException, CryptoManagerException
+  {
+    return cryptoManager.decrypt(data);
+  }
+
+  /**
+   * Encrypts data with the configured cipher transformation and key length.
+   *
+   * @param data the clear-text data to encrypt
+   * @return a byte array with a prologue containing the key identifier followed by cipher-text
+   * @throws GeneralSecurityException if a problem occurs while encrypting the data
+   * @throws CryptoManagerException if a problem occurs during cipher initialization
+   */
+  public byte[] encrypt(byte[] data) throws GeneralSecurityException, CryptoManagerException
+  {
+    return cryptoManager.encrypt(cipherTransformation, cipherKeyLength, data);
+  }
+
+  /**
+   * Returns a {@link CipherOutputStream} for encrypting through a sequence of
+   * OutputStreams.
+   *
+   * @param os the up-link OutputStream
+   * @return a {@link CipherOutputStream} for encrypting through a sequence of
+   * OutputStreams
+   * @throws CryptoManagerException if a problem occurs during cipher initialization
+   */
+  public CipherOutputStream getCipherOutputStream(OutputStream os) throws CryptoManagerException
+  {
+    return cryptoManager.getCipherOutputStream(cipherTransformation, cipherKeyLength, os);
+  }
+
+  /**
+   * Returns a {@link CipherInputStream} for decrypting through a sequence of InputStreams.
+   *
+   * @param is the up-link InputStream
+   * @return a {@link CipherInputStream} for decrypting through a sequence of InputStreams.
+   * @throws CryptoManagerException if a problem occurs during cipher initialization
+   */
+  public CipherInputStream getCipherInputStream(InputStream is) throws CryptoManagerException
+  {
+    return cryptoManager.getCipherInputStream(is);
+  }
+
+  /**
+   * Returns a ByteString of 6 bytes hash of the data.
+   *
+   * @param data a ByteSequence containing the input data to be hashed
+   * @return a ByteString of 6 bytes hash of the data.
+   * @throws DecodeException if digest of the data cannot be computed
+   */
+  public ByteString hash48(ByteSequence data) throws DecodeException
+  {
+    try
+    {
+      byte[] hash = cryptoManager.digest("SHA-1", data.toByteArray());
+      return ByteString.valueOfBytes(hash, 0, 6);
+    }
+    catch (NoSuchAlgorithmException e)
+    {
+      throw DecodeException.error(ERR_CANNOT_HASH_DATA.get());
+    }
+  }
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("CryptoSuite(cipherTransformation=");
+    builder.append(cipherTransformation);
+    builder.append(", keyLength=");
+    builder.append(cipherKeyLength);
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/CryptoManager.java b/opendj-server-legacy/src/main/java/org/opends/server/types/CryptoManager.java
index f26f509..5bdda79 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/CryptoManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/CryptoManager.java
@@ -12,11 +12,12 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2006-2009 Sun Microsystems, Inc.
- * Portions Copyright 2014-2015 ForgeRock AS.
+ * Portions Copyright 2014-2016 ForgeRock AS.
  */
 package org.opends.server.types;
 
 import org.forgerock.opendj.config.server.ConfigException;
+import org.opends.server.crypto.CryptoSuite;
 
 import javax.crypto.Mac;
 import javax.crypto.CipherOutputStream;
@@ -422,4 +423,13 @@
    * @return The set of enabled SSL cipher suites.
    */
   SortedSet<String> getSslCipherSuites();
+
+  /**
+   * Return a new {@link CryptoSuite} for the cipher and key.
+   *
+   * @return a new {@link CryptoSuite} for the cipher and key
+   * @param cipherTransformation cipher transformation string specification
+   * @param cipherKeyLength length of key in bits
+   */
+  CryptoSuite newCryptoSuite(String cipherTransformation, int cipherKeyLength);
 }
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/backend.properties b/opendj-server-legacy/src/messages/org/opends/messages/backend.properties
index bb434f0..46fc025 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/backend.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/backend.properties
@@ -1083,3 +1083,13 @@
 ERR_SCHEMA_PARSE_LINE_600=Ignoring schema definition '%s' because the following error occurred while \
   it was being parsed: %s
 ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION_601=Schema definition could not be parsed as valid attribute value
+ERR_CLEARTEXT_BACKEND_FOR_INDEX_CONFIDENTIALITY_602=Attribute %s is set as confidential on a backend \
+ whose entries are still cleartext. Enable confidentiality on the backend first
+ERR_CONFIG_INDEX_CANNOT_PROTECT_BOTH_603=The attribute '%s' cannot enable confidentiality for keys and values \
+ at the same time
+ERR_CANNOT_ENCODE_ENTRY_604=Cannot encode entry for writing on storage: %s
+ERR_CANNOT_DECODE_ENTRY_605=Input stream ended unexpectedly while decoding entry
+ERR_BACKEND_CANNOT_CHANGE_CONFIDENTIALITY_606=Confidentiality cannot be disabled on suffix '%s' because the \
+ following indexes have confidentiality still enabled: %s
+NOTE_CONFIG_INDEX_CONFIDENTIALITY_REQUIRES_REBUILD_607=Changing confidentiality for index '%s' requires the index \
+ to be rebuilt before it can be used again
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/core.properties b/opendj-server-legacy/src/messages/org/opends/messages/core.properties
index af928b1..c669c17 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/core.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/core.properties
@@ -1325,3 +1325,4 @@
  Write operations to the backend, replication updates included, will fail until the free space rises above the threshold
 NOTE_DISK_SPACE_RESTORED_751=The free space (%d bytes) on the disk containing directory %s is now above the \
  threshold
+ERR_CANNOT_HASH_DATA_752=Cannot properly use SHA-1 using the java provider. Verify java.security is properly configured
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/TestDnKeyFormat.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/TestDnKeyFormat.java
index dba8c21..7e22af0 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/TestDnKeyFormat.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/TestDnKeyFormat.java
@@ -25,6 +25,7 @@
 import org.forgerock.opendj.ldap.DN;
 import org.opends.server.DirectoryServerTestCase;
 import org.opends.server.TestCaseUtils;
+import org.opends.server.backends.pluggable.spi.TreeName;
 import org.opends.server.core.DirectoryServer;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.opends.server.types.*;
@@ -355,12 +356,12 @@
     try (final LDIFReader reader = new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes))))
     {
       Entry entryBefore, entryAfter;
+      DataConfig dataConfig = new DataConfig.Builder().compress(false).encode(false).build();
+      ID2Entry id2entry = new ID2Entry(new TreeName("o=test", "id2entry"), dataConfig);
       while ((entryBefore = reader.readEntry(false)) != null) {
-        ByteString bytes = ID2Entry.entryToDatabase(entryBefore,
-            new DataConfig(false, false, null));
+        ByteString bytes = id2entry.entryToDatabase(entryBefore, dataConfig);
 
-        entryAfter = ID2Entry.entryFromDatabase(bytes,
-                          DirectoryServer.getDefaultCompressedSchema());
+        entryAfter = id2entry.entryFromDatabase(bytes, DirectoryServer.getDefaultCompressedSchema());
 
         // check DN and number of attributes
         assertEquals(entryBefore.getAttributes().size(), entryAfter

--
Gitblit v1.10.0