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