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