/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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. */ 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.decode(ADSContext.getAdministrationSuffixDN()); instanceKeysDN = adminSuffixDN.concat(DN.decode("cn=instance keys")); secretKeysDN = adminSuffixDN.concat(DN.decode("cn=secret keys")); trustStoreRootDN = DN.decode(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.getDN().getRDN(); // Only process the entry if it has the expected form of RDN. if (!srcRDN.isMultiValued() && srcRDN.getAttributeType(0).equals(attrAlias)) { DN dstDN = trustStoreRootDN.concat(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.getDN(); 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.getDN().getRDN(); // Only process the entry if it has the expected form of RDN. if (!srcRDN.isMultiValued() && srcRDN.getAttributeType(0).equals(attrAlias)) { DN dstDN = trustStoreRootDN.concat(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.getDN().getRDN(); // 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.concat(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.getDN().getRDN(); // Only process the entry if it has the expected form of RDN. if (!srcRDN.isMultiValued() && srcRDN.getAttributeType(0).equals(attrAlias)) { DN dstDN = trustStoreRootDN.concat(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. } }