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 }