UNPKG

@bryopsida/crypto

Version:

A crypto library utilizing @bryopsida/key-store to protect data using data encryption keys

212 lines 8.36 kB
import { Stream } from 'stream'; import { readFile } from 'fs/promises'; import { randomBytes, createCipheriv, randomUUID, createDecipheriv, createHmac, } from 'node:crypto'; import { resolveHome } from './resolve.js'; export class Crypto { masterKeyPath; masterKeyContext; keyStore; constructor(keyStore, masterKeyPath, masterKeyContext) { this.masterKeyPath = masterKeyPath; this.masterKeyContext = masterKeyContext; this.keyStore = keyStore; } encodeCipherText(cipherTxt) { const concatenatedBuffer = Buffer.concat([ Buffer.from(cipherTxt.rootKeyId), Buffer.from(cipherTxt.keyId), cipherTxt.iv, cipherTxt.authTag, cipherTxt.ciphertext, ]); return Promise.resolve(concatenatedBuffer.toString('base64')); } async encryptAndEncode(encryptOpts) { const cipherText = await this.encrypt(encryptOpts); return this.encodeCipherText(cipherText); } decryptEncoded(encodedCipherText, rootKeyContext, dekContext, context) { const concatenatedBuffer = Buffer.from(encodedCipherText, 'base64'); const rootKeyId = concatenatedBuffer.slice(0, 36).toString('utf8'); const keyId = concatenatedBuffer.slice(36, 72).toString('utf8'); const iv = concatenatedBuffer.slice(72, 88); const authTag = concatenatedBuffer.slice(88, 104); const ciphertext = concatenatedBuffer.slice(104); return this.decrypt({ algorithm: 'aes-256-gcm', rootKeyId, keyId, iv, authTag, ciphertext, rootKeyContext, dekContext, context, }); } async readFileFromPath(path) { const buffer = await readFile(resolveHome(path), 'utf-8'); return Buffer.from(buffer, 'base64'); } async unsealRootKey(keyId, keyContext) { const sealedKey = await this.keyStore.fetchSealedRootKey(keyId); const iv = sealedKey.slice(0, 16); const authTag = sealedKey.slice(sealedKey.length - 16); const encryptedKey = sealedKey.slice(16, sealedKey.length - 16); const aead = Buffer.from(keyContext || (await this.readFileFromPath(this.masterKeyContext))); const rootKeyDecipher = createDecipheriv('aes-256-gcm', await this.readFileFromPath(this.masterKeyPath), iv, { authTagLength: 16, }); rootKeyDecipher.setAuthTag(authTag); rootKeyDecipher.setAAD(aead); const key = rootKeyDecipher.update(encryptedKey); return Buffer.concat([key, rootKeyDecipher.final()]); } async unsealDekKey(keyId, rootKeyId, keyContext, rootKeyContext) { const rootKey = await this.unsealRootKey(rootKeyId, rootKeyContext); const sealedKey = await this.keyStore.fetchSealedDataEncKey(keyId); const iv = sealedKey.slice(0, 16); const authTag = sealedKey.slice(sealedKey.length - 16); const encryptedKey = sealedKey.slice(16, sealedKey.length - 16); const keyDecipher = createDecipheriv('aes-256-gcm', rootKey, iv, { authTagLength: 16, }); keyDecipher.setAuthTag(authTag); if (keyContext) { keyDecipher.setAAD(Buffer.from(keyContext)); } const key = keyDecipher.update(encryptedKey); return Buffer.concat([key, keyDecipher.final()]); } async seal(data, key, context) { const iv = randomBytes(16); const cipher = createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16, }); let aead; if (context) { aead = Buffer.from(context); } else { aead = await this.readFileFromPath(this.masterKeyContext); } cipher.setAAD(aead); const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]); const authTag = cipher.getAuthTag(); return { keyId: randomUUID(), rootKeyId: 'master', keyCipherText: ciphertext, iv, authTag, }; } async saveSealedKey(sealedKey) { await this.keyStore.saveSealedDataEncKey(sealedKey.keyId, Buffer.concat([ sealedKey.iv, sealedKey.keyCipherText, sealedKey.authTag || Buffer.alloc(0), ])); } async saveSealedRootKey(sealedKey) { await this.keyStore.saveSealedRootKey(sealedKey.keyId, Buffer.concat([ sealedKey.iv, sealedKey.keyCipherText, sealedKey.authTag || Buffer.alloc(0), ])); } async generateRootKey(size, context) { const key = randomBytes(size); if (!context) { context = (await this.readFileFromPath(this.masterKeyContext)).toString('utf-8'); } const sealedKey = await this.seal(key, await this.readFileFromPath(this.masterKeyPath), context); await this.saveSealedRootKey(sealedKey); return sealedKey.keyId; } async generateDataEncKey(size, rootKeyId, rootKeyContext, dekContext) { const key = randomBytes(size); const rootKey = await this.unsealRootKey(rootKeyId, rootKeyContext); const sealedKey = await this.seal(key, rootKey, dekContext); await this.saveSealedKey(sealedKey); return sealedKey.keyId; } async destroyDataEncKey(keyId) { await this.keyStore.destroySealedDataEncKey(keyId); } async destroyRootKey(rootKeyId) { await this.keyStore.destroySealedRootKey(rootKeyId); } async encrypt(encryptRequest) { const dek = await this.unsealDekKey(encryptRequest.keyId, encryptRequest.rootKeyId, encryptRequest.dekContext, encryptRequest.rootKeyContext); const iv = encryptRequest.iv || randomBytes(16); const cipher = createCipheriv('aes-256-gcm', dek, iv, { authTagLength: 16, }); if (encryptRequest.context) { cipher.setAAD(Buffer.from(encryptRequest.context)); } if (encryptRequest.plaintext instanceof Stream) { const retText = { keyId: encryptRequest.keyId, rootKeyId: encryptRequest.rootKeyId, iv, algorithm: 'aes-256-gcm', ciphertext: encryptRequest.plaintext.pipe(cipher).on('finish', () => { retText.authTag = cipher.getAuthTag(); }), }; return retText; } return { keyId: encryptRequest.keyId, rootKeyId: encryptRequest.rootKeyId, iv, algorithm: 'aes-256-gcm', ciphertext: Buffer.concat([ cipher.update(encryptRequest.plaintext), cipher.final(), ]), authTag: cipher.getAuthTag(), }; } async decrypt(decryptOpts) { const dek = await this.unsealDekKey(decryptOpts.keyId, decryptOpts.rootKeyId, decryptOpts.dekContext, decryptOpts.rootKeyContext); const decipher = createDecipheriv('aes-256-gcm', dek, decryptOpts.iv, { authTagLength: 16, }); if (decryptOpts.authTag) { decipher.setAuthTag(decryptOpts.authTag); } if (decryptOpts.context) { decipher.setAAD(Buffer.from(decryptOpts.context)); } if (decryptOpts.ciphertext instanceof Stream) { return decryptOpts.ciphertext.pipe(decipher); } return Buffer.concat([ decipher.update(decryptOpts.ciphertext), decipher.final(), ]); } async close() { await this.keyStore.close(); } hasDataEncKey(keyId) { return this.keyStore.hasSealedDataEncKey(keyId); } hasRootKey(rootKeyId) { return this.keyStore.hasSealedRootKey(rootKeyId); } async mac(keyOpts, message) { const dek = await this.unsealDekKey(keyOpts.keyId, keyOpts.rootKeyId, keyOpts.dekContext, keyOpts.rootKeyContext); const hmac = createHmac('sha256', dek); hmac.update(message); const result = hmac.digest(); return Promise.resolve(result); } async validate(opts, message, digest) { return (await this.mac(opts, message)).equals(digest); } } //# sourceMappingURL=crypto.js.map