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

coulbeck
09.54.2007 eed06b320cbb075d8a47ef94187876534402c071
More changes for Issue 466.
When importing a secret key entry from ADS into the crypto manager, if a symmetric key that can be decoded by this instance is not present then request it from another server using the Get Symmetric Key extended operation.
Tested by configuring two servers to use 3DES password storage scheme in the default password policy, enabling replication between them and verifying that a user can bind with password to both instances.
1 files renamed
4 files modified
461 ■■■■ changed files
opends/resource/config/config.ldif 2 ●●● patch | view | raw | blame | history
opends/src/messages/messages/core.properties 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CryptoManagerSync.java 239 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/CryptoManager.java 209 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -67,7 +67,7 @@
objectClass: top
objectClass: ds-cfg-access-control-handler
objectClass: ds-cfg-dsee-compat-access-control-handler
ds-cfg-global-aci: (extop="1.3.6.1.4.1.26027.1.6.1 || 1.3.6.1.4.1.4203.1.11.1 || 1.3.6.1.4.1.1466.20037 || 1.3.6.1.4.1.4203.1.11.3") (version 3.0; acl "Anonymous extended operation access"; allow(read) userdn="ldap:///anyone";)
ds-cfg-global-aci: (extop="1.3.6.1.4.1.26027.1.6.1 || 1.3.6.1.4.1.26027.1.6.3 || 1.3.6.1.4.1.4203.1.11.1 || 1.3.6.1.4.1.1466.20037 || 1.3.6.1.4.1.4203.1.11.3") (version 3.0; acl "Anonymous extended operation access"; allow(read) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetcontrol="2.16.840.1.113730.3.4.2 || 2.16.840.1.113730.3.4.17 || 2.16.840.1.113730.3.4.19 || 1.3.6.1.4.1.4203.1.10.2 || 1.3.6.1.4.1.42.2.27.8.5.1 || 2.16.840.1.113730.3.4.16") (version 3.0; acl "Anonymous control access"; allow(read) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetattr!="userPassword||authPassword")(version 3.0; acl "Anonymous read access"; allow (read,search,compare) userdn="ldap:///anyone";)
ds-cfg-global-aci: (targetattr="*")(version 3.0; acl "Self entry modification"; allow (write) userdn="ldap:///self";)
opends/src/messages/messages/core.properties
@@ -1735,11 +1735,14 @@
 to decrypt the supplied data because there was an error reading from the \
 input stream:  %s
SEVERE_ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE_687=CryptoManager \
 failed to import the symmetric key entry "%s" because there is no symmetric \
 key attribute value that can be decoded by this instance"
 failed to import the symmetric key entry "%s" because it could not obtain a \
 symmetric key attribute value that can be decoded by this instance"
SEVERE_ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_OTHER_688=CryptoManager failed \
 to import the symmetric key entry "%s":  %s
MILD_ERR_CRYPTOMGR_INVALID_SYMMETRIC_KEY_ALGORITHM_689=CryptoManager failed \
 to instantiate a KeyGenerator for algorithm "%s":  %s
SEVERE_ERR_CRYPTOMGR_SYMMETRIC_KEY_ENTRY_ADD_FAILED_690=CryptoManager failed \
 to add locally produced symmetric key entry "%s":  %s
SEVERE_ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_ADD_KEY_691=CryptoManager \
 failed to import the symmetric key entry "%s" because it could not add \
 a symmetric key attribute value that can be decoded by this instance"
opends/src/server/org/opends/server/core/CryptoManagerSync.java
File was renamed from opends/src/server/org/opends/server/core/TrustStoreSyncThread.java
@@ -27,14 +27,12 @@
package org.opends.server.core;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.Backend;
import org.opends.server.api.BackendInitializationListener;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import org.opends.server.loggers.ErrorLogger;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostResponseAddOperation;
@@ -51,16 +49,12 @@
import static org.opends.server.config.ConfigConstants.OC_CRYPTO_MAC_KEY;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.internal.InternalSearchListener;
import org.opends.server.controls.PersistentSearchChangeType;
import org.opends.server.controls.EntryChangeNotificationControl;
import static org.opends.messages.CoreMessages.*;
import org.opends.messages.Message;
import org.opends.admin.ads.ADSContext;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedHashSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -68,12 +62,12 @@
import java.util.HashMap;
/**
 * This class defines a thread that synchronizes certificates from the admin
 * data branch into the trust store backend.
 * This class defines an object that synchronizes certificates from the admin
 * data branch into the trust store backend, and synchronizes secret-key entries
 * from the admin data branch to the crypto manager secret-key cache.
 */
