2626import org .apache .cloudstack .framework .kms .WrappedKey ;
2727import org .apache .cloudstack .kms .HSMProfileDetailsVO ;
2828import org .apache .cloudstack .kms .KMSKekVersionVO ;
29- import org .apache .cloudstack .kms .dao .HSMProfileDao ;
3029import org .apache .cloudstack .kms .dao .HSMProfileDetailsDao ;
3130import org .apache .cloudstack .kms .dao .KMSKekVersionDao ;
3231import org .apache .commons .lang3 .StringUtils ;
7574public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
7675 private static final Logger logger = LogManager .getLogger (PKCS11HSMProvider .class );
7776 private static final String PROVIDER_NAME = "pkcs11" ;
78- // Security note (#7) : AES-CBC provides confidentiality but not authenticity (no
77+ // Security note: AES-CBC provides confidentiality but not authenticity (no
7978 // HMAC).
8079 // While AES-GCM is preferred, SunPKCS11 support for GCM is often buggy or
8180 // missing
@@ -89,8 +88,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
8988 private static final int [] VALID_KEY_SIZES = {128 , 192 , 256 };
9089 private final Map <Long , HSMSessionPool > sessionPools = new ConcurrentHashMap <>();
9190 @ Inject
92- private HSMProfileDao hsmProfileDao ;
93- @ Inject
9491 private HSMProfileDetailsDao hsmProfileDetailsDao ;
9592 @ Inject
9693 private KMSKekVersionDao kmsKekVersionDao ;
@@ -125,6 +122,85 @@ public void deleteKek(String kekId) throws KMSException {
125122 });
126123 }
127124
125+ Long resolveProfileId (String kekLabel ) throws KMSException {
126+ KMSKekVersionVO version = kmsKekVersionDao .findByKekLabel (kekLabel );
127+ if (version != null && version .getHsmProfileId () != null ) {
128+ return version .getHsmProfileId ();
129+ }
130+ throw new KMSException (KMSException .ErrorType .KEK_NOT_FOUND ,
131+ "Could not resolve HSM profile for KEK: " + kekLabel );
132+ }
133+
134+ /**
135+ * Validates HSM profile configuration for PKCS#11 provider.
136+ *
137+ * <p>
138+ * Validates:
139+ * <ul>
140+ * <li>{@code library}: Required, should point to PKCS#11 library</li>
141+ * <li>{@code slot}, {@code slot_list_index}, or {@code token_label}: At least
142+ * one required</li>
143+ * <li>{@code pin}: Required for HSM authentication</li>
144+ * <li>{@code max_sessions}: Optional, must be positive integer if provided</li>
145+ * </ul>
146+ *
147+ * @param config Configuration map from HSM profile details
148+ * @throws KMSException with {@code INVALID_PARAMETER} if validation fails
149+ */
150+ @ Override
151+ public void validateProfileConfig (Map <String , String > config ) throws KMSException {
152+ String libraryPath = config .get ("library" );
153+ if (StringUtils .isBlank (libraryPath )) {
154+ throw KMSException .invalidParameter ("library is required for PKCS#11 HSM profile" );
155+ }
156+
157+ String slot = config .get ("slot" );
158+ String slotListIndex = config .get ("slot_list_index" );
159+ String tokenLabel = config .get ("token_label" );
160+ if (StringUtils .isAllBlank (slot , slotListIndex , tokenLabel )) {
161+ throw KMSException .invalidParameter (
162+ "One of 'slot', 'slot_list_index', or 'token_label' is required for PKCS#11 HSM profile" );
163+ }
164+
165+ if (StringUtils .isNotBlank (slot )) {
166+ try {
167+ Integer .parseInt (slot );
168+ } catch (NumberFormatException e ) {
169+ throw KMSException .invalidParameter ("slot must be a valid integer: " + slot );
170+ }
171+ }
172+
173+ if (StringUtils .isNotBlank (slotListIndex )) {
174+ try {
175+ int idx = Integer .parseInt (slotListIndex );
176+ if (idx < 0 ) {
177+ throw KMSException .invalidParameter ("slot_list_index must be a non-negative integer" );
178+ }
179+ } catch (NumberFormatException e ) {
180+ throw KMSException .invalidParameter ("slot_list_index must be a valid integer: " + slotListIndex );
181+ }
182+ }
183+
184+ File libraryFile = new File (libraryPath );
185+ if (!libraryFile .exists () && !libraryFile .isAbsolute ()) {
186+ // The HSM library might be in the system library path
187+ logger .debug ("Library path {} does not exist as absolute path, will rely on system library path" ,
188+ libraryPath );
189+ }
190+
191+ String max_sessions = config .get ("max_sessions" );
192+ if (StringUtils .isNotBlank (max_sessions )) {
193+ try {
194+ int idx = Integer .parseInt (max_sessions );
195+ if (idx <= 0 ) {
196+ throw KMSException .invalidParameter ("max_sessions must be greater than 0" );
197+ }
198+ } catch (NumberFormatException e ) {
199+ throw KMSException .invalidParameter ("max_sessions must be a valid integer: " + max_sessions );
200+ }
201+ }
202+ }
203+
128204 @ Override
129205 public boolean isKekAvailable (String kekId ) throws KMSException {
130206 try {
@@ -245,15 +321,6 @@ public void invalidateProfileCache(Long profileId) {
245321 logger .info ("Invalidated HSM session pool for profile {}" , profileId );
246322 }
247323
248- Long resolveProfileId (String kekLabel ) throws KMSException {
249- KMSKekVersionVO version = kmsKekVersionDao .findByKekLabel (kekLabel );
250- if (version != null && version .getHsmProfileId () != null ) {
251- return version .getHsmProfileId ();
252- }
253- throw new KMSException (KMSException .ErrorType .KEK_NOT_FOUND ,
254- "Could not resolve HSM profile for KEK: " + kekLabel );
255- }
256-
257324 /**
258325 * Executes an operation with a session from the pool, handling acquisition and release.
259326 *
@@ -295,82 +362,11 @@ Map<String, String> loadProfileConfig(Long profileId) {
295362 return config ;
296363 }
297364
298- /**
299- * Validates HSM profile configuration for PKCS#11 provider.
300- *
301- * <p>
302- * Validates:
303- * <ul>
304- * <li>{@code library}: Required, should point to PKCS#11 library</li>
305- * <li>{@code slot}, {@code slot_list_index}, or {@code token_label}: At least
306- * one required</li>
307- * <li>{@code pin}: Required for HSM authentication</li>
308- * <li>{@code max_sessions}: Optional, must be positive integer if provided</li>
309- * </ul>
310- *
311- * @param config Configuration map from HSM profile details
312- * @throws KMSException with {@code INVALID_PARAMETER} if validation fails
313- */
314- @ Override
315- public void validateProfileConfig (Map <String , String > config ) throws KMSException {
316- String libraryPath = config .get ("library" );
317- if (StringUtils .isBlank (libraryPath )) {
318- throw KMSException .invalidParameter ("library is required for PKCS#11 HSM profile" );
319- }
320-
321- String slot = config .get ("slot" );
322- String slotListIndex = config .get ("slot_list_index" );
323- String tokenLabel = config .get ("token_label" );
324- if (StringUtils .isAllBlank (slot , slotListIndex , tokenLabel )) {
325- throw KMSException .invalidParameter (
326- "One of 'slot', 'slot_list_index', or 'token_label' is required for PKCS#11 HSM profile" );
327- }
328-
329- if (StringUtils .isNotBlank (slot )) {
330- try {
331- Integer .parseInt (slot );
332- } catch (NumberFormatException e ) {
333- throw KMSException .invalidParameter ("slot must be a valid integer: " + slot );
334- }
335- }
336-
337- if (StringUtils .isNotBlank (slotListIndex )) {
338- try {
339- int idx = Integer .parseInt (slotListIndex );
340- if (idx < 0 ) {
341- throw KMSException .invalidParameter ("slot_list_index must be a non-negative integer" );
342- }
343- } catch (NumberFormatException e ) {
344- throw KMSException .invalidParameter ("slot_list_index must be a valid integer: " + slotListIndex );
345- }
346- }
347-
348- File libraryFile = new File (libraryPath );
349- if (!libraryFile .exists () && !libraryFile .isAbsolute ()) {
350- // The HSM library might be in the system library path
351- logger .debug ("Library path {} does not exist as absolute path, will rely on system library path" ,
352- libraryPath );
353- }
354-
355- String max_sessions = config .get ("max_sessions" );
356- if (StringUtils .isNotBlank (max_sessions )) {
357- try {
358- int idx = Integer .parseInt (max_sessions );
359- if (idx <= 0 ) {
360- throw KMSException .invalidParameter ("max_sessions must be greater than 0" );
361- }
362- } catch (NumberFormatException e ) {
363- throw KMSException .invalidParameter ("max_sessions must be a valid integer: " + max_sessions );
364- }
365- }
366- }
367-
368365 boolean isSensitiveKey (String key ) {
369366 return KMSProvider .isSensitiveKey (key );
370367 }
371368
372369
373-
374370 @ Override
375371 public String getConfigComponentName () {
376372 return PKCS11HSMProvider .class .getSimpleName ();
@@ -641,6 +637,17 @@ private String buildSunPKCS11Config(Map<String, String> config, String nameSuffi
641637 throw KMSException .invalidParameter ("One of 'slot', 'slot_list_index', or 'token_label' is required" );
642638 }
643639
640+ // Explicitly configure SunPKCS11 to generate AES keys as Data Encryption Keys.
641+ // Strict HSMs (like Thales Luna in FIPS mode) forbid a key from having both
642+ // CKA_WRAP and CKA_ENCRYPT attributes. Because CloudStack uses Cipher.ENCRYPT_MODE
643+ // (which maps to C_Encrypt) to protect the DEK, the KEK must have CKA_ENCRYPT=true.
644+ configBuilder .append ("\n attributes(generate, CKO_SECRET_KEY, CKK_AES) = {\n " );
645+ configBuilder .append (" CKA_ENCRYPT = true\n " );
646+ configBuilder .append (" CKA_DECRYPT = true\n " );
647+ configBuilder .append (" CKA_WRAP = false\n " );
648+ configBuilder .append (" CKA_UNWRAP = false\n " );
649+ configBuilder .append ("}\n " );
650+
644651 return configBuilder .toString ();
645652 }
646653
@@ -823,9 +830,9 @@ String generateKey(String label, int keyBits, KeyPurpose purpose) throws KMSExce
823830
824831 } catch (KeyStoreException e ) {
825832 if (e .getMessage () != null
826- && e .getMessage ().contains ("found multiple secret keys sharing same CKA_LABEL" )) {
833+ && e .getMessage ().contains ("found multiple secret keys sharing same CKA_LABEL" )) {
827834 logger .warn ("Multiple duplicate keys found with label '{}' in HSM. Reusing the existing key. " +
828- "Please purge duplicate keys manually if possible." , label );
835+ "Please purge duplicate keys manually if possible." , label );
829836 return label ;
830837 }
831838 handlePKCS11Exception (e , "Failed to store key in HSM KeyStore" );
0 commit comments