001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.crypto; 028 029import static org.opends.messages.CoreMessages.*; 030import static org.opends.server.api.plugin.PluginType.*; 031import static org.opends.server.config.ConfigConstants.*; 032import static org.opends.server.core.DirectoryServer.*; 033import static org.opends.server.protocols.internal.InternalClientConnection.*; 034import static org.opends.server.protocols.internal.Requests.*; 035import static org.opends.server.util.ServerConstants.*; 036import static org.opends.server.util.StaticUtils.*; 037 038import java.util.EnumSet; 039import java.util.HashMap; 040import java.util.LinkedHashMap; 041import java.util.List; 042import java.util.Map; 043 044import org.forgerock.i18n.LocalizableMessage; 045import org.forgerock.i18n.slf4j.LocalizedLogger; 046import org.forgerock.opendj.ldap.ResultCode; 047import org.forgerock.opendj.ldap.SearchScope; 048import org.opends.admin.ads.ADSContext; 049import org.opends.server.api.Backend; 050import org.opends.server.api.BackendInitializationListener; 051import org.opends.server.api.plugin.InternalDirectoryServerPlugin; 052import org.opends.server.api.plugin.PluginResult.PostResponse; 053import org.opends.server.config.ConfigConstants; 054import org.opends.server.controls.EntryChangeNotificationControl; 055import org.opends.server.controls.PersistentSearchChangeType; 056import org.opends.server.core.AddOperation; 057import org.opends.server.core.DeleteOperation; 058import org.opends.server.core.DirectoryServer; 059import org.opends.server.protocols.internal.InternalClientConnection; 060import org.opends.server.protocols.internal.InternalSearchOperation; 061import org.opends.server.protocols.internal.SearchRequest; 062import org.opends.server.protocols.ldap.LDAPControl; 063import org.opends.server.types.Attribute; 064import org.opends.server.types.AttributeType; 065import org.opends.server.types.Control; 066import org.opends.server.types.CryptoManagerException; 067import org.opends.server.types.DN; 068import org.opends.server.types.DirectoryException; 069import org.opends.server.types.Entry; 070import org.opends.server.types.InitializationException; 071import org.opends.server.types.ObjectClass; 072import org.opends.server.types.RDN; 073import org.opends.server.types.SearchFilter; 074import org.opends.server.types.SearchResultEntry; 075import org.opends.server.types.operation.PostResponseAddOperation; 076import org.opends.server.types.operation.PostResponseDeleteOperation; 077import org.opends.server.types.operation.PostResponseModifyOperation; 078 079/** 080 * This class defines an object that synchronizes certificates from the admin 081 * data branch into the trust store backend, and synchronizes secret-key entries 082 * from the admin data branch to the crypto manager secret-key cache. 083 */ 084public class CryptoManagerSync extends InternalDirectoryServerPlugin 085 implements BackendInitializationListener 086{ 087 /** The debug log tracer for this object. */ 088 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 089 090 /** The DN of the administration suffix. */ 091 private DN adminSuffixDN; 092 093 /** The DN of the instance keys container within the admin suffix. */ 094 private DN instanceKeysDN; 095 096 /** The DN of the secret keys container within the admin suffix. */ 097 private DN secretKeysDN; 098 099 /** The DN of the trust store root. */ 100 private DN trustStoreRootDN; 101 102 /** The attribute type that is used to specify a server instance certificate. */ 103 private final AttributeType attrCert; 104 105 /** The attribute type that holds a server certificate identifier. */ 106 private final AttributeType attrAlias; 107 108 /** The attribute type that holds the time a key was compromised. */ 109 private final AttributeType attrCompromisedTime; 110 111 /** A filter on object class to select key entries. */ 112 private SearchFilter keySearchFilter; 113 114 /** The instance key objectclass. */ 115 private final ObjectClass ocInstanceKey; 116 117 /** The cipher key objectclass. */ 118 private final ObjectClass ocCipherKey; 119 120 /** The mac key objectclass. */ 121 private final ObjectClass ocMacKey; 122 123 /** Dummy configuration DN. */ 124 private static final String CONFIG_DN = "cn=Crypto Manager Sync,cn=config"; 125 126 /** 127 * Creates a new instance of this trust store synchronization thread. 128 * 129 * @throws InitializationException in case an exception occurs during 130 * initialization, such as a failure to publish the instance-key-pair 131 * public-key-certificate in ADS. 132 */ 133 public CryptoManagerSync() throws InitializationException 134 { 135 super(toDN(CONFIG_DN), EnumSet.of( 136 // No implementation required for modify_dn operations 137 // FIXME: Technically it is possible to perform a subtree modDN 138 // in this case however such subtree modDN would essentially be 139 // moving configuration branches which should not happen. 140 POST_RESPONSE_ADD, POST_RESPONSE_MODIFY, POST_RESPONSE_DELETE), 141 true); 142 try { 143 CryptoManagerImpl.publishInstanceKeyEntryInADS(); 144 } 145 catch (CryptoManagerException ex) { 146 throw new InitializationException(ex.getMessageObject()); 147 } 148 DirectoryServer.registerBackendInitializationListener(this); 149 150 try 151 { 152 adminSuffixDN = DN.valueOf(ADSContext.getAdministrationSuffixDN()); 153 instanceKeysDN = adminSuffixDN.child(DN.valueOf("cn=instance keys")); 154 secretKeysDN = adminSuffixDN.child(DN.valueOf("cn=secret keys")); 155 trustStoreRootDN = DN.valueOf(ConfigConstants.DN_TRUST_STORE_ROOT); 156 keySearchFilter = 157 SearchFilter.createFilterFromString("(|" + 158 "(objectclass=" + OC_CRYPTO_INSTANCE_KEY + ")" + 159 "(objectclass=" + OC_CRYPTO_CIPHER_KEY + ")" + 160 "(objectclass=" + OC_CRYPTO_MAC_KEY + ")" + 161 ")"); 162 } 163 catch (DirectoryException e) 164 { 165 } 166 167 ocInstanceKey = DirectoryServer.getObjectClass(OC_CRYPTO_INSTANCE_KEY, true); 168 ocCipherKey = DirectoryServer.getObjectClass(OC_CRYPTO_CIPHER_KEY, true); 169 ocMacKey = DirectoryServer.getObjectClass(OC_CRYPTO_MAC_KEY, true); 170 171 attrCert = getAttributeTypeOrDefault(ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 172 attrAlias = getAttributeTypeOrDefault(ATTR_CRYPTO_KEY_ID); 173 attrCompromisedTime = getAttributeTypeOrDefault(ATTR_CRYPTO_KEY_COMPROMISED_TIME); 174 175 if (DirectoryServer.getBackendWithBaseDN(adminSuffixDN) != null) 176 { 177 searchAdminSuffix(); 178 } 179 180 DirectoryServer.registerInternalPlugin(this); 181 } 182 183 private static DN toDN(final String dn) throws InitializationException 184 { 185 try 186 { 187 return DN.valueOf(dn); 188 } 189 catch (DirectoryException e) 190 { 191 throw new RuntimeException(e); 192 } 193 } 194 195 196 private void searchAdminSuffix() 197 { 198 SearchRequest request = newSearchRequest(adminSuffixDN, SearchScope.WHOLE_SUBTREE, keySearchFilter); 199 InternalSearchOperation searchOperation = getRootConnection().processSearch(request); 200 ResultCode resultCode = searchOperation.getResultCode(); 201 if (resultCode != ResultCode.SUCCESS) 202 { 203 logger.debug(INFO_TRUSTSTORESYNC_ADMIN_SUFFIX_SEARCH_FAILED, adminSuffixDN, 204 searchOperation.getErrorMessage()); 205 } 206 207 for (SearchResultEntry searchEntry : searchOperation.getSearchEntries()) 208 { 209 try 210 { 211 handleInternalSearchEntry(searchEntry); 212 } 213 catch (DirectoryException e) 214 { 215 logger.traceException(e); 216 217 logger.error(ERR_TRUSTSTORESYNC_EXCEPTION, stackTraceToSingleLineString(e)); 218 } 219 } 220 } 221 222 223 /** {@inheritDoc} */ 224 @Override 225 public void performBackendPreInitializationProcessing(Backend<?> backend) 226 { 227 DN[] baseDNs = backend.getBaseDNs(); 228 if (baseDNs != null) 229 { 230 for (DN baseDN : baseDNs) 231 { 232 if (baseDN.equals(adminSuffixDN)) 233 { 234 searchAdminSuffix(); 235 } 236 } 237 } 238 } 239 240 /** {@inheritDoc} */ 241 @Override 242 public void performBackendPostFinalizationProcessing(Backend<?> backend) 243 { 244 // No implementation required. 245 } 246 247 @Override 248 public void performBackendPostInitializationProcessing(Backend<?> backend) { 249 // Nothing to do. 250 } 251 252 @Override 253 public void performBackendPreFinalizationProcessing(Backend<?> backend) { 254 // Nothing to do. 255 } 256 257 private void handleInternalSearchEntry(SearchResultEntry searchEntry) 258 throws DirectoryException 259 { 260 if (searchEntry.hasObjectClass(ocInstanceKey)) 261 { 262 handleInstanceKeySearchEntry(searchEntry); 263 } 264 else 265 { 266 try 267 { 268 if (searchEntry.hasObjectClass(ocCipherKey)) 269 { 270 DirectoryServer.getCryptoManager().importCipherKeyEntry(searchEntry); 271 } 272 else if (searchEntry.hasObjectClass(ocMacKey)) 273 { 274 DirectoryServer.getCryptoManager().importMacKeyEntry(searchEntry); 275 } 276 } 277 catch (CryptoManagerException e) 278 { 279 throw new DirectoryException( 280 DirectoryServer.getServerErrorResultCode(), e); 281 } 282 } 283 } 284 285 286 private void handleInstanceKeySearchEntry(SearchResultEntry searchEntry) 287 throws DirectoryException 288 { 289 RDN srcRDN = searchEntry.getName().rdn(); 290 291 // Only process the entry if it has the expected form of RDN. 292 if (!srcRDN.isMultiValued() && 293 srcRDN.getAttributeType(0).equals(attrAlias)) 294 { 295 DN dstDN = trustStoreRootDN.child(srcRDN); 296 297 // Extract any change notification control. 298 EntryChangeNotificationControl ecn = null; 299 List<Control> controls = searchEntry.getControls(); 300 try 301 { 302 for (Control c : controls) 303 { 304 if (OID_ENTRY_CHANGE_NOTIFICATION.equals(c.getOID())) 305 { 306 if (c instanceof LDAPControl) 307 { 308 ecn = EntryChangeNotificationControl.DECODER.decode(c 309 .isCritical(), ((LDAPControl) c).getValue()); 310 } 311 else 312 { 313 ecn = (EntryChangeNotificationControl)c; 314 } 315 } 316 } 317 } 318 catch (DirectoryException e) 319 { 320 // ignore 321 } 322 323 // Get any existing local trust store entry. 324 Entry dstEntry = DirectoryServer.getEntry(dstDN); 325 326 if (ecn != null && 327 ecn.getChangeType() == PersistentSearchChangeType.DELETE) 328 { 329 // entry was deleted so remove it from the local trust store 330 if (dstEntry != null) 331 { 332 deleteEntry(dstDN); 333 } 334 } 335 else if (searchEntry.hasAttribute(attrCompromisedTime)) 336 { 337 // key was compromised so remove it from the local trust store 338 if (dstEntry != null) 339 { 340 deleteEntry(dstDN); 341 } 342 } 343 else if (dstEntry == null) 344 { 345 // The entry was added 346 addEntry(searchEntry, dstDN); 347 } 348 else 349 { 350 // The entry was modified 351 modifyEntry(searchEntry, dstEntry); 352 } 353 } 354 } 355 356 357 /** 358 * Modify an entry in the local trust store if it differs from an entry in 359 * the ADS branch. 360 * @param srcEntry The instance key entry in the ADS branch. 361 * @param dstEntry The local trust store entry. 362 */ 363 private void modifyEntry(Entry srcEntry, Entry dstEntry) 364 { 365 List<Attribute> srcList = srcEntry.getAttribute(attrCert); 366 List<Attribute> dstList = dstEntry.getAttribute(attrCert); 367 368 // Check for changes to the certificate value. 369 boolean differ = false; 370 if (srcList == null) 371 { 372 if (dstList != null) 373 { 374 differ = true; 375 } 376 } 377 else if (dstList == null 378 || srcList.size() != dstList.size() 379 || !srcList.equals(dstList)) 380 { 381 differ = true; 382 } 383 384 if (differ) 385 { 386 // The trust store backend does not implement modify so we need to 387 // delete then add. 388 DN dstDN = dstEntry.getName(); 389 deleteEntry(dstDN); 390 addEntry(srcEntry, dstDN); 391 } 392 } 393 394 395 /** 396 * Delete an entry from the local trust store. 397 * @param dstDN The DN of the entry to be deleted in the local trust store. 398 */ 399 private static void deleteEntry(DN dstDN) 400 { 401 InternalClientConnection conn = 402 InternalClientConnection.getRootConnection(); 403 404 DeleteOperation delOperation = conn.processDelete(dstDN); 405 406 if (delOperation.getResultCode() != ResultCode.SUCCESS) 407 { 408 logger.debug(INFO_TRUSTSTORESYNC_DELETE_FAILED, dstDN, delOperation.getErrorMessage()); 409 } 410 } 411 412 413 /** 414 * Add an entry to the local trust store. 415 * @param srcEntry The instance key entry in the ADS branch. 416 * @param dstDN The DN of the entry to be added in the local trust store. 417 */ 418 private void addEntry(Entry srcEntry, DN dstDN) 419 { 420 Map<ObjectClass, String> ocMap = new LinkedHashMap<>(2); 421 ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP); 422 ocMap.put(ocInstanceKey, OC_CRYPTO_INSTANCE_KEY); 423 424 Map<AttributeType, List<Attribute>> userAttrs = new HashMap<>(); 425 426 List<Attribute> attrList; 427 attrList = srcEntry.getAttribute(attrAlias); 428 if (attrList != null) 429 { 430 userAttrs.put(attrAlias, attrList); 431 } 432 attrList = srcEntry.getAttribute(attrCert); 433 if (attrList != null) 434 { 435 userAttrs.put(attrCert, attrList); 436 } 437 438 Entry addEntry = new Entry(dstDN, ocMap, userAttrs, null); 439 440 InternalClientConnection conn = 441 InternalClientConnection.getRootConnection(); 442 443 AddOperation addOperation = conn.processAdd(addEntry); 444 if (addOperation.getResultCode() != ResultCode.SUCCESS) 445 { 446 logger.debug(INFO_TRUSTSTORESYNC_ADD_FAILED, dstDN, addOperation.getErrorMessage()); 447 } 448 } 449 450 /** {@inheritDoc} */ 451 @Override 452 public PostResponse doPostResponse(PostResponseAddOperation op) 453 { 454 if (op.getResultCode() != ResultCode.SUCCESS) 455 { 456 return PostResponse.continueOperationProcessing(); 457 } 458 459 final Entry entry = op.getEntryToAdd(); 460 final DN entryDN = op.getEntryDN(); 461 if (entryDN.isDescendantOf(instanceKeysDN)) 462 { 463 handleInstanceKeyAddOperation(entry); 464 } 465 else if (entryDN.isDescendantOf(secretKeysDN)) 466 { 467 try 468 { 469 if (entry.hasObjectClass(ocCipherKey)) 470 { 471 DirectoryServer.getCryptoManager().importCipherKeyEntry(entry); 472 } 473 else if (entry.hasObjectClass(ocMacKey)) 474 { 475 DirectoryServer.getCryptoManager().importMacKeyEntry(entry); 476 } 477 } 478 catch (CryptoManagerException e) 479 { 480 logger.error(LocalizableMessage.raw( 481 "Failed to import key entry: %s", e.getMessage())); 482 } 483 } 484 return PostResponse.continueOperationProcessing(); 485 } 486 487 488 private void handleInstanceKeyAddOperation(Entry entry) 489 { 490 RDN srcRDN = entry.getName().rdn(); 491 492 // Only process the entry if it has the expected form of RDN. 493 if (!srcRDN.isMultiValued() && 494 srcRDN.getAttributeType(0).equals(attrAlias)) 495 { 496 DN dstDN = trustStoreRootDN.child(srcRDN); 497 498 if (!entry.hasAttribute(attrCompromisedTime)) 499 { 500 addEntry(entry, dstDN); 501 } 502 } 503 } 504 505 /** {@inheritDoc} */ 506 @Override 507 public PostResponse doPostResponse(PostResponseDeleteOperation op) 508 { 509 if (op.getResultCode() != ResultCode.SUCCESS 510 || !op.getEntryDN().isDescendantOf(instanceKeysDN)) 511 { 512 return PostResponse.continueOperationProcessing(); 513 } 514 515 RDN srcRDN = op.getEntryToDelete().getName().rdn(); 516 517 // Only process the entry if it has the expected form of RDN. 518 // FIXME: Technically it is possible to perform a subtree in 519 // this case however such subtree delete would essentially be 520 // removing configuration branches which should not happen. 521 if (!srcRDN.isMultiValued() && 522 srcRDN.getAttributeType(0).equals(attrAlias)) 523 { 524 DN destDN = trustStoreRootDN.child(srcRDN); 525 deleteEntry(destDN); 526 } 527 return PostResponse.continueOperationProcessing(); 528 } 529 530 /** {@inheritDoc} */ 531 @Override 532 public PostResponse doPostResponse(PostResponseModifyOperation op) 533 { 534 if (op.getResultCode() != ResultCode.SUCCESS) 535 { 536 return PostResponse.continueOperationProcessing(); 537 } 538 539 final Entry newEntry = op.getModifiedEntry(); 540 final DN entryDN = op.getEntryDN(); 541 if (entryDN.isDescendantOf(instanceKeysDN)) 542 { 543 handleInstanceKeyModifyOperation(newEntry); 544 } 545 else if (entryDN.isDescendantOf(secretKeysDN)) 546 { 547 try 548 { 549 if (newEntry.hasObjectClass(ocCipherKey)) 550 { 551 DirectoryServer.getCryptoManager().importCipherKeyEntry(newEntry); 552 } 553 else if (newEntry.hasObjectClass(ocMacKey)) 554 { 555 DirectoryServer.getCryptoManager().importMacKeyEntry(newEntry); 556 } 557 } 558 catch (CryptoManagerException e) 559 { 560 logger.error(LocalizableMessage.raw( 561 "Failed to import modified key entry: %s", e.getMessage())); 562 } 563 } 564 return PostResponse.continueOperationProcessing(); 565 } 566 567 private void handleInstanceKeyModifyOperation(Entry newEntry) 568 { 569 RDN srcRDN = newEntry.getName().rdn(); 570 571 // Only process the entry if it has the expected form of RDN. 572 if (!srcRDN.isMultiValued() && 573 srcRDN.getAttributeType(0).equals(attrAlias)) 574 { 575 DN dstDN = trustStoreRootDN.child(srcRDN); 576 577 // Get any existing local trust store entry. 578 Entry dstEntry = null; 579 try 580 { 581 dstEntry = DirectoryServer.getEntry(dstDN); 582 } 583 catch (DirectoryException e) 584 { 585 // ignore 586 } 587 588 if (newEntry.hasAttribute(attrCompromisedTime)) 589 { 590 // The key was compromised so we should remove it from the local 591 // trust store. 592 if (dstEntry != null) 593 { 594 deleteEntry(dstDN); 595 } 596 } 597 else if (dstEntry == null) 598 { 599 addEntry(newEntry, dstDN); 600 } 601 else 602 { 603 modifyEntry(newEntry, dstEntry); 604 } 605 } 606 } 607}