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