diff --git a/src/EncryptedFS.ts b/src/EncryptedFS.ts index cc250fcc..5ececa13 100644 --- a/src/EncryptedFS.ts +++ b/src/EncryptedFS.ts @@ -14,7 +14,7 @@ import type { OptionsStream } from './streams'; import { code as errno } from 'errno'; import Logger from '@matrixai/logger'; -import { DB } from '@matrixai/db'; +import { DB, errors as dbErrors } from '@matrixai/db'; import CurrentDirectory from './CurrentDirectory'; import Stat from './Stat'; import { INodeManager, errors as inodesErrors } from './inodes'; @@ -90,6 +90,7 @@ class EncryptedFS { logger: logger.getChild(DB.name), }); } + await EncryptedFS.setupCanary(db); iNodeMgr = iNodeMgr ?? (await INodeManager.createINodeManager({ @@ -3570,6 +3571,38 @@ class EncryptedFS { } throw new TypeError('data must be Buffer or Uint8Array or string'); } + + /** + * Performs validation of the database key + */ + protected static async setupCanary(db: DB) { + // Uses the root level that's already available + try { + const deadbeefData: Buffer = await db.db.get('canary'); + try { + const deadbeef = await db.deserializeDecrypt( + deadbeefData, + false, + ); + if (deadbeef !== 'deadbeef') throw new errors.ErrorEncryptedFSKey(); + } catch (e) { + if (e instanceof dbErrors.ErrorDBDecrypt) { + throw new errors.ErrorEncryptedFSKey(); + } + throw e; + } + } catch (e) { + if (e.notFound) { + // If the stored value didn't exist, its a new db and so store and proceed + await db.put([], 'canary', 'deadbeef'); + } else { + // Db must be stopped otherwise the lock will persist and + // other efs instances cannot be created + await db.stop(); + throw e; + } + } + } } export default EncryptedFS; diff --git a/src/errors.ts b/src/errors.ts index a6efe488..57b503fd 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -8,6 +8,8 @@ class ErrorEncryptedFSNotRunning extends ErrorEncryptedFS {} class ErrorEncryptedFSDestroyed extends ErrorEncryptedFS {} +class ErrorEncryptedFSKey extends ErrorEncryptedFS {} + class ErrorEncryptedFSError extends ErrorEncryptedFS { protected _errno: number; protected _code: string; @@ -73,5 +75,6 @@ export { ErrorEncryptedFSRunning, ErrorEncryptedFSNotRunning, ErrorEncryptedFSDestroyed, + ErrorEncryptedFSKey, ErrorEncryptedFSError, }; diff --git a/tests/EncryptedFS.nav.test.ts b/tests/EncryptedFS.nav.test.ts index a80bb0f4..add21b41 100644 --- a/tests/EncryptedFS.nav.test.ts +++ b/tests/EncryptedFS.nav.test.ts @@ -6,6 +6,7 @@ import * as utils from '@/utils'; import { EncryptedFS, constants } from '@'; import { expectError } from './utils'; import { code as errno } from 'errno'; +import * as errors from '@/errors'; describe('EncryptedFS Navigation', () => { const logger = new Logger('EncryptedFS Navigation', LogLevel.WARN, [ @@ -29,6 +30,7 @@ describe('EncryptedFS Navigation', () => { }); }); afterEach(async () => { + await efs.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, @@ -37,6 +39,24 @@ describe('EncryptedFS Navigation', () => { test('creation of EFS', async () => { expect(efs).toBeInstanceOf(EncryptedFS); }); + test('Validation of keys', async () => { + await efs.stop(); + const falseDbKey = await utils.generateKey(256); + await expect( + EncryptedFS.createEncryptedFS({ + dbKey: falseDbKey, + dbPath, + umask: 0o022, + logger, + }), + ).rejects.toThrow(errors.ErrorEncryptedFSKey); + efs = await EncryptedFS.createEncryptedFS({ + dbKey, + dbPath, + umask: 0o022, + logger, + }); + }); test('EFS using callback style functions', (done) => { const str = 'callback'; const flags = constants.O_CREAT | constants.O_RDWR;