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