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/AttributeIndex.java |  169 +++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 142 insertions(+), 27 deletions(-)

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.
    *

--
Gitblit v1.10.0