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