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