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