@proteinjs/db 1.35.1 → 1.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +7 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/generated/test/index.d.ts.map +1 -1
- package/dist/generated/test/index.js +7 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/src/Columns.d.ts +5 -1
- package/dist/src/Columns.d.ts.map +1 -1
- package/dist/src/Columns.js +2 -1
- package/dist/src/Columns.js.map +1 -1
- package/dist/src/Db.d.ts +25 -0
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +219 -22
- package/dist/src/Db.js.map +1 -1
- package/dist/src/Record.d.ts +7 -1
- package/dist/src/Record.d.ts.map +1 -1
- package/dist/src/Record.js +56 -21
- package/dist/src/Record.js.map +1 -1
- package/dist/src/Table.d.ts +82 -1
- package/dist/src/Table.d.ts.map +1 -1
- package/dist/src/Table.js +24 -1
- package/dist/src/Table.js.map +1 -1
- package/dist/src/TableQueryTransformProvider.d.ts +17 -0
- package/dist/src/TableQueryTransformProvider.d.ts.map +1 -0
- package/dist/src/TableQueryTransformProvider.js +34 -0
- package/dist/src/TableQueryTransformProvider.js.map +1 -0
- package/dist/src/encryption/Base64Url.d.ts +12 -0
- package/dist/src/encryption/Base64Url.d.ts.map +1 -0
- package/dist/src/encryption/Base64Url.js +21 -0
- package/dist/src/encryption/Base64Url.js.map +1 -0
- package/dist/src/encryption/DataEncryptionKeyTable.d.ts +33 -0
- package/dist/src/encryption/DataEncryptionKeyTable.d.ts.map +1 -0
- package/dist/src/encryption/DataEncryptionKeyTable.js +45 -0
- package/dist/src/encryption/DataEncryptionKeyTable.js.map +1 -0
- package/dist/src/encryption/DataKeyStore.d.ts +84 -0
- package/dist/src/encryption/DataKeyStore.d.ts.map +1 -0
- package/dist/src/encryption/DataKeyStore.js +481 -0
- package/dist/src/encryption/DataKeyStore.js.map +1 -0
- package/dist/src/encryption/DbEncryptionConfig.d.ts +62 -0
- package/dist/src/encryption/DbEncryptionConfig.d.ts.map +1 -0
- package/dist/src/encryption/DbEncryptionConfig.js +67 -0
- package/dist/src/encryption/DbEncryptionConfig.js.map +1 -0
- package/dist/src/encryption/EncryptedColumnQueryError.d.ts +10 -0
- package/dist/src/encryption/EncryptedColumnQueryError.d.ts.map +1 -0
- package/dist/src/encryption/EncryptedColumnQueryError.js +38 -0
- package/dist/src/encryption/EncryptedColumnQueryError.js.map +1 -0
- package/dist/src/encryption/EncryptedColumnQueryTransform.d.ts +52 -0
- package/dist/src/encryption/EncryptedColumnQueryTransform.d.ts.map +1 -0
- package/dist/src/encryption/EncryptedColumnQueryTransform.js +423 -0
- package/dist/src/encryption/EncryptedColumnQueryTransform.js.map +1 -0
- package/dist/src/encryption/EncryptedColumns.d.ts +69 -0
- package/dist/src/encryption/EncryptedColumns.d.ts.map +1 -0
- package/dist/src/encryption/EncryptedColumns.js +286 -0
- package/dist/src/encryption/EncryptedColumns.js.map +1 -0
- package/dist/src/encryption/EncryptionDerivedTableRegistry.d.ts +20 -0
- package/dist/src/encryption/EncryptionDerivedTableRegistry.d.ts.map +1 -0
- package/dist/src/encryption/EncryptionDerivedTableRegistry.js +36 -0
- package/dist/src/encryption/EncryptionDerivedTableRegistry.js.map +1 -0
- package/dist/src/encryption/EncryptionEnvelope.d.ts +25 -0
- package/dist/src/encryption/EncryptionEnvelope.d.ts.map +1 -0
- package/dist/src/encryption/EncryptionEnvelope.js +59 -0
- package/dist/src/encryption/EncryptionEnvelope.js.map +1 -0
- package/dist/src/encryption/EncryptionLifecycleWalker.d.ts +65 -0
- package/dist/src/encryption/EncryptionLifecycleWalker.d.ts.map +1 -0
- package/dist/src/encryption/EncryptionLifecycleWalker.js +343 -0
- package/dist/src/encryption/EncryptionLifecycleWalker.js.map +1 -0
- package/dist/src/encryption/EncryptionRecordHooks.d.ts +47 -0
- package/dist/src/encryption/EncryptionRecordHooks.d.ts.map +1 -0
- package/dist/src/encryption/EncryptionRecordHooks.js +229 -0
- package/dist/src/encryption/EncryptionRecordHooks.js.map +1 -0
- package/dist/src/encryption/EncryptionTokenMaintenance.d.ts +34 -0
- package/dist/src/encryption/EncryptionTokenMaintenance.d.ts.map +1 -0
- package/dist/src/encryption/EncryptionTokenMaintenance.js +211 -0
- package/dist/src/encryption/EncryptionTokenMaintenance.js.map +1 -0
- package/dist/src/encryption/InMemoryMasterKeyProvider.d.ts +20 -0
- package/dist/src/encryption/InMemoryMasterKeyProvider.d.ts.map +1 -0
- package/dist/src/encryption/InMemoryMasterKeyProvider.js +87 -0
- package/dist/src/encryption/InMemoryMasterKeyProvider.js.map +1 -0
- package/dist/src/encryption/MasterKeyProvider.d.ts +27 -0
- package/dist/src/encryption/MasterKeyProvider.d.ts.map +1 -0
- package/dist/src/encryption/MasterKeyProvider.js +3 -0
- package/dist/src/encryption/MasterKeyProvider.js.map +1 -0
- package/dist/src/encryption/SearchTokenizer.d.ts +49 -0
- package/dist/src/encryption/SearchTokenizer.d.ts.map +1 -0
- package/dist/src/encryption/SearchTokenizer.js +106 -0
- package/dist/src/encryption/SearchTokenizer.js.map +1 -0
- package/dist/src/schema/TableManager.d.ts +10 -0
- package/dist/src/schema/TableManager.d.ts.map +1 -1
- package/dist/src/schema/TableManager.js +62 -5
- package/dist/src/schema/TableManager.js.map +1 -1
- package/dist/src/tables/MigrationTable.d.ts +2 -2
- package/dist/test/EncryptedColumnsSchema.test.d.ts +2 -0
- package/dist/test/EncryptedColumnsSchema.test.d.ts.map +1 -0
- package/dist/test/EncryptedColumnsSchema.test.js +151 -0
- package/dist/test/EncryptedColumnsSchema.test.js.map +1 -0
- package/dist/test/EncryptionEnvelope.test.d.ts +2 -0
- package/dist/test/EncryptionEnvelope.test.d.ts.map +1 -0
- package/dist/test/EncryptionEnvelope.test.js +109 -0
- package/dist/test/EncryptionEnvelope.test.js.map +1 -0
- package/dist/test/EncryptionQueryContract.test.d.ts +2 -0
- package/dist/test/EncryptionQueryContract.test.d.ts.map +1 -0
- package/dist/test/EncryptionQueryContract.test.js +399 -0
- package/dist/test/EncryptionQueryContract.test.js.map +1 -0
- package/dist/test/SearchTokenizer.test.d.ts +2 -0
- package/dist/test/SearchTokenizer.test.d.ts.map +1 -0
- package/dist/test/SearchTokenizer.test.js +64 -0
- package/dist/test/SearchTokenizer.test.js.map +1 -0
- package/dist/test/reusable/CascadeDeleteTests.js +2 -2
- package/dist/test/reusable/CascadeDeleteTests.js.map +1 -1
- package/dist/test/reusable/ColumnTypesTests.js +2 -2
- package/dist/test/reusable/ColumnTypesTests.js.map +1 -1
- package/dist/test/reusable/CrudTests.js +2 -2
- package/dist/test/reusable/CrudTests.js.map +1 -1
- package/dist/test/reusable/DynamicReferenceColumnTests.js +2 -2
- package/dist/test/reusable/DynamicReferenceColumnTests.js.map +1 -1
- package/dist/test/reusable/PreloadReferencesTests.js +2 -2
- package/dist/test/reusable/PreloadReferencesTests.js.map +1 -1
- package/dist/test/reusable/RecordIteratorTests.js +2 -2
- package/dist/test/reusable/RecordIteratorTests.js.map +1 -1
- package/dist/test/reusable/TableManagerTests.js +2 -2
- package/dist/test/reusable/TableManagerTests.js.map +1 -1
- package/dist/test/reusable/TransactionTests.js +2 -2
- package/dist/test/reusable/TransactionTests.js.map +1 -1
- package/generated/index.ts +7 -1
- package/generated/test/index.ts +7 -1
- package/index.ts +16 -0
- package/package.json +3 -3
- package/src/Columns.ts +6 -2
- package/src/Db.ts +137 -12
- package/src/Record.ts +21 -4
- package/src/Table.ts +109 -2
- package/src/TableQueryTransformProvider.ts +31 -0
- package/src/encryption/Base64Url.ts +14 -0
- package/src/encryption/DataEncryptionKeyTable.ts +40 -0
- package/src/encryption/DataKeyStore.ts +305 -0
- package/src/encryption/DbEncryptionConfig.ts +92 -0
- package/src/encryption/EncryptedColumnQueryError.ts +15 -0
- package/src/encryption/EncryptedColumnQueryTransform.ts +383 -0
- package/src/encryption/EncryptedColumns.ts +318 -0
- package/src/encryption/EncryptionDerivedTableRegistry.ts +34 -0
- package/src/encryption/EncryptionEnvelope.ts +60 -0
- package/src/encryption/EncryptionLifecycleWalker.ts +243 -0
- package/src/encryption/EncryptionRecordHooks.ts +167 -0
- package/src/encryption/EncryptionTokenMaintenance.ts +123 -0
- package/src/encryption/InMemoryMasterKeyProvider.ts +42 -0
- package/src/encryption/MasterKeyProvider.ts +26 -0
- package/src/encryption/SearchTokenizer.ts +103 -0
- package/src/schema/TableManager.ts +43 -1
- package/test/EncryptedColumnsSchema.test.ts +172 -0
- package/test/EncryptionEnvelope.test.ts +66 -0
- package/test/EncryptionQueryContract.test.ts +195 -0
- package/test/SearchTokenizer.test.ts +67 -0
- package/test/reusable/CascadeDeleteTests.ts +2 -2
- package/test/reusable/ColumnTypesTests.ts +2 -2
- package/test/reusable/CrudTests.ts +2 -2
- package/test/reusable/DynamicReferenceColumnTests.ts +2 -2
- package/test/reusable/PreloadReferencesTests.ts +2 -2
- package/test/reusable/RecordIteratorTests.ts +2 -2
- package/test/reusable/TableManagerTests.ts +2 -2
- package/test/reusable/TransactionTests.ts +2 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Table } from '../Table';
|
|
2
|
+
import { Record } from '../Record';
|
|
3
|
+
/**
|
|
4
|
+
* One wrapped per-owner data key version (see `DataKeyStore`).
|
|
5
|
+
*
|
|
6
|
+
* - `active` — encrypts new writes (the highest active version) and fingerprints queries.
|
|
7
|
+
* - `retired` — decrypt-only: envelopes referencing it still unwrap, but no new writes or
|
|
8
|
+
* query fingerprints use it. A rotation walk moves every row off a version, then retires it.
|
|
9
|
+
*
|
|
10
|
+
* Crypto-shred = deleting an owner's rows here: every envelope naming that owner becomes
|
|
11
|
+
* permanently unreadable, including inside backups.
|
|
12
|
+
*/
|
|
13
|
+
export interface DataEncryptionKey extends Record {
|
|
14
|
+
/** The key owner (a user id — the row's permission-source scope owner). */
|
|
15
|
+
owner: string;
|
|
16
|
+
/** Monotonic per-owner version; envelopes name (owner, version). */
|
|
17
|
+
version: number;
|
|
18
|
+
/** The data-key material, wrapped by the master key (`MasterKeyProvider`). */
|
|
19
|
+
wrappedKey: string;
|
|
20
|
+
status: 'active' | 'retired';
|
|
21
|
+
}
|
|
22
|
+
export declare class DataEncryptionKeyTable extends Table<DataEncryptionKey> {
|
|
23
|
+
name: string;
|
|
24
|
+
columns: Table<DataEncryptionKey>['columns'];
|
|
25
|
+
/** (owner, version) is the identity an envelope names — the race loser on concurrent
|
|
26
|
+
* first-write key creation fails here and re-reads the winner's key. */
|
|
27
|
+
indexes: {
|
|
28
|
+
columns: (keyof DataEncryptionKey)[];
|
|
29
|
+
name?: string;
|
|
30
|
+
unique?: boolean;
|
|
31
|
+
}[];
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=DataEncryptionKeyTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataEncryptionKeyTable.d.ts","sourceRoot":"","sources":["../../../src/encryption/DataEncryptionKeyTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,MAAM,EAAqB,MAAM,WAAW,CAAC;AAGtD;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAkB,SAAQ,MAAM;IAC/C,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC9B;AAED,qBAAa,sBAAuB,SAAQ,KAAK,CAAC,iBAAiB,CAAC;IAC3D,IAAI,SAAyB;IAC7B,OAAO,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC,SAAS,CAAC,CAKhD;IACH;6EACyE;IAClE,OAAO,EAAE;QAAE,OAAO,EAAE,CAAC,MAAM,iBAAiB,CAAC,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAEzF;CAGH"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.DataEncryptionKeyTable = void 0;
|
|
19
|
+
var Table_1 = require("../Table");
|
|
20
|
+
var Record_1 = require("../Record");
|
|
21
|
+
var Columns_1 = require("../Columns");
|
|
22
|
+
var DataEncryptionKeyTable = /** @class */ (function (_super) {
|
|
23
|
+
__extends(DataEncryptionKeyTable, _super);
|
|
24
|
+
function DataEncryptionKeyTable() {
|
|
25
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
26
|
+
_this.name = 'data_encryption_key';
|
|
27
|
+
_this.columns = (0, Record_1.withRecordColumns)({
|
|
28
|
+
owner: new Columns_1.StringColumn('owner', { nullable: false }, 36),
|
|
29
|
+
version: new Columns_1.IntegerColumn('version', { nullable: false }),
|
|
30
|
+
wrappedKey: new Columns_1.StringColumn('wrapped_key', { nullable: false }, 'MAX'),
|
|
31
|
+
status: new Columns_1.StringColumn('status', { nullable: false }, 16),
|
|
32
|
+
});
|
|
33
|
+
/** (owner, version) is the identity an envelope names — the race loser on concurrent
|
|
34
|
+
* first-write key creation fails here and re-reads the winner's key. */
|
|
35
|
+
_this.indexes = [
|
|
36
|
+
{ columns: ['owner', 'version'], name: 'data_encryption_key_owner_version_unique', unique: true },
|
|
37
|
+
];
|
|
38
|
+
return _this;
|
|
39
|
+
// No auth block: default-deny for every non-system caller. Key rows are framework
|
|
40
|
+
// machinery, only ever touched through system paths (DataKeyStore).
|
|
41
|
+
}
|
|
42
|
+
return DataEncryptionKeyTable;
|
|
43
|
+
}(Table_1.Table));
|
|
44
|
+
exports.DataEncryptionKeyTable = DataEncryptionKeyTable;
|
|
45
|
+
//# sourceMappingURL=DataEncryptionKeyTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataEncryptionKeyTable.js","sourceRoot":"","sources":["../../../src/encryption/DataEncryptionKeyTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,kCAAiC;AACjC,oCAAsD;AACtD,sCAAyD;AAsBzD;IAA4C,0CAAwB;IAApE;QAAA,qEAeC;QAdQ,UAAI,GAAG,qBAAqB,CAAC;QAC7B,aAAO,GAAwC,IAAA,0BAAiB,EAAoB;YACzF,KAAK,EAAE,IAAI,sBAAY,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YACzD,OAAO,EAAE,IAAI,uBAAa,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAC1D,UAAU,EAAE,IAAI,sBAAY,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC;YACvE,MAAM,EAAE,IAAI,sBAAY,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;SAC5D,CAAC,CAAC;QACH;iFACyE;QAClE,aAAO,GAAgF;YAC5F,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,0CAA0C,EAAE,MAAM,EAAE,IAAI,EAAE;SAClG,CAAC;;QACF,kFAAkF;QAClF,oEAAoE;IACtE,CAAC;IAAD,6BAAC;AAAD,CAAC,AAfD,CAA4C,aAAK,GAehD;AAfY,wDAAsB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/**
|
|
4
|
+
* Decrypt was asked for a key that does not exist — most often because the owner's keys were
|
|
5
|
+
* crypto-shredded (account deletion): the ciphertext is permanently unreadable, by design.
|
|
6
|
+
*/
|
|
7
|
+
export declare class EncryptionKeyUnavailableError extends Error {
|
|
8
|
+
constructor(owner: string, version: number);
|
|
9
|
+
}
|
|
10
|
+
/** Unwrapped material of one data-key version. */
|
|
11
|
+
export interface DataKeyMaterial {
|
|
12
|
+
owner: string;
|
|
13
|
+
version: number;
|
|
14
|
+
/** AES-256 key for row values (first half of the 64-byte material). */
|
|
15
|
+
cipherKey: Buffer;
|
|
16
|
+
/** HMAC key for search-token fingerprints (second half). */
|
|
17
|
+
indexKey: Buffer;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Per-owner data keys for column encryption: creation, unwrap-once caching, rotation,
|
|
21
|
+
* retirement, and crypto-shred. Key rows live in `DataEncryptionKeyTable`, wrapped by the
|
|
22
|
+
* deployment's `MasterKeyProvider`; the vault is called once per key (then cached), never
|
|
23
|
+
* per row.
|
|
24
|
+
*
|
|
25
|
+
* All database access runs as system, OUTSIDE any ambient transaction: a key row must be
|
|
26
|
+
* durable independent of the caller's transaction outcome, because unwrapped material is
|
|
27
|
+
* cached in-process the moment it exists — a key created inside a transaction that later
|
|
28
|
+
* rolls back would leave the cache claiming a key the database lost. An orphaned key row
|
|
29
|
+
* from a rolled-back first write is harmless (a key no envelope names).
|
|
30
|
+
*
|
|
31
|
+
* The caches live on the global object (the per-package-install duplicate-module pattern —
|
|
32
|
+
* see `Db.getDefaultDbDriver`), so every live copy of this package shares one cache.
|
|
33
|
+
*/
|
|
34
|
+
export declare class DataKeyStore {
|
|
35
|
+
/** Unwrapped material cache TTL — bounds how long a shredded key can linger in a warm process. */
|
|
36
|
+
private static readonly MATERIAL_TTL_MS;
|
|
37
|
+
/** Owner key-row list TTL — bounds cross-process staleness of the active-version set. */
|
|
38
|
+
private static readonly OWNER_ROWS_TTL_MS;
|
|
39
|
+
private static readonly CACHES_GLOBAL_KEY;
|
|
40
|
+
private static readonly KEY_MATERIAL_BYTES;
|
|
41
|
+
private logger;
|
|
42
|
+
private table;
|
|
43
|
+
/**
|
|
44
|
+
* The key that encrypts new writes for `owner`: the highest active version, created on
|
|
45
|
+
* first use (each owner gets a randomly generated data key, wrapped by the master key).
|
|
46
|
+
*/
|
|
47
|
+
getWriteKey(owner: string): Promise<DataKeyMaterial>;
|
|
48
|
+
/** The key an envelope names — any status; decrypt must work until the key is shredded. */
|
|
49
|
+
getKeyByVersion(owner: string, version: number): Promise<DataKeyMaterial>;
|
|
50
|
+
/**
|
|
51
|
+
* The index keys a query fingerprints with: every ACTIVE version of every accessible
|
|
52
|
+
* owner (normally one per owner; two during a rotation window). Owners with no keys yet
|
|
53
|
+
* contribute nothing — they cannot have encrypted rows.
|
|
54
|
+
*/
|
|
55
|
+
getQueryIndexKeys(owners: string[]): Promise<DataKeyMaterial[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Mint the next key version for `owner` (new writes use it immediately; the old version
|
|
58
|
+
* stays active — still fingerprinting queries — until a rotation walk
|
|
59
|
+
* (`EncryptionLifecycleWalker` mode 'rotate-keys') has rewritten every row, after which
|
|
60
|
+
* the caller retires it via `retireKeyVersion`).
|
|
61
|
+
* @returns the new version number
|
|
62
|
+
*/
|
|
63
|
+
rotateKey(owner: string): Promise<number>;
|
|
64
|
+
/** Retire a version: envelopes naming it still decrypt; queries and writes stop using it. */
|
|
65
|
+
retireKeyVersion(owner: string, version: number): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Crypto-shred: delete every key version of `owner` and evict the caches. Every envelope
|
|
68
|
+
* naming the owner — in live rows and in every backup — becomes permanently unreadable.
|
|
69
|
+
*/
|
|
70
|
+
shredOwnerKeys(owner: string): Promise<number>;
|
|
71
|
+
private createKeyVersion;
|
|
72
|
+
private getOwnerRows;
|
|
73
|
+
private unwrap;
|
|
74
|
+
private highestActive;
|
|
75
|
+
private requireOwner;
|
|
76
|
+
/**
|
|
77
|
+
* A system Db pinned OUTSIDE any ambient transaction (see class doc). Imported lazily —
|
|
78
|
+
* this module is reachable from the serializer layer, below `Db` in the module graph.
|
|
79
|
+
*/
|
|
80
|
+
private systemDb;
|
|
81
|
+
private caches;
|
|
82
|
+
private evictOwner;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=DataKeyStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataKeyStore.d.ts","sourceRoot":"","sources":["../../../src/encryption/DataKeyStore.ts"],"names":[],"mappings":";;AAKA;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAQ3C;AAED,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiBD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;IACvB,kGAAkG;IAClG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAkB;IACzD,yFAAyF;IACzF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAa;IACtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAkC;IAC3E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAM;IAChD,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,KAAK,CAAgC;IAE7C;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAiB1D,2FAA2F;IACrF,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAkB/E;;;;OAIG;IACG,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAcrE;;;;;;OAMG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAU/C,6FAA6F;IACvF,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAarE;;;OAGG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAetC,gBAAgB;YAsBhB,YAAY;YAsBZ,MAAM;IA2BpB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,YAAY;IAMpB;;;OAGG;YACW,QAAQ;IAuBtB,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
41
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
42
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
43
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
44
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
45
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
46
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
50
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
51
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
52
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
53
|
+
function step(op) {
|
|
54
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
55
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
56
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
57
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
58
|
+
switch (op[0]) {
|
|
59
|
+
case 0: case 1: t = op; break;
|
|
60
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
61
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
62
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
63
|
+
default:
|
|
64
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
65
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
66
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
67
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
68
|
+
if (t[2]) _.ops.pop();
|
|
69
|
+
_.trys.pop(); continue;
|
|
70
|
+
}
|
|
71
|
+
op = body.call(thisArg, _);
|
|
72
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
73
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
77
|
+
exports.DataKeyStore = exports.EncryptionKeyUnavailableError = void 0;
|
|
78
|
+
var crypto_1 = require("crypto");
|
|
79
|
+
var logger_1 = require("@proteinjs/logger");
|
|
80
|
+
var DbEncryptionConfig_1 = require("./DbEncryptionConfig");
|
|
81
|
+
var DataEncryptionKeyTable_1 = require("./DataEncryptionKeyTable");
|
|
82
|
+
/**
|
|
83
|
+
* Decrypt was asked for a key that does not exist — most often because the owner's keys were
|
|
84
|
+
* crypto-shredded (account deletion): the ciphertext is permanently unreadable, by design.
|
|
85
|
+
*/
|
|
86
|
+
var EncryptionKeyUnavailableError = /** @class */ (function (_super) {
|
|
87
|
+
__extends(EncryptionKeyUnavailableError, _super);
|
|
88
|
+
function EncryptionKeyUnavailableError(owner, version) {
|
|
89
|
+
var _this = _super.call(this, "No data key exists for owner '".concat(owner, "' version ").concat(version, ". If the owner's keys were ") +
|
|
90
|
+
"deleted (crypto-shred), rows encrypted under them are permanently unreadable.") || this;
|
|
91
|
+
_this.name = 'EncryptionKeyUnavailableError';
|
|
92
|
+
Object.setPrototypeOf(_this, EncryptionKeyUnavailableError.prototype);
|
|
93
|
+
return _this;
|
|
94
|
+
}
|
|
95
|
+
return EncryptionKeyUnavailableError;
|
|
96
|
+
}(Error));
|
|
97
|
+
exports.EncryptionKeyUnavailableError = EncryptionKeyUnavailableError;
|
|
98
|
+
/**
|
|
99
|
+
* Per-owner data keys for column encryption: creation, unwrap-once caching, rotation,
|
|
100
|
+
* retirement, and crypto-shred. Key rows live in `DataEncryptionKeyTable`, wrapped by the
|
|
101
|
+
* deployment's `MasterKeyProvider`; the vault is called once per key (then cached), never
|
|
102
|
+
* per row.
|
|
103
|
+
*
|
|
104
|
+
* All database access runs as system, OUTSIDE any ambient transaction: a key row must be
|
|
105
|
+
* durable independent of the caller's transaction outcome, because unwrapped material is
|
|
106
|
+
* cached in-process the moment it exists — a key created inside a transaction that later
|
|
107
|
+
* rolls back would leave the cache claiming a key the database lost. An orphaned key row
|
|
108
|
+
* from a rolled-back first write is harmless (a key no envelope names).
|
|
109
|
+
*
|
|
110
|
+
* The caches live on the global object (the per-package-install duplicate-module pattern —
|
|
111
|
+
* see `Db.getDefaultDbDriver`), so every live copy of this package shares one cache.
|
|
112
|
+
*/
|
|
113
|
+
var DataKeyStore = /** @class */ (function () {
|
|
114
|
+
function DataKeyStore() {
|
|
115
|
+
this.logger = new logger_1.Logger({ name: this.constructor.name });
|
|
116
|
+
this.table = new DataEncryptionKeyTable_1.DataEncryptionKeyTable();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The key that encrypts new writes for `owner`: the highest active version, created on
|
|
120
|
+
* first use (each owner gets a randomly generated data key, wrapped by the master key).
|
|
121
|
+
*/
|
|
122
|
+
DataKeyStore.prototype.getWriteKey = function (owner) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
124
|
+
var rows, active;
|
|
125
|
+
return __generator(this, function (_a) {
|
|
126
|
+
switch (_a.label) {
|
|
127
|
+
case 0:
|
|
128
|
+
this.requireOwner(owner);
|
|
129
|
+
return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
130
|
+
case 1:
|
|
131
|
+
rows = _a.sent();
|
|
132
|
+
active = this.highestActive(rows);
|
|
133
|
+
if (!!active) return [3 /*break*/, 4];
|
|
134
|
+
return [4 /*yield*/, this.createKeyVersion(owner, 1)];
|
|
135
|
+
case 2:
|
|
136
|
+
_a.sent();
|
|
137
|
+
this.evictOwner(owner);
|
|
138
|
+
return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
139
|
+
case 3:
|
|
140
|
+
rows = _a.sent();
|
|
141
|
+
active = this.highestActive(rows);
|
|
142
|
+
if (!active) {
|
|
143
|
+
throw new EncryptionKeyUnavailableError(owner, 1);
|
|
144
|
+
}
|
|
145
|
+
_a.label = 4;
|
|
146
|
+
case 4: return [4 /*yield*/, this.unwrap(active)];
|
|
147
|
+
case 5: return [2 /*return*/, _a.sent()];
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
/** The key an envelope names — any status; decrypt must work until the key is shredded. */
|
|
153
|
+
DataKeyStore.prototype.getKeyByVersion = function (owner, version) {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
155
|
+
var rows, row, freshRows, freshRow;
|
|
156
|
+
return __generator(this, function (_a) {
|
|
157
|
+
switch (_a.label) {
|
|
158
|
+
case 0: return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
159
|
+
case 1:
|
|
160
|
+
rows = _a.sent();
|
|
161
|
+
row = rows.find(function (candidate) { return candidate.version === version; });
|
|
162
|
+
if (!!row) return [3 /*break*/, 4];
|
|
163
|
+
// The row list may be cached from before this version existed (e.g. a fresh rotation
|
|
164
|
+
// in another process) — refresh once before declaring the key gone.
|
|
165
|
+
this.evictOwner(owner);
|
|
166
|
+
return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
167
|
+
case 2:
|
|
168
|
+
freshRows = _a.sent();
|
|
169
|
+
freshRow = freshRows.find(function (candidate) { return candidate.version === version; });
|
|
170
|
+
if (!freshRow) {
|
|
171
|
+
throw new EncryptionKeyUnavailableError(owner, version);
|
|
172
|
+
}
|
|
173
|
+
return [4 /*yield*/, this.unwrap(freshRow)];
|
|
174
|
+
case 3: return [2 /*return*/, _a.sent()];
|
|
175
|
+
case 4: return [4 /*yield*/, this.unwrap(row)];
|
|
176
|
+
case 5: return [2 /*return*/, _a.sent()];
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* The index keys a query fingerprints with: every ACTIVE version of every accessible
|
|
183
|
+
* owner (normally one per owner; two during a rotation window). Owners with no keys yet
|
|
184
|
+
* contribute nothing — they cannot have encrypted rows.
|
|
185
|
+
*/
|
|
186
|
+
DataKeyStore.prototype.getQueryIndexKeys = function (owners) {
|
|
187
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
188
|
+
var keys, _i, owners_1, owner, rows, _a, rows_1, row, _b, _c;
|
|
189
|
+
return __generator(this, function (_d) {
|
|
190
|
+
switch (_d.label) {
|
|
191
|
+
case 0:
|
|
192
|
+
keys = [];
|
|
193
|
+
_i = 0, owners_1 = owners;
|
|
194
|
+
_d.label = 1;
|
|
195
|
+
case 1:
|
|
196
|
+
if (!(_i < owners_1.length)) return [3 /*break*/, 7];
|
|
197
|
+
owner = owners_1[_i];
|
|
198
|
+
return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
199
|
+
case 2:
|
|
200
|
+
rows = _d.sent();
|
|
201
|
+
_a = 0, rows_1 = rows;
|
|
202
|
+
_d.label = 3;
|
|
203
|
+
case 3:
|
|
204
|
+
if (!(_a < rows_1.length)) return [3 /*break*/, 6];
|
|
205
|
+
row = rows_1[_a];
|
|
206
|
+
if (!(row.status === 'active')) return [3 /*break*/, 5];
|
|
207
|
+
_c = (_b = keys).push;
|
|
208
|
+
return [4 /*yield*/, this.unwrap(row)];
|
|
209
|
+
case 4:
|
|
210
|
+
_c.apply(_b, [_d.sent()]);
|
|
211
|
+
_d.label = 5;
|
|
212
|
+
case 5:
|
|
213
|
+
_a++;
|
|
214
|
+
return [3 /*break*/, 3];
|
|
215
|
+
case 6:
|
|
216
|
+
_i++;
|
|
217
|
+
return [3 /*break*/, 1];
|
|
218
|
+
case 7: return [2 /*return*/, keys];
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Mint the next key version for `owner` (new writes use it immediately; the old version
|
|
225
|
+
* stays active — still fingerprinting queries — until a rotation walk
|
|
226
|
+
* (`EncryptionLifecycleWalker` mode 'rotate-keys') has rewritten every row, after which
|
|
227
|
+
* the caller retires it via `retireKeyVersion`).
|
|
228
|
+
* @returns the new version number
|
|
229
|
+
*/
|
|
230
|
+
DataKeyStore.prototype.rotateKey = function (owner) {
|
|
231
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
232
|
+
var rows, highest, newVersion;
|
|
233
|
+
return __generator(this, function (_a) {
|
|
234
|
+
switch (_a.label) {
|
|
235
|
+
case 0:
|
|
236
|
+
this.requireOwner(owner);
|
|
237
|
+
return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
238
|
+
case 1:
|
|
239
|
+
rows = _a.sent();
|
|
240
|
+
highest = rows.reduce(function (max, row) { return Math.max(max, row.version); }, 0);
|
|
241
|
+
newVersion = highest + 1;
|
|
242
|
+
return [4 /*yield*/, this.createKeyVersion(owner, newVersion)];
|
|
243
|
+
case 2:
|
|
244
|
+
_a.sent();
|
|
245
|
+
this.evictOwner(owner);
|
|
246
|
+
return [2 /*return*/, newVersion];
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
/** Retire a version: envelopes naming it still decrypt; queries and writes stop using it. */
|
|
252
|
+
DataKeyStore.prototype.retireKeyVersion = function (owner, version) {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
254
|
+
var db;
|
|
255
|
+
return __generator(this, function (_a) {
|
|
256
|
+
switch (_a.label) {
|
|
257
|
+
case 0: return [4 /*yield*/, this.systemDb()];
|
|
258
|
+
case 1:
|
|
259
|
+
db = _a.sent();
|
|
260
|
+
return [4 /*yield*/, db.update(this.table, { status: 'retired' }, {
|
|
261
|
+
owner: owner,
|
|
262
|
+
version: version,
|
|
263
|
+
})];
|
|
264
|
+
case 2:
|
|
265
|
+
_a.sent();
|
|
266
|
+
this.evictOwner(owner);
|
|
267
|
+
return [2 /*return*/];
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Crypto-shred: delete every key version of `owner` and evict the caches. Every envelope
|
|
274
|
+
* naming the owner — in live rows and in every backup — becomes permanently unreadable.
|
|
275
|
+
*/
|
|
276
|
+
DataKeyStore.prototype.shredOwnerKeys = function (owner) {
|
|
277
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
278
|
+
var db, deleted, caches, _i, _a, cacheKey;
|
|
279
|
+
return __generator(this, function (_b) {
|
|
280
|
+
switch (_b.label) {
|
|
281
|
+
case 0: return [4 /*yield*/, this.systemDb()];
|
|
282
|
+
case 1:
|
|
283
|
+
db = _b.sent();
|
|
284
|
+
return [4 /*yield*/, db.delete(this.table, { owner: owner })];
|
|
285
|
+
case 2:
|
|
286
|
+
deleted = _b.sent();
|
|
287
|
+
this.evictOwner(owner);
|
|
288
|
+
caches = this.caches();
|
|
289
|
+
for (_i = 0, _a = Array.from(caches.material.keys()); _i < _a.length; _i++) {
|
|
290
|
+
cacheKey = _a[_i];
|
|
291
|
+
if (cacheKey.startsWith("".concat(owner, ":"))) {
|
|
292
|
+
caches.material.delete(cacheKey);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
this.logger.info({ message: "Crypto-shredded data keys", obj: { owner: owner, deleted: deleted } });
|
|
296
|
+
return [2 /*return*/, deleted];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
DataKeyStore.prototype.createKeyVersion = function (owner, version) {
|
|
302
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
303
|
+
var config, material, wrappedKey, db, error_1, rows;
|
|
304
|
+
return __generator(this, function (_a) {
|
|
305
|
+
switch (_a.label) {
|
|
306
|
+
case 0:
|
|
307
|
+
config = (0, DbEncryptionConfig_1.getDbEncryptionConfig)();
|
|
308
|
+
material = (0, crypto_1.randomBytes)(DataKeyStore.KEY_MATERIAL_BYTES);
|
|
309
|
+
return [4 /*yield*/, config.masterKeyProvider.wrapDataKey(material)];
|
|
310
|
+
case 1:
|
|
311
|
+
wrappedKey = _a.sent();
|
|
312
|
+
return [4 /*yield*/, this.systemDb()];
|
|
313
|
+
case 2:
|
|
314
|
+
db = _a.sent();
|
|
315
|
+
_a.label = 3;
|
|
316
|
+
case 3:
|
|
317
|
+
_a.trys.push([3, 5, , 7]);
|
|
318
|
+
return [4 /*yield*/, db.insert(this.table, { owner: owner, version: version, wrappedKey: wrappedKey, status: 'active' })];
|
|
319
|
+
case 4:
|
|
320
|
+
_a.sent();
|
|
321
|
+
return [3 /*break*/, 7];
|
|
322
|
+
case 5:
|
|
323
|
+
error_1 = _a.sent();
|
|
324
|
+
// Named race: two first writes for the same owner both create version 1; the unique
|
|
325
|
+
// (owner, version) index rejects the loser. Re-read — if the winner's key is there,
|
|
326
|
+
// use it; otherwise the failure was real.
|
|
327
|
+
this.evictOwner(owner);
|
|
328
|
+
return [4 /*yield*/, this.getOwnerRows(owner)];
|
|
329
|
+
case 6:
|
|
330
|
+
rows = _a.sent();
|
|
331
|
+
if (!rows.some(function (row) { return row.version === version; })) {
|
|
332
|
+
throw error_1;
|
|
333
|
+
}
|
|
334
|
+
return [3 /*break*/, 7];
|
|
335
|
+
case 7: return [2 /*return*/];
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
DataKeyStore.prototype.getOwnerRows = function (owner) {
|
|
341
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
342
|
+
var caches, cached, db, rows, entry;
|
|
343
|
+
return __generator(this, function (_a) {
|
|
344
|
+
switch (_a.label) {
|
|
345
|
+
case 0:
|
|
346
|
+
caches = this.caches();
|
|
347
|
+
cached = caches.ownerRows.get(owner);
|
|
348
|
+
if (cached && Date.now() - cached.fetchedAt < DataKeyStore.OWNER_ROWS_TTL_MS) {
|
|
349
|
+
return [2 /*return*/, cached.rows];
|
|
350
|
+
}
|
|
351
|
+
return [4 /*yield*/, this.systemDb()];
|
|
352
|
+
case 1:
|
|
353
|
+
db = _a.sent();
|
|
354
|
+
return [4 /*yield*/, db.query(this.table, { owner: owner })];
|
|
355
|
+
case 2:
|
|
356
|
+
rows = _a.sent();
|
|
357
|
+
entry = {
|
|
358
|
+
fetchedAt: Date.now(),
|
|
359
|
+
rows: rows.map(function (row) { return ({
|
|
360
|
+
owner: row.owner,
|
|
361
|
+
version: row.version,
|
|
362
|
+
wrappedKey: row.wrappedKey,
|
|
363
|
+
status: row.status,
|
|
364
|
+
}); }),
|
|
365
|
+
};
|
|
366
|
+
caches.ownerRows.set(owner, entry);
|
|
367
|
+
return [2 /*return*/, entry.rows];
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
DataKeyStore.prototype.unwrap = function (row) {
|
|
373
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
374
|
+
var caches, cacheKey, cached, config, materialBytes, material;
|
|
375
|
+
return __generator(this, function (_a) {
|
|
376
|
+
switch (_a.label) {
|
|
377
|
+
case 0:
|
|
378
|
+
caches = this.caches();
|
|
379
|
+
cacheKey = "".concat(row.owner, ":").concat(row.version);
|
|
380
|
+
cached = caches.material.get(cacheKey);
|
|
381
|
+
if (cached && Date.now() - cached.cachedAt < DataKeyStore.MATERIAL_TTL_MS) {
|
|
382
|
+
return [2 /*return*/, cached.material];
|
|
383
|
+
}
|
|
384
|
+
config = (0, DbEncryptionConfig_1.getDbEncryptionConfig)();
|
|
385
|
+
return [4 /*yield*/, config.masterKeyProvider.unwrapDataKey(row.wrappedKey)];
|
|
386
|
+
case 1:
|
|
387
|
+
materialBytes = _a.sent();
|
|
388
|
+
if (materialBytes.length !== DataKeyStore.KEY_MATERIAL_BYTES) {
|
|
389
|
+
throw new Error("Data key material for owner '".concat(row.owner, "' version ").concat(row.version, " has unexpected length ") +
|
|
390
|
+
"".concat(materialBytes.length, " (expected ").concat(DataKeyStore.KEY_MATERIAL_BYTES, ") \u2014 wrong master key?"));
|
|
391
|
+
}
|
|
392
|
+
material = {
|
|
393
|
+
owner: row.owner,
|
|
394
|
+
version: row.version,
|
|
395
|
+
cipherKey: materialBytes.subarray(0, 32),
|
|
396
|
+
indexKey: materialBytes.subarray(32, 64),
|
|
397
|
+
};
|
|
398
|
+
caches.material.set(cacheKey, { cachedAt: Date.now(), material: material });
|
|
399
|
+
return [2 /*return*/, material];
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
DataKeyStore.prototype.highestActive = function (rows) {
|
|
405
|
+
return rows.filter(function (row) { return row.status === 'active'; }).sort(function (a, b) { return b.version - a.version; })[0];
|
|
406
|
+
};
|
|
407
|
+
DataKeyStore.prototype.requireOwner = function (owner) {
|
|
408
|
+
if (!owner || typeof owner !== 'string') {
|
|
409
|
+
throw new Error("Data key operations require a non-empty owner id; got: ".concat(JSON.stringify(owner)));
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* A system Db pinned OUTSIDE any ambient transaction (see class doc). Imported lazily —
|
|
414
|
+
* this module is reachable from the serializer layer, below `Db` in the module graph.
|
|
415
|
+
*/
|
|
416
|
+
DataKeyStore.prototype.systemDb = function () {
|
|
417
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
418
|
+
var Db, getDefaultTransactionContextFactory, db, factory;
|
|
419
|
+
return __generator(this, function (_a) {
|
|
420
|
+
switch (_a.label) {
|
|
421
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require('../Db')); })];
|
|
422
|
+
case 1:
|
|
423
|
+
Db = (_a.sent()).Db;
|
|
424
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require('../transaction/TransactionContextFactory')); })];
|
|
425
|
+
case 2:
|
|
426
|
+
getDefaultTransactionContextFactory = (_a.sent()).getDefaultTransactionContextFactory;
|
|
427
|
+
db = new Db(undefined, undefined, undefined, true);
|
|
428
|
+
factory = getDefaultTransactionContextFactory();
|
|
429
|
+
if (!factory) {
|
|
430
|
+
return [2 /*return*/, db];
|
|
431
|
+
}
|
|
432
|
+
// Proxy every operation through an empty transaction context so key-table IO
|
|
433
|
+
// auto-commits even when the caller is mid-transaction.
|
|
434
|
+
return [2 /*return*/, new Proxy(db, {
|
|
435
|
+
get: function (target, prop, receiver) {
|
|
436
|
+
var _this = this;
|
|
437
|
+
var value = Reflect.get(target, prop, receiver);
|
|
438
|
+
if (typeof value !== 'function') {
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
return function () {
|
|
442
|
+
var args = [];
|
|
443
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
444
|
+
args[_i] = arguments[_i];
|
|
445
|
+
}
|
|
446
|
+
return factory.runInContext({}, function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
447
|
+
switch (_a.label) {
|
|
448
|
+
case 0: return [4 /*yield*/, value.apply(target, args)];
|
|
449
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
450
|
+
}
|
|
451
|
+
}); }); });
|
|
452
|
+
};
|
|
453
|
+
},
|
|
454
|
+
})];
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
DataKeyStore.prototype.caches = function () {
|
|
460
|
+
var globalObject = globalThis;
|
|
461
|
+
if (!globalObject[DataKeyStore.CACHES_GLOBAL_KEY]) {
|
|
462
|
+
globalObject[DataKeyStore.CACHES_GLOBAL_KEY] = {
|
|
463
|
+
ownerRows: new Map(),
|
|
464
|
+
material: new Map(),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return globalObject[DataKeyStore.CACHES_GLOBAL_KEY];
|
|
468
|
+
};
|
|
469
|
+
DataKeyStore.prototype.evictOwner = function (owner) {
|
|
470
|
+
this.caches().ownerRows.delete(owner);
|
|
471
|
+
};
|
|
472
|
+
/** Unwrapped material cache TTL — bounds how long a shredded key can linger in a warm process. */
|
|
473
|
+
DataKeyStore.MATERIAL_TTL_MS = 10 * 60 * 1000;
|
|
474
|
+
/** Owner key-row list TTL — bounds cross-process staleness of the active-version set. */
|
|
475
|
+
DataKeyStore.OWNER_ROWS_TTL_MS = 60 * 1000;
|
|
476
|
+
DataKeyStore.CACHES_GLOBAL_KEY = '__proteinjs_db_dataKeyCaches';
|
|
477
|
+
DataKeyStore.KEY_MATERIAL_BYTES = 64;
|
|
478
|
+
return DataKeyStore;
|
|
479
|
+
}());
|
|
480
|
+
exports.DataKeyStore = DataKeyStore;
|
|
481
|
+
//# sourceMappingURL=DataKeyStore.js.map
|