diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/AddHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/AddHSMProfileCmd.java index 9617a48b260..b7410e4cfea 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/AddHSMProfileCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/AddHSMProfileCmd.java @@ -47,7 +47,7 @@ import java.util.Map; @APICommand(name = "addHSMProfile", description = "Adds a new HSM profile", responseObject = HSMProfileResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + authorized = { RoleType.Admin }) public class AddHSMProfileCmd extends BaseCmd { @Inject diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java index 8fc0a47c3ad..5f70eb7a899 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java @@ -40,7 +40,7 @@ import javax.inject.Inject; @APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + authorized = { RoleType.Admin }) public class DeleteHSMProfileCmd extends BaseCmd { @Inject diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java index 36de6d447e1..a408c967362 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java @@ -40,7 +40,7 @@ import javax.inject.Inject; @APICommand(name = "updateHSMProfile", description = "Updates an HSM profile", responseObject = HSMProfileResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + authorized = { RoleType.Admin }) public class UpdateHSMProfileCmd extends BaseCmd { @Inject diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java index d444588a32a..8a763623fba 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java @@ -170,9 +170,7 @@ public class KMSWrappedKeyVO { @Override public String toString() { - return String.format("KMSWrappedKey %s", - ReflectionToStringBuilderUtils.reflectOnlySelectedFields( - this, "id", "uuid", "kmsKeyId", "kekVersionId", "accountId", "zoneId", "state", "created", - "removed")); + return String.format("KMSWrappedKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "kmsKeyId", "kekVersionId", "zoneId", "created", "removed")); } } diff --git a/framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSProvider.java b/framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSProvider.java index cea8ccd82a9..2b793fe560a 100644 --- a/framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSProvider.java +++ b/framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSProvider.java @@ -36,6 +36,31 @@ import org.apache.cloudstack.framework.config.Configurable; */ public interface KMSProvider extends Configurable, Adapter { + /** + * Returns {@code true} if the given HSM profile configuration key name refers + * to a + * sensitive value (PIN, password, secret, or private key) that must be + * encrypted at + * rest and masked in API responses. + * + *
+ * This is a shared naming-convention helper used by both KMS providers (when + * loading/storing profile details) and the KMS manager (when building API + * responses). + * + * @param key configuration key name (case-insensitive); null returns false + * @return true if the key is considered sensitive + */ + static boolean isSensitiveKey(String key) { + if (key == null) { + return false; + } + return key.equalsIgnoreCase("pin") || + key.equalsIgnoreCase("password") || + key.toLowerCase().contains("secret") || + key.equalsIgnoreCase("private_key"); + } + /** * Get the unique name of this provider * @@ -43,18 +68,6 @@ public interface KMSProvider extends Configurable, Adapter { */ String getProviderName(); - /** - * Create a new Key Encryption Key (KEK) in the secure backend with explicit HSM profile. - * - * @param purpose the purpose/scope for this KEK - * @param label human-readable label for the KEK (must be unique within purpose) - * @param keyBits key size in bits (typically 128, 192, or 256) - * @param hsmProfileId optional HSM profile ID to create the KEK in (null for auto-resolution/default) - * @return the KEK identifier (label or handle) for later reference - * @throws KMSException if KEK creation fails - */ - String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException; - /** * Create a new Key Encryption Key (KEK) in the secure backend. * Delegates to {@link #createKek(KeyPurpose, String, int, Long)} with null profile ID. @@ -69,6 +82,18 @@ public interface KMSProvider extends Configurable, Adapter { return createKek(purpose, label, keyBits, null); } + /** + * Create a new Key Encryption Key (KEK) in the secure backend with explicit HSM profile. + * + * @param purpose the purpose/scope for this KEK + * @param label human-readable label for the KEK (must be unique within purpose) + * @param keyBits key size in bits (typically 128, 192, or 256) + * @param hsmProfileId optional HSM profile ID to create the KEK in (null for auto-resolution/default) + * @return the KEK identifier (label or handle) for later reference + * @throws KMSException if KEK creation fails + */ + String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException; + /** * Delete a KEK from the secure backend. * WARNING: This will make all DEKs wrapped by this KEK unrecoverable. @@ -78,7 +103,6 @@ public interface KMSProvider extends Configurable, Adapter { */ void deleteKek(String kekId) throws KMSException; - /** * Check if a KEK exists and is accessible * @@ -88,18 +112,6 @@ public interface KMSProvider extends Configurable, Adapter { */ boolean isKekAvailable(String kekId) throws KMSException; - /** - * Wrap (encrypt) a plaintext Data Encryption Key with a KEK using explicit HSM profile. - * - * @param plainDek the plaintext DEK to wrap (caller must zeroize after call) - * @param purpose the intended purpose of this DEK - * @param kekLabel the label of the KEK to use for wrapping - * @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default) - * @return WrappedKey containing the encrypted DEK and metadata - * @throws KMSException if wrapping fails or KEK not found - */ - WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException; - /** * Wrap (encrypt) a plaintext Data Encryption Key with a KEK. * Delegates to {@link #wrapKey(byte[], KeyPurpose, String, Long)} with null profile ID. @@ -115,16 +127,16 @@ public interface KMSProvider extends Configurable, Adapter { } /** - * Unwrap (decrypt) a wrapped DEK to obtain the plaintext key using explicit HSM profile. - *
- * SECURITY: Caller MUST zeroize the returned byte array after use + * Wrap (encrypt) a plaintext Data Encryption Key with a KEK using explicit HSM profile. * - * @param wrappedKey the wrapped key to decrypt + * @param plainDek the plaintext DEK to wrap (caller must zeroize after call) + * @param purpose the intended purpose of this DEK + * @param kekLabel the label of the KEK to use for wrapping * @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default) - * @return plaintext DEK (caller must zeroize!) - * @throws KMSException if unwrapping fails or KEK not found + * @return WrappedKey containing the encrypted DEK and metadata + * @throws KMSException if wrapping fails or KEK not found */ - byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException; + WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException; /** * Unwrap (decrypt) a wrapped DEK to obtain the plaintext key. @@ -141,18 +153,16 @@ public interface KMSProvider extends Configurable, Adapter { } /** - * Generate a new random DEK and immediately wrap it with a KEK using explicit HSM profile. - * (convenience method combining generation + wrapping) + * Unwrap (decrypt) a wrapped DEK to obtain the plaintext key using explicit HSM profile. + *
+ * SECURITY: Caller MUST zeroize the returned byte array after use
*
- * @param purpose the intended purpose of the new DEK
- * @param kekLabel the label of the KEK to use for wrapping
- * @param keyBits DEK size in bits (typically 128, 192, or 256)
+ * @param wrappedKey the wrapped key to decrypt
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
- * @return WrappedKey containing the newly generated and wrapped DEK
- * @throws KMSException if generation or wrapping fails
+ * @return plaintext DEK (caller must zeroize!)
+ * @throws KMSException if unwrapping fails or KEK not found
*/
- WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits,
- Long hsmProfileId) throws KMSException;
+ byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException;
/**
* Generate a new random DEK and immediately wrap it with a KEK.
@@ -170,16 +180,18 @@ public interface KMSProvider extends Configurable, Adapter {
}
/**
- * Rewrap a DEK with a different KEK (used during key rotation) using explicit target HSM profile.
- * This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
+ * Generate a new random DEK and immediately wrap it with a KEK using explicit HSM profile.
+ * (convenience method combining generation + wrapping)
*
- * @param oldWrappedKey the currently wrapped key
- * @param newKekLabel the label of the new KEK to wrap with
- * @param targetHsmProfileId optional target HSM profile ID to wrap with (null for auto-resolution/default)
- * @return new WrappedKey encrypted with the new KEK
- * @throws KMSException if rewrapping fails
+ * @param purpose the intended purpose of the new DEK
+ * @param kekLabel the label of the KEK to use for wrapping
+ * @param keyBits DEK size in bits (typically 128, 192, or 256)
+ * @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
+ * @return WrappedKey containing the newly generated and wrapped DEK
+ * @throws KMSException if generation or wrapping fails
*/
- WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException;
+ WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits,
+ Long hsmProfileId) throws KMSException;
/**
* Rewrap a DEK with a different KEK (used during key rotation).
@@ -195,6 +207,18 @@ public interface KMSProvider extends Configurable, Adapter {
return rewrapKey(oldWrappedKey, newKekLabel, null);
}
+ /**
+ * Rewrap a DEK with a different KEK (used during key rotation) using explicit target HSM profile.
+ * This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
+ *
+ * @param oldWrappedKey the currently wrapped key
+ * @param newKekLabel the label of the new KEK to wrap with
+ * @param targetHsmProfileId optional target HSM profile ID to wrap with (null for auto-resolution/default)
+ * @return new WrappedKey encrypted with the new KEK
+ * @throws KMSException if rewrapping fails
+ */
+ WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException;
+
/**
* Perform health check on the provider backend
*
diff --git a/plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java b/plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java
index ae86d8ced7f..54376f55c08 100644
--- a/plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java
+++ b/plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java
@@ -37,10 +37,10 @@ import javax.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import java.io.Closeable;
import java.io.File;
@@ -75,6 +75,12 @@ import java.util.concurrent.TimeUnit;
public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
private static final Logger logger = LogManager.getLogger(PKCS11HSMProvider.class);
private static final String PROVIDER_NAME = "pkcs11";
+ // Security note (#7): AES-CBC provides confidentiality but not authenticity (no
+ // HMAC).
+ // While AES-GCM is preferred, SunPKCS11 support for GCM is often buggy or
+ // missing
+ // depending on the underlying driver. We rely on the HSM/storage for tamper
+ // resistance.
// AES-CBC with PKCS5Padding: FIPS-compliant (NIST SP 800-38A) with universal PKCS#11 support
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
@@ -228,6 +234,15 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
}
}
+ @Override
+ public void invalidateProfileCache(Long profileId) {
+ HSMSessionPool pool = sessionPools.remove(profileId);
+ if (pool != null) {
+ pool.invalidate();
+ }
+ logger.info("Invalidated HSM session pool for profile {}", profileId);
+ }
+
Long resolveProfileId(String kekLabel) throws KMSException {
KMSKekVersionVO version = kmsKekVersionDao.findByKekLabel(kekLabel);
if (version != null && version.getHsmProfileId() != null) {
@@ -312,11 +327,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
}
}
- String pin = config.get("pin");
- if (StringUtils.isEmpty(pin)) {
- throw KMSException.invalidParameter("pin is required for PKCS#11 HSM profile");
- }
-
File libraryFile = new File(libraryPath);
if (!libraryFile.exists() && !libraryFile.isAbsolute()) {
// The HSM library might be in the system library path
@@ -353,10 +363,7 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
}
boolean isSensitiveKey(String key) {
- return key.equalsIgnoreCase("pin") ||
- key.equalsIgnoreCase("password") ||
- key.toLowerCase().contains("secret") ||
- key.equalsIgnoreCase("private_key");
+ return KMSProvider.isSensitiveKey(key);
}
String generateKekLabel(KeyPurpose purpose) {
@@ -373,15 +380,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
return new ConfigKey>[0];
}
- @Override
- public void invalidateProfileCache(Long profileId) {
- HSMSessionPool pool = sessionPools.remove(profileId);
- if (pool != null) {
- pool.invalidate();
- }
- logger.info("Invalidated HSM session pool for profile {}", profileId);
- }
-
@FunctionalInterface
private interface SessionOperation This method:
- * Note: Errors during cleanup are logged but do not throw exceptions
+ *
+ * Note: Errors during cleanup are logged but do not throw exceptions
* to ensure cleanup continues even if some steps fail.
*/
void close() {
@@ -770,25 +761,26 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
/**
* Generates an AES key directly in the HSM with the specified label.
*
- * Implementation note: Due to limitations in the Java PKCS#11 API, this method:
- *
+ * This method generates the key natively inside the HSM using a
+ * {@link KeyGenerator} configured with the PKCS#11 provider, so the key
+ * material never leaves the HSM boundary. The returned PKCS#11-native key
+ * reference ({@code P11Key}) is then stored in the KeyStore under the
+ * requested label.
*
- * While the key is briefly in software memory, this is necessary because:
- *
+ * Using {@code KeyGenerator} with the HSM provider is required for
+ * HSMs such as NetHSM that do not support importing raw secret-key bytes
+ * via {@code KeyStore.setKeyEntry()}. By generating the key on the HSM first,
+ * the value passed to {@code setKeyEntry()} is already a PKCS#11 token object,
+ * so no raw-bytes import is attempted.
*
- * Once imported, the key:
+ *
+ * Once stored, the key:
* Uses AES-CBC with PKCS5Padding (FIPS 197 + NIST SP 800-38A):
- *
+ * Uses AES-CBC with PKCS5Padding. Expected format: [IV (16 bytes)][ciphertext].
*
- * Security: The returned plaintext DEK must be zeroized by the caller after use.
- *
- * Expected format: [IV (16 bytes)][ciphertext]
+ *
+ * Security: The returned plaintext DEK must be zeroized by the caller after
+ * use.
*
* @param wrappedBlob Wrapped DEK blob (IV + ciphertext)
* @param kekLabel Label of the KEK stored in the HSM
* @return Plaintext DEK
* @throws KMSException with appropriate ErrorType:
*
- *
- *
- *
- *
+ *
- *
+ *
- *
*
* @param label Unique label for the key in the HSM
@@ -800,36 +792,35 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
String generateKey(String label, int keyBits, KeyPurpose purpose) throws KMSException {
validateKeySize(keyBits);
- byte[] keyBytes = null;
try {
// Check if key with this label already exists
if (keyStore.containsAlias(label)) {
throw KMSException.keyAlreadyExists("Key with label '" + label + "' already exists in HSM");
}
- // Generate cryptographically secure random key material
- // Using SecureRandom instead of HSM generation due to Java PKCS#11 API limitations
- keyBytes = new byte[keyBits / 8];
- SecureRandom.getInstanceStrong().nextBytes(keyBytes);
+ // Generate the AES key natively inside the HSM using the PKCS#11 provider.
+ // This avoids importing raw key bytes into the HSM, which is not supported
+ // by all HSMs (e.g. NetHSM rejects SecretKeySpec via storeSkey()).
+ // The resulting key is a PKCS#11-native P11Key that lives inside the token.
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES", provider);
+ keyGen.init(keyBits);
+ SecretKey hsmKey = keyGen.generateKey();
- // Wrap key bytes in a SecretKeySpec for import into HSM
- SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
+ // Associate the HSM-generated key with the requested label by storing
+ // it in the PKCS#11 KeyStore. Because hsmKey is already a P11Key
+ // (not a software SecretKeySpec), P11KeyStore.storeSkey() stores it
+ // as a persistent token object (CKA_TOKEN=true) with CKA_LABEL=label
+ // without attempting any raw-bytes conversion.
+ keyStore.setKeyEntry(label, hsmKey, null, null);
- // Import into PKCS#11 KeyStore with label
- // Uses setKeyEntry(String, Key, char[], Certificate[]) which is the only
- // variant supported by P11KeyStore (the byte[] variant throws UnsupportedOperationException)
- // The P11KeyStore will internally convert the SecretKeySpec to a P11 token object with:
- // - CKA_TOKEN=true, CKA_LABEL=label, CKA_EXTRACTABLE=false
- keyStore.setKeyEntry(label, secretKey, null, null);
-
- logger.info("Generated and imported AES-{} key '{}' into HSM (purpose: {})",
+ logger.info("Generated AES-{} key '{}' in HSM (purpose: {})",
keyBits, label, purpose);
return label;
} catch (KeyStoreException e) {
- handlePKCS11Exception(e, "Failed to import key into HSM KeyStore");
+ handlePKCS11Exception(e, "Failed to store key in HSM KeyStore");
} catch (NoSuchAlgorithmException e) {
- handlePKCS11Exception(e, "SecureRandom algorithm not available or key not retrievable");
+ handlePKCS11Exception(e, "AES KeyGenerator not available via PKCS#11 provider");
} catch (Exception e) {
String errorMsg = e.getMessage();
if (errorMsg != null && (errorMsg.contains("CKR_OBJECT_HANDLE_INVALID")
@@ -838,13 +829,8 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
} else {
handlePKCS11Exception(e, "Failed to generate key in HSM");
}
- } finally {
- // Immediately clear sensitive key material from memory
- if (keyBytes != null) {
- Arrays.fill(keyBytes, (byte) 0);
- }
}
- return null; // Unreachable
+ return null;
}
/**
@@ -886,22 +872,15 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
throw KMSException.invalidParameter("Plain DEK cannot be null or empty");
}
- SecretKey kek = null;
+ SecretKey kek = getKekFromKeyStore(kekLabel);
try {
- kek = getKekFromKeyStore(kekLabel);
-
- // Generate random IV for CBC
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
- // Create cipher with AES-CBC
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, provider);
cipher.init(Cipher.ENCRYPT_MODE, kek, new IvParameterSpec(iv));
-
- // Encrypt the plaintext DEK
byte[] ciphertext = cipher.doFinal(plainDek);
- // Prepend IV to ciphertext: [IV][ciphertext]
byte[] result = new byte[IV_LENGTH + ciphertext.length];
System.arraycopy(iv, 0, result, 0, IV_LENGTH);
System.arraycopy(ciphertext, 0, result, IV_LENGTH, ciphertext.length);
@@ -917,10 +896,9 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
} catch (Exception e) {
handlePKCS11Exception(e, "Failed to wrap key with HSM");
} finally {
- // Zeroize KEK reference (actual key material is in HSM, but clear reference)
kek = null;
}
- return null; // Unreachable
+ return null;
}
/**
@@ -947,33 +925,29 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
} catch (KeyStoreException e) {
handlePKCS11Exception(e, "Failed to retrieve KEK from HSM");
}
- return null; // Unreachable
+ return null;
}
-
/**
* Unwraps (decrypts) a wrapped DEK using a KEK stored in the HSM.
*
- *
- *
+ *
- *
*/
byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
@@ -981,27 +955,21 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
throw KMSException.invalidParameter("Wrapped blob cannot be null or empty");
}
- // Minimum size: IV (16) + at least one block of ciphertext (16)
+ // Minimum size: IV (16 bytes) + at least one AES block (16 bytes)
if (wrappedBlob.length < IV_LENGTH + 16) {
throw KMSException.invalidParameter("Wrapped blob too short: expected at least " +
(IV_LENGTH + 16) + " bytes");
}
- SecretKey kek = null;
+ SecretKey kek = getKekFromKeyStore(kekLabel);
try {
- kek = getKekFromKeyStore(kekLabel);
-
- // Extract IV and ciphertext from wrapped blob
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(wrappedBlob, 0, iv, 0, IV_LENGTH);
byte[] ciphertext = new byte[wrappedBlob.length - IV_LENGTH];
System.arraycopy(wrappedBlob, IV_LENGTH, ciphertext, 0, ciphertext.length);
- // Create cipher with AES-CBC
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, provider);
cipher.init(Cipher.DECRYPT_MODE, kek, new IvParameterSpec(iv));
-
- // Decrypt the ciphertext to get plaintext DEK
byte[] plainDek = cipher.doFinal(ciphertext);
logger.debug("Unwrapped key with KEK '{}' using AES-CBC", kekLabel);
@@ -1018,13 +986,11 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
} catch (Exception e) {
handlePKCS11Exception(e, "Failed to unwrap key with HSM");
} finally {
- // Zeroize KEK reference
kek = null;
}
- return null; // Unreachable
+ return null;
}
-
/**
* Deletes a key from the HSM.
*
@@ -1040,12 +1006,10 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
*/
void deleteKey(String label) throws KMSException {
try {
- // Check if key exists first
if (!keyStore.containsAlias(label)) {
throw KMSException.kekNotFound("Key with label '" + label + "' not found in HSM");
}
- // Delete key from KeyStore
keyStore.deleteEntry(label);
logger.debug("Deleted key '{}' from HSM", label);
@@ -1074,7 +1038,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
*/
boolean checkKeyExists(String label) throws KMSException {
try {
- // Try to retrieve key from HSM KeyStore
Key key = keyStore.getKey(label, null);
return key != null;
} catch (KeyStoreException e) {
diff --git a/server/src/main/java/org/apache/cloudstack/kms/KMSManagerImpl.java b/server/src/main/java/org/apache/cloudstack/kms/KMSManagerImpl.java
index fe552358a3f..9f791d75131 100644
--- a/server/src/main/java/org/apache/cloudstack/kms/KMSManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/kms/KMSManagerImpl.java
@@ -40,8 +40,12 @@ import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.db.Filter;
+import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallbackWithException;
+import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.command.admin.kms.MigrateVolumesToKMSCmd;
@@ -69,7 +73,7 @@ import org.apache.cloudstack.kms.dao.HSMProfileDetailsDao;
import org.apache.cloudstack.kms.dao.KMSKekVersionDao;
import org.apache.cloudstack.kms.dao.KMSKeyDao;
import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
-import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.secret.PassphraseVO;
import org.apache.cloudstack.secret.dao.PassphraseDao;
import org.apache.commons.collections4.CollectionUtils;
@@ -83,19 +87,22 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Timer;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class KMSManagerImpl extends ManagerBase implements KMSManager, PluggableService {
private static final Logger logger = LogManager.getLogger(KMSManagerImpl.class);
private static final Map