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