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 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.crypto;
028    
029    import org.opends.messages.Message;
030    import static org.opends.messages.CoreMessages.*;
031    
032    import java.io.InputStream;
033    import java.io.IOException;
034    import java.io.OutputStream;
035    import java.io.ByteArrayInputStream;
036    import java.io.PrintStream;
037    import java.security.*;
038    import java.security.cert.Certificate;
039    import java.security.cert.CertificateFactory;
040    import java.util.*;
041    import java.util.concurrent.ConcurrentHashMap;
042    import java.util.concurrent.atomic.AtomicInteger;
043    import java.util.zip.DataFormatException;
044    import java.util.zip.Deflater;
045    import java.util.zip.Inflater;
046    import java.text.ParseException;
047    import javax.crypto.*;
048    import javax.crypto.spec.IvParameterSpec;
049    import javax.crypto.spec.SecretKeySpec;
050    import javax.net.ssl.KeyManager;
051    import javax.net.ssl.TrustManager;
052    import javax.net.ssl.SSLContext;
053    import javax.net.ssl.X509ExtendedKeyManager;
054    
055    import org.opends.admin.ads.ADSContext;
056    import org.opends.server.admin.std.server.CryptoManagerCfg;
057    import org.opends.server.admin.server.ConfigurationChangeListener;
058    import org.opends.server.api.Backend;
059    import org.opends.server.backends.TrustStoreBackend;
060    import org.opends.server.config.ConfigException;
061    import org.opends.server.config.ConfigConstants;
062    import org.opends.server.core.DirectoryServer;
063    import org.opends.server.core.AddOperation;
064    import org.opends.server.core.ModifyOperation;
065    import static org.opends.server.loggers.debug.DebugLogger.*;
066    import org.opends.server.loggers.debug.DebugTracer;
067    import static org.opends.server.util.StaticUtils.*;
068    import org.opends.server.util.Validator;
069    import org.opends.server.util.SelectableCertificateKeyManager;
070    import org.opends.server.util.StaticUtils;
071    import org.opends.server.util.Base64;
072    import org.opends.server.util.ServerConstants;
073    import static org.opends.server.util.ServerConstants.OC_TOP;
074    import org.opends.server.protocols.internal.InternalClientConnection;
075    import org.opends.server.protocols.internal.InternalSearchOperation;
076    import org.opends.server.protocols.asn1.ASN1OctetString;
077    import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
078    import org.opends.server.protocols.ldap.LDAPMessage;
079    import org.opends.server.protocols.ldap.LDAPControl;
080    import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
081    import org.opends.server.protocols.ldap.LDAPResultCode;
082    import org.opends.server.schema.DirectoryStringSyntax;
083    import org.opends.server.schema.IntegerSyntax;
084    import org.opends.server.schema.BinarySyntax;
085    import org.opends.server.tools.LDAPConnection;
086    import org.opends.server.tools.LDAPConnectionOptions;
087    import org.opends.server.tools.LDAPReader;
088    import org.opends.server.tools.LDAPWriter;
089    import org.opends.server.types.*;
090    
091    /**
092     This class implements the Directory Server cryptographic framework,
093     which is described in the
094     <a href="https://www.opends.org/wiki//page/TheCryptoManager">
095     CrytpoManager design document</a>.  {@code CryptoManager} implements
096     inter-OpenDS-instance authentication and authorization using the
097     ADS-based truststore, and secret key distribution. The interface also
098     provides methods for hashing, encryption, and other kinds of
099     cryptographic operations.
100     <p>
101     Note that it also contains methods for compressing and uncompressing
102     data: while these are not strictly cryptographic operations, there
103     are a lot of similarities and it is conceivable at some point that
104     accelerated compression may be available just as it is for
105     cryptographic operations.
106     <p>
107     Other components of CryptoManager:
108     @see "src/admin/defn/org/opends/server/admin/std\
109                                          /CryptoManagerConfiguration.xml"
110     @see org.opends.server.crypto.CryptoManagerSync
111     @see org.opends.server.crypto.GetSymmetricKeyExtendedOperation
112     */
113    public class CryptoManagerImpl
114            implements ConfigurationChangeListener<CryptoManagerCfg>, CryptoManager
115    {
116      /**
117       * The tracer object for the debug logger.
118       */
119      private static final DebugTracer TRACER = getTracer();
120    
121      // Various schema element references.
122      private static AttributeType attrKeyID;
123      private static AttributeType attrPublicKeyCertificate;
124      private static AttributeType attrTransformation;
125      private static AttributeType attrMacAlgorithm;
126      private static AttributeType attrSymmetricKey;
127      private static AttributeType attrInitVectorLength;
128      private static AttributeType attrKeyLength;
129      private static AttributeType attrCompromisedTime;
130      private static ObjectClass   ocCertRequest;
131      private static ObjectClass   ocInstanceKey;
132      private static ObjectClass   ocCipherKey;
133      private static ObjectClass   ocMacKey;
134    
135      // The DN of the local truststore backend.
136      private static DN localTruststoreDN;
137    
138      // The DN of the ADS instance keys container.
139      private static DN instanceKeysDN;
140    
141      // The DN of the ADS secret keys container.
142      private static DN secretKeysDN;
143    
144      // The DN of the ADS servers container.
145      private static DN serversDN;
146    
147      // Indicates whether the schema references have been initialized.
148      private static boolean schemaInitDone = false;
149    
150      // The secure random number generator used for key generation,
151      // initialization vector PRNG seed...
152      private static final SecureRandom secureRandom = new SecureRandom();
153    
154      // The random number generator used for initialization vector
155      // production.
156      private static final Random pseudoRandom
157              = new Random(secureRandom.nextLong());
158    
159      // The first byte in any ciphertext produced by CryptoManager is the
160      // prologue version. At present, this constant is both the version written
161      // and the expected version. If a new version is introduced (e.g., to allow
162      // embedding the HMAC key identifier and signature in a signed backup) the
163      // prologue version will likely need to be configurable at the granularity
164      // of the CryptoManager client (e.g., password encryption might use version 1,
165      // while signed backups might use version 2.
166      private static final int CIPHERTEXT_PROLOGUE_VERSION = 1 ;
167    
168      // The map from encryption key ID to CipherKeyEntry (cache). The
169      // cache is accessed by methods that request, publish, and import
170      // keys.
171      private final Map<KeyEntryID, CipherKeyEntry> cipherKeyEntryCache
172              = new ConcurrentHashMap<KeyEntryID, CipherKeyEntry>();
173    
174      // The map from encryption key ID to MacKeyEntry (cache). The cache
175      // is accessed by methods that request, publish, and import keys.
176      private final Map<KeyEntryID, MacKeyEntry> macKeyEntryCache
177              = new ConcurrentHashMap<KeyEntryID, MacKeyEntry>();
178    
179    
180      // The preferred key wrapping transformation
181      private String preferredKeyWrappingTransformation;
182    
183    
184      // TODO: Move the following configuration to backup or backend configuration.
185      // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2472
186    
187      // The preferred message digest algorithm for the Directory Server.
188      private String preferredDigestAlgorithm;
189    
190      // The preferred cipher for the Directory Server.
191      private String preferredCipherTransformation;
192    
193      // The preferred key length for the preferred cipher.
194      private int preferredCipherTransformationKeyLengthBits;
195    
196      // The preferred MAC algorithm for the Directory Server.
197      private String preferredMACAlgorithm;
198    
199      // The preferred key length for the preferred MAC algorithm.
200      private int preferredMACAlgorithmKeyLengthBits;
201    
202    
203      // TODO: Move the following configuration to replication configuration.
204      // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2473
205    
206      // The name of the local certificate to use for SSL.
207      private final String sslCertNickname;
208    
209      // Whether replication sessions use SSL encryption.
210      private final boolean sslEncryption;
211    
212      // The set of SSL protocols enabled or null for the default set.
213      private final SortedSet<String> sslProtocols;
214    
215      // The set of SSL cipher suites enabled or null for the default set.
216      private final SortedSet<String> sslCipherSuites;
217    
218    
219      /**
220       Creates a new instance of this crypto manager object from a given
221       configuration, plus some static member initialization.
222    
223       @param cfg  The configuration of this crypto manager.
224    
225       @throws ConfigException  If a problem occurs while creating this
226       {@code CryptoManager} that is a result of a problem in the configuration.
227    
228       @throws org.opends.server.types.InitializationException  If a problem
229       occurs while creating this {@code CryptoManager} that is not the result of a
230       problem in the configuration.
231       */
232      public CryptoManagerImpl(CryptoManagerCfg cfg)
233             throws ConfigException, InitializationException {
234        if (!schemaInitDone) {
235          // Initialize various schema references.
236          attrKeyID = DirectoryServer.getAttributeType(
237               ConfigConstants.ATTR_CRYPTO_KEY_ID);
238          attrPublicKeyCertificate = DirectoryServer.getAttributeType(
239               ConfigConstants.ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
240          attrTransformation = DirectoryServer.getAttributeType(
241               ConfigConstants.ATTR_CRYPTO_CIPHER_TRANSFORMATION_NAME);
242          attrMacAlgorithm = DirectoryServer.getAttributeType(
243               ConfigConstants.ATTR_CRYPTO_MAC_ALGORITHM_NAME);
244          attrSymmetricKey = DirectoryServer.getAttributeType(
245               ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY);
246          attrInitVectorLength = DirectoryServer.getAttributeType(
247               ConfigConstants.ATTR_CRYPTO_INIT_VECTOR_LENGTH_BITS);
248          attrKeyLength = DirectoryServer.getAttributeType(
249               ConfigConstants.ATTR_CRYPTO_KEY_LENGTH_BITS);
250          attrCompromisedTime = DirectoryServer.getAttributeType(
251               ConfigConstants.ATTR_CRYPTO_KEY_COMPROMISED_TIME);
252          ocCertRequest = DirectoryServer.getObjectClass(
253                  "ds-cfg-self-signed-cert-request"); // TODO: ConfigConstants
254          ocInstanceKey = DirectoryServer.getObjectClass(
255               ConfigConstants.OC_CRYPTO_INSTANCE_KEY);
256          ocCipherKey = DirectoryServer.getObjectClass(
257               ConfigConstants.OC_CRYPTO_CIPHER_KEY);
258          ocMacKey = DirectoryServer.getObjectClass(
259               ConfigConstants.OC_CRYPTO_MAC_KEY);
260    
261          try {
262            localTruststoreDN
263                    = DN.decode(ConfigConstants.DN_TRUST_STORE_ROOT);
264            DN adminSuffixDN = DN.decode(
265                    ADSContext.getAdministrationSuffixDN());
266            instanceKeysDN = adminSuffixDN.concat(
267                    DN.decode("cn=instance keys"));
268            secretKeysDN = adminSuffixDN.concat(
269                 DN.decode("cn=secret keys"));
270            serversDN = adminSuffixDN.concat(
271                 DN.decode("cn=Servers"));
272          }
273          catch (DirectoryException ex) {
274            if (debugEnabled()) {
275              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
276            }
277            throw new InitializationException(ex.getMessageObject());
278          }
279    
280          schemaInitDone = true;
281        }
282    
283        // CryptoMangager crypto config parameters.
284        List<Message> why = new LinkedList<Message>();
285        if (! isConfigurationChangeAcceptable(cfg, why)) {
286          throw new InitializationException(why.get(0));
287        }
288        applyConfigurationChange(cfg);
289    
290        // Secure replication related...
291        sslCertNickname = cfg.getSSLCertNickname();
292        sslEncryption   = cfg.isSSLEncryption();
293        sslProtocols    = cfg.getSSLProtocol();
294        sslCipherSuites = cfg.getSSLCipherSuite();
295    
296        // Register as a configuration change listener.
297        cfg.addChangeListener(this);
298      }
299    
300    
301      /**
302       * {@inheritDoc}
303       */
304      public boolean isConfigurationChangeAcceptable(
305           CryptoManagerCfg cfg,
306           List<Message> unacceptableReasons)
307      {
308        // Acceptable until we find an error.
309        boolean isAcceptable = true;
310    
311        // Requested digest validation.
312        String requestedDigestAlgorithm =
313             cfg.getDigestAlgorithm();
314        if (! requestedDigestAlgorithm.equals(this.preferredDigestAlgorithm))
315        {
316          try{
317            MessageDigest.getInstance(requestedDigestAlgorithm);
318          }
319          catch (Exception ex) {
320            if (debugEnabled()) {
321              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
322            }
323            unacceptableReasons.add(
324                 ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_DIGEST.get(
325                      requestedDigestAlgorithm, getExceptionMessage(ex)));
326            isAcceptable = false;
327          }
328        }
329    
330        // Requested encryption cipher validation.
331        String requestedCipherTransformation =
332             cfg.getCipherTransformation();
333        Integer requestedCipherTransformationKeyLengthBits =
334             cfg.getCipherKeyLength();
335        if (! requestedCipherTransformation.equals(
336                this.preferredCipherTransformation) ||
337            requestedCipherTransformationKeyLengthBits !=
338                this.preferredCipherTransformationKeyLengthBits) {
339          if (3 != requestedCipherTransformation.split("/",0).length) {
340            unacceptableReasons.add(
341                    ERR_CRYPTOMGR_FULL_CIPHER_TRANSFORMATION_REQUIRED.get(
342                            requestedCipherTransformation));
343            isAcceptable = false;
344          }
345          else {
346            try {
347              CipherKeyEntry.generateKeyEntry(null,
348                      requestedCipherTransformation,
349                      requestedCipherTransformationKeyLengthBits);
350            }
351            catch (Exception ex) {
352              if (debugEnabled()) {
353                TRACER.debugCaught(DebugLogLevel.ERROR, ex);
354              }
355              unacceptableReasons.add(
356                 ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_ENCRYPTION_CIPHER.get(
357                         requestedCipherTransformation, getExceptionMessage(ex)));
358              isAcceptable = false;
359            }
360          }
361        }
362    
363        // Requested MAC algorithm validation.
364        String requestedMACAlgorithm = cfg.getMacAlgorithm();
365        Integer requestedMACAlgorithmKeyLengthBits =
366             cfg.getMacKeyLength();
367        if (!requestedMACAlgorithm.equals(this.preferredMACAlgorithm) ||
368             requestedMACAlgorithmKeyLengthBits !=
369                  this.preferredMACAlgorithmKeyLengthBits)
370        {
371          try {
372            MacKeyEntry.generateKeyEntry(
373                 null,
374                 requestedMACAlgorithm,
375                 requestedMACAlgorithmKeyLengthBits);
376          }
377          catch (Exception ex) {
378            if (debugEnabled()) {
379              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
380            }
381            unacceptableReasons.add(
382                    ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_MAC_ENGINE.get(
383                            requestedMACAlgorithm, getExceptionMessage(ex)));
384            isAcceptable = false;
385          }
386        }
387        // Requested secret key wrapping cipher and validation. Validation
388        // depends on MAC cipher for secret key.
389        String requestedKeyWrappingTransformation
390                = cfg.getKeyWrappingTransformation();
391        if (! requestedKeyWrappingTransformation.equals(
392                this.preferredKeyWrappingTransformation)) {
393          if (3 != requestedKeyWrappingTransformation.split("/", 0).length) {
394            unacceptableReasons.add(
395                    ERR_CRYPTOMGR_FULL_KEY_WRAPPING_TRANSFORMATION_REQUIRED.get(
396                            requestedKeyWrappingTransformation));
397            isAcceptable = false;
398          }
399          else {
400            try {
401              /* Note that the TrustStoreBackend not available at initial,
402             CryptoManager configuration, hence a "dummy" certificate must be used
403             to validate the choice of secret key wrapping cipher. Otherwise, call
404             getInstanceKeyCertificateFromLocalTruststore() */
405              final String certificateBase64 =
406                    "MIIB2jCCAUMCBEb7wpYwDQYJKoZIhvcNAQEEBQAwNDEbMBkGA1UEChMST3B" +
407                    "lbkRTIENlcnRpZmljYXRlMRUwEwYDVQQDEwwxMC4wLjI0OC4yNTEwHhcNMD" +
408                    "cwOTI3MTQ0NzUwWhcNMjcwOTIyMTQ0NzUwWjA0MRswGQYDVQQKExJPcGVuR" +
409                    "FMgQ2VydGlmaWNhdGUxFTATBgNVBAMTDDEwLjAuMjQ4LjI1MTCBnzANBgkq" +
410                    "hkiG9w0BAQEFAAOBjQAwgYkCgYEAnIm6ELyuNVbpaacBQ7fzHlHMmQO/CYJ" +
411                    "b2gPTdb9n1HLOBqh2lmLLHvt2SgBeN5TSa1PAHW8zJy9LDhpWKZvsUOIdQD" +
412                    "8Ula/0d/jvMEByEj/hr00P6yqgLXk+EudPgOkFXHA+IfkkOSghMooWc/L8H" +
413                    "nD1REdqeZuxp+ARNU+cc/ECAwEAATANBgkqhkiG9w0BAQQFAAOBgQBemyCU" +
414                    "jucN34MZwvzbmFHT/leUu3/cpykbGM9HL2QUX7iKvv2LJVqexhj7CLoXxZP" +
415                    "oNL+HHKW0vi5/7W5KwOZsPqKI2SdYV7nDqTZklm5ZP0gmIuNO6mTqBRtC2D" +
416                    "lplX1Iq+BrQJAmteiPtwhdZD+EIghe51CaseImjlLlY2ZK8w==";
417              final byte[] certificate = Base64.decode(certificateBase64);
418              final String keyID = getInstanceKeyID(certificate);
419              final SecretKey macKey = MacKeyEntry.generateKeyEntry(null,
420                      requestedMACAlgorithm,
421                      requestedMACAlgorithmKeyLengthBits).getSecretKey();
422              encodeSymmetricKeyAttribute(requestedKeyWrappingTransformation,
423                      keyID, certificate, macKey);
424            }
425            catch (Exception ex) {
426              if (debugEnabled()) {
427                TRACER.debugCaught(DebugLogLevel.ERROR, ex);
428              }
429              unacceptableReasons.add(
430                      ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.get(
431                              getExceptionMessage(ex)));
432              isAcceptable = false;
433            }
434          }
435        }
436        return isAcceptable;
437      }
438    
439    
440      /**
441       * {@inheritDoc}
442       */
443      public ConfigChangeResult applyConfigurationChange(
444           CryptoManagerCfg cfg)
445      {
446        ResultCode resultCode = ResultCode.SUCCESS;
447        boolean adminActionRequired = false;
448        List<Message> messages = new ArrayList<Message>();
449    
450        preferredDigestAlgorithm = cfg.getDigestAlgorithm();
451        preferredMACAlgorithm = cfg.getMacAlgorithm();
452        preferredMACAlgorithmKeyLengthBits = cfg.getMacKeyLength();
453        preferredCipherTransformation = cfg.getCipherTransformation();
454        preferredCipherTransformationKeyLengthBits = cfg.getCipherKeyLength();
455        preferredKeyWrappingTransformation = cfg.getKeyWrappingTransformation();
456        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
457      }
458    
459    
460      /**
461       * Retrieve the ADS trust store backend.
462       * @return The ADS trust store backend.
463       * @throws ConfigException If the ADS trust store backend is
464       *                         not configured.
465       */
466      private TrustStoreBackend getTrustStoreBackend()
467           throws ConfigException
468      {
469        Backend b = DirectoryServer.getBackend(
470             ConfigConstants.ID_ADS_TRUST_STORE_BACKEND);
471        if (b == null)
472        {
473          Message msg =
474               ERR_CRYPTOMGR_ADS_TRUST_STORE_BACKEND_NOT_ENABLED.get(
475                    ConfigConstants.ID_ADS_TRUST_STORE_BACKEND);
476          throw new ConfigException(msg);
477        }
478        if (!(b instanceof TrustStoreBackend))
479        {
480          Message msg =
481               ERR_CRYPTOMGR_ADS_TRUST_STORE_BACKEND_WRONG_CLASS.get(
482                    ConfigConstants.ID_ADS_TRUST_STORE_BACKEND);
483          throw new ConfigException(msg);
484        }
485        return (TrustStoreBackend)b;
486      }
487    
488    
489      /**
490       * Returns this instance's instance-key public-key certificate from
491       * the local keystore (i.e., from the truststore-backend and not
492       * from the ADS backed keystore). If the certificate entry does not
493       * yet exist in the truststore backend, the truststore is signaled
494       * to initialized that entry, and the newly generated certificate
495       * is then retrieved and returned.
496       * @return This instance's instance-key public-key certificate from
497       * the local truststore backend.
498       * @throws CryptoManagerException If the certificate cannot be
499       * retrieved.
500       */
501      static byte[] getInstanceKeyCertificateFromLocalTruststore()
502              throws CryptoManagerException {
503        // Construct the key entry DN.
504        final AttributeValue distinguishedValue = new AttributeValue(
505                attrKeyID, ConfigConstants.ADS_CERTIFICATE_ALIAS);
506        final DN entryDN = localTruststoreDN.concat(
507                RDN.create(attrKeyID, distinguishedValue));
508        // Construct the search filter.
509        final String FILTER_OC_INSTANCE_KEY =
510                new StringBuilder("(objectclass=")
511                        .append(ocInstanceKey.getNameOrOID())
512                        .append(")").toString();
513        // Construct the attribute list.
514        final LinkedHashSet<String> requestedAttributes
515                = new LinkedHashSet<String>();
516        requestedAttributes.add(
517                attrPublicKeyCertificate.getNameOrOID() + ";binary");
518    
519        // Retrieve the certificate from the entry.
520        final InternalClientConnection icc
521                = InternalClientConnection.getRootConnection();
522        byte[] certificate = null;
523        try {
524          for (int i = 0; i < 2; ++i) {
525            try {
526              /* If the entry does not exist in the instance's truststore
527                 backend, add it using a special object class that induces
528                 the backend to create the public-key certificate
529                 attribute, then repeat the search. */
530              InternalSearchOperation searchOp = icc.processSearch(
531                      entryDN,
532                      SearchScope.BASE_OBJECT,
533                      DereferencePolicy.NEVER_DEREF_ALIASES,
534                      /* size limit */ 0, /* time limit */ 0,
535                      /* types only */ false,
536                      SearchFilter.createFilterFromString(
537                              FILTER_OC_INSTANCE_KEY),
538                      requestedAttributes);
539              for (Entry e : searchOp.getSearchEntries()) {
540                /* attribute ds-cfg-public-key-certificate is a MUST in
541                   the schema */
542                certificate = e.getAttributeValue(
543                        attrPublicKeyCertificate, BinarySyntax.DECODER);
544              }
545              break;
546            }
547            catch (DirectoryException ex) {
548              if (0 == i
549                      && ResultCode.NO_SUCH_OBJECT == ex.getResultCode()){
550                final Entry entry = new Entry(entryDN, null, null, null);
551                entry.addObjectClass(DirectoryServer.getTopObjectClass());
552                entry.addObjectClass(ocCertRequest);
553                AddOperation addOperation = icc.processAdd(entry.getDN(),
554                        entry.getObjectClasses(),
555                        entry.getUserAttributes(),
556                        entry.getOperationalAttributes());
557                if (ResultCode.SUCCESS != addOperation.getResultCode()) {
558                  throw new DirectoryException(
559                          addOperation.getResultCode(),
560             ERR_CRYPTOMGR_FAILED_TO_INITIATE_INSTANCE_KEY_GENERATION.get(
561                     entry.getDN().toString()));
562                }
563              }
564              else {
565                throw ex;
566              }
567            }
568          }
569        }
570        catch (DirectoryException ex) {
571          if (debugEnabled()) {
572            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
573          }
574          throw new CryptoManagerException(
575                ERR_CRYPTOMGR_FAILED_TO_RETRIEVE_INSTANCE_CERTIFICATE.get(
576                        entryDN.toString(), getExceptionMessage(ex)), ex);
577        }
578        return(certificate);
579      }
580    
581    
582      /**
583       * Return the identifier of this instance's instance-key. An
584       * instance-key identifier is a hex string of the MD5 hash of an
585       * instance's instance-key public-key certificate.
586       * @see #getInstanceKeyID(byte[])
587       * @return This instance's instance-key identifier.
588       * @throws CryptoManagerException If there is a problem retrieving
589       * the instance-key public-key certificate or computing its MD5
590       * hash.
591       */
592      String getInstanceKeyID()
593              throws CryptoManagerException {
594        return getInstanceKeyID(
595                getInstanceKeyCertificateFromLocalTruststore());
596      }
597    
598    
599      /**
600       * Return the identifier of an instance's instance key. An
601       * instance-key identifier is a hex string of the MD5 hash of an
602       * instance's instance-key public-key certificate.
603       * @see #getInstanceKeyID()
604       * @param instanceKeyCertificate The instance key for which to
605       * return an identifier.
606       * @return The identifier of the supplied instance key.
607       * @throws CryptoManagerException If there is a problem computing
608       * the identifier from the instance key.
609       *
610       * TODO: Make package-private if ADSContextHelper can get keyID from ADS
611       * TODO: suffix: Issue https://opends.dev.java.net/issues/show_bug.cgi?id=2442
612       */
613      public static String getInstanceKeyID(byte[] instanceKeyCertificate)
614                throws CryptoManagerException {
615        MessageDigest md;
616        final String mdAlgorithmName = "MD5";
617        try {
618          md = MessageDigest.getInstance(mdAlgorithmName);
619        }
620        catch (NoSuchAlgorithmException ex) {
621          if (debugEnabled()) {
622            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
623          }
624          throw new CryptoManagerException(
625              ERR_CRYPTOMGR_FAILED_TO_COMPUTE_INSTANCE_KEY_IDENTIFIER.get(
626                      getExceptionMessage(ex)), ex);
627        }
628        return StaticUtils.bytesToHexNoSpace(
629             md.digest(instanceKeyCertificate));
630      }
631    
632    
633      /**
634       Publishes the instance key entry in ADS, if it does not already
635       exist.
636    
637       @throws CryptoManagerException In case there is a problem
638       searching for the entry, or, if necessary, adding it.
639       */
640      static void publishInstanceKeyEntryInADS()
641              throws CryptoManagerException {
642        final byte[] instanceKeyCertificate
643                = getInstanceKeyCertificateFromLocalTruststore();
644        final String instanceKeyID
645                = getInstanceKeyID(instanceKeyCertificate);
646        // Construct the key entry DN.
647        final AttributeValue distinguishedValue =
648                new AttributeValue(attrKeyID, instanceKeyID);
649        final DN entryDN = instanceKeysDN.concat(
650             RDN.create(attrKeyID, distinguishedValue));
651        // Construct the search filter.
652        final String FILTER_OC_INSTANCE_KEY =
653                new StringBuilder("(objectclass=")
654                        .append(ocInstanceKey.getNameOrOID())
655                        .append(")").toString();
656        // Construct the attribute list.
657        final LinkedHashSet<String> requestedAttributes
658                = new LinkedHashSet<String>();
659        requestedAttributes.add("dn");
660    
661        // Check for the entry. If it does not exist, create it.
662        final InternalClientConnection icc
663                = InternalClientConnection.getRootConnection();
664        try {
665          final InternalSearchOperation searchOp
666                  = icc.processSearch( entryDN, SearchScope.BASE_OBJECT,
667                  DereferencePolicy.NEVER_DEREF_ALIASES,
668                  /* size limit */ 0, /* time limit */ 0,
669                  /* types only */ false,
670                  SearchFilter.createFilterFromString(
671                          FILTER_OC_INSTANCE_KEY),
672                  requestedAttributes);
673          if (0 == searchOp.getSearchEntries().size()) {
674            final Entry entry = new Entry(entryDN, null, null, null);
675            entry.addObjectClass(DirectoryServer.getTopObjectClass());
676            entry.addObjectClass(ocInstanceKey);
677            // Add the key ID attribute.
678            final LinkedHashSet<AttributeValue> keyIDValueSet =
679                    new LinkedHashSet<AttributeValue>(1);
680            keyIDValueSet.add(distinguishedValue);
681            final Attribute keyIDAttr = new Attribute(
682                    attrKeyID,
683                    attrKeyID.getNameOrOID(),
684                    keyIDValueSet);
685            entry.addAttribute(keyIDAttr,
686                    new ArrayList<AttributeValue>(0));
687            // Add the public key certificate attribute.
688            final LinkedHashSet<AttributeValue> certificateValueSet =
689                    new LinkedHashSet<AttributeValue>(1);
690            final AttributeValue certificateValue = new AttributeValue(
691                    attrPublicKeyCertificate,
692                    ByteStringFactory.create(instanceKeyCertificate));
693            certificateValueSet.add(certificateValue);
694            final LinkedHashSet<String> certificateOptions =
695                    new LinkedHashSet<String>(1);
696            certificateOptions.add("binary");
697            final Attribute certificateAttr = new Attribute(
698                    attrPublicKeyCertificate,
699                    attrPublicKeyCertificate.getNameOrOID(),
700                    certificateOptions,
701                    certificateValueSet);
702            entry.addAttribute(certificateAttr,
703                    new ArrayList<AttributeValue>(0));
704    
705            AddOperation addOperation = icc.processAdd(entry.getDN(),
706                    entry.getObjectClasses(),
707                    entry.getUserAttributes(),
708                    entry.getOperationalAttributes());
709            if (ResultCode.SUCCESS != addOperation.getResultCode()) {
710              throw new DirectoryException(
711                      addOperation.getResultCode(),
712                ERR_CRYPTOMGR_FAILED_TO_ADD_INSTANCE_KEY_ENTRY_TO_ADS.get(
713                        entry.getDN().toString()));
714            }
715          }
716        } catch (DirectoryException ex) {
717          if (debugEnabled()) {
718            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
719          }
720          throw new CryptoManagerException(
721                  ERR_CRYPTOMGR_FAILED_TO_PUBLISH_INSTANCE_KEY_ENTRY.get(
722                          getExceptionMessage(ex)), ex);
723        }
724      }
725    
726    
727      /**
728       Return the set of valid (i.e., not tagged as compromised) instance
729       key-pair public-key certificate entries in ADS.
730       @return The set of valid (i.e., not tagged as compromised) instance
731       key-pair public-key certificate entries in ADS represented as a Map
732       from ds-cfg-key-id value to ds-cfg-public-key-certificate value.
733       Note that the collection might be empty.
734       @throws CryptoManagerException  In case of a problem with the
735       search operation.
736       @see org.opends.admin.ads.ADSContext#getTrustedCertificates()
737       */
738      private Map<String, byte[]> getTrustedCertificates()
739              throws CryptoManagerException {
740        final Map<String, byte[]> certificateMap
741                = new HashMap<String, byte[]>();
742        try {
743          // Construct the search filter.
744          final String FILTER_OC_INSTANCE_KEY
745                  = new StringBuilder("(objectclass=")
746                  .append(ocInstanceKey.getNameOrOID())
747                  .append(")").toString();
748          final String FILTER_NOT_COMPROMISED = new StringBuilder("(!(")
749                  .append(attrCompromisedTime.getNameOrOID())
750                  .append("=*))").toString();
751          final String searchFilter = new StringBuilder("(&")
752                  .append(FILTER_OC_INSTANCE_KEY)
753                  .append(FILTER_NOT_COMPROMISED)
754                  .append(")").toString();
755          // Construct the attribute list.
756          final LinkedHashSet<String> requestedAttributes
757                  = new LinkedHashSet<String>();
758          requestedAttributes.add(attrKeyID.getNameOrOID());
759          requestedAttributes.add(
760                  attrPublicKeyCertificate.getNameOrOID() + ";binary");
761          // Invoke the search operation.
762          final InternalClientConnection icc
763                  = InternalClientConnection.getRootConnection();
764          InternalSearchOperation searchOp = icc.processSearch(
765                  instanceKeysDN,
766                  SearchScope.SINGLE_LEVEL,
767                  DereferencePolicy.NEVER_DEREF_ALIASES,
768                  /* size limit */ 0, /* time limit */ 0,
769                  /* types only */ false,
770                  SearchFilter.createFilterFromString(searchFilter),
771                  requestedAttributes);
772          // Evaluate the search response.
773          for (Entry e : searchOp.getSearchEntries()) {
774            /* attribute ds-cfg-key-id is the RDN and attribute
775               ds-cfg-public-key-certificate is a MUST in the schema */
776            final String keyID = e.getAttributeValue(
777                    attrKeyID, DirectoryStringSyntax.DECODER);
778            final byte[] certificate = e.getAttributeValue(
779                    attrPublicKeyCertificate, BinarySyntax.DECODER);
780            certificateMap.put(keyID, certificate);
781          }
782        }
783        catch (DirectoryException ex) {
784          if (debugEnabled()) {
785            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
786          }
787          throw new CryptoManagerException(
788                ERR_CRYPTOMGR_FAILED_TO_RETRIEVE_ADS_TRUSTSTORE_CERTS.get(
789                        instanceKeysDN.toString(),
790                        getExceptionMessage(ex)), ex);
791        }
792        return(certificateMap);
793      }
794    
795    
796      /**
797       * Encodes a ds-cfg-symmetric-key attribute value with the preferred
798       * key wrapping transformation and using the supplied arguments.
799       *
800       * The syntax of the ds-cfg-symmetric-key attribute:
801       * <pre>
802       * wrappingKeyID:wrappingTransformation:wrappedKeyAlgorithm:\
803       * wrappedKeyType:hexWrappedKey
804       *
805       * wrappingKeyID ::= hexBytes[16]
806       * wrappingTransformation
807       *                   ::= e.g., RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING
808       * wrappedKeyAlgorithm ::= e.g., DESede
809       * hexifiedwrappedKey ::= 0123456789abcdef01...
810       * </pre>
811       *
812       * @param wrappingKeyID The key identifier of the wrapping key. This
813       * parameter is the first field in the encoded value and identifies
814       * the instance that will be able to unwrap the secret key.
815       *
816       * @param wrappingKeyCertificateData The public key certificate used
817       * to derive the wrapping key.
818       *
819       * @param secretKey The secret key value to be wrapped for the
820       * encoded value.
821       *
822       * @return The encoded representation of the ds-cfg-symmetric-key
823       * attribute with the secret key wrapped with the supplied public
824       * key.
825       *
826       * @throws CryptoManagerException  If there is a problem wrapping
827       * the secret key.
828       */
829      private String encodeSymmetricKeyAttribute(
830              final String wrappingKeyID,
831              final byte[] wrappingKeyCertificateData,
832              final SecretKey secretKey)
833              throws CryptoManagerException {
834        return encodeSymmetricKeyAttribute(
835                preferredKeyWrappingTransformation,
836             wrappingKeyID,
837             wrappingKeyCertificateData,
838             secretKey);
839      }
840    
841    
842      /**
843       * Encodes a ds-cfg-symmetric-key attribute value with a specified
844       * key wrapping transformation and using the supplied arguments.
845       *
846       * @param wrappingTransformationName The name of the key wrapping
847       * transformation.
848       *
849       * @param wrappingKeyID The key identifier of the wrapping key. This
850       * parameter is the first field in the encoded value and identifies
851       * the instance that will be able to unwrap the secret key.
852       *
853       * @param wrappingKeyCertificateData The public key certificate used
854       * to derive the wrapping key.
855       *
856       * @param secretKey The secret key value to be wrapped for the
857       * encoded value.
858       *
859       * @return The encoded representation of the ds-cfg-symmetric-key
860       * attribute with the secret key wrapped with the supplied public
861       * key.
862       *
863       * @throws CryptoManagerException  If there is a problem wrapping
864       * the secret key.
865       */
866      private String encodeSymmetricKeyAttribute(
867              final String wrappingTransformationName,
868              final String wrappingKeyID,
869              final byte[] wrappingKeyCertificateData,
870              final SecretKey secretKey)
871              throws CryptoManagerException {
872        // Wrap secret key.
873        String wrappedKeyElement;
874        try {
875          final CertificateFactory cf
876                  = CertificateFactory.getInstance("X.509");
877          final Certificate certificate = cf.generateCertificate(
878                  new ByteArrayInputStream(wrappingKeyCertificateData));
879          final Cipher wrapper
880                  = Cipher.getInstance(wrappingTransformationName);
881          wrapper.init(Cipher.WRAP_MODE, certificate);
882          byte[] wrappedKey = wrapper.wrap(secretKey);
883          wrappedKeyElement = StaticUtils.bytesToHexNoSpace(wrappedKey);
884        }
885        catch (GeneralSecurityException ex) {
886          if (debugEnabled()) {
887            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
888          }
889          throw new CryptoManagerException(
890               ERR_CRYPTOMGR_FAILED_TO_ENCODE_SYMMETRIC_KEY_ATTRIBUTE.get(
891                       getExceptionMessage(ex)), ex);
892        }
893    
894        // Compose ds-cfg-symmetric-key value.
895        StringBuilder symmetricKeyAttribute = new StringBuilder();
896        symmetricKeyAttribute.append(wrappingKeyID);
897        symmetricKeyAttribute.append(":");
898        symmetricKeyAttribute.append(wrappingTransformationName);
899        symmetricKeyAttribute.append(":");
900        symmetricKeyAttribute.append(secretKey.getAlgorithm());
901        symmetricKeyAttribute.append(":");
902        symmetricKeyAttribute.append(wrappedKeyElement);
903    
904        return symmetricKeyAttribute.toString();
905      }
906    
907    
908      /**
909       * Takes an encoded ds-cfg-symmetric-key attribute value and the
910       * associated key algorithm name, and returns an initialized
911       * {@code java.security.Key} object.
912       * @param symmetricKeyAttribute The encoded
913       * ds-cfg-symmetric-key-attribute value.
914       * @return A SecretKey object instantiated with the key data,
915       * algorithm, and Ciper.SECRET_KEY type, or {@code null} if the
916       * supplied symmetricKeyAttribute was encoded for another instance.
917       * @throws CryptoManagerException If there is a problem decomposing
918       * the supplied attribute value or unwrapping the encoded key.
919       */
920      private SecretKey decodeSymmetricKeyAttribute(
921              final String symmetricKeyAttribute)
922              throws CryptoManagerException {
923        // Initial decomposition.
924        String[] elements = symmetricKeyAttribute.split(":", 0);
925        if (4 != elements.length) {
926          throw new CryptoManagerException(
927             ERR_CRYPTOMGR_DECODE_SYMMETRIC_KEY_ATTRIBUTE_FIELD_COUNT.get(
928                      symmetricKeyAttribute));
929         }
930    
931        // Parse individual fields.
932        String wrappingKeyIDElement;
933        String wrappingTransformationElement;
934        String wrappedKeyAlgorithmElement;
935        byte[] wrappedKeyCipherTextElement;
936        String fieldName = null;
937        try {
938          fieldName = "instance key identifier";
939          wrappingKeyIDElement = elements[0];
940          fieldName = "key wrapping transformation";
941          wrappingTransformationElement = elements[1];
942          fieldName = "wrapped key algorithm";
943          wrappedKeyAlgorithmElement = elements[2];
944          fieldName = "wrapped key data";
945          wrappedKeyCipherTextElement
946                  = StaticUtils.hexStringToByteArray(elements[3]);
947        }
948        catch (ParseException ex) {
949          if (debugEnabled()) {
950            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
951          }
952          throw new CryptoManagerException(
953                  ERR_CRYPTOMGR_DECODE_SYMMETRIC_KEY_ATTRIBUTE_SYNTAX.get(
954                          symmetricKeyAttribute, fieldName,
955                          ex.getErrorOffset()), ex);
956        }
957    
958        // Confirm key can be unwrapped at this instance.
959        final String instanceKeyID = getInstanceKeyID();
960        if (! wrappingKeyIDElement.equals(instanceKeyID)) {
961          return null;
962        }
963    
964        // Retrieve instance-key-pair private key part.
965        PrivateKey privateKey;
966        try {
967          privateKey = (PrivateKey)getTrustStoreBackend()
968                  .getKey(ConfigConstants.ADS_CERTIFICATE_ALIAS);
969        }
970        catch (IdentifiedException ex) {
971          // ConfigException, DirectoryException
972          if (debugEnabled()) {
973            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
974          }
975          throw new CryptoManagerException(
976              ERR_CRYPTOMGR_DECODE_SYMMETRIC_KEY_ATTRIBUTE_NO_PRIVATE.get(
977                      getExceptionMessage(ex)), ex);
978        }
979    
980        // Unwrap secret key.
981        SecretKey secretKey;
982        try {
983          final Cipher unwrapper
984                  = Cipher.getInstance(wrappingTransformationElement);
985          unwrapper.init(Cipher.UNWRAP_MODE, privateKey);
986          secretKey = (SecretKey)unwrapper.unwrap(wrappedKeyCipherTextElement,
987                  wrappedKeyAlgorithmElement, Cipher.SECRET_KEY);
988        } catch(GeneralSecurityException ex) {
989          if (debugEnabled()) {
990            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
991          }
992          throw new CryptoManagerException(
993                ERR_CRYPTOMGR_DECODE_SYMMETRIC_KEY_ATTRIBUTE_DECIPHER.get(
994                        getExceptionMessage(ex)), ex);
995        }
996    
997        return secretKey;
998      }
999    
1000    
1001      /**
1002       * Decodes the supplied symmetric key attribute value and re-encodes
1003       * it with the public key referred to by the requested instance key
1004       * identifier. The symmetric key attribute must be wrapped in this
1005       * instance's instance-key-pair public key.
1006       * @param symmetricKeyAttribute The symmetric key attribute value to
1007       * unwrap and rewrap.
1008       * @param requestedInstanceKeyID The key identifier of the public
1009       * key to use in the re-wrapping.
1010       * @return The symmetric key attribute value with the symmetric key
1011       * re-wrapped in the requested public key.
1012       * @throws CryptoManagerException If there is a problem decoding
1013       * the supplied symmetric key attribute value, unwrapping the
1014       * embedded secret key, or retrieving the requested public key.
1015       */
1016      String reencodeSymmetricKeyAttribute(
1017              final String symmetricKeyAttribute,
1018              final String requestedInstanceKeyID)
1019              throws CryptoManagerException {
1020        final SecretKey secretKey
1021                = decodeSymmetricKeyAttribute(symmetricKeyAttribute);
1022        final Map<String, byte[]> certMap = getTrustedCertificates();
1023        if (! (certMap.containsKey(requestedInstanceKeyID)
1024                && null != certMap.get(requestedInstanceKeyID))) {
1025          throw new CryptoManagerException(
1026              ERR_CRYPTOMGR_REWRAP_SYMMETRIC_KEY_ATTRIBUTE_NO_WRAPPER.get(
1027                      requestedInstanceKeyID));
1028        }
1029        final byte[] wrappingKeyCert =
1030                certMap.get(requestedInstanceKeyID);
1031        return encodeSymmetricKeyAttribute(
1032                preferredKeyWrappingTransformation,
1033             requestedInstanceKeyID, wrappingKeyCert, secretKey);
1034      }
1035    
1036    
1037      /**
1038       * Given a set of other servers' symmetric key values for
1039       * a given secret key, use the Get Symmetric Key extended
1040       * operation to request this server's symmetric key value.
1041       *
1042       * @param  symmetricKeys  The known symmetric key values for
1043       *                        a given secret key.
1044       *
1045       * @return The symmetric key value for this server, or null if
1046       *         none could be obtained.
1047       */
1048      private String getSymmetricKey(List<String> symmetricKeys)
1049      {
1050        InternalClientConnection internalConnection =
1051             InternalClientConnection.getRootConnection();
1052        for (String symmetricKey : symmetricKeys)
1053        {
1054          try
1055          {
1056            // Get the server instance key ID from the symmetric key.
1057            String[] elements = symmetricKey.split(":", 0);
1058            String instanceKeyID = elements[0];
1059    
1060            // Find the server entry from the instance key ID.
1061            String filter = "(" +
1062                 ConfigConstants.ATTR_CRYPTO_KEY_ID + "=" +
1063                 instanceKeyID + ")";
1064            InternalSearchOperation internalSearch =
1065                 internalConnection.processSearch(
1066                      serversDN, SearchScope.SUBORDINATE_SUBTREE,
1067                      SearchFilter.createFilterFromString(filter));
1068            if (internalSearch.getResultCode() != ResultCode.SUCCESS)
1069              continue;
1070    
1071            LinkedList<SearchResultEntry> resultEntries =
1072                 internalSearch.getSearchEntries();
1073            for (SearchResultEntry resultEntry : resultEntries)
1074            {
1075              AttributeType hostnameAttr =
1076                   DirectoryServer.getAttributeType("hostname", true);
1077              String hostname = resultEntry.getAttributeValue(
1078                   hostnameAttr, DirectoryStringSyntax.DECODER);
1079              AttributeType ldapPortAttr =
1080                   DirectoryServer.getAttributeType("ldapport", true);
1081              Integer ldapPort = resultEntry.getAttributeValue(
1082                   ldapPortAttr, IntegerSyntax.DECODER);
1083    
1084              // Connect to the server.
1085              AtomicInteger nextMessageID = new AtomicInteger(1);
1086              LDAPConnectionOptions connectionOptions =
1087                   new LDAPConnectionOptions();
1088              PrintStream nullPrintStream =
1089                   new PrintStream(new OutputStream() {
1090                     public void write ( int b ) { }
1091                   });
1092              LDAPConnection connection =
1093                   new LDAPConnection(hostname, ldapPort,
1094                                      connectionOptions,
1095                                      nullPrintStream,
1096                                      nullPrintStream);
1097    
1098              connection.connectToHost(null, null, nextMessageID);
1099    
1100              try
1101              {
1102                LDAPReader reader = connection.getLDAPReader();
1103                LDAPWriter writer = connection.getLDAPWriter();
1104    
1105                // Send the Get Symmetric Key extended request.
1106    
1107                ASN1OctetString requestValue =
1108                     GetSymmetricKeyExtendedOperation.encodeRequestValue(
1109                          symmetricKey, getInstanceKeyID());
1110    
1111                ExtendedRequestProtocolOp extendedRequest =
1112                     new ExtendedRequestProtocolOp(
1113                          ServerConstants.
1114                               OID_GET_SYMMETRIC_KEY_EXTENDED_OP,
1115                          requestValue);
1116    
1117                ArrayList<LDAPControl> controls =
1118                     new ArrayList<LDAPControl>();
1119                LDAPMessage requestMessage =
1120                     new LDAPMessage(nextMessageID.getAndIncrement(),
1121                                     extendedRequest, controls);
1122                writer.writeMessage(requestMessage);
1123                LDAPMessage responseMessage = reader.readMessage();
1124    
1125                ExtendedResponseProtocolOp extendedResponse =
1126                     responseMessage.getExtendedResponseProtocolOp();
1127                if (extendedResponse.getResultCode() ==
1128                     LDAPResultCode.SUCCESS)
1129                {
1130                  // Got our symmetric key value.
1131                  return extendedResponse.getValue().stringValue();
1132                }
1133              }
1134              finally
1135              {
1136                connection.close(nextMessageID);
1137              }
1138            }
1139          }
1140          catch (Exception e)
1141          {
1142            // Just try another server.
1143          }
1144        }
1145    
1146        // Give up.
1147        return null;
1148      }
1149    
1150    
1151      /**
1152       * Imports a cipher key entry from an entry in ADS.
1153       *
1154       * @param entry  The ADS cipher key entry to be imported.
1155       *               The entry will be ignored if it does not have
1156       *               the ds-cfg-cipher-key objectclass, or if the
1157       *               key is already present.
1158       *
1159       * @throws CryptoManagerException
1160       *               If the entry had the correct objectclass,
1161       *               was not already present but could not
1162       *               be imported.
1163       */
1164      void importCipherKeyEntry(Entry entry)
1165           throws CryptoManagerException
1166      {
1167        // Ignore the entry if it does not have the appropriate
1168        // objectclass.
1169        if (!entry.hasObjectClass(ocCipherKey)) return;
1170    
1171        try
1172        {
1173          String keyID =
1174               entry.getAttributeValue(attrKeyID,
1175                                       DirectoryStringSyntax.DECODER);
1176          int ivLengthBits =
1177               entry.getAttributeValue(attrInitVectorLength,
1178                                       IntegerSyntax.DECODER);
1179          int keyLengthBits =
1180               entry.getAttributeValue(attrKeyLength,
1181                                       IntegerSyntax.DECODER);
1182          String transformation =
1183               entry.getAttributeValue(attrTransformation,
1184                                       DirectoryStringSyntax.DECODER);
1185          String compromisedTime =
1186               entry.getAttributeValue(attrCompromisedTime,
1187                                       DirectoryStringSyntax.DECODER);
1188    
1189          boolean isCompromised = compromisedTime != null;
1190    
1191          ArrayList<String> symmetricKeys = new ArrayList<String>();
1192          entry.getAttributeValues(attrSymmetricKey,
1193                                 DirectoryStringSyntax.DECODER,
1194                                 symmetricKeys);
1195    
1196          // Find the symmetric key value that was wrapped using
1197          // our instance key.
1198          SecretKey secretKey = null;
1199          for (String symmetricKey : symmetricKeys)
1200          {
1201            secretKey = decodeSymmetricKeyAttribute(symmetricKey);
1202            if (secretKey != null) break;
1203          }
1204    
1205          if (null != secretKey) {
1206            CipherKeyEntry.importCipherKeyEntry(this, keyID, transformation,
1207                    secretKey, keyLengthBits, ivLengthBits, isCompromised);
1208            return;
1209          }
1210    
1211          // Request the value from another server.
1212          String symmetricKey = getSymmetricKey(symmetricKeys);
1213          if (symmetricKey == null)
1214          {
1215            throw new CryptoManagerException(
1216                    ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE.get(
1217                            entry.getDN().toString()));
1218          }
1219          secretKey = decodeSymmetricKeyAttribute(symmetricKey);
1220          CipherKeyEntry.importCipherKeyEntry(this, keyID, transformation,
1221                  secretKey, keyLengthBits, ivLengthBits, isCompromised);
1222    
1223          // Write the value to the entry.
1224          InternalClientConnection internalConnection =
1225                  InternalClientConnection.getRootConnection();
1226          List<Modification> modifications =
1227                  new ArrayList<Modification>(1);
1228          Attribute attribute =
1229                  new Attribute(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY,
1230                          symmetricKey);
1231          modifications.add(
1232                  new Modification(ModificationType.ADD, attribute,
1233                          false));
1234          ModifyOperation internalModify =
1235                  internalConnection.processModify(entry.getDN(),
1236                          modifications);
1237          if (internalModify.getResultCode() != ResultCode.SUCCESS)
1238          {
1239            throw new CryptoManagerException(
1240                    ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_ADD_KEY.get(
1241                            entry.getDN().toString()));
1242          }
1243        }
1244        catch (DirectoryException ex)
1245        {
1246          if (debugEnabled()) {
1247            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
1248          }
1249          throw new CryptoManagerException(
1250                  ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_OTHER.get(
1251                          entry.getDN().toString(), ex.getMessage()), ex);
1252        }
1253      }
1254    
1255    
1256      /**
1257       * Imports a mac key entry from an entry in ADS.
1258       *
1259       * @param entry  The ADS mac key entry to be imported. The
1260       *               entry will be ignored if it does not have the
1261       *               ds-cfg-mac-key objectclass, or if the key is
1262       *               already present.
1263       *
1264       * @throws CryptoManagerException
1265       *               If the entry had the correct objectclass,
1266       *               was not already present but could not
1267       *               be imported.
1268       */
1269      void importMacKeyEntry(Entry entry)
1270           throws CryptoManagerException
1271      {
1272        // Ignore the entry if it does not have the appropriate
1273        // objectclass.
1274        if (!entry.hasObjectClass(ocMacKey)) return;
1275    
1276        try
1277        {
1278          String keyID =
1279               entry.getAttributeValue(attrKeyID,
1280                                       DirectoryStringSyntax.DECODER);
1281          int keyLengthBits =
1282               entry.getAttributeValue(attrKeyLength,
1283                                       IntegerSyntax.DECODER);
1284          String algorithm =
1285               entry.getAttributeValue(attrMacAlgorithm,
1286                                       DirectoryStringSyntax.DECODER);
1287          String compromisedTime =
1288               entry.getAttributeValue(attrCompromisedTime,
1289                                       DirectoryStringSyntax.DECODER);
1290    
1291          boolean isCompromised = compromisedTime != null;
1292    
1293          ArrayList<String> symmetricKeys = new ArrayList<String>();
1294          entry.getAttributeValues(attrSymmetricKey,
1295                                 DirectoryStringSyntax.DECODER,
1296                                 symmetricKeys);
1297    
1298          // Find the symmetric key value that was wrapped using our
1299          // instance key.
1300          SecretKey secretKey = null;
1301          for (String symmetricKey : symmetricKeys)
1302          {
1303            secretKey = decodeSymmetricKeyAttribute(symmetricKey);
1304            if (secretKey != null) break;
1305          }
1306    
1307          if (secretKey == null)
1308          {
1309            // Request the value from another server.
1310            String symmetricKey = getSymmetricKey(symmetricKeys);
1311            if (symmetricKey == null)
1312            {
1313              throw new CryptoManagerException(
1314                   ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_DECODE.get(
1315                        entry.getDN().toString()));
1316            }
1317            secretKey = decodeSymmetricKeyAttribute(symmetricKey);
1318            MacKeyEntry.importMacKeyEntry(this, keyID, algorithm,
1319                                          secretKey, keyLengthBits,
1320                                          isCompromised);
1321    
1322            // Write the value to the entry.
1323            InternalClientConnection internalConnection =
1324                 InternalClientConnection.getRootConnection();
1325            List<Modification> modifications =
1326                 new ArrayList<Modification>(1);
1327            Attribute attribute =
1328                 new Attribute(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY,
1329                               symmetricKey);
1330            modifications.add(
1331                 new Modification(ModificationType.ADD, attribute,
1332                                  false));
1333            ModifyOperation internalModify =
1334                 internalConnection.processModify(entry.getDN(),
1335                                                  modifications);
1336            if (internalModify.getResultCode() != ResultCode.SUCCESS)
1337            {
1338              throw new CryptoManagerException(
1339                   ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_TO_ADD_KEY.get(
1340                        entry.getDN().toString()));
1341            }
1342          }
1343          else
1344          {
1345            MacKeyEntry.importMacKeyEntry(this, keyID, algorithm,
1346                                          secretKey, keyLengthBits,
1347                                          isCompromised);
1348          }
1349    
1350        }
1351        catch (DirectoryException ex)
1352        {
1353          if (debugEnabled()) {
1354            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
1355          }
1356          throw new CryptoManagerException(
1357                  ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FAILED_OTHER.get(
1358                          entry.getDN().toString(), ex.getMessage()), ex);
1359        }
1360      }
1361    
1362    
1363      /**
1364       * This class implements a utility interface to the unique
1365       * identifier corresponding to a cryptographic key. For each key
1366       * stored in an entry in ADS, the key identifier is the naming
1367       * attribute of the entry. The external binary representation of the
1368       * key entry identifier is compact, because it is typically stored
1369       * as a prefix of encrypted data.
1370       */
1371      private static class KeyEntryID
1372      {
1373        /**
1374         *  Constructs a KeyEntryID using a new unique identifier.
1375         */
1376        public KeyEntryID() {
1377          fValue = UUID.randomUUID();
1378        }
1379    
1380        /**
1381         * Construct a {@code KeyEntryID} from its {@code byte[]}
1382         * representation.
1383         *
1384         * @param keyEntryID The {@code byte[]} representation of a
1385         * {@code KeyEntryID}.
1386         */
1387        public KeyEntryID(final byte[] keyEntryID) {
1388          Validator.ensureTrue(getByteValueLength() == keyEntryID.length);
1389          long hiBytes = 0;
1390          long loBytes = 0;
1391          for (int i = 0; i < 8; ++i) {
1392            hiBytes = (hiBytes << 8) | (keyEntryID[i] & 0xff);
1393            loBytes = (loBytes << 8) | (keyEntryID[8 + i] & 0xff);
1394          }
1395          fValue = new UUID(hiBytes, loBytes);
1396        }
1397    
1398        /**
1399         * Constructs a {@code KeyEntryID} from its {@code String}
1400         * representation.
1401         *
1402         * @param  keyEntryID The {@code String} reprentation of a
1403         * {@code KeyEntryID}.
1404         *
1405         * @throws  CryptoManagerException  If the argument does
1406         * not conform to the {@code KeyEntryID} string syntax.
1407         */
1408        public KeyEntryID(final String keyEntryID)
1409                throws CryptoManagerException {
1410          try {
1411            fValue = UUID.fromString(keyEntryID);
1412          }
1413          catch (IllegalArgumentException ex) {
1414            if (debugEnabled()) {
1415              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
1416            }
1417            throw new CryptoManagerException(
1418                    ERR_CRYPTOMGR_INVALID_KEY_IDENTIFIER_SYNTAX.get(
1419                            keyEntryID, getExceptionMessage(ex)), ex);
1420          }
1421        }
1422    
1423        /**
1424         * Copy constructor.
1425         *
1426         * @param keyEntryID  The {@code KeyEntryID} to copy.
1427         */
1428        public KeyEntryID(final KeyEntryID keyEntryID) {
1429          fValue = new UUID(keyEntryID.fValue.getMostSignificantBits(),
1430                            keyEntryID.fValue.getLeastSignificantBits());
1431        }
1432    
1433        /**
1434         * Returns the compact {@code byte[]} representation of this
1435         * {@code KeyEntryID}.
1436         * @return The compact {@code byte[]} representation of this
1437         * {@code KeyEntryID
1438         */
1439        public byte[] getByteValue(){
1440          final byte[] uuidBytes = new byte[16];
1441          long hiBytes = fValue.getMostSignificantBits();
1442          long loBytes = fValue.getLeastSignificantBits();
1443          for (int i = 7; i >= 0; --i) {
1444            uuidBytes[i] = (byte)hiBytes;
1445            hiBytes >>>= 8;
1446            uuidBytes[8 + i] = (byte)loBytes;
1447            loBytes >>>= 8;
1448          }
1449          return uuidBytes;
1450        }
1451    
1452        /**
1453         * Returns the {@code String} representation of this
1454         * {@code KeyEntryID}.
1455         * @return The {@code String} representation of this
1456         * {@code KeyEntryID}.
1457         */
1458        public String getStringValue() {
1459          return fValue.toString();
1460        }
1461    
1462        /**
1463         * Returns the length of the compact {@code byte[]} representation
1464         * of a {@code KeyEntryID}.
1465         *
1466         * @return The length of the compact {@code byte[]} representation
1467         * of a {@code KeyEntryID}.
1468         */
1469        public static int getByteValueLength() {
1470          return 16;
1471        }
1472    
1473        /**
1474         * Compares this object to the specified object. The result is
1475         * true if and only if the argument is not null, is of type
1476         * {@code KeyEntryID}, and has the same value (i.e., the
1477         * {@code String} and {@code byte[]} representations are
1478         * identical).
1479         *
1480         * @param obj The object to which to compare this instance.
1481         *
1482         * @return {@code true} if the objects are the same, {@code false}
1483         * otherwise.
1484         */
1485        public boolean equals(final Object obj){
1486          return obj instanceof KeyEntryID
1487                  && fValue.equals(((KeyEntryID) obj).fValue);
1488        }
1489    
1490        /**
1491         * Returns a hash code for this {@code KeyEntryID}.
1492         *
1493         * @return a hash code value for this {@code KeyEntryID}.
1494         */
1495        public int hashCode() {
1496          return fValue.hashCode();
1497        }
1498    
1499        // state
1500        private final UUID fValue;
1501      }
1502    
1503    
1504      /**
1505       This class corresponds to the secret key portion if a secret
1506       key entry in ADS.
1507       <p>
1508       Note that the generated key length is in some cases longer than requested
1509       key length. For example, when a 56-bit key is requested for DES (or 168-bit
1510       for DESede) the default provider for the Sun JRE produces an 8-byte (24-byte)
1511       key, which embeds the generated key in an array with one parity bit per byte.
1512       The requested key length is what is recorded in this object and in the
1513       published key entry; hence, users of the actual key data must be sure to
1514       operate on the full key byte array, and not truncate it to the key length.
1515       */
1516      private static class SecretKeyEntry
1517      {
1518        /**
1519         Construct an instance of {@code SecretKeyEntry} using the specified
1520         parameters. This constructor is used for key generation.
1521         <p>
1522         Note the relationship between the secret key data array length and the
1523         secret key length parameter described in {@link SecretKeyEntry}
1524    
1525         @param algorithm  The name of the secret key algorithm for which the key
1526         entry is to be produced.
1527    
1528         @param keyLengthBits  The length of the requested key in bits.
1529    
1530         @throws CryptoManagerException If there is a problem instantiating the key
1531         generator.
1532         */
1533        public SecretKeyEntry(final String algorithm, final int keyLengthBits)
1534        throws CryptoManagerException {
1535          KeyGenerator keyGen;
1536          try {
1537            keyGen = KeyGenerator.getInstance(algorithm);
1538          }
1539          catch (NoSuchAlgorithmException ex) {
1540            throw new CryptoManagerException(
1541                   ERR_CRYPTOMGR_INVALID_SYMMETRIC_KEY_ALGORITHM.get(
1542                           algorithm, getExceptionMessage(ex)), ex);
1543          }
1544          keyGen.init(keyLengthBits, secureRandom);
1545          final byte[] key = keyGen.generateKey().getEncoded();
1546    
1547          this.fKeyID = new KeyEntryID();
1548          this.fSecretKey = new SecretKeySpec(key, algorithm);
1549          this.fKeyLengthBits = keyLengthBits;
1550          this.fIsCompromised = false;
1551        }
1552    
1553    
1554        /**
1555         Construct an instance of {@code SecretKeyEntry} using the specified
1556         parameters. This constructor would typically be used for key entries
1557         imported from ADS, for which the full set of paramters is known.
1558         <p>
1559         Note the relationship between the secret key data array length and the
1560         secret key length parameter described in {@link SecretKeyEntry}
1561    
1562         @param keyID  The unique identifier of this algorithm/key pair.
1563    
1564         @param secretKey  The secret key.
1565    
1566         @param secretKeyLengthBits The length in bits of the secret key.
1567    
1568         @param isCompromised {@code false} if the key may be used
1569         for operations on new data, or {@code true} if the key is being
1570         retained only for use in validation.
1571         */
1572        public SecretKeyEntry(final KeyEntryID keyID,
1573                              final SecretKey secretKey,
1574                              final int secretKeyLengthBits,
1575                              final boolean isCompromised) {
1576          // copy arguments
1577          this.fKeyID = new KeyEntryID(keyID);
1578          this.fSecretKey = secretKey;
1579          this.fKeyLengthBits = secretKeyLengthBits;
1580          this.fIsCompromised = isCompromised;
1581        }
1582    
1583    
1584        /**
1585         * The unique identifier of this algorithm/key pair.
1586         *
1587         * @return The unique identifier of this algorithm/key pair.
1588         */
1589        public KeyEntryID getKeyID() {
1590          return fKeyID;
1591        }
1592    
1593    
1594        /**
1595         * The secret key spec containing the secret key.
1596         *
1597         * @return The secret key spec containing the secret key.
1598         */
1599        public SecretKey getSecretKey() {
1600          return fSecretKey;
1601        }
1602    
1603    
1604        /**
1605         * Mark a key entry as compromised. The entry will no longer be
1606         * eligible for use as an encryption key.
1607         * <p>
1608         * There is no need to lock the entry to make this change: The
1609         * only valid transition for this field is from false to true,
1610         * the change is asynchronous across the topology (i.e., a key
1611         * might continue to be used at this instance for at least the
1612         * replication propagation delay after being marked compromised at
1613         * another instance), and modifying a boolean is guaranteed to be
1614         * atomic.
1615         */
1616        public void setIsCompromised() {
1617          fIsCompromised = true;
1618        }
1619    
1620        /**
1621         Returns the length of the secret key in bits.
1622         <p>
1623         Note the relationship between the secret key data array length and the
1624         secret key length parameter described in {@link SecretKeyEntry}
1625    
1626         @return the length of the secret key in bits.
1627         */
1628        public int getKeyLengthBits() {
1629          return fKeyLengthBits;
1630        }
1631    
1632        /**
1633         * Returns the status of the key.
1634         * @return  {@code false} if the key may be used for operations on
1635         * new data, or {@code true} if the key is being retained only for
1636         * use in validation.
1637         */
1638        public boolean isCompromised() {
1639          return fIsCompromised;
1640        }
1641    
1642        // state
1643        private final KeyEntryID fKeyID;
1644        private final SecretKey fSecretKey;
1645        private final int fKeyLengthBits;
1646        private boolean fIsCompromised = false;
1647      }
1648    
1649      /**
1650       * This class corresponds to the cipher key entry in ADS. It is
1651       * used in the local cache of key entries that have been requested
1652       * by CryptoManager clients.
1653       */
1654      private static class CipherKeyEntry extends SecretKeyEntry
1655      {
1656        /**
1657         * This method generates a key according to the key parameters,
1658         * and creates a key entry and registers it in the supplied map.
1659         *
1660         * @param  cryptoManager The CryptoManager instance for which the
1661         * key is to be generated. Pass {@code null} as the argument to
1662         * this parameter in order to validate a proposed cipher
1663         * transformation and key length without publishing the key.
1664         *
1665         * @param transformation  The cipher transformation for which the
1666         * key is to be produced. This argument is required.
1667         *
1668         * @param keyLengthBits  The cipher key length in bits. This argument is
1669         * required and must be suitable for the requested transformation.
1670         *
1671         * @return The key entry corresponding to the parameters.
1672         *
1673         * @throws CryptoManagerException If there is a problem
1674         * instantiating a Cipher object in order to validate the supplied
1675         * parameters when creating a new entry.
1676         *
1677         * @see MacKeyEntry#getKeyEntry(CryptoManagerImpl, String, int)
1678         */
1679        public static CipherKeyEntry generateKeyEntry(
1680                final CryptoManagerImpl cryptoManager,
1681                final String transformation,
1682                final int keyLengthBits)
1683        throws CryptoManagerException {
1684    
1685          final Map<KeyEntryID, CipherKeyEntry> cache
1686                  = (null == cryptoManager)
1687                  ? null : cryptoManager.cipherKeyEntryCache;
1688    
1689          CipherKeyEntry keyEntry = new CipherKeyEntry(transformation,
1690                  keyLengthBits);
1691    
1692          // Validate the key entry. Record the initialization vector length, if
1693          // any.
1694          final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
1695          // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2471
1696          final byte[] iv = cipher.getIV();
1697          keyEntry.setIVLengthBits((null == iv) ? 0 : iv.length * Byte.SIZE);
1698    
1699          if (null != cache) {
1700            /* The key is published to ADS before making it available in the local
1701               cache with the intention to ensure the key is persisted before use.
1702               This ordering allows the possibility that data encrypted at another
1703               instance could arrive at this instance before the key is available in
1704               the local cache to decode the data. */
1705            publishKeyEntry(cryptoManager, keyEntry);
1706            cache.put(keyEntry.getKeyID(), keyEntry);
1707          }
1708    
1709          return keyEntry;
1710        }
1711    
1712    
1713        /**
1714         * Publish a new cipher key by adding an entry into ADS.
1715         * @param  cryptoManager The CryptoManager instance for which the
1716         *                       key was generated.
1717         * @param  keyEntry      The cipher key to be published.
1718         * @throws CryptoManagerException
1719         *                       If the key entry could not be added to
1720         *                       ADS.
1721         */
1722        private static void publishKeyEntry(CryptoManagerImpl cryptoManager,
1723                                            CipherKeyEntry keyEntry)
1724             throws CryptoManagerException
1725        {
1726          // Construct the key entry DN.
1727          AttributeValue distinguishedValue =
1728               new AttributeValue(attrKeyID,
1729                                  keyEntry.getKeyID().getStringValue());
1730          DN entryDN = secretKeysDN.concat(
1731               RDN.create(attrKeyID, distinguishedValue));
1732    
1733          // Set the entry object classes.
1734          LinkedHashMap<ObjectClass,String> ocMap =
1735              new LinkedHashMap<ObjectClass,String>(2);
1736          ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
1737          ocMap.put(ocCipherKey, ConfigConstants.OC_CRYPTO_CIPHER_KEY);
1738    
1739          // Create the operational and user attributes.
1740          LinkedHashMap<AttributeType,List<Attribute>> opAttrs =
1741               new LinkedHashMap<AttributeType,List<Attribute>>(0);
1742          LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
1743               new LinkedHashMap<AttributeType,List<Attribute>>();
1744    
1745          // Add the key ID attribute.
1746          LinkedHashSet<AttributeValue> valueSet =
1747               new LinkedHashSet<AttributeValue>(1);
1748          valueSet.add(distinguishedValue);
1749    
1750          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
1751          attrList.add(new Attribute(attrKeyID,
1752                                     attrKeyID.getNameOrOID(),
1753                                     valueSet));
1754          userAttrs.put(attrKeyID, attrList);
1755    
1756          // Add the transformation name attribute.
1757          valueSet = new LinkedHashSet<AttributeValue>(1);
1758          valueSet.add(new AttributeValue(attrTransformation,
1759                                          keyEntry.getType()));
1760    
1761          attrList = new ArrayList<Attribute>(1);
1762          attrList.add(
1763               new Attribute(attrTransformation,
1764                             attrTransformation.getNameOrOID(),
1765                             valueSet));
1766          userAttrs.put(attrTransformation, attrList);
1767    
1768    
1769          // Add the init vector length attribute.
1770          valueSet = new LinkedHashSet<AttributeValue>(1);
1771          valueSet.add(new AttributeValue(
1772               attrInitVectorLength,
1773               String.valueOf(keyEntry.getIVLengthBits())));
1774    
1775          attrList = new ArrayList<Attribute>(1);
1776          attrList.add(
1777               new Attribute(attrInitVectorLength,
1778                             attrInitVectorLength.getNameOrOID(),
1779                             valueSet));
1780          userAttrs.put(attrInitVectorLength, attrList);
1781    
1782    
1783          // Add the key length attribute.
1784          valueSet = new LinkedHashSet<AttributeValue>(1);
1785          valueSet.add(new AttributeValue(attrKeyLength,
1786                  String.valueOf(keyEntry.getKeyLengthBits())));
1787    
1788          attrList = new ArrayList<Attribute>(1);
1789          attrList.add(
1790               new Attribute(attrKeyLength,
1791                             attrKeyLength.getNameOrOID(),
1792                             valueSet));
1793          userAttrs.put(attrKeyLength, attrList);
1794    
1795    
1796          // Get the trusted certificates.
1797          Map<String, byte[]> trustedCerts =
1798               cryptoManager.getTrustedCertificates();
1799    
1800          // Need to add our own instance certificate.
1801          byte[] instanceKeyCertificate =
1802             CryptoManagerImpl.getInstanceKeyCertificateFromLocalTruststore();
1803          trustedCerts.put(getInstanceKeyID(instanceKeyCertificate),
1804                           instanceKeyCertificate);
1805    
1806          // Add the symmetric key attribute.
1807          LinkedHashSet<AttributeValue> symmetricKeyValues =
1808               new LinkedHashSet<AttributeValue>(trustedCerts.size());
1809    
1810          for (Map.Entry<String, byte[]> mapEntry :
1811               trustedCerts.entrySet())
1812          {
1813            String symmetricKey =
1814                 cryptoManager.encodeSymmetricKeyAttribute(
1815                      mapEntry.getKey(),
1816                      mapEntry.getValue(),
1817                      keyEntry.getSecretKey());
1818    
1819            symmetricKeyValues.add(
1820                 new AttributeValue(attrSymmetricKey, symmetricKey));
1821    
1822            attrList = new ArrayList<Attribute>(1);
1823            attrList.add(new Attribute(attrSymmetricKey,
1824                                       attrSymmetricKey.getNameOrOID(),
1825                                       symmetricKeyValues));
1826            userAttrs.put(attrSymmetricKey, attrList);
1827          }
1828    
1829          // Create the entry.
1830          Entry entry = new Entry(entryDN, ocMap, userAttrs, opAttrs);
1831    
1832          InternalClientConnection connection =
1833               InternalClientConnection.getRootConnection();
1834          AddOperation addOperation = connection.processAdd(entry);
1835          if (addOperation.getResultCode() != ResultCode.SUCCESS)
1836          {
1837            throw new CryptoManagerException(
1838                    ERR_CRYPTOMGR_SYMMETRIC_KEY_ENTRY_ADD_FAILED.get(
1839                            entry.getDN().toString(),
1840                            addOperation.getErrorMessage()));
1841          }
1842        }
1843    
1844    
1845        /**
1846         * Initializes a secret key entry from the supplied parameters,
1847         * validates it, and registers it in the supplied map. The
1848         * anticipated use of this method is to import a key entry from
1849         * ADS.
1850         *
1851         * @param cryptoManager  The CryptoManager instance.
1852         *
1853         * @param keyIDString  The key identifier.
1854         *
1855         * @param transformation  The cipher transformation for which the
1856         * key entry was produced.
1857         *
1858         * @param secretKey  The cipher key.
1859         *
1860         * @param secretKeyLengthBits  The length of the cipher key in
1861         * bits.
1862         *
1863         * @param ivLengthBits  The length of the initialization vector,
1864         * which will be zero in the case of any stream cipher algorithm,
1865         * any block cipher algorithm for which the transformation mode
1866         * does not use an initialization vector, and any HMAC algorithm.
1867         *
1868         * @param isCompromised  Mark the key as compromised, so that it
1869         * will not subsequently be used for encryption. The key entry
1870         * must be maintained in order to decrypt existing ciphertext.
1871         *
1872         * @return  The key entry, if one was successfully produced.
1873         *
1874         * @throws CryptoManagerException  In case of an error in the
1875         * parameters used to initialize or validate the key entry.
1876         */
1877        public static CipherKeyEntry importCipherKeyEntry(
1878                final CryptoManagerImpl cryptoManager,
1879                final String keyIDString,
1880                final String transformation,
1881                final SecretKey secretKey,
1882                final int secretKeyLengthBits,
1883                final int ivLengthBits,
1884                final boolean isCompromised)
1885                throws CryptoManagerException {
1886          Validator.ensureNotNull(keyIDString, transformation, secretKey);
1887          Validator.ensureTrue(0 <= ivLengthBits);
1888    
1889          final KeyEntryID keyID = new KeyEntryID(keyIDString);
1890    
1891          // Check map for existing key entry with the supplied keyID.
1892          CipherKeyEntry keyEntry = getKeyEntry(cryptoManager, keyID);
1893          if (null != keyEntry) {
1894            // Paranoiac check to ensure exact type match.
1895            if (! (keyEntry.getType().equals(transformation)
1896                    && keyEntry.getKeyLengthBits() == secretKeyLengthBits
1897                    && keyEntry.getIVLengthBits() == ivLengthBits)) {
1898                   throw new CryptoManagerException(
1899                        ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FIELD_MISMATCH.get(
1900                             keyIDString));
1901            }
1902            // Allow transition to compromised.
1903            if (isCompromised && !keyEntry.isCompromised()) {
1904              keyEntry.setIsCompromised();
1905            }
1906            return keyEntry;
1907          }
1908    
1909          // Instantiate new entry.
1910          keyEntry = new CipherKeyEntry(keyID, transformation, secretKey,
1911                  secretKeyLengthBits, ivLengthBits, isCompromised);
1912    
1913          // Validate new entry.
1914          byte[] iv = null;
1915          if (0 < ivLengthBits) {
1916            iv = new byte[ivLengthBits / Byte.SIZE];
1917            pseudoRandom.nextBytes(iv);
1918          }
1919          getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
1920    
1921          // Cache new entry.
1922          cryptoManager.cipherKeyEntryCache.put(keyEntry.getKeyID(),
1923                  keyEntry);
1924    
1925          return keyEntry;
1926        }
1927    
1928    
1929        /**
1930         * Retrieve a CipherKeyEntry from the CipherKeyEntry Map based on
1931         * the algorithm name and key length.
1932         * <p>
1933         * ADS is not searched in the case a key entry meeting the
1934         * specifications is not found. Instead, the ADS monitoring thread
1935         * is responsible for asynchronous updates to the key map.
1936         *
1937         * @param cryptoManager  The CryptoManager instance with which the
1938         * key entry is associated.
1939         *
1940         * @param transformation  The cipher transformation for which the
1941         * key was produced.
1942         *
1943         * @param keyLengthBits  The cipher key length in bits.
1944         *
1945         * @return  The key entry corresponding to the parameters, or
1946         * {@code null} if no such entry exists.
1947         */
1948        public static CipherKeyEntry getKeyEntry(
1949                final CryptoManagerImpl cryptoManager,
1950                final String transformation,
1951                final int keyLengthBits) {
1952          Validator.ensureNotNull(cryptoManager, transformation);
1953          Validator.ensureTrue(0 < keyLengthBits);
1954    
1955          CipherKeyEntry keyEntry = null;
1956          // search for an existing key that satisfies the request
1957          for (Map.Entry<KeyEntryID, CipherKeyEntry> i
1958                  : cryptoManager.cipherKeyEntryCache.entrySet()) {
1959            CipherKeyEntry entry = i.getValue();
1960            if (! entry.isCompromised()
1961                    && entry.getType().equals(transformation)
1962                    && entry.getKeyLengthBits() == keyLengthBits) {
1963              keyEntry = entry;
1964              break;
1965            }
1966          }
1967    
1968          return keyEntry;
1969        }
1970    
1971    
1972        /**
1973         * Given a key identifier, return the associated cipher key entry
1974         * from the supplied map. This method would typically be used by
1975         * a decryption routine.
1976         * <p>
1977         * Although the existence of data tagged with the requested keyID
1978         * implies the key entry exists in the system, it is possible for
1979         * the distribution of the key entry to lag that of the data;
1980         * hence this routine might return null. No attempt is made to
1981         * query the other instances in the ADS topology (presumably at
1982         * least the instance producing the key entry will have it), due
1983         * to the presumed infrequency of key generation and expected low
1984         * latency of replication, compared to the complexity of finding
1985         * the set of instances and querying them. Instead, the caller
1986         * must retry the operation requesting the decryption.
1987         *
1988         * @param cryptoManager  The CryptoManager instance with which the
1989         * key entry is associated.
1990         *
1991         * @param keyID  The key identifier.
1992         *
1993         * @return  The key entry associated with the key identifier, or
1994         * {@code null} if no such entry exists.
1995         *
1996         * @see CryptoManagerImpl.MacKeyEntry
1997         *  #getKeyEntry(org.opends.server.types.CryptoManager,
1998         *               java.lang.String, int)
1999         */
2000        public static CipherKeyEntry getKeyEntry(
2001                CryptoManagerImpl cryptoManager,
2002                final KeyEntryID keyID) {
2003          return cryptoManager.cipherKeyEntryCache.get(keyID);
2004        }
2005    
2006        /**
2007         In case a transformation is supplied instead of an algorithm:
2008         E.g., AES/CBC/PKCS5Padding -> AES.
2009    
2010         @param transformation The cipher transformation from which to
2011         extract the cipher algorithm.
2012    
2013         @return  The algorithm prefix of the Cipher transformation. If
2014         the transformation is supplied as an algorithm-only (no mode or
2015         padding), return the transformation as-is.
2016         */
2017        private static String keyAlgorithmFromTransformation(
2018                String transformation){
2019        final int separatorIndex = transformation.indexOf('/');
2020          return (0 < separatorIndex)
2021                  ? transformation.substring(0, separatorIndex)
2022                  : transformation;
2023        }
2024    
2025        /**
2026         * Construct an instance of {@code CipherKeyEntry} using the
2027         * specified parameters. This constructor would typically be used
2028         * for key generation.
2029         *
2030         * @param transformation  The name of the Cipher transformation
2031         * for which the key entry is to be produced.
2032         *
2033         * @param keyLengthBits  The length of the requested key in bits.
2034         *
2035         * @throws CryptoManagerException If there is a problem
2036         * instantiating the key generator.
2037         */
2038        private CipherKeyEntry(final String transformation, final int keyLengthBits)
2039                throws CryptoManagerException {
2040          // Generate a new key.
2041          super(keyAlgorithmFromTransformation(transformation), keyLengthBits);
2042    
2043          // copy arguments.
2044          this.fType = transformation;
2045          this.fIVLengthBits = -1; /* compute IV length */
2046        }
2047    
2048        /**
2049         * Construct an instance of CipherKeyEntry using the specified
2050         * parameters. This constructor would typically be used for key
2051         * entries imported from ADS, for which the full set of paramters
2052         * is known, and for a newly generated key entry, for which the
2053         * initialization vector length might not yet be known, but which
2054         * must be set prior to using the key.
2055         *
2056         * @param keyID  The unique identifier of this cipher
2057         * transformation/key pair.
2058         *
2059         * @param transformation  The name of the secret-key cipher
2060         * transformation for which the key entry is to be produced.
2061         *
2062         * @param secretKey  The cipher key.
2063         *
2064         * @param secretKeyLengthBits  The length of the secret key in
2065         * bits.
2066         *
2067         * @param ivLengthBits  The length in bits of a mandatory
2068         * initialization vector or 0 if none is required. Set this
2069         * parameter to -1 when generating a new encryption key and this
2070         * method will attempt to compute the proper value by first using
2071         * the cipher block size and then, if the cipher block size is
2072         * non-zero, using 0 (i.e., no initialization vector).
2073         *
2074         * @param isCompromised {@code false} if the key may be used
2075         * for encryption, or {@code true} if the key is being retained
2076         * only for use in decrypting existing data.
2077         *
2078         * @throws  CryptoManagerException If there is a problem
2079         * instantiating a Cipher object in order to validate the supplied
2080         * parameters when creating a new entry.
2081         */
2082        private CipherKeyEntry(final KeyEntryID keyID,
2083                               final String transformation,
2084                               final SecretKey secretKey,
2085                               final int secretKeyLengthBits,
2086                               final int ivLengthBits,
2087                               final boolean isCompromised)
2088                throws CryptoManagerException {
2089          super(keyID, secretKey, secretKeyLengthBits, isCompromised);
2090    
2091          // copy arguments
2092          this.fType = transformation;
2093          this.fIVLengthBits = ivLengthBits;
2094        }
2095    
2096    
2097        /**
2098         * The cipher transformation for which the key entry was created.
2099         *
2100         * @return The cipher transformation.
2101         */
2102        public String getType() {
2103          return fType;
2104        }
2105    
2106        /**
2107         * Set the algorithm/key pair's required initialization vector
2108         * length in bits. Typically, this will be the cipher's block
2109         * size, or 0 for a stream cipher or a block cipher mode that does
2110         * not use an initialization vector (e.g., ECB).
2111         *
2112         * @param ivLengthBits The initiazliation vector length in bits.
2113         */
2114        private void setIVLengthBits(int ivLengthBits) {
2115          Validator.ensureTrue(-1 == fIVLengthBits && 0 <= ivLengthBits);
2116          fIVLengthBits = ivLengthBits;
2117        }
2118    
2119        /**
2120         * The initialization vector length in bits: 0 is a stream cipher
2121         * or a block cipher that does not use an IV (e.g., ECB); or a
2122         * positive integer, typically the block size of the cipher.
2123         * <p>
2124         * This method returns -1 if the object initialization has not
2125         * been completed.
2126         *
2127         * @return The initialization vector length.
2128         */
2129        public int getIVLengthBits() {
2130          return fIVLengthBits;
2131        }
2132    
2133        // state
2134        private final String fType;
2135        private int fIVLengthBits = -1;
2136      }
2137    
2138    
2139      /**
2140       * This method produces an initialized Cipher based on the supplied
2141       * CipherKeyEntry's state.
2142       *
2143       * @param keyEntry  The secret key entry containing the cipher
2144       * transformation and secret key for which to instantiate
2145       * the cipher.
2146       *
2147       * @param mode  Either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE.
2148       *
2149       * @param initializationVector  For Cipher.DECRYPT_MODE, supply
2150       * the initialzation vector used in the corresponding encryption
2151       * cipher, or {@code null} if none.
2152       *
2153       * @return  The initialized cipher object.
2154       *
2155       * @throws  CryptoManagerException In case of a problem creating
2156       * or initializing the requested cipher object. Possible causes
2157       * include NoSuchAlgorithmException, NoSuchPaddingException,
2158       * InvalidKeyException, and InvalidAlgorithmParameterException.
2159       */
2160      private static Cipher getCipher(final CipherKeyEntry keyEntry,
2161                                      final int mode,
2162                                      final byte[] initializationVector)
2163              throws CryptoManagerException {
2164        Validator.ensureTrue(Cipher.ENCRYPT_MODE == mode
2165                || Cipher.DECRYPT_MODE == mode);
2166        Validator.ensureTrue(Cipher.ENCRYPT_MODE != mode
2167                || null == initializationVector);
2168        Validator.ensureTrue(-1 != keyEntry.getIVLengthBits()
2169                || Cipher.ENCRYPT_MODE == mode);
2170        Validator.ensureTrue(null == initializationVector
2171                || initializationVector.length * Byte.SIZE
2172                                           == keyEntry.getIVLengthBits());
2173    
2174        Cipher cipher;
2175        try {
2176          String transformation = keyEntry.getType();
2177          /* If a client specifies only an algorithm for a transformation, the
2178             Cipher provider can supply default values for mode and padding. Hence
2179             in order to avoid a decryption error due to mismatched defaults in the
2180             provider implementation of JREs supplied by different vendors, the
2181             {@code CryptoManager} configuration validator requires the mode and
2182             padding be explicitly specified. Some cipher algorithms, including
2183             RC4 and ARCFOUR, do not have a mode or padding, and hence must be
2184             specified as {@code algorithm/NONE/NoPadding}. */
2185          String fields[] = transformation.split("/",0);
2186          if (1 < fields.length && "NONE".equals(fields[1])) {
2187            assert "RC4".equals(fields[0]) || "ARCFOUR".equals(fields[0]);
2188            assert "NoPadding".equals(fields[2]);
2189            transformation = fields[0];
2190          }
2191          cipher = Cipher.getInstance(transformation);
2192        }
2193        catch (GeneralSecurityException ex) {
2194          // NoSuchAlgorithmException, NoSuchPaddingException
2195          if (debugEnabled()) {
2196            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2197          }
2198          throw new CryptoManagerException(
2199               ERR_CRYPTOMGR_GET_CIPHER_INVALID_CIPHER_TRANSFORMATION.get(
2200                       keyEntry.getType(), getExceptionMessage(ex)), ex);
2201        }
2202    
2203        try {
2204          if (0 < keyEntry.getIVLengthBits()) {
2205            byte[] iv;
2206            if (Cipher.ENCRYPT_MODE == mode && null == initializationVector) {
2207              iv = new byte[keyEntry.getIVLengthBits() / Byte.SIZE];
2208              pseudoRandom.nextBytes(iv);
2209            }
2210            else {
2211              iv = initializationVector;
2212            }
2213            // TODO: https://opends.dev.java.net/issues/show_bug.cgi?id=2471
2214            cipher.init(mode, keyEntry.getSecretKey(), new IvParameterSpec(iv));
2215          }
2216          else {
2217            cipher.init(mode, keyEntry.getSecretKey());
2218          }
2219        }
2220        catch (GeneralSecurityException ex) {
2221          // InvalidKeyException, InvalidAlgorithmParameterException
2222          if (debugEnabled()) {
2223            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2224          }
2225          throw new CryptoManagerException(
2226                  ERR_CRYPTOMGR_GET_CIPHER_CANNOT_INITIALIZE.get(
2227                          getExceptionMessage(ex)), ex);
2228        }
2229    
2230        return cipher;
2231      }
2232    
2233    
2234      /**
2235       * This class corresponds to the MAC key entry in ADS. It is
2236       * used in the local cache of key entries that have been requested
2237       * by CryptoManager clients.
2238       */
2239      private static class MacKeyEntry extends SecretKeyEntry
2240      {
2241        /**
2242         * This method generates a key according to the key parameters,
2243         * creates a key entry, and optionally registers it in the
2244         * supplied CryptoManager context.
2245         *
2246         * @param  cryptoManager The CryptoManager instance for which the
2247         * key is to be generated. Pass {@code null} as the argument to
2248         * this parameter in order to validate a proposed MAC algorithm
2249         * and key length, but not publish the key entry.
2250         *
2251         * @param algorithm  The MAC algorithm for which the
2252         * key is to be produced. This argument is required.
2253         *
2254         * @param keyLengthBits  The MAC key length in bits. The argument is
2255         * required and must be suitable for the requested algorithm.
2256         *
2257         * @return The key entry corresponding to the parameters.
2258         *
2259         * @throws CryptoManagerException If there is a problem
2260         * instantiating a Mac object in order to validate the supplied
2261         * parameters when creating a new entry.
2262         *
2263         * @see CipherKeyEntry#getKeyEntry(CryptoManagerImpl, String, int)
2264         */
2265        public static MacKeyEntry generateKeyEntry(
2266                final CryptoManagerImpl cryptoManager,
2267                final String algorithm,
2268                final int keyLengthBits)
2269        throws CryptoManagerException {
2270          Validator.ensureNotNull(algorithm);
2271    
2272          final Map<KeyEntryID, MacKeyEntry> cache = (null == cryptoManager)
2273                  ? null : cryptoManager.macKeyEntryCache;
2274    
2275          final MacKeyEntry keyEntry = new MacKeyEntry(algorithm, keyLengthBits);
2276    
2277          // Validate the key entry.
2278          getMacEngine(keyEntry);
2279    
2280          if (null != cache) {
2281            /* The key is published to ADS before making it available in the local
2282               cache with the intention to ensure the key is persisted before use.
2283               This ordering allows the possibility that data encrypted at another
2284               instance could arrive at this instance before the key is available in
2285               the local cache to decode the data. */
2286            publishKeyEntry(cryptoManager, keyEntry);
2287            cache.put(keyEntry.getKeyID(), keyEntry);
2288          }
2289    
2290          return keyEntry;
2291        }
2292    
2293    
2294        /**
2295         * Publish a new mac key by adding an entry into ADS.
2296         * @param  cryptoManager The CryptoManager instance for which the
2297         *                       key was generated.
2298         * @param  keyEntry      The mac key to be published.
2299         * @throws CryptoManagerException
2300         *                       If the key entry could not be added to
2301         *                       ADS.
2302         */
2303        private static void publishKeyEntry(CryptoManagerImpl cryptoManager,
2304                                            MacKeyEntry keyEntry)
2305             throws CryptoManagerException
2306        {
2307          // Construct the key entry DN.
2308          AttributeValue distinguishedValue =
2309               new AttributeValue(attrKeyID,
2310                                  keyEntry.getKeyID().getStringValue());
2311          DN entryDN = secretKeysDN.concat(
2312               RDN.create(attrKeyID, distinguishedValue));
2313    
2314          // Set the entry object classes.
2315          LinkedHashMap<ObjectClass,String> ocMap =
2316              new LinkedHashMap<ObjectClass,String>(2);
2317          ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);
2318          ocMap.put(ocMacKey, ConfigConstants.OC_CRYPTO_MAC_KEY);
2319    
2320          // Create the operational and user attributes.
2321          LinkedHashMap<AttributeType,List<Attribute>> opAttrs =
2322               new LinkedHashMap<AttributeType,List<Attribute>>(0);
2323          LinkedHashMap<AttributeType,List<Attribute>> userAttrs =
2324               new LinkedHashMap<AttributeType,List<Attribute>>();
2325    
2326          // Add the key ID attribute.
2327          LinkedHashSet<AttributeValue> valueSet =
2328               new LinkedHashSet<AttributeValue>(1);
2329          valueSet.add(distinguishedValue);
2330    
2331          ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
2332          attrList.add(new Attribute(attrKeyID,
2333                                     attrKeyID.getNameOrOID(),
2334                                     valueSet));
2335          userAttrs.put(attrKeyID, attrList);
2336    
2337          // Add the mac algorithm name attribute.
2338          valueSet = new LinkedHashSet<AttributeValue>(1);
2339          valueSet.add(new AttributeValue(attrMacAlgorithm,
2340                                          keyEntry.getType()));
2341    
2342          attrList = new ArrayList<Attribute>(1);
2343          attrList.add(
2344               new Attribute(attrMacAlgorithm,
2345                             attrMacAlgorithm.getNameOrOID(),
2346                             valueSet));
2347          userAttrs.put(attrMacAlgorithm, attrList);
2348    
2349    
2350          // Add the key length attribute.
2351          valueSet = new LinkedHashSet<AttributeValue>(1);
2352          valueSet.add(new AttributeValue(
2353                  attrKeyLength, String.valueOf(keyEntry.getKeyLengthBits())));
2354    
2355          attrList = new ArrayList<Attribute>(1);
2356          attrList.add(
2357               new Attribute(attrKeyLength,
2358                             attrKeyLength.getNameOrOID(),
2359                             valueSet));
2360          userAttrs.put(attrKeyLength, attrList);
2361    
2362    
2363          // Get the trusted certificates.
2364          Map<String, byte[]> trustedCerts =
2365               cryptoManager.getTrustedCertificates();
2366    
2367          // Need to add our own instance certificate.
2368          byte[] instanceKeyCertificate =
2369             CryptoManagerImpl.getInstanceKeyCertificateFromLocalTruststore();
2370          trustedCerts.put(getInstanceKeyID(instanceKeyCertificate),
2371                           instanceKeyCertificate);
2372    
2373          // Add the symmetric key attribute.
2374          LinkedHashSet<AttributeValue> symmetricKeyValues =
2375               new LinkedHashSet<AttributeValue>(trustedCerts.size());
2376    
2377          for (Map.Entry<String, byte[]> mapEntry :
2378               trustedCerts.entrySet())
2379          {
2380            String symmetricKey =
2381                 cryptoManager.encodeSymmetricKeyAttribute(
2382                      mapEntry.getKey(),
2383                      mapEntry.getValue(),
2384                      keyEntry.getSecretKey());
2385    
2386            symmetricKeyValues.add(
2387                 new AttributeValue(attrSymmetricKey, symmetricKey));
2388    
2389            attrList = new ArrayList<Attribute>(1);
2390            attrList.add(new Attribute(attrSymmetricKey,
2391                                       attrSymmetricKey.getNameOrOID(),
2392                                       symmetricKeyValues));
2393            userAttrs.put(attrSymmetricKey, attrList);
2394          }
2395    
2396          // Create the entry.
2397          Entry entry = new Entry(entryDN, ocMap, userAttrs, opAttrs);
2398    
2399          InternalClientConnection connection =
2400               InternalClientConnection.getRootConnection();
2401          AddOperation addOperation = connection.processAdd(entry);
2402          if (addOperation.getResultCode() != ResultCode.SUCCESS)
2403          {
2404            throw new CryptoManagerException(
2405                    ERR_CRYPTOMGR_SYMMETRIC_KEY_ENTRY_ADD_FAILED.get(
2406                            entry.getDN().toString(),
2407                            addOperation.getErrorMessage()));
2408          }
2409        }
2410    
2411        /**
2412         * Initializes a secret key entry from the supplied parameters,
2413         * validates it, and registers it in the supplied map. The
2414         * anticipated use of this method is to import a key entry from
2415         * ADS.
2416         *
2417         * @param cryptoManager  The CryptoManager instance.
2418         *
2419         * @param keyIDString  The key identifier.
2420         *
2421         * @param algorithm  The name of the MAC algorithm for which the
2422         * key entry is to be produced.
2423         *
2424         * @param secretKey  The MAC key.
2425         *
2426         * @param secretKeyLengthBits  The length of the secret key in
2427         * bits.
2428         *
2429         * @param isCompromised  Mark the key as compromised, so that it
2430         * will not subsequently be used for new data. The key entry
2431         * must be maintained in order to verify existing signatures.
2432         *
2433         * @return  The key entry, if one was successfully produced.
2434         *
2435         * @throws CryptoManagerException  In case of an error in the
2436         * parameters used to initialize or validate the key entry.
2437         */
2438        public static MacKeyEntry importMacKeyEntry(
2439                final CryptoManagerImpl cryptoManager,
2440                final String keyIDString,
2441                final String algorithm,
2442                final SecretKey secretKey,
2443                final int secretKeyLengthBits,
2444                final boolean isCompromised)
2445                throws CryptoManagerException {
2446          Validator.ensureNotNull(keyIDString, secretKey);
2447    
2448          final KeyEntryID keyID = new KeyEntryID(keyIDString);
2449    
2450          // Check map for existing key entry with the supplied keyID.
2451          MacKeyEntry keyEntry = getKeyEntry(cryptoManager, keyID);
2452          if (null != keyEntry) {
2453            // Paranoiac check to ensure exact type match.
2454            if (! (keyEntry.getType().equals(algorithm)
2455                    && keyEntry.getKeyLengthBits() == secretKeyLengthBits)) {
2456                   throw new CryptoManagerException(
2457                        ERR_CRYPTOMGR_IMPORT_KEY_ENTRY_FIELD_MISMATCH.get(
2458                             keyIDString));
2459            }
2460            // Allow transition to compromised.
2461            if (isCompromised && !keyEntry.isCompromised()) {
2462              keyEntry.setIsCompromised();
2463            }
2464            return keyEntry;
2465          }
2466    
2467          // Instantiate new entry.
2468          keyEntry = new MacKeyEntry(keyID, algorithm, secretKey,
2469                  secretKeyLengthBits, isCompromised);
2470    
2471          // Validate new entry.
2472          getMacEngine(keyEntry);
2473    
2474          // Cache new entry.
2475          cryptoManager.macKeyEntryCache.put(keyEntry.getKeyID(),
2476                  keyEntry);
2477    
2478          return keyEntry;
2479        }
2480    
2481    
2482        /**
2483         * Retrieve a MacKeyEntry from the MacKeyEntry Map based on
2484         * the algorithm name and key length.
2485         * <p>
2486         * ADS is not searched in the case a key entry meeting the
2487         * specifications is not found. Instead, the ADS monitoring thread
2488         * is responsible for asynchronous updates to the key map.
2489         *
2490         * @param cryptoManager  The CryptoManager instance with which the
2491         * key entry is associated.
2492         *
2493         * @param algorithm  The MAC algorithm for which the key was
2494         * produced.
2495         *
2496         * @param keyLengthBits  The MAC key length in bits.
2497         *
2498         * @return  The key entry corresponding to the parameters, or
2499         * {@code null} if no such entry exists.
2500         */
2501        public static MacKeyEntry getKeyEntry(
2502                final CryptoManagerImpl cryptoManager,
2503                final String algorithm,
2504                final int keyLengthBits) {
2505          Validator.ensureNotNull(cryptoManager, algorithm);
2506          Validator.ensureTrue(0 < keyLengthBits);
2507    
2508          MacKeyEntry keyEntry = null;
2509          // search for an existing key that satisfies the request
2510          for (Map.Entry<KeyEntryID, MacKeyEntry> i
2511                  : cryptoManager.macKeyEntryCache.entrySet()) {
2512            MacKeyEntry entry = i.getValue();
2513            if (! entry.isCompromised()
2514                    && entry.getType().equals(algorithm)
2515                    && entry.getKeyLengthBits() == keyLengthBits) {
2516              keyEntry = entry;
2517              break;
2518            }
2519          }
2520    
2521          return keyEntry;
2522        }
2523    
2524    
2525        /**
2526         * Given a key identifier, return the associated cipher key entry
2527         * from the supplied map. This method would typically be used by
2528         * a decryption routine.
2529         * <p>
2530         * Although the existence of data tagged with the requested keyID
2531         * implies the key entry exists in the system, it is possible for
2532         * the distribution of the key entry to lag that of the data;
2533         * hence this routine might return null. No attempt is made to
2534         * query the other instances in the ADS topology (presumably at
2535         * least the instance producing the key entry will have it), due
2536         * to the presumed infrequency of key generation and expected low
2537         * latency of replication, compared to the complexity of finding
2538         * the set of instances and querying them. Instead, the caller
2539         * must retry the operation requesting the decryption.
2540         *
2541         * @param cryptoManager  The CryptoManager instance with which the
2542         * key entry is associated.
2543         *
2544         * @param keyID  The key identifier.
2545         *
2546         * @return  The key entry associated with the key identifier, or
2547         * {@code null} if no such entry exists.
2548         *
2549         * @see CryptoManagerImpl.CipherKeyEntry
2550         *     #getKeyEntry(org.opends.server.types.CryptoManager,
2551         *                  java.lang.String, int)
2552         */
2553        public static MacKeyEntry getKeyEntry(
2554                final CryptoManagerImpl cryptoManager,
2555                final KeyEntryID keyID) {
2556          return cryptoManager.macKeyEntryCache.get(keyID);
2557        }
2558    
2559        /**
2560         * Construct an instance of {@code MacKeyEntry} using the
2561         * specified parameters. This constructor would typically be used
2562         * for key generation.
2563         *
2564         * @param algorithm  The name of the MAC algorithm for which the
2565         * key entry is to be produced.
2566         *
2567         * @param keyLengthBits  The length of the requested key in bits.
2568         *
2569         * @throws CryptoManagerException If there is a problem
2570         * instantiating the key generator.
2571         */
2572        private MacKeyEntry(final String algorithm,
2573                            final int keyLengthBits)
2574                throws CryptoManagerException {
2575          // Generate a new key.
2576          super(algorithm, keyLengthBits);
2577    
2578          // copy arguments
2579          this.fType = algorithm;
2580        }
2581    
2582        /**
2583         * Construct an instance of MacKeyEntry using the specified
2584         * parameters. This constructor would typically be used for key
2585         * entries imported from ADS, for which the full set of paramters
2586         * is known.
2587         *
2588         * @param keyID  The unique identifier of this MAC algorithm/key
2589         * pair.
2590         *
2591         * @param algorithm  The name of the MAC algorithm for which the
2592         * key entry is to be produced.
2593         *
2594         * @param secretKey  The MAC key.
2595         *
2596         * @param secretKeyLengthBits  The length of the secret key in
2597         * bits.
2598         *
2599         * @param isCompromised {@code false} if the key may be used
2600         * for signing, or {@code true} if the key is being retained only
2601         * for use in signature verification.
2602         */
2603        private MacKeyEntry(final KeyEntryID keyID,
2604                            final String algorithm,
2605                            final SecretKey secretKey,
2606                            final int secretKeyLengthBits,
2607                            final boolean isCompromised) {
2608          super(keyID, secretKey, secretKeyLengthBits, isCompromised);
2609    
2610          // copy arguments
2611          this.fType = algorithm;
2612        }
2613    
2614    
2615        /**
2616         * The algorithm for which the key entry was created.
2617         *
2618         * @return The algorithm.
2619         */
2620        public String getType() {
2621          return fType;
2622        }
2623    
2624        // state
2625        private final String fType;
2626      }
2627    
2628    
2629      /**
2630       * This method produces an initialized MAC engine based on the
2631       * supplied MacKeyEntry's state.
2632       *
2633       * @param keyEntry The MacKeyEntry specifying the Mac properties.
2634       *
2635       * @return  An initialized Mac object.
2636       *
2637       * @throws CryptoManagerException  In case there was a error
2638       * instantiating the Mac object.
2639       */
2640      private static Mac getMacEngine(MacKeyEntry keyEntry)
2641              throws CryptoManagerException
2642      {
2643        Mac mac;
2644        try {
2645          mac = Mac.getInstance(keyEntry.getType());
2646        }
2647        catch (NoSuchAlgorithmException ex){
2648          if (debugEnabled()) {
2649            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2650          }
2651          throw new CryptoManagerException(
2652                  ERR_CRYPTOMGR_GET_MAC_ENGINE_INVALID_MAC_ALGORITHM.get(
2653                          keyEntry.getType(), getExceptionMessage(ex)),
2654                  ex);
2655        }
2656    
2657        try {
2658          mac.init(keyEntry.getSecretKey());
2659        }
2660        catch (InvalidKeyException ex) {
2661          if (debugEnabled()) {
2662            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2663          }
2664          throw new CryptoManagerException(
2665               ERR_CRYPTOMGR_GET_MAC_ENGINE_CANNOT_INITIALIZE.get(
2666                       getExceptionMessage(ex)), ex);
2667        }
2668    
2669        return mac;
2670      }
2671    
2672    
2673      /** {@inheritDoc} */
2674      public String getPreferredMessageDigestAlgorithm()
2675      {
2676        return preferredDigestAlgorithm;
2677      }
2678    
2679    
2680      /** {@inheritDoc} */
2681      public MessageDigest getPreferredMessageDigest()
2682             throws NoSuchAlgorithmException
2683      {
2684        return MessageDigest.getInstance(preferredDigestAlgorithm);
2685      }
2686    
2687    
2688      /** {@inheritDoc} */
2689      public MessageDigest getMessageDigest(String digestAlgorithm)
2690             throws NoSuchAlgorithmException
2691      {
2692        return MessageDigest.getInstance(digestAlgorithm);
2693      }
2694    
2695    
2696      /** {@inheritDoc} */
2697      public byte[] digest(byte[] data)
2698             throws NoSuchAlgorithmException
2699      {
2700        return MessageDigest.getInstance(preferredDigestAlgorithm).
2701                    digest(data);
2702      }
2703    
2704    
2705      /** {@inheritDoc} */
2706      public byte[] digest(String digestAlgorithm, byte[] data)
2707             throws NoSuchAlgorithmException
2708      {
2709        return MessageDigest.getInstance(digestAlgorithm).digest(data);
2710      }
2711    
2712    
2713      /** {@inheritDoc} */
2714      public byte[] digest(InputStream inputStream)
2715             throws IOException, NoSuchAlgorithmException
2716      {
2717        MessageDigest digest =
2718             MessageDigest.getInstance(preferredDigestAlgorithm);
2719    
2720        byte[] buffer = new byte[8192];
2721        while (true)
2722        {
2723          int bytesRead = inputStream.read(buffer);
2724          if (bytesRead < 0)
2725          {
2726            break;
2727          }
2728    
2729          digest.update(buffer, 0, bytesRead);
2730        }
2731    
2732        return digest.digest();
2733      }
2734    
2735    
2736      /** {@inheritDoc} */
2737      public byte[] digest(String digestAlgorithm,
2738                           InputStream inputStream)
2739             throws IOException, NoSuchAlgorithmException
2740      {
2741        MessageDigest digest = MessageDigest.getInstance(digestAlgorithm);
2742    
2743        byte[] buffer = new byte[8192];
2744        while (true)
2745        {
2746          int bytesRead = inputStream.read(buffer);
2747          if (bytesRead < 0)
2748          {
2749            break;
2750          }
2751    
2752          digest.update(buffer, 0, bytesRead);
2753        }
2754    
2755        return digest.digest();
2756      }
2757    
2758    
2759      /** {@inheritDoc} */
2760      public String getMacEngineKeyEntryID()
2761              throws CryptoManagerException
2762      {
2763        return getMacEngineKeyEntryID(preferredMACAlgorithm,
2764                preferredMACAlgorithmKeyLengthBits);
2765      }
2766    
2767    
2768      /** {@inheritDoc} */
2769      public String getMacEngineKeyEntryID(final String macAlgorithm,
2770                                           final int keyLengthBits)
2771             throws CryptoManagerException {
2772        Validator.ensureNotNull(macAlgorithm);
2773    
2774        MacKeyEntry keyEntry = MacKeyEntry.getKeyEntry(this, macAlgorithm,
2775                                                       keyLengthBits);
2776        if (null == keyEntry) {
2777          keyEntry = MacKeyEntry.generateKeyEntry(this, macAlgorithm,
2778                                                  keyLengthBits);
2779        }
2780    
2781        return keyEntry.getKeyID().getStringValue();
2782      }
2783    
2784    
2785      /** {@inheritDoc} */
2786      public Mac getMacEngine(String keyEntryID)
2787              throws CryptoManagerException
2788      {
2789        final MacKeyEntry keyEntry = MacKeyEntry.getKeyEntry(this,
2790                new KeyEntryID(keyEntryID));
2791        return (null == keyEntry) ? null : getMacEngine(keyEntry);
2792      }
2793    
2794    
2795      /** {@inheritDoc} */
2796      public byte[] encrypt(byte[] data)
2797             throws GeneralSecurityException, CryptoManagerException
2798      {
2799        return encrypt(preferredCipherTransformation,
2800                preferredCipherTransformationKeyLengthBits, data);
2801      }
2802    
2803    
2804      /** {@inheritDoc} */
2805      public byte[] encrypt(String cipherTransformation,
2806                            int keyLengthBits,
2807                            byte[] data)
2808             throws GeneralSecurityException, CryptoManagerException
2809      {
2810        Validator.ensureNotNull(cipherTransformation, data);
2811    
2812        CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(this,
2813                cipherTransformation, keyLengthBits);
2814        if (null == keyEntry) {
2815          keyEntry = CipherKeyEntry.generateKeyEntry(this, cipherTransformation,
2816                  keyLengthBits);
2817        }
2818    
2819        final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
2820    
2821        final byte[] keyID = keyEntry.getKeyID().getByteValue();
2822        final byte[] iv = cipher.getIV();
2823        final int prologueLength
2824                = /* version */ 1 + keyID.length + ((null == iv) ? 0 : iv.length);
2825        final int dataLength = cipher.getOutputSize(data.length);
2826        final byte[] cipherText = new byte[prologueLength + dataLength];
2827        int writeIndex = 0;
2828        cipherText[writeIndex++] = CIPHERTEXT_PROLOGUE_VERSION;
2829        System.arraycopy(keyID, 0, cipherText, writeIndex, keyID.length);
2830        writeIndex += keyID.length;
2831        if (null != iv) {
2832          System.arraycopy(iv, 0, cipherText, writeIndex, iv.length);
2833          writeIndex += iv.length;
2834        }
2835        System.arraycopy(cipher.doFinal(data), 0, cipherText,
2836                         prologueLength, dataLength);
2837        return cipherText;
2838      }
2839    
2840    
2841      /** {@inheritDoc} */
2842      public CipherOutputStream getCipherOutputStream(
2843              OutputStream outputStream) throws CryptoManagerException
2844      {
2845        return getCipherOutputStream(preferredCipherTransformation,
2846                preferredCipherTransformationKeyLengthBits, outputStream);
2847      }
2848    
2849    
2850      /** {@inheritDoc} */
2851      public CipherOutputStream getCipherOutputStream(
2852              String cipherTransformation, int keyLengthBits,
2853              OutputStream outputStream)
2854             throws CryptoManagerException
2855      {
2856        Validator.ensureNotNull(cipherTransformation, outputStream);
2857    
2858        CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(
2859                this, cipherTransformation, keyLengthBits);
2860        if (null == keyEntry) {
2861          keyEntry = CipherKeyEntry.generateKeyEntry(this, cipherTransformation,
2862                  keyLengthBits);
2863        }
2864    
2865        final Cipher cipher = getCipher(keyEntry, Cipher.ENCRYPT_MODE, null);
2866        final byte[] keyID = keyEntry.getKeyID().getByteValue();
2867        try {
2868          outputStream.write((byte)CIPHERTEXT_PROLOGUE_VERSION);
2869          outputStream.write(keyID);
2870          if (null != cipher.getIV()) {
2871            outputStream.write(cipher.getIV());
2872          }
2873        }
2874        catch (IOException ex) {
2875          if (debugEnabled()) {
2876            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2877          }
2878          throw new CryptoManagerException(
2879                 ERR_CRYPTOMGR_GET_CIPHER_STREAM_PROLOGUE_WRITE_ERROR.get(
2880                         getExceptionMessage(ex)), ex);
2881        }
2882    
2883        return new CipherOutputStream(outputStream, cipher);
2884      }
2885    
2886    
2887      /** {@inheritDoc} */
2888      public byte[] decrypt(byte[] data)
2889             throws GeneralSecurityException,
2890                    CryptoManagerException
2891      {
2892        int readIndex = 0;
2893    
2894        int version;
2895        try {
2896          version = data[readIndex++];
2897        }
2898        catch (Exception ex) {
2899          // IndexOutOfBoundsException, ArrayStoreException, ...
2900          if (debugEnabled()) {
2901            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2902          }
2903          throw new CryptoManagerException(
2904                  ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_PROLOGUE_VERSION.get(
2905                          ex.getMessage()), ex);
2906        }
2907        switch (version) {
2908          case CIPHERTEXT_PROLOGUE_VERSION:
2909            // Encryption key identifier only in the data prologue.
2910            break;
2911    
2912          default:
2913            throw new CryptoManagerException(
2914                    ERR_CRYPTOMGR_DECRYPT_UNKNOWN_PROLOGUE_VERSION.get(version));
2915        }
2916    
2917        KeyEntryID keyID;
2918        try {
2919          final byte[] keyIDBytes
2920                  = new byte[KeyEntryID.getByteValueLength()];
2921          System.arraycopy(data, readIndex, keyIDBytes, 0, keyIDBytes.length);
2922          readIndex += keyIDBytes.length;
2923          keyID = new KeyEntryID(keyIDBytes);
2924        }
2925        catch (Exception ex) {
2926          // IndexOutOfBoundsException, ArrayStoreException, ...
2927          if (debugEnabled()) {
2928            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2929          }
2930          throw new CryptoManagerException(
2931               ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get(
2932                       ex.getMessage()), ex);
2933        }
2934    
2935        CipherKeyEntry keyEntry = CipherKeyEntry.getKeyEntry(this, keyID);
2936        if (null == keyEntry) {
2937          throw new CryptoManagerException(
2938                  ERR_CRYPTOMGR_DECRYPT_UNKNOWN_KEY_IDENTIFIER.get());
2939        }
2940    
2941        byte[] iv = null;
2942        if (0 < keyEntry.getIVLengthBits()) {
2943          iv = new byte[keyEntry.getIVLengthBits()/Byte.SIZE];
2944          try {
2945            System.arraycopy(data, readIndex, iv, 0, iv.length);
2946            readIndex += iv.length;
2947          }
2948          catch (Exception ex) {
2949            // IndexOutOfBoundsException, ArrayStoreException, ...
2950            if (debugEnabled()) {
2951              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
2952            }
2953            throw new CryptoManagerException(
2954                   ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_IV.get(), ex);
2955          }
2956        }
2957    
2958        final Cipher cipher = getCipher(keyEntry, Cipher.DECRYPT_MODE, iv);
2959        return cipher.doFinal(data, readIndex, data.length - readIndex);
2960      }
2961    
2962    
2963     /** {@inheritDoc} */
2964      public CipherInputStream getCipherInputStream(
2965              InputStream inputStream) throws CryptoManagerException
2966      {
2967        int version;
2968        CipherKeyEntry keyEntry;
2969        byte[] iv = null;
2970        try {
2971          final byte[] rawVersion = new byte[1];
2972          if (rawVersion.length != inputStream.read(rawVersion)) {
2973            throw new CryptoManagerException(
2974                    ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_PROLOGUE_VERSION.get(
2975                          "stream underflow"));
2976          }
2977          version = rawVersion[0];
2978          switch (version) {
2979            case CIPHERTEXT_PROLOGUE_VERSION:
2980              // Encryption key identifier only in the data prologue.
2981              break;
2982    
2983            default:
2984              throw new CryptoManagerException(
2985                      ERR_CRYPTOMGR_DECRYPT_UNKNOWN_PROLOGUE_VERSION.get(version));
2986          }
2987    
2988          final byte[] keyID = new byte[KeyEntryID.getByteValueLength()];
2989          if (keyID.length != inputStream.read(keyID)) {
2990            throw new CryptoManagerException(
2991               ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_KEY_IDENTIFIER.get(
2992                       "stream underflow"));
2993          }
2994          keyEntry = CipherKeyEntry.getKeyEntry(this,
2995                  new KeyEntryID(keyID));
2996          if (null == keyEntry) {
2997            throw new CryptoManagerException(
2998                    ERR_CRYPTOMGR_DECRYPT_UNKNOWN_KEY_IDENTIFIER.get());
2999          }
3000    
3001          if (0 < keyEntry.getIVLengthBits()) {
3002            iv = new byte[keyEntry.getIVLengthBits() / Byte.SIZE];
3003            if (iv.length != inputStream.read(iv)) {
3004              throw new CryptoManagerException(
3005                      ERR_CRYPTOMGR_DECRYPT_FAILED_TO_READ_IV.get());
3006            }
3007          }
3008        }
3009        catch (IOException ex) {
3010          throw new CryptoManagerException(
3011                 ERR_CRYPTOMGR_DECRYPT_CIPHER_INPUT_STREAM_ERROR.get(
3012                         getExceptionMessage(ex)), ex);
3013        }
3014    
3015        return new CipherInputStream(inputStream,
3016                getCipher(keyEntry, Cipher.DECRYPT_MODE, iv));
3017      }
3018    
3019    
3020      /** {@inheritDoc} */
3021      public int compress(byte[] src, byte[] dst)
3022      {
3023        Deflater deflater = new Deflater();
3024        try
3025        {
3026          deflater.setInput(src);
3027          deflater.finish();
3028    
3029          int compressedLength = deflater.deflate(dst);
3030          if (deflater.finished())
3031          {
3032            return compressedLength;
3033          }
3034          else
3035          {
3036            return -1;
3037          }
3038        }
3039        finally
3040        {
3041          deflater.end();
3042        }
3043      }
3044    
3045    
3046      /** {@inheritDoc} */
3047      public int uncompress(byte[] src, byte[] dst)
3048             throws DataFormatException
3049      {
3050        Inflater inflater = new Inflater();
3051        try
3052        {
3053          inflater.setInput(src);
3054    
3055          int decompressedLength = inflater.inflate(dst);
3056          if (inflater.finished())
3057          {
3058            return decompressedLength;
3059          }
3060          else
3061          {
3062            int totalLength = decompressedLength;
3063    
3064            while (! inflater.finished())
3065            {
3066              totalLength += inflater.inflate(dst);
3067            }
3068    
3069            return -totalLength;
3070          }
3071        }
3072        finally
3073        {
3074          inflater.end();
3075        }
3076      }
3077    
3078    
3079      /** {@inheritDoc} */
3080      public SSLContext getSslContext(String sslCertNickname)
3081           throws ConfigException
3082      {
3083        SSLContext sslContext;
3084        try
3085        {
3086          TrustStoreBackend trustStoreBackend = getTrustStoreBackend();
3087          KeyManager[] keyManagers = trustStoreBackend.getKeyManagers();
3088          TrustManager[] trustManagers =
3089               trustStoreBackend.getTrustManagers();
3090    
3091          sslContext = SSLContext.getInstance("TLS");
3092    
3093          if (sslCertNickname == null)
3094          {
3095            sslContext.init(keyManagers, trustManagers, null);
3096          }
3097          else
3098          {
3099            X509ExtendedKeyManager[] extendedKeyManagers =
3100                 SelectableCertificateKeyManager.wrap(
3101                      keyManagers,
3102                      sslCertNickname);
3103            sslContext.init(extendedKeyManagers, trustManagers, null);
3104          }
3105        }
3106        catch (Exception e)
3107        {
3108          if (debugEnabled())
3109          {
3110            TRACER.debugCaught(DebugLogLevel.ERROR, e);
3111          }
3112    
3113          Message message =
3114               ERR_CRYPTOMGR_SSL_CONTEXT_CANNOT_INITIALIZE.get(
3115                    getExceptionMessage(e));
3116          throw new ConfigException(message, e);
3117        }
3118    
3119        return sslContext;
3120      }
3121    
3122    
3123      /** {@inheritDoc} */
3124      public String getSslCertNickname()
3125      {
3126        return sslCertNickname;
3127      }
3128    
3129      /** {@inheritDoc} */
3130      public boolean isSslEncryption()
3131      {
3132        return sslEncryption;
3133      }
3134    
3135      /** {@inheritDoc} */
3136      public SortedSet<String> getSslProtocols()
3137      {
3138        return sslProtocols;
3139      }
3140    
3141      /** {@inheritDoc} */
3142      public SortedSet<String> getSslCipherSuites()
3143      {
3144        return sslCipherSuites;
3145      }
3146    }