diff --git a/packages/cli/src/__tests__/credentials-helper.test.ts b/packages/cli/src/__tests__/credentials-helper.test.ts index 0345548f0d6..d223ee3e0b1 100644 --- a/packages/cli/src/__tests__/credentials-helper.test.ts +++ b/packages/cli/src/__tests__/credentials-helper.test.ts @@ -67,14 +67,29 @@ describe('CredentialsHelper', () => { const dynamicCredentialProxy = new DynamicCredentialsProxy(mockLogger); // Setup cipher for testing + const encryptionKeyProxy = new EncryptionKeyProxy(); const cipher = new Cipher( mock({ encryptionKey: 'test_key_for_testing' }), new CipherAes256GCM(), new CipherAes256CBC(), - new EncryptionKeyProxy(), + encryptionKeyProxy, ); Container.set(Cipher, cipher); + // The default deployment: no rotation, so the active key is the legacy + // instance-key descriptor (no-prefix, instance-key-wrapped). + const legacyDescriptor = { + id: 'instance-key', + value: cipher.encryptDEKWithInstanceKey('test_key_for_testing'), + algorithm: 'aes-256-cbc' as const, + format: 'no-prefix' as const, + }; + encryptionKeyProxy.setProvider({ + getActiveKey: async () => legacyDescriptor, + getKeyById: async () => null, + getLegacyKey: async () => legacyDescriptor, + }); + const credentialsHelper = new CredentialsHelper( new CredentialTypes(mockNodesAndCredentials), mock(), diff --git a/packages/cli/src/commands/__tests__/execute.test.ts b/packages/cli/src/commands/__tests__/execute.test.ts index 99182bdee0b..bad96f54ec3 100644 --- a/packages/cli/src/commands/__tests__/execute.test.ts +++ b/packages/cli/src/commands/__tests__/execute.test.ts @@ -17,6 +17,7 @@ import { mock } from 'vitest-mock-extended'; import { ActiveExecutions } from '@/active-executions'; import { DeprecationService } from '@/deprecation/deprecation.service'; +import { EncryptionBootstrapService } from '@/encryption/encryption-bootstrap.service'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { ActivityEventRelay } from '@/events/relays/activity.event-relay'; import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay'; @@ -66,6 +67,10 @@ const deploymentKeyRepository = mockInstance(DeploymentKeyRepository); deploymentKeyRepository.findActiveByType.mockResolvedValue(null); deploymentKeyRepository.insertOrIgnore.mockResolvedValue(undefined); +// BaseCommand.init() wires the encryption key provider; this command-init test +// does not exercise encryption, so keep the bootstrap a no-op. +mockInstance(EncryptionBootstrapService); + // minimal command for exercising BaseCommand.init(): // Execute.init() chains singletons that cannot init twice per process class ReadOnlyCommand extends BaseCommand {} diff --git a/packages/cli/src/commands/base-command.ts b/packages/cli/src/commands/base-command.ts index 18d13c909a0..b7eae142b8e 100644 --- a/packages/cli/src/commands/base-command.ts +++ b/packages/cli/src/commands/base-command.ts @@ -32,6 +32,7 @@ import { Expression, UnexpectedError } from 'n8n-workflow'; import type { AbstractServer } from '@/abstract-server'; import * as CrashJournal from '@/crash-journal'; import { getDataDeduplicationService } from '@/deduplication'; +import { EncryptionBootstrapService } from '@/encryption/encryption-bootstrap.service'; import { TestRunCleanupService } from '@/evaluation.ee/test-runner/test-run-cleanup.service.ee'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { ActivityEventRelay } from '@/events/relays/activity.event-relay'; @@ -201,6 +202,11 @@ export abstract class BaseCommand { }); } + // Wire the encryption key provider (and seed keys on a seeding main) before + // anything encrypts or decrypts. This must run for every entrypoint — + // servers and one-off commands — since the cipher has no fallback path. + await Container.get(EncryptionBootstrapService).run(); + if (process.env.EXECUTIONS_PROCESS === 'own') process.exit(-1); if ( diff --git a/packages/cli/src/modules/encryption-key-manager/__tests__/encryption-bootstrap.service.integration.test.ts b/packages/cli/src/encryption/__tests__/encryption-bootstrap.service.integration.test.ts similarity index 100% rename from packages/cli/src/modules/encryption-key-manager/__tests__/encryption-bootstrap.service.integration.test.ts rename to packages/cli/src/encryption/__tests__/encryption-bootstrap.service.integration.test.ts diff --git a/packages/cli/src/modules/encryption-key-manager/__tests__/encryption-bootstrap.service.test.ts b/packages/cli/src/encryption/__tests__/encryption-bootstrap.service.test.ts similarity index 100% rename from packages/cli/src/modules/encryption-key-manager/__tests__/encryption-bootstrap.service.test.ts rename to packages/cli/src/encryption/__tests__/encryption-bootstrap.service.test.ts diff --git a/packages/cli/src/modules/encryption-key-manager/__tests__/key-manager.service.test.ts b/packages/cli/src/encryption/__tests__/key-manager.service.test.ts similarity index 99% rename from packages/cli/src/modules/encryption-key-manager/__tests__/key-manager.service.test.ts rename to packages/cli/src/encryption/__tests__/key-manager.service.test.ts index 8a0ade1c6af..4e612829a51 100644 --- a/packages/cli/src/modules/encryption-key-manager/__tests__/key-manager.service.test.ts +++ b/packages/cli/src/encryption/__tests__/key-manager.service.test.ts @@ -7,7 +7,7 @@ import { Cipher, InstanceSettings } from 'n8n-core'; import { mock } from 'vitest-mock-extended'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; -import { KeyManagerService } from '@/modules/encryption-key-manager/key-manager.service'; +import { KeyManagerService } from '@/encryption/key-manager.service'; const makeKey = (overrides: Partial = {}): DeploymentKey => ({ diff --git a/packages/cli/src/modules/encryption-key-manager/__tests__/key-rotation-cycle.integration.test.ts b/packages/cli/src/encryption/__tests__/key-rotation-cycle.integration.test.ts similarity index 96% rename from packages/cli/src/modules/encryption-key-manager/__tests__/key-rotation-cycle.integration.test.ts rename to packages/cli/src/encryption/__tests__/key-rotation-cycle.integration.test.ts index 6f1ad4666f0..02f12cc57b5 100644 --- a/packages/cli/src/modules/encryption-key-manager/__tests__/key-rotation-cycle.integration.test.ts +++ b/packages/cli/src/encryption/__tests__/key-rotation-cycle.integration.test.ts @@ -2,7 +2,7 @@ import { mockInstance, testDb } from '@n8n/backend-test-utils'; import { Container } from '@n8n/di'; import { Cipher, InstanceSettings } from 'n8n-core'; -import { KeyManagerService } from '@/modules/encryption-key-manager/key-manager.service'; +import { KeyManagerService } from '@/encryption/key-manager.service'; import { EncryptionBootstrapService } from '../encryption-bootstrap.service'; diff --git a/packages/cli/src/modules/encryption-key-manager/encryption-bootstrap.service.ts b/packages/cli/src/encryption/encryption-bootstrap.service.ts similarity index 100% rename from packages/cli/src/modules/encryption-key-manager/encryption-bootstrap.service.ts rename to packages/cli/src/encryption/encryption-bootstrap.service.ts diff --git a/packages/cli/src/modules/encryption-key-manager/key-manager.service.ts b/packages/cli/src/encryption/key-manager.service.ts similarity index 100% rename from packages/cli/src/modules/encryption-key-manager/key-manager.service.ts rename to packages/cli/src/encryption/key-manager.service.ts diff --git a/packages/cli/src/modules/encryption-key-manager/key-rotation-flag.ts b/packages/cli/src/encryption/key-rotation-flag.ts similarity index 100% rename from packages/cli/src/modules/encryption-key-manager/key-rotation-flag.ts rename to packages/cli/src/encryption/key-rotation-flag.ts diff --git a/packages/cli/src/modules/encryption-key-manager/__tests__/encryption-key.controller.test.ts b/packages/cli/src/modules/encryption-key-manager/__tests__/encryption-key.controller.test.ts index 1ed96ac7ee5..8bcdf5ec3f6 100644 --- a/packages/cli/src/modules/encryption-key-manager/__tests__/encryption-key.controller.test.ts +++ b/packages/cli/src/modules/encryption-key-manager/__tests__/encryption-key.controller.test.ts @@ -4,7 +4,7 @@ import { Container } from '@n8n/di'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { EncryptionKeyController } from '@/modules/encryption-key-manager/encryption-key.controller'; -import { KeyManagerService } from '@/modules/encryption-key-manager/key-manager.service'; +import { KeyManagerService } from '@/encryption/key-manager.service'; const makeKey = (overrides: Partial = {}): DeploymentKey => ({ diff --git a/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts b/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts index 28ccb266d93..575cfb47573 100644 --- a/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts +++ b/packages/cli/src/modules/encryption-key-manager/encryption-key-manager.module.ts @@ -6,25 +6,20 @@ import { InstanceSettings } from 'n8n-core'; @BackendModule({ name: 'encryption-key-manager' }) export class EncryptionKeyManagerModule implements ModuleInterface { async init() { - // Loading and seeding run on every instance, independent of the rotation - // flag: the whole fleet must hold the keys before any instance turns the - // flag on and starts using them. - await import('./key-manager.service.js'); - const { isKeyRotationEnabled } = await import('./key-rotation-flag.js'); + // Key seeding and provider wiring run early in BaseCommand for every + // entrypoint (including one-off commands), so the module only registers + // the management API. The API (list + rotate) stays behind the flag: + // rotating keys only makes sense once the rotation write path is enabled. + const { isKeyRotationEnabled } = await import('@/encryption/key-rotation-flag.js'); - // The management API (list + rotate) stays behind the flag: rotating keys - // only makes sense once the rotation write path is enabled. if (isKeyRotationEnabled() && Container.get(InstanceSettings).instanceType === 'main') { await import('./encryption-key.controller.js'); } - - const { EncryptionBootstrapService } = await import('./encryption-bootstrap.service.js'); - await Container.get(EncryptionBootstrapService).run(); } /** Settings exposed to the frontend under `/rest/module-settings`. */ async settings() { - const { isKeyRotationEnabled } = await import('./key-rotation-flag.js'); + const { isKeyRotationEnabled } = await import('@/encryption/key-rotation-flag.js'); return { rotationEnabled: isKeyRotationEnabled() }; } } diff --git a/packages/cli/src/modules/encryption-key-manager/encryption-key.controller.ts b/packages/cli/src/modules/encryption-key-manager/encryption-key.controller.ts index 5515d918a75..797d947210a 100644 --- a/packages/cli/src/modules/encryption-key-manager/encryption-key.controller.ts +++ b/packages/cli/src/modules/encryption-key-manager/encryption-key.controller.ts @@ -9,7 +9,7 @@ import { Body, Get, GlobalScope, Post, Query, RestController } from '@n8n/decora import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -import { KeyManagerService } from './key-manager.service'; +import { KeyManagerService } from '@/encryption/key-manager.service'; function toResponseDto(row: DeploymentKey): EncryptionKey { return { diff --git a/packages/cli/test/integration/environments/source-control-export.service.test.ts b/packages/cli/test/integration/environments/source-control-export.service.test.ts index 53ef6dc20fb..fbdac062a8f 100644 --- a/packages/cli/test/integration/environments/source-control-export.service.test.ts +++ b/packages/cli/test/integration/environments/source-control-export.service.test.ts @@ -65,9 +65,13 @@ describe('SourceControlExportService Integration', () => { // Setup export directory for testing (no longer creating real directories) exportDirectory = path.join(process.cwd(), 'test-exports-' + uuid()); - // Mock the instance settings to use our test directory + // Mock the instance settings to use our test directory. Keep the real + // encryptionKey: the cipher and the test key provider both read it, so a + // mock function here breaks credential encrypt/decrypt. + const { encryptionKey } = Container.get(InstanceSettings); mockInstance(InstanceSettings, { n8nFolder: exportDirectory, + encryptionKey, }); // Get the services from container (this will use real dependencies) diff --git a/packages/cli/test/setup-encryption-key.ts b/packages/cli/test/setup-encryption-key.ts new file mode 100644 index 00000000000..c4eb35de351 --- /dev/null +++ b/packages/cli/test/setup-encryption-key.ts @@ -0,0 +1,29 @@ +import 'reflect-metadata'; +import { Container } from '@n8n/di'; +import { Cipher, EncryptionKeyProxy, InstanceSettings } from 'n8n-core'; + +// The cipher has no fallback path: every encrypt/decrypt needs a key provider. +// Production wires one in `BaseCommand.init()`; the test harness has no equivalent, +// so register the default no-rotation provider here for every suite. +// +// The descriptor is the legacy instance-key row (no-prefix, instance-key-wrapped), +// matching `KeyManagerService.instanceKeyLegacyInfo()`. It resolves the cipher and +// instance key lazily, so it always tracks the configured key (real or mocked) and +// does nothing for suites that never encrypt. A suite that needs a different +// provider (rotation, key-not-found) overwrites this by calling `setProvider`. +const legacyDescriptor = async () => { + const cipher = Container.get(Cipher); + const { encryptionKey } = Container.get(InstanceSettings); + return { + id: 'instance-key', + value: cipher.encryptDEKWithInstanceKey(encryptionKey), + algorithm: 'aes-256-cbc' as const, + format: 'no-prefix' as const, + }; +}; + +Container.get(EncryptionKeyProxy).setProvider({ + getActiveKey: legacyDescriptor, + getLegacyKey: legacyDescriptor, + getKeyById: async () => null, +}); diff --git a/packages/cli/vitest.config.base.ts b/packages/cli/vitest.config.base.ts index ac47bf44d07..39be0d15eb6 100644 --- a/packages/cli/vitest.config.base.ts +++ b/packages/cli/vitest.config.base.ts @@ -38,7 +38,12 @@ export const baseConfig = mergeConfig(createVitestConfigWithDecorators(), { // would leak that state across files. pool: 'forks', globalSetup: ['./test/global-setup.ts'], - setupFiles: ['./test/setup-test-folder.ts', './test/setup-mocks.ts', './test/extend-expect.ts'], + setupFiles: [ + './test/setup-test-folder.ts', + './test/setup-mocks.ts', + './test/setup-encryption-key.ts', + './test/extend-expect.ts', + ], // Vitest's default exclude does not cover dist; compiled test files left in // dist would otherwise be collected and fail as CJS. exclude: [...configDefaults.exclude, '**/dist/**'], diff --git a/packages/core/src/__tests__/credentials.test.ts b/packages/core/src/__tests__/credentials.test.ts index 37d760c6688..3587c1370d6 100644 --- a/packages/core/src/__tests__/credentials.test.ts +++ b/packages/core/src/__tests__/credentials.test.ts @@ -16,14 +16,29 @@ describe('Credentials', () => { const nodeCredentials = { id: '123', name: 'Test Credential' }; const credentialType = 'testApi'; + const encryptionKeyProxy = new EncryptionKeyProxy(); const cipher = new Cipher( mock({ encryptionKey: 'password' }), new CipherAes256GCM(), new CipherAes256CBC(), - new EncryptionKeyProxy(), + encryptionKeyProxy, ); Container.set(Cipher, cipher); + // The default deployment: no rotation, so every key is the legacy instance-key + // descriptor (no-prefix, instance-key-wrapped), matching KeyManagerService. + const legacyDescriptor = { + id: 'instance-key', + value: cipher.encryptDEKWithInstanceKey('password'), + algorithm: 'aes-256-cbc' as const, + format: 'no-prefix' as const, + }; + encryptionKeyProxy.setProvider({ + getActiveKey: async () => legacyDescriptor, + getKeyById: async () => null, + getLegacyKey: async () => legacyDescriptor, + }); + const setDataKey = async (credentials: Credentials, key: string, data: CredentialInformation) => { let fullData; try { @@ -77,7 +92,7 @@ describe('Credentials', () => { test('should throw an error when decryption fails', async () => { const credentials = new Credentials(nodeCredentials, credentialType); - credentials.data = '{"key": "already-decrypted-credentials-data" }'; + credentials.data = 'already-decrypted-credentials-data'; await expect(credentials.getData()).rejects.toThrow(CREDENTIAL_ERRORS.DECRYPTION_FAILED); diff --git a/packages/core/src/encryption/__tests__/cipher.test.ts b/packages/core/src/encryption/__tests__/cipher.test.ts index ec9fdd88406..464851779b7 100644 --- a/packages/core/src/encryption/__tests__/cipher.test.ts +++ b/packages/core/src/encryption/__tests__/cipher.test.ts @@ -235,14 +235,16 @@ describe('Cipher', () => { }); it('should write the byte-compatible legacy format when the descriptor is no-prefix', async () => { + const legacyDescriptor = { + id: 'legacy', + value: cipher.encryptDEKWithInstanceKey(instanceKey), + algorithm: 'aes-256-cbc', + format: 'no-prefix' as const, + }; withProvider({ - getActiveKey: async () => ({ - id: 'legacy', - value: cipher.encryptDEKWithInstanceKey(instanceKey), - algorithm: 'aes-256-cbc', - format: 'no-prefix', - }), + getActiveKey: async () => legacyDescriptor, getKeyById: async () => null, + getLegacyKey: async () => legacyDescriptor, }); const encrypted = await cipher.encryptV2('legacy-write'); @@ -302,9 +304,14 @@ describe('Cipher', () => { expect(getKeyById).not.toHaveBeenCalled(); }); - it('should decrypt unprefixed data with the instance key without consulting the provider', async () => { + it('should decrypt unprefixed data through the legacy key from the provider', async () => { const getKeyById = vi.fn(); - const getLegacyKey = vi.fn(); + const getLegacyKey = vi.fn(async () => ({ + id: 'legacy', + value: cipher.encryptDEKWithInstanceKey(instanceKey), + algorithm: 'aes-256-cbc', + format: 'no-prefix' as const, + })); withProvider({ getKeyById, getLegacyKey }); const legacyCiphertext = cipher.encryptWithKey('legacy-data', instanceKey, 'aes-256-cbc'); @@ -312,7 +319,7 @@ describe('Cipher', () => { const decrypted = await cipher.decryptV2(legacyCiphertext); expect(decrypted).toEqual('legacy-data'); expect(getKeyById).not.toHaveBeenCalled(); - expect(getLegacyKey).not.toHaveBeenCalled(); + expect(getLegacyKey).toHaveBeenCalled(); }); it('should bypass the provider when customEncryptionKey is provided', async () => { @@ -326,11 +333,13 @@ describe('Cipher', () => { expect(getActiveKey).not.toHaveBeenCalled(); }); - it('should fall back to the instance key when no provider is configured', async () => { - const encrypted = await cipher.encryptV2('no-provider'); - expect(encrypted.includes(':')).toBe(false); - expect(await cipher.decryptV2(encrypted)).toEqual('no-provider'); - expect(cipher.decryptWithInstanceKey(encrypted)).toEqual('no-provider'); + it('should throw when no provider is configured', async () => { + await expect(cipher.encryptV2('no-provider')).rejects.toThrow( + 'Encryption key provider is not configured', + ); + await expect(cipher.decryptV2('some-data')).rejects.toThrow( + 'Encryption key provider is not configured', + ); }); }); }); diff --git a/packages/core/src/encryption/cipher.ts b/packages/core/src/encryption/cipher.ts index d5ec2fc1b11..e77dd70a09f 100644 --- a/packages/core/src/encryption/cipher.ts +++ b/packages/core/src/encryption/cipher.ts @@ -7,7 +7,7 @@ import { assertUnreachable } from '@/utils/assertions'; import { CipherAes256CBC } from './aes-256-cbc'; import { CipherAes256GCM } from './aes-256-gcm'; -import { EncryptionKeyProxy } from './encryption-key-proxy'; +import { EncryptionKeyProxy, KeyInfo } from './encryption-key-proxy'; import { CipherAlgorithm } from './interface'; /** @@ -61,36 +61,34 @@ export class Cipher { return this.encryptWithKey(plaintext, customEncryptionKey, 'aes-256-cbc'); } - if (this.encryptionKeyProxy.isConfigured()) { - const keyInfo = await this.encryptionKeyProxy.getActiveKey(); + const keyInfo = await this.encryptionKeyProxy.getActiveKey(); - if (keyInfo.format === 'no-prefix') { - // No-prefix output must stay byte-compatible with the pre-rotation - // format, which readers decrypt with the instance key directly. - if (!this.verifiedLegacyDescriptors.has(keyInfo)) { - if ( - this.decryptDEKWithInstanceKey(keyInfo.value) !== this.instanceSettings.encryptionKey || - keyInfo.algorithm !== 'aes-256-cbc' - ) { - throw new UnexpectedError( - 'A no-prefix encryption descriptor must resolve to the instance key', - ); - } - this.verifiedLegacyDescriptors.add(keyInfo); + if (keyInfo.format === 'no-prefix') { + // No-prefix output must stay byte-compatible with the pre-rotation + // format, which readers decrypt with the instance key directly. + if (!this.verifiedLegacyDescriptors.has(keyInfo)) { + if ( + this.decryptDEKWithInstanceKey(keyInfo.value) !== this.instanceSettings.encryptionKey || + keyInfo.algorithm !== 'aes-256-cbc' + ) { + throw new UnexpectedError( + 'A no-prefix encryption descriptor must resolve to the instance key', + ); } - return this.encryptWithKey(plaintext, this.instanceSettings.encryptionKey, 'aes-256-cbc'); + this.verifiedLegacyDescriptors.add(keyInfo); } - - const plaintextKey = this.decryptDEKWithInstanceKey(keyInfo.value); - const ciphertext = this.encryptWithKey( - plaintext, - plaintextKey, - keyInfo.algorithm as CipherAlgorithm, - ); - return `${keyInfo.id}:${ciphertext}`; } - return this.encryptWithKey(plaintext, this.instanceSettings.encryptionKey, 'aes-256-cbc'); + const plaintextKey = this.decryptDEKWithInstanceKey(keyInfo.value); + const ciphertext = this.encryptWithKey( + plaintext, + plaintextKey, + keyInfo.algorithm as CipherAlgorithm, + ); + if (keyInfo.format === 'no-prefix') { + return ciphertext; + } + return `${keyInfo.id}:${ciphertext}`; } /** @@ -103,25 +101,25 @@ export class Cipher { return this.decryptWithKey(data, customEncryptionKey, 'aes-256-cbc'); } - if (this.encryptionKeyProxy.isConfigured()) { - const colonIdx = data.indexOf(':'); - if (colonIdx !== -1) { - const keyId = data.slice(0, colonIdx); - if (KEY_ID_PATTERN.test(keyId)) { - const ciphertext = data.slice(colonIdx + 1); - const keyInfo = await this.encryptionKeyProxy.getKeyById(keyId); - if (!keyInfo) throw new UnexpectedError(`Encryption key not found: ${keyId}`); - const plaintextKey = this.decryptDEKWithInstanceKey(keyInfo.value); - return this.decryptWithKey( - ciphertext, - plaintextKey, - keyInfo.algorithm as CipherAlgorithm, - ); - } + let keyInfo: KeyInfo | null = null; + let ciphertext = data; + + const colonIdx = data.indexOf(':'); + if (colonIdx !== -1) { + const keyId = data.slice(0, colonIdx); + if (KEY_ID_PATTERN.test(keyId)) { + ciphertext = data.slice(colonIdx + 1); + keyInfo = await this.encryptionKeyProxy.getKeyById(keyId); + if (!keyInfo) throw new UnexpectedError(`Encryption key not found: ${keyId}`); } + } else { + keyInfo = await this.encryptionKeyProxy.getLegacyKey(); } - return this.decryptWithKey(data, this.instanceSettings.encryptionKey, 'aes-256-cbc'); + if (!keyInfo) throw new UnexpectedError('Encryption key not found!'); + + const plaintextKey = this.decryptDEKWithInstanceKey(keyInfo.value); + return this.decryptWithKey(ciphertext, plaintextKey, keyInfo.algorithm as CipherAlgorithm); } /** diff --git a/packages/core/src/encryption/encryption-key-proxy.ts b/packages/core/src/encryption/encryption-key-proxy.ts index a3e5c7b49b1..de6f1e21e18 100644 --- a/packages/core/src/encryption/encryption-key-proxy.ts +++ b/packages/core/src/encryption/encryption-key-proxy.ts @@ -10,9 +10,10 @@ export interface IEncryptionKeyProvider { getActiveKey(): Promise; getKeyById(id: string): Promise; /** - * The seeded legacy CBC row. Not on the decrypt path — no-prefix data - * decrypts with the instance key directly — but kept for later - * re-encryption tooling that must enumerate what the legacy key was. + * The seeded legacy CBC row. Resolves the no-prefix decrypt path: ciphertext + * without a key-id prefix is legacy data, and this key unwraps to the + * instance key it was encrypted with. The provider falls back to the + * instance key when the row is absent, so old data stays readable. */ getLegacyKey(): Promise; } @@ -34,10 +35,6 @@ export class EncryptionKeyProxy { this.provider = provider; } - isConfigured(): boolean { - return this.provider !== undefined; - } - async getActiveKey(): Promise { if (!this.provider) throw new UnexpectedError('Encryption key provider is not configured'); return await this.provider.getActiveKey();