/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2008-2010 Sun Microsystems, Inc.
|
* Portions Copyright 2014 ForgeRock AS
|
*/
|
|
package org.opends.server.crypto;
|
|
import org.opends.server.api.Backend;
|
import org.opends.server.api.BackendInitializationListener;
|
import org.opends.server.api.ChangeNotificationListener;
|
import org.opends.server.loggers.debug.DebugTracer;
|
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;
|
import org.opends.server.types.operation.PostResponseDeleteOperation;
|
import org.opends.server.types.operation.PostResponseModifyOperation;
|
import org.opends.server.types.operation.PostResponseModifyDNOperation;
|
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
|
import static org.opends.server.util.ServerConstants.OC_TOP;
|
import static org.opends.server.util.ServerConstants.
|
OID_ENTRY_CHANGE_NOTIFICATION;
|
import org.opends.server.config.ConfigConstants;
|
import static org.opends.server.config.ConfigConstants.OC_CRYPTO_INSTANCE_KEY;
|
import static org.opends.server.config.ConfigConstants.OC_CRYPTO_CIPHER_KEY;
|
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.ldap.LDAPControl;
|
import org.opends.server.controls.PersistentSearchChangeType;
|
import org.opends.server.controls.EntryChangeNotificationControl;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.core.DeleteOperation;
|
import org.opends.server.core.AddOperation;
|
import static org.opends.messages.CoreMessages.*;
|
import org.opends.messages.Message;
|
import org.opends.admin.ads.ADSContext;
|
|
import java.util.LinkedHashSet;
|
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.HashMap;
|
|
/**
|
* 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 CryptoManagerSync
|
implements BackendInitializationListener, ChangeNotificationListener
|
{
|
/**
|
* The debug log tracer for this object.
|
*/
|
private static final DebugTracer TRACER = getTracer();
|
|
|
|
// The DN of the administration suffix.
|
private DN adminSuffixDN;
|
|
// The DN of the instance keys container within the admin suffix.
|
private DN instanceKeysDN;
|
|
// The DN of the secret keys container within the admin suffix.
|
private DN secretKeysDN;
|
|
// The DN of the trust store root.
|
private DN trustStoreRootDN;
|
|
// The attribute type that is used to specify a server instance certificate.
|
AttributeType attrCert;
|
|
// The attribute type that holds a server certificate identifier.
|
AttributeType attrAlias;
|
|
// The attribute type that holds the time a key was compromised.
|
AttributeType attrCompromisedTime;
|
|
// A filter on object class to select key entries.
|
private SearchFilter keySearchFilter;
|
|
// The instance key objectclass.
|
private ObjectClass ocInstanceKey;
|
|
// The cipher key objectclass.
|
private ObjectClass ocCipherKey;
|
|
// The mac key objectclass.
|
private ObjectClass ocMacKey;
|
|
/**
|
* Creates a new instance of this trust store synchronization thread.
|
*
|
* @throws InitializationException in case an exception occurs during
|
* initialization, such as a failure to publish the instance-key-pair
|
* public-key-certificate in ADS.
|
*/
|
public CryptoManagerSync()
|
throws InitializationException
|
{
|
this(true);
|
}
|
|
/**
|
* Creates a new instance of this trust store synchronization thread.
|
*
|
* @param publishInstanceKey whether the instance key must be published in
|
* the ADS or not.
|
* @throws InitializationException in case an exception occurs during
|
* initialization, such as a failure to publish the instance-key-pair
|
* public-key-certificate in ADS.
|
*/
|
public CryptoManagerSync(boolean publishInstanceKey)
|
throws InitializationException
|
{
|
try {
|
if (publishInstanceKey)
|
{
|
CryptoManagerImpl.publishInstanceKeyEntryInADS();
|
}
|
}
|
catch (CryptoManagerException ex) {
|
throw new InitializationException(ex.getMessageObject());
|
}
|
DirectoryServer.registerBackendInitializationListener(this);
|
|
try
|
{
|
adminSuffixDN = DN.valueOf(ADSContext.getAdministrationSuffixDN());
|
instanceKeysDN = adminSuffixDN.child(DN.valueOf("cn=instance keys"));
|
secretKeysDN = adminSuffixDN.child(DN.valueOf("cn=secret keys"));
|
trustStoreRootDN = DN.valueOf(ConfigConstants.DN_TRUST_STORE_ROOT);
|
keySearchFilter =
|
SearchFilter.createFilterFromString("(|" +
|
"(objectclass=" + OC_CRYPTO_INSTANCE_KEY + ")" +
|
"(objectclass=" + OC_CRYPTO_CIPHER_KEY + ")" +
|
"(objectclass=" + OC_CRYPTO_MAC_KEY + ")" +
|
")");
|
}
|
catch (DirectoryException e)
|
{
|
//
|
}
|
|
ocInstanceKey = DirectoryServer.getObjectClass(
|
OC_CRYPTO_INSTANCE_KEY, true);
|
ocCipherKey = DirectoryServer.getObjectClass(
|
OC_CRYPTO_CIPHER_KEY, true);
|
ocMacKey = DirectoryServer.getObjectClass(
|
OC_CRYPTO_MAC_KEY, true);
|
|
attrCert = DirectoryServer.getAttributeType(
|
ConfigConstants.ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE, true);
|
attrAlias = DirectoryServer.getAttributeType(
|
ConfigConstants.ATTR_CRYPTO_KEY_ID, true);
|
attrCompromisedTime = DirectoryServer.getAttributeType(
|
ConfigConstants.ATTR_CRYPTO_KEY_COMPROMISED_TIME, true);
|
|
if (DirectoryServer.getBackendWithBaseDN(adminSuffixDN) != null)
|
{
|
searchAdminSuffix();
|
}
|
|
DirectoryServer.registerChangeNotificationListener(this);
|
}
|
|
|
private void searchAdminSuffix()
|
{
|
InternalClientConnection conn =
|
InternalClientConnection.getRootConnection();
|
LinkedHashSet<String> attributes = new LinkedHashSet<String>(0);
|
|
ArrayList<Control> controls = new ArrayList<Control>(0);
|
|
InternalSearchOperation searchOperation =
|
new InternalSearchOperation(conn,
|
InternalClientConnection.nextOperationID(),
|
InternalClientConnection.nextMessageID(),
|
controls,
|
adminSuffixDN, SearchScope.WHOLE_SUBTREE,
|
DereferencePolicy.NEVER_DEREF_ALIASES,
|
0, 0,
|
false, keySearchFilter, attributes,
|
null);
|
|
searchOperation.run();
|
|
ResultCode resultCode = searchOperation.getResultCode();
|
if (resultCode != ResultCode.SUCCESS)
|
{
|
Message message =
|
INFO_TRUSTSTORESYNC_ADMIN_SUFFIX_SEARCH_FAILED.get(
|
String.valueOf(adminSuffixDN),
|
searchOperation.getErrorMessage().toString());
|
ErrorLogger.logError(message);
|
}
|
|
for (SearchResultEntry searchEntry : searchOperation.getSearchEntries())
|
{
|
try
|
{
|
handleInternalSearchEntry(searchEntry);
|
}
|
catch (DirectoryException e)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
Message message = ERR_TRUSTSTORESYNC_EXCEPTION.get(
|
stackTraceToSingleLineString(e));
|
ErrorLogger.logError(message);
|
}
|
}
|
|
}
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public void performBackendInitializationProcessing(Backend backend)
|
{
|
DN[] baseDNs = backend.getBaseDNs();
|
if (baseDNs != null)
|
{
|
for (DN baseDN : baseDNs)
|
{
|
if (baseDN.equals(adminSuffixDN))
|
{
|
searchAdminSuffix();
|
}
|
}
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
public void performBackendFinalizationProcessing(Backend backend)
|
{
|
// No implementation required.
|
}
|
|
private void handleInternalSearchEntry(SearchResultEntry searchEntry)
|
throws DirectoryException
|
{
|
if (searchEntry.hasObjectClass(ocInstanceKey))
|
{
|
handleInstanceKeySearchEntry(searchEntry);
|
}
|
else
|
{
|
try
|
{
|
if (searchEntry.hasObjectClass(ocCipherKey))
|
{
|
DirectoryServer.getCryptoManager().importCipherKeyEntry(searchEntry);
|
}
|
else if (searchEntry.hasObjectClass(ocMacKey))
|
{
|
DirectoryServer.getCryptoManager().importMacKeyEntry(searchEntry);
|
}
|
}
|
catch (CryptoManagerException e)
|
{
|
throw new DirectoryException(
|
DirectoryServer.getServerErrorResultCode(), e);
|
}
|
}
|
}
|
|
|
private void handleInstanceKeySearchEntry(SearchResultEntry searchEntry)
|
throws DirectoryException
|
{
|
RDN srcRDN = searchEntry.getName().rdn();
|
|
// Only process the entry if it has the expected form of RDN.
|
if (!srcRDN.isMultiValued() &&
|
srcRDN.getAttributeType(0).equals(attrAlias))
|
{
|
DN dstDN = trustStoreRootDN.child(srcRDN);
|
|
// Extract any change notification control.
|
EntryChangeNotificationControl ecn = null;
|
List<Control> controls = searchEntry.getControls();
|
try
|
{
|
for (Control c : controls)
|
{
|
if (c.getOID().equals(OID_ENTRY_CHANGE_NOTIFICATION))
|
{
|
if (c instanceof LDAPControl)
|
{
|
ecn = EntryChangeNotificationControl.DECODER.decode(c
|
.isCritical(), ((LDAPControl) c).getValue());
|
}
|
else
|
{
|
ecn = (EntryChangeNotificationControl)c;
|
}
|
}
|
}
|
}
|
catch (DirectoryException e)
|
{
|
// ignore
|
}
|
|
// Get any existing local trust store entry.
|
Entry dstEntry = DirectoryServer.getEntry(dstDN);
|
|
if (ecn != null &&
|
ecn.getChangeType() == PersistentSearchChangeType.DELETE)
|
{
|
// The entry was deleted so we should remove it from the local trust
|
// store.
|
if (dstEntry != null)
|
{
|
deleteEntry(dstDN);
|
}
|
}
|
else if (searchEntry.hasAttribute(attrCompromisedTime))
|
{
|
// The key was compromised so we should remove it from the local
|
// trust store.
|
if (dstEntry != null)
|
{
|
deleteEntry(dstDN);
|
}
|
}
|
else
|
{
|
// The entry was added or modified.
|
if (dstEntry == null)
|
{
|
addEntry(searchEntry, dstDN);
|
}
|
else
|
{
|
modifyEntry(searchEntry, dstEntry);
|
}
|
}
|
}
|
}
|
|
|
/**
|
* Modify an entry in the local trust store if it differs from an entry in
|
* the ADS branch.
|
* @param srcEntry The instance key entry in the ADS branch.
|
* @param dstEntry The local trust store entry.
|
*/
|
private void modifyEntry(Entry srcEntry, Entry dstEntry)
|
{
|
List<Attribute> srcList;
|
srcList = srcEntry.getAttribute(attrCert);
|
|
List<Attribute> dstList;
|
dstList = dstEntry.getAttribute(attrCert);
|
|
// Check for changes to the certificate value.
|
boolean differ = false;
|
if (srcList == null)
|
{
|
if (dstList != null)
|
{
|
differ = true;
|
}
|
}
|
else if (dstList == null)
|
{
|
differ = true;
|
}
|
else if (srcList.size() != dstList.size())
|
{
|
differ = true;
|
}
|
else
|
{
|
if (!srcList.equals(dstList))
|
{
|
differ = true;
|
}
|
}
|
|
if (differ)
|
{
|
// The trust store backend does not implement modify so we need to
|
// delete then add.
|
DN dstDN = dstEntry.getName();
|
deleteEntry(dstDN);
|
addEntry(srcEntry, dstDN);
|
}
|
}
|
|
|
/**
|
* Delete an entry from the local trust store.
|
* @param dstDN The DN of the entry to be deleted in the local trust store.
|
*/
|
private void deleteEntry(DN dstDN)
|
{
|
InternalClientConnection conn =
|
InternalClientConnection.getRootConnection();
|
|
DeleteOperation delOperation = conn.processDelete(dstDN);
|
|
if (delOperation.getResultCode() != ResultCode.SUCCESS)
|
{
|
Message message = INFO_TRUSTSTORESYNC_DELETE_FAILED.get(
|
String.valueOf(dstDN),
|
String.valueOf(delOperation.getErrorMessage()));
|
ErrorLogger.logError(message);
|
}
|
}
|
|
|
/**
|
* Add an entry to the local trust store.
|
* @param srcEntry The instance key entry in the ADS branch.
|
* @param dstDN The DN of the entry to be added in the local trust store.
|
*/
|
private void addEntry(Entry srcEntry, DN dstDN)
|
{
|
LinkedHashMap<ObjectClass,String> ocMap =
|
new LinkedHashMap<ObjectClass,String>(2);
|
ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
|
ocMap.put(ocInstanceKey, OC_CRYPTO_INSTANCE_KEY);
|
|
HashMap<AttributeType, List<Attribute>> userAttrs =
|
new HashMap<AttributeType, List<Attribute>>();
|
|
List<Attribute> attrList;
|
attrList = srcEntry.getAttribute(attrAlias);
|
if (attrList != null)
|
{
|
userAttrs.put(attrAlias, attrList);
|
}
|
attrList = srcEntry.getAttribute(attrCert);
|
if (attrList != null)
|
{
|
userAttrs.put(attrCert, attrList);
|
}
|
|
Entry addEntry = new Entry(dstDN, ocMap, userAttrs, null);
|
|
InternalClientConnection conn =
|
InternalClientConnection.getRootConnection();
|
|
AddOperation addOperation = conn.processAdd(addEntry);
|
if (addOperation.getResultCode() != ResultCode.SUCCESS)
|
{
|
Message message = INFO_TRUSTSTORESYNC_ADD_FAILED.get(
|
String.valueOf(dstDN),
|
String.valueOf(addOperation.getErrorMessage()));
|
ErrorLogger.logError(message);
|
}
|
}
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public void handleAddOperation(PostResponseAddOperation addOperation,
|
Entry entry)
|
{
|
if (addOperation.getEntryDN().isDescendantOf(instanceKeysDN))
|
{
|
handleInstanceKeyAddOperation(entry);
|
}
|
else if (addOperation.getEntryDN().isDescendantOf(secretKeysDN))
|
{
|
try
|
{
|
if (entry.hasObjectClass(ocCipherKey))
|
{
|
DirectoryServer.getCryptoManager().importCipherKeyEntry(entry);
|
}
|
else if (entry.hasObjectClass(ocMacKey))
|
{
|
DirectoryServer.getCryptoManager().importMacKeyEntry(entry);
|
}
|
}
|
catch (CryptoManagerException e)
|
{
|
Message message = Message.raw("Failed to import key entry: %s",
|
e.getMessage());
|
ErrorLogger.logError(message);
|
}
|
}
|
}
|
|
|
private void handleInstanceKeyAddOperation(Entry entry)
|
{
|
RDN srcRDN = entry.getName().rdn();
|
|
// Only process the entry if it has the expected form of RDN.
|
if (!srcRDN.isMultiValued() &&
|
srcRDN.getAttributeType(0).equals(attrAlias))
|
{
|
DN dstDN = trustStoreRootDN.child(srcRDN);
|
|
if (!entry.hasAttribute(attrCompromisedTime))
|
{
|
addEntry(entry, dstDN);
|
}
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
public void handleDeleteOperation(PostResponseDeleteOperation deleteOperation,
|
Entry entry)
|
{
|
if (!deleteOperation.getEntryDN().isDescendantOf(instanceKeysDN))
|
{
|
return;
|
}
|
|
RDN srcRDN = entry.getName().rdn();
|
|
// Only process the entry if it has the expected form of RDN.
|
// FIXME: Technically it is possible to perform a subtree in
|
// this case however such subtree delete would essentially be
|
// removing configuration branches which should not happen.
|
if (!srcRDN.isMultiValued() &&
|
srcRDN.getAttributeType(0).equals(attrAlias))
|
{
|
DN dstDN = trustStoreRootDN.child(srcRDN);
|
|
deleteEntry(dstDN);
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
public void handleModifyOperation(PostResponseModifyOperation modifyOperation,
|
Entry oldEntry, Entry newEntry)
|
{
|
if (modifyOperation.getEntryDN().isDescendantOf(instanceKeysDN))
|
{
|
handleInstanceKeyModifyOperation(newEntry);
|
}
|
else if (modifyOperation.getEntryDN().isDescendantOf(secretKeysDN))
|
{
|
try
|
{
|
if (newEntry.hasObjectClass(ocCipherKey))
|
{
|
DirectoryServer.getCryptoManager().importCipherKeyEntry(newEntry);
|
}
|
else if (newEntry.hasObjectClass(ocMacKey))
|
{
|
DirectoryServer.getCryptoManager().importMacKeyEntry(newEntry);
|
}
|
}
|
catch (CryptoManagerException e)
|
{
|
Message message = Message.raw("Failed to import modified key entry: %s",
|
e.getMessage());
|
ErrorLogger.logError(message);
|
}
|
}
|
}
|
|
private void handleInstanceKeyModifyOperation(Entry newEntry)
|
{
|
RDN srcRDN = newEntry.getName().rdn();
|
|
// Only process the entry if it has the expected form of RDN.
|
if (!srcRDN.isMultiValued() &&
|
srcRDN.getAttributeType(0).equals(attrAlias))
|
{
|
DN dstDN = trustStoreRootDN.child(srcRDN);
|
|
// Get any existing local trust store entry.
|
Entry dstEntry = null;
|
try
|
{
|
dstEntry = DirectoryServer.getEntry(dstDN);
|
}
|
catch (DirectoryException e)
|
{
|
// ignore
|
}
|
|
if (newEntry.hasAttribute(attrCompromisedTime))
|
{
|
// The key was compromised so we should remove it from the local
|
// trust store.
|
if (dstEntry != null)
|
{
|
deleteEntry(dstDN);
|
}
|
}
|
else
|
{
|
if (dstEntry == null)
|
{
|
addEntry(newEntry, dstDN);
|
}
|
else
|
{
|
modifyEntry(newEntry, dstEntry);
|
}
|
}
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
public void handleModifyDNOperation(
|
PostResponseModifyDNOperation modifyDNOperation, Entry oldEntry,
|
Entry newEntry)
|
{
|
// No implementation required.
|
// FIXME: Technically it is possible to perform a subtree modDN
|
// in this case however such subtree modDN would essentially be
|
// moving configuration branches which should not happen.
|
}
|
}
|