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