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    }