@bryopsida/key-store
Version:
A extendable TypeScript Node.JS library that provides a basic key store for data encryption keys
100 lines • 4.02 kB
JavaScript
import { createCipheriv, createDecipheriv, createHash, randomBytes, scrypt, } from 'crypto';
export class BaseKeyStore {
keyStorePasswordProvider;
keyStoreSaltProvider;
keyStoreContextProvider;
constructor(keyStorePasswordProvider, keyStoreSaltProvider, keyStoreContextProvider) {
this.keyStorePasswordProvider = keyStorePasswordProvider;
this.keyStoreSaltProvider = keyStoreSaltProvider;
this.keyStoreContextProvider = keyStoreContextProvider;
}
async hasSealedRootKey(rootKeyId) {
const keySlot = await this.getKeySlot('dek', rootKeyId, await this.keyStoreSaltProvider());
return this.hasKeyInSlot(keySlot);
}
async saveSealedRootKey(rootKeyId, key) {
await this.saveSealedKey('root', rootKeyId, key);
}
async saveSealedDataEncKey(keyId, key) {
await this.saveSealedKey('dek', keyId, key);
}
async hasSealedDataEncKey(keyId) {
const keySlot = await this.getKeySlot('dek', keyId, await this.keyStoreSaltProvider());
return this.hasKeyInSlot(keySlot);
}
fetchSealedRootKey(rootKeyId) {
return this.fetchSealedKey('root', rootKeyId);
}
fetchSealedDataEncKey(keyId) {
return this.fetchSealedKey('dek', keyId);
}
async destroySealedRootKey(rootKeyId) {
await this.deleteSealedKey('root', rootKeyId);
}
async destroySealedDataEncKey(keyId) {
await this.deleteSealedKey('dek', keyId);
}
async destroyAllKeys() {
await this.clearKeySlots();
}
async getScryptKey(password, salt, context) {
return new Promise((resolve, reject) => {
scrypt(password, salt, 32, (err, key) => {
if (err) {
return reject(err);
}
return resolve(key);
});
});
}
async getKeySlot(type, keyId, salt) {
const hash = createHash('sha256');
hash.update(type);
hash.update(keyId);
hash.update(salt);
return hash.digest('hex');
}
async saveSealedKey(type, keyId, key) {
const salt = await this.keyStoreSaltProvider();
const keySlot = await this.getKeySlot(type, keyId, salt);
const context = await this.keyStoreContextProvider(keyId);
const password = await this.keyStorePasswordProvider();
const scryptKey = await this.getScryptKey(password, salt, context);
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-gcm', scryptKey, iv, {
authTagLength: 16,
}).setAAD(context);
const ciphertext = Buffer.concat([
iv,
cipher.update(key),
cipher.final(),
cipher.getAuthTag(),
]);
await this.putKeyInSlot(keySlot, ciphertext);
}
async fetchSealedKey(type, keyId) {
const salt = await this.keyStoreSaltProvider();
const keySlot = await this.getKeySlot(type, keyId, salt);
if (!(await this.hasKeyInSlot(keySlot))) {
throw new Error('Key not found');
}
const key = await this.getKeyInSlot(keySlot);
const context = await this.keyStoreContextProvider(keyId);
const password = await this.keyStorePasswordProvider();
const scryptKey = await this.getScryptKey(password, salt, context);
const iv = key.slice(0, 16);
const authTag = key.slice(key.length - 16);
const keyCipherText = key.slice(16, key.length - 16);
const decipher = createDecipheriv('aes-256-gcm', scryptKey, iv, {
authTagLength: 16,
}).setAAD(context);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(keyCipherText), decipher.final()]);
}
async deleteSealedKey(type, keyId) {
const salt = await this.keyStoreSaltProvider();
const keySlot = await this.getKeySlot(type, keyId, salt);
await this.deleteKeySlot(keySlot);
}
}
//# sourceMappingURL=baseKeyStore.js.map