mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 09:26:37 +00:00
feat: hsm cryptographic tests
This commit is contained in:
@@ -13,6 +13,11 @@ const USER_ALREADY_LOGGED_IN_ERROR = "CKR_USER_ALREADY_LOGGED_IN";
|
|||||||
|
|
||||||
export type THsmServiceFactory = ReturnType<typeof hsmServiceFactory>;
|
export type THsmServiceFactory = ReturnType<typeof hsmServiceFactory>;
|
||||||
|
|
||||||
|
enum RequiredMechanisms {
|
||||||
|
AesGcm = "AES_GCM",
|
||||||
|
AesKeyWrap = "AES_KEY_WRAP"
|
||||||
|
}
|
||||||
|
|
||||||
class HsmSessionManager {
|
class HsmSessionManager {
|
||||||
private session: grapheneLib.Session | null = null;
|
private session: grapheneLib.Session | null = null;
|
||||||
|
|
||||||
@@ -195,61 +200,7 @@ export const hsmServiceFactory = ({ pkcs11Module: { module, graphene } }: THsmSe
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isActive = () => {
|
const encrypt = (data: Buffer) => {
|
||||||
if (!module || !appCfg.isHsmConfigured || !sessionManager) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return appCfg.isHsmConfigured && module !== null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const startService = () => {
|
|
||||||
if (!appCfg.isHsmConfigured || !module) return;
|
|
||||||
|
|
||||||
sessionManager = new HsmSessionManager(module, graphene);
|
|
||||||
const session = sessionManager.getSession();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if master key exists, create if not
|
|
||||||
if (!$keyExists(session)) {
|
|
||||||
// Generate 256-bit AES master key with persistent storage
|
|
||||||
session.generateKey(graphene.KeyGenMechanism.AES, {
|
|
||||||
class: graphene.ObjectClass.SECRET_KEY,
|
|
||||||
token: true,
|
|
||||||
valueLen: 256 / 8,
|
|
||||||
keyType: graphene.KeyType.AES,
|
|
||||||
label: appCfg.HSM_KEY_LABEL,
|
|
||||||
derive: true, // Enable key derivation
|
|
||||||
extractable: false,
|
|
||||||
sensitive: true,
|
|
||||||
private: true
|
|
||||||
});
|
|
||||||
logger.info(`Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify HSM supports required mechanisms
|
|
||||||
const mechs = session.slot.getMechanisms();
|
|
||||||
let gotAesGcmMechanism = false;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (let i = 0; i < mechs.length; i++) {
|
|
||||||
const mech = mechs.items(i);
|
|
||||||
if (mech.name === "AES_GCM") {
|
|
||||||
gotAesGcmMechanism = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gotAesGcmMechanism) {
|
|
||||||
throw new Error("HSM does not support AES_GCM mechanism");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, "Error initializing HSM service");
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function encrypt(data: Buffer): Buffer {
|
|
||||||
if (!module) {
|
if (!module) {
|
||||||
throw new Error("PKCS#11 module is not initialized");
|
throw new Error("PKCS#11 module is not initialized");
|
||||||
}
|
}
|
||||||
@@ -279,9 +230,9 @@ export const hsmServiceFactory = ({ pkcs11Module: { module, graphene } }: THsmSe
|
|||||||
|
|
||||||
// Format: [Wrapped Key (40)][IV (16)][Encrypted Data + Tag]
|
// Format: [Wrapped Key (40)][IV (16)][Encrypted Data + Tag]
|
||||||
return Buffer.concat([wrappedKey, iv, encryptedData]);
|
return Buffer.concat([wrappedKey, iv, encryptedData]);
|
||||||
}
|
};
|
||||||
|
|
||||||
function decrypt(encryptedBlob: Buffer): Buffer {
|
const decrypt = (encryptedBlob: Buffer) => {
|
||||||
const WRAPPED_KEY_LENGTH = 32 + 8; // AES-256 key + padding
|
const WRAPPED_KEY_LENGTH = 32 + 8; // AES-256 key + padding
|
||||||
|
|
||||||
if (!module || !sessionManager) {
|
if (!module || !sessionManager) {
|
||||||
@@ -307,7 +258,118 @@ export const hsmServiceFactory = ({ pkcs11Module: { module, graphene } }: THsmSe
|
|||||||
const outputBuffer = Buffer.alloc(ciphertext.length);
|
const outputBuffer = Buffer.alloc(ciphertext.length);
|
||||||
|
|
||||||
return decipher.once(ciphertext, outputBuffer);
|
return decipher.once(ciphertext, outputBuffer);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// We test the core functionality of the PKCS#11 module that we are using throughout Infisical. This is to ensure that the user doesn't configure a faulty or unsupported HSM device.
|
||||||
|
const $testPkcs11Module = () => {
|
||||||
|
try {
|
||||||
|
if (!module || !sessionManager) {
|
||||||
|
throw new Error("HSM service not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = sessionManager.getSession();
|
||||||
|
|
||||||
|
let randomData: Buffer;
|
||||||
|
let encryptedData: Buffer;
|
||||||
|
let decryptedData: Buffer;
|
||||||
|
|
||||||
|
try {
|
||||||
|
randomData = session.generateRandom(256);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error generating random bytes: ${(error as Error).message || "Unknown error"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
encryptedData = encrypt(Buffer.from(randomData));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error encrypting data: ${(error as Error).message || "Unknown error"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
decryptedData = decrypt(encryptedData);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error decrypting data: ${(error as Error).message || "Unknown error"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer.from(randomData).toString("hex") !== Buffer.from(decryptedData).toString("hex")) {
|
||||||
|
throw new Error("Decrypted data does not match original data");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Error testing PKCS#11 module");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isActive = () => {
|
||||||
|
if (!module || !appCfg.isHsmConfigured || !sessionManager) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkcs11TestPassed = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
pkcs11TestPassed = $testPkcs11Module();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err, "isActive: Error testing PKCS#11 module");
|
||||||
|
}
|
||||||
|
|
||||||
|
return appCfg.isHsmConfigured && module !== null && pkcs11TestPassed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startService = () => {
|
||||||
|
if (!appCfg.isHsmConfigured || !module) return;
|
||||||
|
|
||||||
|
sessionManager = new HsmSessionManager(module, graphene);
|
||||||
|
const session = sessionManager.getSession();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if master key exists, create if not
|
||||||
|
if (!$keyExists(session)) {
|
||||||
|
// Generate 256-bit AES master key with persistent storage
|
||||||
|
session.generateKey(graphene.KeyGenMechanism.AES, {
|
||||||
|
class: graphene.ObjectClass.SECRET_KEY,
|
||||||
|
token: true,
|
||||||
|
valueLen: 256 / 8,
|
||||||
|
keyType: graphene.KeyType.AES,
|
||||||
|
label: appCfg.HSM_KEY_LABEL,
|
||||||
|
derive: true, // Enable key derivation
|
||||||
|
extractable: false,
|
||||||
|
sensitive: true,
|
||||||
|
private: true
|
||||||
|
});
|
||||||
|
logger.info(`Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify HSM supports required mechanisms
|
||||||
|
const mechs = session.slot.getMechanisms();
|
||||||
|
const mechNames: string[] = [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-plusplus
|
||||||
|
for (let i = 0; i < mechs.length; i++) {
|
||||||
|
mechNames.push(mechs.items(i).name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAesGcm = mechNames.includes(RequiredMechanisms.AesGcm);
|
||||||
|
const hasAesKeyWrap = mechNames.includes(RequiredMechanisms.AesKeyWrap);
|
||||||
|
|
||||||
|
if (!hasAesGcm) {
|
||||||
|
throw new Error(`Required mechanism ${RequiredMechanisms.AesGcm} not supported by HSM`);
|
||||||
|
}
|
||||||
|
if (!hasAesKeyWrap) {
|
||||||
|
throw new Error(`Required mechanism ${RequiredMechanisms.AesKeyWrap} not supported by HSM`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a test to verify module is working
|
||||||
|
if (!$testPkcs11Module()) {
|
||||||
|
throw new Error("PKCS#11 module test failed. Please ensure that the HSM is correctly configured.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Error initializing HSM service");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
encrypt,
|
encrypt,
|
||||||
startService,
|
startService,
|
||||||
|
|||||||
Reference in New Issue
Block a user