Key-Store

What is this?
This is a typescript library that can be used to create a key store for managing root and data encryption keys. A basic file store key store is included but it can also be extended to persist to a shared store such as redis.
What problem does this solve? This was intiailly created as part of a data munging project in which I needed to be able to dynamically encrypt credentials and share them across a distributed system using redis.
How do I use it?
To create your own store extend from the BaseKeyStore and implement the required key slot functions. For example:
import { writeFile, mkdir, access, readFile, unlink, rm } from 'fs/promises'
import { resolveHome } from './resolve.js'
import {
BaseKeyStore,
IKeyStoreContextProvider,
IKeyStoreValueProvider,
} from './baseKeyStore'
export class FileKeyStore extends BaseKeyStore {
private readonly keyStorePath: string
constructor(
keyStorePath: string,
keyStorePasswordProvider: IKeyStoreValueProvider,
keyStoreSaltProvider: IKeyStoreValueProvider,
keyStoreContextProvider: IKeyStoreContextProvider
) {
super(
keyStorePasswordProvider,
keyStoreSaltProvider,
keyStoreContextProvider
)
this.keyStorePath = resolveHome(keyStorePath)
}
protected hasKeyInSlot(keySlot: string): Promise<boolean> {
return access(this.keyStorePath + '/' + keySlot)
.then(() => true)
.catch(() => false)
}
private async createKeyStoreDirIfNotExists(): Promise<void> {
await access(this.keyStorePath).catch(async () => {
await mkdir(this.keyStorePath, { recursive: true })
})
}
protected async putKeyInSlot(keySlot: string, key: Buffer): Promise<void> {
await this.createKeyStoreDirIfNotExists()
await writeFile(this.keyStorePath + '/' + keySlot, key)
}
protected async getKeyInSlot(keySlot: string): Promise<Buffer> {
return readFile(this.keyStorePath + '/' + keySlot)
}
protected async deleteKeySlot(keySlot: string): Promise<void> {
await unlink(this.keyStorePath + '/' + keySlot)
}
protected async clearKeySlots(): Promise<void> {
await rm(this.keyStorePath, { recursive: true, force: true, maxRetries: 3 })
}
async close(): Promise<void> {
}
}
You can then use the store like this (snippet from a test):
const storeDir = tmpdir()
const key = randomBytes(32)
const salt = randomBytes(16)
const context = randomBytes(32)
const keystore = new FileKeyStore(
storeDir + '/keystore',
() => Promise.resolve(key),
() => Promise.resolve(salt),
() => Promise.resolve(context)
)
const dek = randomBytes(32)
const id = randomUUID()
await keystore.saveSealedDataEncKey(id, dek)
const fetchedDek = await keystore.fetchSealedDataEncKey(id)
expect(fetchedDek).toEqual(dek)