Skip to content

Commit 2da2477

Browse files
committed
Change wrapping algo for pkcs
1 parent b36a019 commit 2da2477

File tree

6 files changed

+157
-187
lines changed

6 files changed

+157
-187
lines changed

api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.admin.kms;
1818

19+
import com.cloud.dc.DataCenter;
1920
import com.cloud.user.Account;
2021
import org.apache.cloudstack.acl.RoleType;
2122
import org.apache.cloudstack.api.APICommand;
@@ -127,7 +128,7 @@ public String getEventType() {
127128

128129
@Override
129130
public String getEventDescription() {
130-
return "Migrating volumes to KMS for zone: " + _uuidMgr.getUuid(ZoneResponse.class, zoneId);
131+
return "Migrating volumes to KMS for zone: " + _uuidMgr.getUuid(DataCenter.class, zoneId);
131132
}
132133

133134
@Override

api/src/main/java/org/apache/cloudstack/api/command/admin/kms/RotateKMSKeyCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.cloudstack.api.response.HSMProfileResponse;
3030
import org.apache.cloudstack.api.response.KMSKeyResponse;
3131
import org.apache.cloudstack.framework.kms.KMSException;
32+
import org.apache.cloudstack.kms.KMSKey;
3233
import org.apache.cloudstack.kms.KMSManager;
3334

3435
import javax.inject.Inject;
@@ -103,7 +104,7 @@ public String getEventType() {
103104

104105
@Override
105106
public String getEventDescription() {
106-
return "Rotating KMS key: " + _uuidMgr.getUuid(KMSKeyResponse.class, id);
107+
return "Rotating KMS key: " + _uuidMgr.getUuid(KMSKey.class, id);
107108
}
108109

109110
@Override

plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java

Lines changed: 54 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import javax.crypto.IllegalBlockSizeException;
4040
import javax.crypto.NoSuchPaddingException;
4141
import javax.crypto.SecretKey;
42-
import javax.crypto.spec.GCMParameterSpec;
42+
import javax.crypto.spec.IvParameterSpec;
4343
import javax.crypto.spec.SecretKeySpec;
4444
import javax.inject.Inject;
4545
import java.io.Closeable;
@@ -564,9 +564,10 @@ void releaseSession(PKCS11Session session) {
564564
* {@link KMSException.ErrorType} values for proper retry logic and error reporting.
565565
*/
566566
private static class PKCS11Session {
567-
private static final String ALGORITHM = "AES/GCM/NoPadding";
568-
private static final int GCM_IV_LENGTH = 12; // 96 bits
569-
private static final int GCM_TAG_LENGTH = 16; // 128 bits
567+
// Use AES-CBC with PKCS5Padding for key wrapping
568+
// This is FIPS-compliant (NIST SP 800-38A) and has universal PKCS#11 support
569+
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
570+
private static final int IV_LENGTH = 16; // 128 bits for CBC
570571
private static final String PROVIDER_PREFIX = "CloudStackPKCS11-";
571572

572573
private final Map<String, String> config;
@@ -658,7 +659,7 @@ private void connect() throws KMSException {
658659
// Zeroize PIN from memory
659660
Arrays.fill(pinChars, '\0');
660661

661-
logger.debug("Successfully connected to PKCS#11 HSM at {}", config.get("library"));
662+
logger.debug("aSuccessfully connected to PKCS#11 HSM at {}", config.get("library"));
662663
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException e) {
663664
handlePKCS11Exception(e, "Failed to initialize PKCS#11 connection");
664665
} catch (IOException e) {
@@ -937,19 +938,18 @@ private void validateKeySize(int keyBits) throws KMSException {
937938
/**
938939
* Wraps (encrypts) a plaintext DEK using a KEK stored in the HSM.
939940
*
940-
* <p>Uses AES-GCM for authenticated encryption:
941+
* <p>Uses AES-CBC with PKCS5Padding (FIPS 197 + NIST SP 800-38A):
941942
* <ul>
942-
* <li>Generates a random 96-bit IV</li>
943-
* <li>Encrypts the DEK using the KEK from HSM</li>
944-
* <li>Appends a 128-bit authentication tag</li>
945-
* <li>Returns format: [IV (12 bytes)][ciphertext+tag]</li>
943+
* <li>Generates a random 128-bit IV</li>
944+
* <li>Encrypts the DEK using AES-CBC with the KEK from HSM</li>
945+
* <li>Returns format: [IV (16 bytes)][ciphertext]</li>
946946
* </ul>
947947
*
948948
* <p>Security: The plaintext DEK should be zeroized by the caller after wrapping.
949949
*
950950
* @param plainDek Plaintext DEK to wrap (will be encrypted)
951951
* @param kekLabel Label of the KEK stored in the HSM
952-
* @return Wrapped blob: [IV][ciphertext+tag]
952+
* @return Wrapped key blob: [IV][ciphertext]
953953
* @throws KMSException with appropriate ErrorType:
954954
* <ul>
955955
* <li>{@code INVALID_PARAMETER} if plainDek is null or empty</li>
@@ -966,23 +966,30 @@ byte[] wrapKey(byte[] plainDek, String kekLabel) throws KMSException {
966966
try {
967967
kek = getKekFromKeyStore(kekLabel);
968968

969-
// Generate random IV for GCM
970-
byte[] iv = new byte[GCM_IV_LENGTH];
969+
// Generate random IV for CBC
970+
byte[] iv = new byte[IV_LENGTH];
971971
new SecureRandom().nextBytes(iv);
972972

973-
// Create and initialize AES-GCM cipher in ENCRYPT_MODE
974-
Cipher cipher = createGCMCipher(kek, iv, Cipher.ENCRYPT_MODE);
973+
// Create cipher with AES-CBC
974+
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, provider);
975+
cipher.init(Cipher.ENCRYPT_MODE, kek, new IvParameterSpec(iv));
975976

976-
// Encrypt the plaintext DEK using doFinal (GCM includes authentication tag)
977-
byte[] wrappedBlob = cipher.doFinal(plainDek);
977+
// Encrypt the plaintext DEK
978+
byte[] ciphertext = cipher.doFinal(plainDek);
978979

979-
// Prepend IV to wrapped blob: [IV][ciphertext+tag]
980-
byte[] result = prependIV(iv, wrappedBlob);
980+
// Prepend IV to ciphertext: [IV][ciphertext]
981+
byte[] result = new byte[IV_LENGTH + ciphertext.length];
982+
System.arraycopy(iv, 0, result, 0, IV_LENGTH);
983+
System.arraycopy(ciphertext, 0, result, IV_LENGTH, ciphertext.length);
981984

982-
logger.debug("Wrapped key with KEK '{}'", kekLabel);
985+
logger.debug("Wrapped key with KEK '{}' using AES-CBC", kekLabel);
983986
return result;
984-
} catch (IllegalBlockSizeException e) {
985-
handlePKCS11Exception(e, "Invalid block size for wrapping");
987+
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) {
988+
handlePKCS11Exception(e, "Invalid key or data for wrapping");
989+
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
990+
handlePKCS11Exception(e, "AES-CBC not supported by HSM");
991+
} catch (InvalidAlgorithmParameterException e) {
992+
handlePKCS11Exception(e, "Invalid IV for CBC mode");
986993
} catch (Exception e) {
987994
handlePKCS11Exception(e, "Failed to wrap key with HSM");
988995
} finally {
@@ -1019,104 +1026,71 @@ private SecretKey getKekFromKeyStore(String kekLabel) throws KMSException {
10191026
return null; // Unreachable
10201027
}
10211028

1022-
/**
1023-
* Prepends IV to data, creating a new byte array.
1024-
*
1025-
* @param iv Initialization vector
1026-
* @param data Data to prepend IV to
1027-
* @return Combined array: [IV][data]
1028-
*/
1029-
private byte[] prependIV(byte[] iv, byte[] data) {
1030-
byte[] result = new byte[GCM_IV_LENGTH + data.length];
1031-
System.arraycopy(iv, 0, result, 0, GCM_IV_LENGTH);
1032-
System.arraycopy(data, 0, result, GCM_IV_LENGTH, data.length);
1033-
return result;
1034-
}
1035-
1036-
/**
1037-
* Creates and initializes an AES-GCM cipher.
1038-
*
1039-
* @param kek Key Encryption Key
1040-
* @param iv Initialization vector
1041-
* @param mode Cipher mode (ENCRYPT_MODE or DECRYPT_MODE)
1042-
* @return Initialized Cipher instance
1043-
* @throws KMSException if cipher creation or initialization fails
1044-
*/
1045-
private Cipher createGCMCipher(SecretKey kek, byte[] iv, int mode) throws KMSException {
1046-
try {
1047-
Cipher cipher = Cipher.getInstance(ALGORITHM, provider);
1048-
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
1049-
cipher.init(mode, kek, gcmSpec);
1050-
return cipher;
1051-
} catch (NoSuchPaddingException e) {
1052-
handlePKCS11Exception(e, "GCM padding not supported");
1053-
} catch (InvalidKeyException e) {
1054-
handlePKCS11Exception(e, "Invalid KEK");
1055-
} catch (InvalidAlgorithmParameterException e) {
1056-
handlePKCS11Exception(e, "Invalid GCM parameters");
1057-
} catch (NoSuchAlgorithmException e) {
1058-
handlePKCS11Exception(e, String.format("Algorithm %s not supported.", ALGORITHM));
1059-
}
1060-
return null; // Unreachable
1061-
}
10621029

10631030
/**
10641031
* Unwraps (decrypts) a wrapped DEK using a KEK stored in the HSM.
10651032
*
1066-
* <p>Process:
1033+
* <p>Uses AES-CBC with PKCS5Padding (FIPS 197 + NIST SP 800-38A):
10671034
* <ol>
10681035
* <li>Extracts IV from the wrapped blob</li>
10691036
* <li>Retrieves KEK from HSM using the label</li>
1070-
* <li>Decrypts using AES-GCM (verifies authentication tag)</li>
1037+
* <li>Decrypts using AES-CBC</li>
10711038
* <li>Returns plaintext DEK</li>
10721039
* </ol>
10731040
*
10741041
* <p>Security: The returned plaintext DEK must be zeroized by the caller after use.
10751042
*
1076-
* <p>Expected format: [IV (12 bytes)][ciphertext+tag]
1043+
* <p>Expected format: [IV (16 bytes)][ciphertext]
10771044
*
1078-
* @param wrappedBlob Wrapped DEK blob (IV + ciphertext + tag)
1045+
* @param wrappedBlob Wrapped DEK blob (IV + ciphertext)
10791046
* @param kekLabel Label of the KEK stored in the HSM
10801047
* @return Plaintext DEK
10811048
* @throws KMSException with appropriate ErrorType:
10821049
* <ul>
10831050
* <li>{@code INVALID_PARAMETER} if wrappedBlob is null, empty, or too short</li>
10841051
* <li>{@code KEK_NOT_FOUND} if KEK with label doesn't exist or is not accessible</li>
1085-
* <li>{@code WRAP_UNWRAP_FAILED} if unwrapping fails (e.g., authentication tag
1086-
* verification fails)</li>
1052+
* <li>{@code WRAP_UNWRAP_FAILED} if unwrapping fails</li>
10871053
* </ul>
10881054
*/
10891055
byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
10901056
if (wrappedBlob == null || wrappedBlob.length == 0) {
10911057
throw KMSException.invalidParameter("Wrapped blob cannot be null or empty");
10921058
}
10931059

1094-
if (wrappedBlob.length < GCM_IV_LENGTH + GCM_TAG_LENGTH) {
1060+
// Minimum size: IV (16) + at least one block of ciphertext (16)
1061+
if (wrappedBlob.length < IV_LENGTH + 16) {
10951062
throw KMSException.invalidParameter("Wrapped blob too short: expected at least " +
1096-
(GCM_IV_LENGTH + GCM_TAG_LENGTH) + " bytes");
1063+
(IV_LENGTH + 16) + " bytes");
10971064
}
10981065

10991066
SecretKey kek = null;
11001067
try {
11011068
kek = getKekFromKeyStore(kekLabel);
11021069

11031070
// Extract IV and ciphertext from wrapped blob
1104-
IVAndCiphertext extracted = extractIVAndCiphertext(wrappedBlob);
1071+
byte[] iv = new byte[IV_LENGTH];
1072+
System.arraycopy(wrappedBlob, 0, iv, 0, IV_LENGTH);
1073+
byte[] ciphertext = new byte[wrappedBlob.length - IV_LENGTH];
1074+
System.arraycopy(wrappedBlob, IV_LENGTH, ciphertext, 0, ciphertext.length);
11051075

1106-
// Create and initialize AES-GCM cipher in DECRYPT_MODE
1107-
Cipher cipher = createGCMCipher(kek, extracted.iv, Cipher.DECRYPT_MODE);
1076+
// Create cipher with AES-CBC
1077+
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, provider);
1078+
cipher.init(Cipher.DECRYPT_MODE, kek, new IvParameterSpec(iv));
11081079

1109-
// Decrypt the ciphertext to get plaintext DEK (GCM verifies authentication tag)
1110-
byte[] plainDek = cipher.doFinal(extracted.ciphertextWithTag);
1080+
// Decrypt the ciphertext to get plaintext DEK
1081+
byte[] plainDek = cipher.doFinal(ciphertext);
11111082

1112-
logger.debug("Unwrapped key with KEK '{}'", kekLabel);
1083+
logger.debug("Unwrapped key with KEK '{}' using AES-CBC", kekLabel);
11131084
return plainDek;
11141085
} catch (BadPaddingException e) {
1115-
// GCM authentication tag verification failed
11161086
throw KMSException.wrapUnwrapFailed(
1117-
"Authentication failed: wrapped key may be corrupted or KEK is incorrect", e);
1118-
} catch (IllegalBlockSizeException e) {
1119-
handlePKCS11Exception(e, "Invalid block size for unwrapping");
1087+
"Decryption failed: wrapped key may be corrupted or KEK is incorrect", e);
1088+
} catch (IllegalBlockSizeException | InvalidKeyException e) {
1089+
handlePKCS11Exception(e, "Invalid key or data for unwrapping");
1090+
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
1091+
handlePKCS11Exception(e, "AES-CBC not supported by HSM");
1092+
} catch (InvalidAlgorithmParameterException e) {
1093+
handlePKCS11Exception(e, "Invalid IV for CBC mode");
11201094
} catch (Exception e) {
11211095
handlePKCS11Exception(e, "Failed to unwrap key with HSM");
11221096
} finally {
@@ -1126,24 +1100,6 @@ byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
11261100
return null; // Unreachable
11271101
}
11281102

1129-
/**
1130-
* Extracts IV and ciphertext from a wrapped blob.
1131-
*
1132-
* @param wrappedBlob Wrapped blob containing IV and ciphertext
1133-
* @return IVAndCiphertext containing extracted IV and ciphertext
1134-
* @throws KMSException if wrapped blob is too short
1135-
*/
1136-
private IVAndCiphertext extractIVAndCiphertext(byte[] wrappedBlob) throws KMSException {
1137-
if (wrappedBlob.length < GCM_IV_LENGTH + GCM_TAG_LENGTH) {
1138-
throw KMSException.invalidParameter("Wrapped blob too short: expected at least " +
1139-
(GCM_IV_LENGTH + GCM_TAG_LENGTH) + " bytes");
1140-
}
1141-
byte[] iv = new byte[GCM_IV_LENGTH];
1142-
System.arraycopy(wrappedBlob, 0, iv, 0, GCM_IV_LENGTH);
1143-
byte[] ciphertextWithTag = new byte[wrappedBlob.length - GCM_IV_LENGTH];
1144-
System.arraycopy(wrappedBlob, GCM_IV_LENGTH, ciphertextWithTag, 0, ciphertextWithTag.length);
1145-
return new IVAndCiphertext(iv, ciphertextWithTag);
1146-
}
11471103

11481104
/**
11491105
* Deletes a key from the HSM.
@@ -1212,18 +1168,5 @@ boolean checkKeyExists(String label) throws KMSException {
12121168
return false;
12131169
}
12141170
}
1215-
1216-
/**
1217-
* Helper class to hold IV and ciphertext extracted from wrapped blob.
1218-
*/
1219-
private static class IVAndCiphertext {
1220-
final byte[] iv;
1221-
final byte[] ciphertextWithTag;
1222-
1223-
IVAndCiphertext(byte[] iv, byte[] ciphertextWithTag) {
1224-
this.iv = iv;
1225-
this.ciphertextWithTag = ciphertextWithTag;
1226-
}
1227-
}
12281171
}
12291172
}

0 commit comments

Comments
 (0)