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