public class TrustStoreSyncThread extends DirectoryThread
     implements ServerShutdownListener, BackendInitializationListener,
     InternalSearchListener, ChangeNotificationListener
public class CryptoManagerSync
     implements BackendInitializationListener, ChangeNotificationListener
{
  /**
   * The debug log tracer for this object.
@@ -82,10 +76,6 @@
  // A lock and condition for notifying this thread.
  private Lock lock;
  private Condition condition;
  // The DN of the administration suffix.
  private DN adminSuffixDN;
@@ -110,18 +100,6 @@
  // A filter on object class to select key entries.
  private SearchFilter keySearchFilter;
  // Indicates whether the ADS suffix backend is initialized.
  private boolean adminBackendInitialized;
  // Indicates whether the trust store backend is initialized.
  private boolean trustStoreBackendInitialized;
  // Indicates whether a shutdown request has been received.
  private boolean shutdownRequested;
  // Indicates whether the initial search has been done.
  private boolean searchDone;
  // The instance key objectclass.
  private ObjectClass ocInstanceKey;
@@ -134,22 +112,10 @@
  /**
   * Creates a new instance of this trust store synchronization thread.
   */
  public TrustStoreSyncThread()
  public CryptoManagerSync()
  {
    super("Trust Store Synchronization Thread");
    setDaemon(true);
    shutdownRequested = false;
    adminBackendInitialized = false;
    trustStoreBackendInitialized = false;
    searchDone = false;
    DirectoryServer.registerShutdownListener(this);
    DirectoryServer.registerBackendInitializationListener(this);
    lock = new ReentrantLock();
    condition = lock.newCondition();
    try
    {
      adminSuffixDN = DN.decode(ADSContext.getAdministrationSuffixDN());
@@ -184,58 +150,14 @@
    if (DirectoryServer.getBackendWithBaseDN(adminSuffixDN) != null)
    {
      adminBackendInitialized = true;
      searchAdminSuffix();
    }
    if (DirectoryServer.getBackendWithBaseDN(trustStoreRootDN) != null)
    {
      trustStoreBackendInitialized = true;
    }
    DirectoryServer.registerChangeNotificationListener(this);
  }
//  We would need this to detect changes if the admin branch was remote.
//  private SearchOperation runPersistentSearch()
//  {
//    InternalClientConnection conn =
//         InternalClientConnection.getRootConnection();
//    LinkedHashSet<String> attributes = new LinkedHashSet<String>(0);
//
//    // Specify the persistent search control.
//    Set<PersistentSearchChangeType> changeTypes =
//         new HashSet<PersistentSearchChangeType>();
//    changeTypes.add(PersistentSearchChangeType.ADD);
//    changeTypes.add(PersistentSearchChangeType.DELETE);
//    changeTypes.add(PersistentSearchChangeType.MODIFY);
//
//    boolean changesOnly = false;
//    boolean returnECs = true;
//
//    PersistentSearchControl psc =
//         new PersistentSearchControl(changeTypes, changesOnly, returnECs);
//    ArrayList<Control> controls = new ArrayList<Control>(1);
//    controls.add(psc);
//
//    InternalSearchOperation searchOperation =
//         new InternalSearchOperation(
//              conn,
//              InternalClientConnection.nextOperationID(),
//              InternalClientConnection.nextMessageID(),
//              controls,
//              adminSuffixDN, SearchScope.WHOLE_SUBTREE,
//              DereferencePolicy.NEVER_DEREF_ALIASES,
//              0, 0,
//              false, instanceKeyFilter, attributes,
//              this);
//
//    searchOperation.run();
//
//    return searchOperation;
//  }
  private SearchOperation runSearch()
  private void searchAdminSuffix()
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
@@ -252,30 +174,10 @@
                                     DereferencePolicy.NEVER_DEREF_ALIASES,
                                     0, 0,
                                     false, keySearchFilter, attributes,
                                     this);
                                     null);
    searchOperation.run();
    return searchOperation;
  }
  /**
   * Performs an initial search on the ADS branch when enabled, then listens
   * for changes within the branch, writing certificates from instance key
   * entries to the trust store backend.
   */
  public void run()
  {
    while (!shutdownRequested)
    {
      try
      {
        if (!searchDone && adminBackendInitialized &&
             trustStoreBackendInitialized)
        {
          SearchOperation searchOperation = runSearch();
          ResultCode resultCode = searchOperation.getResultCode();
          if (resultCode != ResultCode.SUCCESS)
          {
@@ -285,14 +187,14 @@
                      searchOperation.getErrorMessage().toString());
            ErrorLogger.logError(message);
          }
          searchDone = true;
          DirectoryServer.registerChangeNotificationListener(this);
        }
        // Wait until a backend changes state or a shutdown is requested.
        awaitCondition();
    for (SearchResultEntry searchEntry : searchOperation.getSearchEntries())
    {
      try
      {
        handleInternalSearchEntry(searchEntry);
      }
      catch (Exception e)
      catch (DirectoryException e)
      {
        if (debugEnabled())
        {
@@ -304,58 +206,7 @@
        ErrorLogger.logError(message);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public String getShutdownListenerName()
  {
    return "Trust Store Synchronization Thread";
  }
  /**
   * {@inheritDoc}
   */
  public void processServerShutdown(Message reason)
  {
    shutdownRequested = true;
    notifyCondition();
  }
  private void notifyCondition()
  {
    lock.lock();
    try
    {
      condition.signalAll();
    }
    finally
    {
      lock.unlock();
    }
  }
  private void awaitCondition()
  {
    lock.lock();
    try
    {
      condition.await();
    }
    catch (InterruptedException e)
    {
      // ignore
    }
    finally
    {
      lock.unlock();
    }
  }
@@ -364,8 +215,6 @@
   */
  public void performBackendInitializationProcessing(Backend backend)
  {
    boolean notify = false;
    DN[] baseDNs = backend.getBaseDNs();
    if (baseDNs != null)
    {
@@ -373,21 +222,10 @@
      {
        if (baseDN.equals(adminSuffixDN))
        {
          adminBackendInitialized = true;
          notify = true;
        }
        else if (baseDN.equals(trustStoreRootDN))
        {
          trustStoreBackendInitialized = true;
          notify = true;
          searchAdminSuffix();
        }
      }
    }
    if (notify)
    {
      notifyCondition();
    }
  }
  /**
@@ -395,37 +233,10 @@
   */
  public void performBackendFinalizationProcessing(Backend backend)
  {
    boolean notify = false;
    DN[] baseDNs = backend.getBaseDNs();
    if (baseDNs != null)
    {
      for (DN baseDN : baseDNs)
      {
        if (baseDN.equals(adminSuffixDN))
        {
          adminBackendInitialized = false;
          notify = true;
        }
        else if (baseDN.equals(trustStoreRootDN))
        {
          adminBackendInitialized = false;
          notify = true;
        }
      }
    // No implementation required.
    }
    if (notify)
    {
      notifyCondition();
    }
  }
  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
                                        SearchResultEntry searchEntry)
  private void handleInternalSearchEntry(SearchResultEntry searchEntry)
       throws DirectoryException
  {
    if (searchEntry.hasObjectClass(ocInstanceKey))
@@ -638,16 +449,6 @@
  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchReference(
       InternalSearchOperation searchOperation,
       SearchResultReference searchReference) throws DirectoryException
  {
    // No implementation required.
  }
  /**
   * {@inheritDoc}
   */
  public void handleAddOperation(PostResponseAddOperation addOperation,
                                 Entry entry)
  {
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -1427,8 +1427,8 @@
      }
      // Start a thread to synchronize the trust store.
      new TrustStoreSyncThread().start();
      // Create an object to synchronize ADS with the crypto manager.
      new CryptoManagerSync();
      // If we should write a copy of the config on successful startup, then do
      // so now.
opends/src/server/org/opends/server/types/CryptoManager.java
@@ -38,6 +38,7 @@
import java.security.cert.CertificateFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
@@ -58,6 +59,7 @@
import org.opends.server.config.ConfigConstants;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.AddOperation;
import org.opends.server.core.ModifyOperation;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.util.StaticUtils.*;
@@ -65,12 +67,24 @@
import org.opends.server.util.SelectableCertificateKeyManager;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.Base64;
import org.opends.server.util.ServerConstants;
import static org.opends.server.util.ServerConstants.OC_TOP;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.schema.DirectoryStringSyntax;
import org.opends.server.schema.IntegerSyntax;
import org.opends.server.schema.BinarySyntax;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.tools.LDAPConnectionOptions;
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.extensions.GetSymmetricKeyExtendedOperation;
/**
 This class implements the Directory Server cryptographic framework,
@@ -123,6 +137,9 @@
  // The DN of the ADS secret keys container.
  private static DN secretKeysDN;
  // The DN of the ADS servers container.
  private static DN serversDN;
  // Indicates whether the schema references have been initialized.
  private static boolean schemaInitDone = false;
@@ -227,6 +244,8 @@
                DN.decode("cn=instance keys"));
        secretKeysDN = adminSuffixDN.concat(
             DN.decode("cn=secret keys"));
        serversDN = adminSuffixDN.concat(
             DN.decode("cn=Servers"));
      }
      catch (DirectoryException ex) {
        if (debugEnabled()) {
@@ -1803,6 +1822,8 @@
           entry.getAttributeValue(attrCompromisedTime,
                                   DirectoryStringSyntax.DECODER);
      boolean isCompromised = compromisedTime != null;
      ArrayList<String> symmetricKeys = new ArrayList<String>();
      entry.getAttributeValues(attrSymmetricKey,
                             DirectoryStringSyntax.DECODER,
@@ -1819,19 +1840,52 @@
      if (secretKey == null)
      {
        // Request the value from another server.
        String symmetricKey = getSymmetricKey(symmetricKeys);
        if (symmetricKey == null)
        {
        throw new CryptoManagerException(
                ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE.get(
                        entry.getDN().toString()));
      }
      boolean isCompromised = compromisedTime != null;
        secretKey = decodeSymmetricKeyAttribute(symmetricKey);
      CipherKeyEntry.importCipherKeyEntry(this, keyID,
                                          transformation,
                                          secretKey, keyLengthBits,
                                            secretKey,
                                            keyLengthBits,
                                          ivLengthBits,
                                          isCompromised);
        // Write the value to the entry.
        InternalClientConnection internalConnection =
             InternalClientConnection.getRootConnection();
        List<Modification> modifications =
             new ArrayList<Modification>(1);
        Attribute attribute =
             new Attribute(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY,
                           symmetricKey);
        modifications.add(
             new Modification(ModificationType.ADD, attribute,
                              false));
        ModifyOperation internalModify =
             internalConnection.processModify(entry.getDN(),
                                              modifications);
        if (internalModify.getResultCode() != ResultCode.SUCCESS)
        {
          throw new CryptoManagerException(
               ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_ADD_KEY.get(
                    entry.getDN().toString()));
        }
      }
      else
      {
        CipherKeyEntry.importCipherKeyEntry(this, keyID,
                                            transformation,
                                            secretKey,
                                            keyLengthBits,
                                            ivLengthBits,
                                            isCompromised);
      }
    }
    catch (DirectoryException ex)
    {
@@ -1845,6 +1899,115 @@
  }
  /**
   * Given a set of other servers' symmetric key values for
   * a given secret key, use the Get Symmetric Key extended
   * operation to request this server's symmetric key value.
   *
   * @param  symmetricKeys  The known symmetric key values for
   *                        a given secret key.
   *
   * @return The symmetric key value for this server, or null if
   *         none could be obtained.
   */
  private String getSymmetricKey(List<String> symmetricKeys)
  {
    InternalClientConnection internalConnection =
         InternalClientConnection.getRootConnection();
    for (String symmetricKey : symmetricKeys)
    {
      try
      {
        // Get the server instance key ID from the symmetric key.
        String[] elements = symmetricKey.split(":", 0);
        String instanceKeyID = elements[0];
        // Find the server entry from the instance key ID.
        String filter = "(" +
             ConfigConstants.ATTR_CRYPTO_KEY_ID + "=" +
             instanceKeyID + ")";
        InternalSearchOperation internalSearch =
             internalConnection.processSearch(
                  serversDN, SearchScope.SUBORDINATE_SUBTREE,
                  SearchFilter.createFilterFromString(filter));
        if (internalSearch.getResultCode() != ResultCode.SUCCESS)
          continue;
        LinkedList<SearchResultEntry> resultEntries =
             internalSearch.getSearchEntries();
        for (SearchResultEntry resultEntry : resultEntries)
        {
          AttributeType hostnameAttr =
               DirectoryServer.getAttributeType("hostname", true);
          String hostname = resultEntry.getAttributeValue(
               hostnameAttr, DirectoryStringSyntax.DECODER);
          AttributeType ldapPortAttr =
               DirectoryServer.getAttributeType("ldapport", true);
          Integer ldapPort = resultEntry.getAttributeValue(
               ldapPortAttr, IntegerSyntax.DECODER);
          // Connect to the server.
          AtomicInteger nextMessageID = new AtomicInteger(1);
          LDAPConnectionOptions connectionOptions =
               new LDAPConnectionOptions();
          LDAPConnection connection =
               new LDAPConnection(hostname, ldapPort,
                                  connectionOptions,
                                  System.out, System.err); //FIXME
          connection.connectToHost(null, null, nextMessageID);
          try
          {
            LDAPReader reader = connection.getLDAPReader();
            LDAPWriter writer = connection.getLDAPWriter();
            // Send the Get Symmetric Key extended request.
            ASN1OctetString requestValue =
                 GetSymmetricKeyExtendedOperation.encodeRequestValue(
                      symmetricKey, getInstanceKeyID());
            ExtendedRequestProtocolOp extendedRequest =
                 new ExtendedRequestProtocolOp(
                      ServerConstants.
                           OID_GET_SYMMETRIC_KEY_EXTENDED_OP,
                      requestValue);
            ArrayList<LDAPControl> controls =
                 new ArrayList<LDAPControl>();
            LDAPMessage requestMessage =
                 new LDAPMessage(nextMessageID.getAndIncrement(),
                                 extendedRequest, controls);
            writer.writeMessage(requestMessage);
            LDAPMessage responseMessage = reader.readMessage();
            ExtendedResponseProtocolOp extendedResponse =
                 responseMessage.getExtendedResponseProtocolOp();
            if (extendedResponse.getResultCode() ==
                 LDAPResultCode.SUCCESS)
            {
              // Got our symmetric key value.
              return extendedResponse.getValue().stringValue();
            }
          }
          finally
          {
            connection.close(nextMessageID);
          }
        }
      }
      catch (Exception e)
      {
        // Just try another server.
      }
    }
    // Give up.
    return null;
  }
  /**
   * Imports a mac key entry from an entry in ADS.
   *
   * @param entry  The ADS mac key entry to be imported. The
@@ -1879,6 +2042,8 @@
           entry.getAttributeValue(attrCompromisedTime,
                                   DirectoryStringSyntax.DECODER);
      boolean isCompromised = compromisedTime != null;
      ArrayList<String> symmetricKeys = new ArrayList<String>();
      entry.getAttributeValues(attrSymmetricKey,
                             DirectoryStringSyntax.DECODER,
@@ -1895,17 +2060,47 @@
      if (secretKey == null)
      {
        // Request the value from another server.
        String symmetricKey = getSymmetricKey(symmetricKeys);
        if (symmetricKey == null)
        {
        throw new CryptoManagerException(
                ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE.get(
                        entry.getDN().toString()));
      }
      boolean isCompromised = compromisedTime != null;
        secretKey = decodeSymmetricKeyAttribute(symmetricKey);
      MacKeyEntry.importMacKeyEntry(this, keyID, algorithm,
                                    secretKey, keyLengthBits,
                                    isCompromised);
        // Write the value to the entry.
        InternalClientConnection internalConnection =
             InternalClientConnection.getRootConnection();
        List<Modification> modifications =
             new ArrayList<Modification>(1);
        Attribute attribute =
             new Attribute(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY,
                           symmetricKey);
        modifications.add(
             new Modification(ModificationType.ADD, attribute,
                              false));
        ModifyOperation internalModify =
             internalConnection.processModify(entry.getDN(),
                                              modifications);
        if (internalModify.getResultCode() != ResultCode.SUCCESS)
        {
          throw new CryptoManagerException(
               ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_ADD_KEY.get(
                    entry.getDN().toString()));
        }
      }
      else
      {
        MacKeyEntry.importMacKeyEntry(this, keyID, algorithm,
                                      secretKey, keyLengthBits,
                                      isCompromised);
      }
    }
    catch (DirectoryException ex)
    {