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