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