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 }