mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat: wait for session wrapper
This commit is contained in:
@@ -1278,11 +1278,13 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
//
|
|
||||||
// setup the communication with license key server
|
// setup the communication with license key server
|
||||||
await licenseService.init();
|
await licenseService.init();
|
||||||
|
|
||||||
hsmService.startService();
|
// Start HSM service if it's configured/enabled.
|
||||||
|
await hsmService.startService();
|
||||||
|
|
||||||
await telemetryQueue.startTelemetryCheck();
|
await telemetryQueue.startTelemetryCheck();
|
||||||
await dailyResourceCleanUp.startCleanUp();
|
await dailyResourceCleanUp.startCleanUp();
|
||||||
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
||||||
|
|||||||
@@ -8,105 +8,80 @@ import { HsmModule, RequiredMechanisms } from "./hsm-types";
|
|||||||
type THsmServiceFactoryDep = {
|
type THsmServiceFactoryDep = {
|
||||||
hsmModule: HsmModule;
|
hsmModule: HsmModule;
|
||||||
};
|
};
|
||||||
const SESSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
||||||
const USER_ALREADY_LOGGED_IN_ERROR = "CKR_USER_ALREADY_LOGGED_IN";
|
const USER_ALREADY_LOGGED_IN_ERROR = "CKR_USER_ALREADY_LOGGED_IN";
|
||||||
|
const WRAPPED_KEY_LENGTH = 32 + 8; // AES-256 key + padding
|
||||||
|
|
||||||
export type THsmServiceFactory = ReturnType<typeof hsmServiceFactory>;
|
export type THsmServiceFactory = ReturnType<typeof hsmServiceFactory>;
|
||||||
|
|
||||||
class HsmSessionManager {
|
type SyncOrAsync<T> = T | Promise<T>;
|
||||||
private session: grapheneLib.Session | null = null;
|
type SessionCallback<T> = (session: grapheneLib.Session) => SyncOrAsync<T>;
|
||||||
|
|
||||||
private lastUsed: number = 0;
|
export const withSession = async <T>(
|
||||||
|
{ module, graphene }: HsmModule,
|
||||||
|
callbackWithSession: SessionCallback<T>
|
||||||
|
): Promise<T> => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
private module: grapheneLib.Module;
|
let session: grapheneLib.Session | null = null;
|
||||||
|
try {
|
||||||
private graphene: typeof grapheneLib;
|
if (!module) {
|
||||||
|
throw new Error("PKCS#11 module is not initialized");
|
||||||
private sessionCheckInterval: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
private startSessionMonitoring() {
|
|
||||||
// Check session health every minute
|
|
||||||
this.sessionCheckInterval = setInterval(() => {
|
|
||||||
this.checkAndRefreshSession();
|
|
||||||
}, 60 * 1000); // 1 minute
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkAndRefreshSession() {
|
|
||||||
if (!this.session) return;
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.lastUsed > SESSION_TIMEOUT) {
|
|
||||||
logger.info("Session expired, cleaning up...");
|
|
||||||
this.cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private cleanup() {
|
|
||||||
if (this.session) {
|
|
||||||
try {
|
|
||||||
this.session.logout();
|
|
||||||
this.session.close();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error during session cleanup:", error);
|
|
||||||
}
|
|
||||||
this.session = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.sessionCheckInterval) {
|
|
||||||
clearInterval(this.sessionCheckInterval);
|
|
||||||
this.sessionCheckInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSession(): grapheneLib.Session {
|
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
// If we have a valid session, update its last used time and return it
|
|
||||||
if (this.session) {
|
|
||||||
try {
|
|
||||||
// Try a simple operation to verify session is still valid
|
|
||||||
this.session.generateRandom(16);
|
|
||||||
this.lastUsed = Date.now();
|
|
||||||
return this.session;
|
|
||||||
} catch (error) {
|
|
||||||
logger.info("HSM Session validation failed, creating new session...");
|
|
||||||
this.cleanup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new session
|
// Create new session
|
||||||
const slot = this.module.getSlots(appCfg.HSM_SLOT);
|
const slot = module.getSlots(appCfg.HSM_SLOT);
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
if (!(slot.flags & this.graphene.SlotFlag.TOKEN_PRESENT)) {
|
if (!(slot.flags & graphene.SlotFlag.TOKEN_PRESENT)) {
|
||||||
throw new Error("Slot is not initialized");
|
throw new Error("Slot is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-bitwise
|
for (let i = 0; i < 10; i += 1) {
|
||||||
const session = slot.open(this.graphene.SessionFlag.RW_SESSION | this.graphene.SessionFlag.SERIAL_SESSION);
|
try {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
try {
|
session = slot.open(graphene.SessionFlag.RW_SESSION | graphene.SessionFlag.SERIAL_SESSION);
|
||||||
session.login(appCfg.HSM_PIN!);
|
session.login(appCfg.HSM_PIN!);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (error) {
|
||||||
} catch (error: any) {
|
if ((error as Error)?.message !== USER_ALREADY_LOGGED_IN_ERROR) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- The error is of type `Pkcs11Error`, but this error is not exported by graphene. And we don't want to install another library just for an error assertion.
|
throw error;
|
||||||
if (error.message !== USER_ALREADY_LOGGED_IN_ERROR) {
|
}
|
||||||
throw error;
|
logger.warn("HSM session already logged in");
|
||||||
|
session = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("Waiting for session to be available...");
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
let sleepAmount = 1_500 * (i + 1);
|
||||||
|
if (sleepAmount > 5000) sleepAmount = 5000;
|
||||||
|
|
||||||
|
setTimeout(resolve, sleepAmount);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session = session;
|
if (!session) {
|
||||||
this.lastUsed = Date.now();
|
throw new Error("Failed to open session");
|
||||||
|
}
|
||||||
|
|
||||||
return session;
|
// Execute the callback and await its result (works for both sync and async)
|
||||||
|
const result = await callbackWithSession(session);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
// Clean up session if it was created
|
||||||
|
if (session) {
|
||||||
|
try {
|
||||||
|
session.logout();
|
||||||
|
session.close();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error cleaning up HSM session:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
constructor(module: grapheneLib.Module, graphene: typeof grapheneLib) {
|
|
||||||
this.module = module;
|
|
||||||
this.graphene = graphene;
|
|
||||||
|
|
||||||
this.startSessionMonitoring();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty-pattern
|
// eslint-disable-next-line no-empty-pattern
|
||||||
export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServiceFactoryDep) => {
|
export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServiceFactoryDep) => {
|
||||||
@@ -116,8 +91,6 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
const IV_LENGTH = 16;
|
const IV_LENGTH = 16;
|
||||||
const TAG_LENGTH = 16;
|
const TAG_LENGTH = 16;
|
||||||
|
|
||||||
let sessionManager: HsmSessionManager | null = null;
|
|
||||||
|
|
||||||
const $findMasterKey = (session: grapheneLib.Session) => {
|
const $findMasterKey = (session: grapheneLib.Session) => {
|
||||||
// Find the master key (root key)
|
// Find the master key (root key)
|
||||||
const template = {
|
const template = {
|
||||||
@@ -195,100 +168,101 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const encrypt = (data: Buffer) => {
|
const encrypt: {
|
||||||
|
(data: Buffer, providedSession: grapheneLib.Session): Promise<Buffer>;
|
||||||
|
(data: Buffer): Promise<Buffer>;
|
||||||
|
} = async (data: Buffer, providedSession?: grapheneLib.Session) => {
|
||||||
if (!module) {
|
if (!module) {
|
||||||
throw new Error("PKCS#11 module is not initialized");
|
throw new Error("PKCS#11 module is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionManager) {
|
const $performEncryption = (s: grapheneLib.Session) => {
|
||||||
throw new Error("HSM Session manager is not initialized");
|
// Generate IV for encryption
|
||||||
|
const iv = s.generateRandom(IV_LENGTH);
|
||||||
|
|
||||||
|
// Generate and wrap a new session key
|
||||||
|
const { wrappedKey, sessionKey } = $generateAndWrapKey(s);
|
||||||
|
|
||||||
|
const alg = {
|
||||||
|
name: appCfg.HSM_MECHANISM,
|
||||||
|
params: new graphene.AesGcm240Params(iv)
|
||||||
|
} as grapheneLib.IAlgorithm;
|
||||||
|
|
||||||
|
const cipher = s.createCipher(alg, new graphene.Key(sessionKey).toType());
|
||||||
|
|
||||||
|
// Calculate the output buffer size based on input length
|
||||||
|
// GCM adds a 16-byte auth tag, so we need input length + 16
|
||||||
|
const outputBuffer = Buffer.alloc(data.length + TAG_LENGTH);
|
||||||
|
const encryptedData = cipher.once(data, outputBuffer);
|
||||||
|
|
||||||
|
// Format: [Wrapped Key (40)][IV (16)][Encrypted Data + Tag]
|
||||||
|
return Buffer.concat([wrappedKey, iv, encryptedData]);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (providedSession) {
|
||||||
|
return $performEncryption(providedSession);
|
||||||
}
|
}
|
||||||
const session = sessionManager.getSession();
|
|
||||||
|
|
||||||
// Generate IV for encryption
|
const encrypted = await withSession({ module, graphene }, $performEncryption);
|
||||||
const iv = session.generateRandom(IV_LENGTH);
|
|
||||||
|
|
||||||
// Generate and wrap a new session key
|
return encrypted;
|
||||||
const { wrappedKey, sessionKey } = $generateAndWrapKey(session);
|
|
||||||
|
|
||||||
const alg = {
|
|
||||||
name: appCfg.HSM_MECHANISM,
|
|
||||||
params: new graphene.AesGcm240Params(iv)
|
|
||||||
} as grapheneLib.IAlgorithm;
|
|
||||||
|
|
||||||
const cipher = session.createCipher(alg, new graphene.Key(sessionKey).toType());
|
|
||||||
|
|
||||||
// Calculate the output buffer size based on input length
|
|
||||||
// GCM adds a 16-byte auth tag, so we need input length + 16
|
|
||||||
const outputBuffer = Buffer.alloc(data.length + TAG_LENGTH);
|
|
||||||
const encryptedData = cipher.once(data, outputBuffer);
|
|
||||||
|
|
||||||
// Format: [Wrapped Key (40)][IV (16)][Encrypted Data + Tag]
|
|
||||||
return Buffer.concat([wrappedKey, iv, encryptedData]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const decrypt = (encryptedBlob: Buffer) => {
|
const decrypt: {
|
||||||
const WRAPPED_KEY_LENGTH = 32 + 8; // AES-256 key + padding
|
(encryptedBlob: Buffer, providedSession: grapheneLib.Session): Promise<Buffer>;
|
||||||
|
(encryptedBlob: Buffer): Promise<Buffer>;
|
||||||
if (!module || !sessionManager) {
|
} = async (encryptedBlob: Buffer, providedSession?: grapheneLib.Session) => {
|
||||||
|
if (!module) {
|
||||||
throw new Error("HSM service not initialized");
|
throw new Error("HSM service not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = sessionManager.getSession();
|
const $performDecryption = (s: grapheneLib.Session) => {
|
||||||
|
const wrappedKey = encryptedBlob.subarray(0, WRAPPED_KEY_LENGTH);
|
||||||
|
const iv = encryptedBlob.subarray(WRAPPED_KEY_LENGTH, WRAPPED_KEY_LENGTH + IV_LENGTH);
|
||||||
|
const ciphertext = encryptedBlob.subarray(WRAPPED_KEY_LENGTH + IV_LENGTH);
|
||||||
|
|
||||||
// Extract wrapped key, IV, and ciphertext
|
// Unwrap the session key
|
||||||
const wrappedKey = encryptedBlob.subarray(0, WRAPPED_KEY_LENGTH);
|
const sessionKey = $unwrapKey(s, wrappedKey);
|
||||||
const iv = encryptedBlob.subarray(WRAPPED_KEY_LENGTH, WRAPPED_KEY_LENGTH + IV_LENGTH);
|
|
||||||
const ciphertext = encryptedBlob.subarray(WRAPPED_KEY_LENGTH + IV_LENGTH);
|
|
||||||
|
|
||||||
// Unwrap the session key
|
const algo = {
|
||||||
const sessionKey = $unwrapKey(session, wrappedKey);
|
name: appCfg.HSM_MECHANISM,
|
||||||
|
params: new graphene.AesGcm240Params(iv)
|
||||||
|
};
|
||||||
|
|
||||||
const algo = {
|
const decipher = s.createDecipher(algo, new graphene.Key(sessionKey).toType());
|
||||||
name: appCfg.HSM_MECHANISM,
|
const outputBuffer = Buffer.alloc(ciphertext.length);
|
||||||
params: new graphene.AesGcm240Params(iv)
|
|
||||||
|
// Extract wrapped key, IV, and ciphertext
|
||||||
|
return decipher.once(ciphertext, outputBuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
const decipher = session.createDecipher(algo, new graphene.Key(sessionKey).toType());
|
if (providedSession) {
|
||||||
const outputBuffer = Buffer.alloc(ciphertext.length);
|
return $performDecryption(providedSession);
|
||||||
|
}
|
||||||
|
const decrypted = await withSession({ module, graphene }, (newSession) => $performDecryption(newSession));
|
||||||
|
|
||||||
return decipher.once(ciphertext, outputBuffer);
|
return decrypted;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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.
|
// 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 = () => {
|
const $testPkcs11Module = async (session: grapheneLib.Session) => {
|
||||||
try {
|
try {
|
||||||
if (!module || !sessionManager) {
|
if (!module) {
|
||||||
throw new Error("HSM service not initialized");
|
throw new Error("HSM service not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = sessionManager.getSession();
|
if (!session) {
|
||||||
|
throw new Error("Session not initialized");
|
||||||
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 {
|
const randomData = session.generateRandom(256);
|
||||||
encryptedData = encrypt(Buffer.from(randomData));
|
const encryptedData = await encrypt(Buffer.from(randomData), session);
|
||||||
} catch (error) {
|
const decryptedData = await decrypt(encryptedData, session);
|
||||||
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")) {
|
if (Buffer.from(randomData).toString("hex") !== Buffer.from(decryptedData).toString("hex")) {
|
||||||
throw new Error("Decrypted data does not match original data");
|
throw new Error("Decrypted data does not match original data");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error, "Error testing PKCS#11 module");
|
logger.error(error, "Error testing PKCS#11 module");
|
||||||
@@ -296,15 +270,15 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isActive = () => {
|
const isActive = async () => {
|
||||||
if (!module || !appCfg.isHsmConfigured || !sessionManager) {
|
if (!module || !appCfg.isHsmConfigured) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pkcs11TestPassed = false;
|
let pkcs11TestPassed = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pkcs11TestPassed = $testPkcs11Module();
|
pkcs11TestPassed = await withSession({ module, graphene }, $testPkcs11Module);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err, "isActive: Error testing PKCS#11 module");
|
logger.error(err, "isActive: Error testing PKCS#11 module");
|
||||||
}
|
}
|
||||||
@@ -312,53 +286,54 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
return appCfg.isHsmConfigured && module !== null && pkcs11TestPassed;
|
return appCfg.isHsmConfigured && module !== null && pkcs11TestPassed;
|
||||||
};
|
};
|
||||||
|
|
||||||
const startService = () => {
|
const startService = async () => {
|
||||||
if (!appCfg.isHsmConfigured || !module) return;
|
if (!appCfg.isHsmConfigured || !module) return;
|
||||||
|
|
||||||
sessionManager = new HsmSessionManager(module, graphene);
|
|
||||||
const session = sessionManager.getSession();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if master key exists, create if not
|
await withSession({ module, graphene }, async (session) => {
|
||||||
if (!$keyExists(session)) {
|
// Check if master key exists, create if not
|
||||||
// Generate 256-bit AES master key with persistent storage
|
if (!$keyExists(session)) {
|
||||||
session.generateKey(graphene.KeyGenMechanism.AES, {
|
// Generate 256-bit AES master key with persistent storage
|
||||||
class: graphene.ObjectClass.SECRET_KEY,
|
session.generateKey(graphene.KeyGenMechanism.AES, {
|
||||||
token: true,
|
class: graphene.ObjectClass.SECRET_KEY,
|
||||||
valueLen: 256 / 8,
|
token: true,
|
||||||
keyType: graphene.KeyType.AES,
|
valueLen: 256 / 8,
|
||||||
label: appCfg.HSM_KEY_LABEL,
|
keyType: graphene.KeyType.AES,
|
||||||
derive: true, // Enable key derivation
|
label: appCfg.HSM_KEY_LABEL,
|
||||||
extractable: false,
|
derive: true, // Enable key derivation
|
||||||
sensitive: true,
|
extractable: false,
|
||||||
private: true
|
sensitive: true,
|
||||||
});
|
private: true
|
||||||
logger.info(`Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`);
|
});
|
||||||
}
|
logger.info(`Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Verify HSM supports required mechanisms
|
// Verify HSM supports required mechanisms
|
||||||
const mechs = session.slot.getMechanisms();
|
const mechs = session.slot.getMechanisms();
|
||||||
const mechNames: string[] = [];
|
const mechNames: string[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
for (let i = 0; i < mechs.length; i++) {
|
for (let i = 0; i < mechs.length; i++) {
|
||||||
mechNames.push(mechs.items(i).name);
|
mechNames.push(mechs.items(i).name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAesGcm = mechNames.includes(RequiredMechanisms.AesGcm);
|
const hasAesGcm = mechNames.includes(RequiredMechanisms.AesGcm);
|
||||||
const hasAesKeyWrap = mechNames.includes(RequiredMechanisms.AesKeyWrap);
|
const hasAesKeyWrap = mechNames.includes(RequiredMechanisms.AesKeyWrap);
|
||||||
|
|
||||||
if (!hasAesGcm) {
|
if (!hasAesGcm) {
|
||||||
throw new Error(`Required mechanism ${RequiredMechanisms.AesGcm} not supported by HSM`);
|
throw new Error(`Required mechanism ${RequiredMechanisms.AesGcm} not supported by HSM`);
|
||||||
}
|
}
|
||||||
if (!hasAesKeyWrap) {
|
if (!hasAesKeyWrap) {
|
||||||
throw new Error(`Required mechanism ${RequiredMechanisms.AesKeyWrap} not supported by HSM`);
|
throw new Error(`Required mechanism ${RequiredMechanisms.AesKeyWrap} not supported by HSM`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a test to verify module is working
|
const testPassed = await $testPkcs11Module(session);
|
||||||
if (!$testPkcs11Module()) {
|
|
||||||
throw new Error("PKCS#11 module test failed. Please ensure that the HSM is correctly configured.");
|
// Run a test to verify module is working
|
||||||
}
|
if (!testPassed) {
|
||||||
|
throw new Error("PKCS#11 module test failed. Please ensure that the HSM is correctly configured.");
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error, "Error initializing HSM service");
|
logger.error(error, "Error initializing HSM service");
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -630,11 +630,13 @@ export const kmsServiceFactory = ({
|
|||||||
const $decryptRootKey = async (kmsRootConfig: TKmsRootConfig) => {
|
const $decryptRootKey = async (kmsRootConfig: TKmsRootConfig) => {
|
||||||
// case 1: root key is encrypted with HSM
|
// case 1: root key is encrypted with HSM
|
||||||
if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.HSM) {
|
if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.HSM) {
|
||||||
if (!hsmService.isActive()) {
|
const hsmIsActive = await hsmService.isActive();
|
||||||
|
if (!hsmIsActive) {
|
||||||
throw new Error("Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?");
|
throw new Error("Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?");
|
||||||
}
|
}
|
||||||
|
|
||||||
return hsmService.decrypt(kmsRootConfig.encryptedRootKey);
|
const decryptedKey = await hsmService.decrypt(kmsRootConfig.encryptedRootKey);
|
||||||
|
return decryptedKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 2: root key is encrypted with software encryption
|
// case 2: root key is encrypted with software encryption
|
||||||
@@ -650,10 +652,12 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const $encryptRootKey = async (plainKeyBuffer: Buffer, strategy: RootKeyEncryptionStrategy) => {
|
const $encryptRootKey = async (plainKeyBuffer: Buffer, strategy: RootKeyEncryptionStrategy) => {
|
||||||
if (strategy === RootKeyEncryptionStrategy.HSM) {
|
if (strategy === RootKeyEncryptionStrategy.HSM) {
|
||||||
if (!hsmService.isActive()) {
|
const hsmIsActive = await hsmService.isActive();
|
||||||
|
if (!hsmIsActive) {
|
||||||
throw new Error("Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?");
|
throw new Error("Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?");
|
||||||
}
|
}
|
||||||
return hsmService.encrypt(plainKeyBuffer);
|
const encrypted = await hsmService.encrypt(plainKeyBuffer);
|
||||||
|
return encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strategy === RootKeyEncryptionStrategy.Software) {
|
if (strategy === RootKeyEncryptionStrategy.Software) {
|
||||||
@@ -828,7 +832,6 @@ export const kmsServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
return kmsDAL.findByIdWithAssociatedKms(key.id, tx);
|
return kmsDAL.findByIdWithAssociatedKms(key.id, tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -866,12 +869,9 @@ export const kmsServiceFactory = ({
|
|||||||
// case 1: a root key already exists in the DB
|
// case 1: a root key already exists in the DB
|
||||||
if (kmsRootConfig) {
|
if (kmsRootConfig) {
|
||||||
if (lock) await lock.release();
|
if (lock) await lock.release();
|
||||||
logger.info("KMS: Encrypted ROOT Key found from DB. Decrypting.");
|
logger.info(`KMS: Encrypted ROOT Key found from DB. Decrypting. [strategy=${kmsRootConfig.encryptionStrategy}]`);
|
||||||
|
|
||||||
const decryptedRootKey = await $decryptRootKey(kmsRootConfig).catch((err) => {
|
const decryptedRootKey = await $decryptRootKey(kmsRootConfig);
|
||||||
logger.error(err, `KMS: Failed to decrypt ROOT Key [strategy=${kmsRootConfig.encryptionStrategy}]`);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
// set the flag so that other instance nodes can start
|
// set the flag so that other instance nodes can start
|
||||||
await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true");
|
await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true");
|
||||||
|
|||||||
Reference in New Issue
Block a user