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