@proteinjs/db-encryption-driver-gcp-kms 0.2.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/.eslintrc.js +20 -0
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/dist/generated/index.d.ts +5 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +31 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/src/GcpKmsMasterKeyProvider.d.ts +37 -0
- package/dist/src/GcpKmsMasterKeyProvider.d.ts.map +1 -0
- package/dist/src/GcpKmsMasterKeyProvider.js +107 -0
- package/dist/src/GcpKmsMasterKeyProvider.js.map +1 -0
- package/dist/test/GcpKmsMasterKeyProvider.test.d.ts +2 -0
- package/dist/test/GcpKmsMasterKeyProvider.test.d.ts.map +1 -0
- package/dist/test/GcpKmsMasterKeyProvider.test.js +135 -0
- package/dist/test/GcpKmsMasterKeyProvider.test.js.map +1 -0
- package/generated/index.ts +24 -0
- package/index.ts +1 -0
- package/jest.config.js +10 -0
- package/package.json +46 -0
- package/src/GcpKmsMasterKeyProvider.ts +65 -0
- package/test/GcpKmsMasterKeyProvider.test.ts +74 -0
- package/tsconfig.json +22 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'prettier'],
|
|
3
|
+
parser: '@typescript-eslint/parser',
|
|
4
|
+
plugins: ['@typescript-eslint', 'prettier'],
|
|
5
|
+
root: true,
|
|
6
|
+
ignorePatterns: ['**/dist/*', '**/node_modules/*', '*.md'],
|
|
7
|
+
|
|
8
|
+
rules: {
|
|
9
|
+
'prettier/prettier': ['warn'],
|
|
10
|
+
curly: ['warn'],
|
|
11
|
+
'eol-last': ['warn', 'always'],
|
|
12
|
+
'keyword-spacing': ['warn', { before: true }],
|
|
13
|
+
'no-undef': 'off',
|
|
14
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
15
|
+
'@typescript-eslint/no-var-requires': 'off',
|
|
16
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
17
|
+
'@typescript-eslint/prefer-as-const': 'off',
|
|
18
|
+
'@typescript-eslint/ban-types': 'off',
|
|
19
|
+
},
|
|
20
|
+
};
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# 0.2.0 (2026-08-28)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* column encryption as a first-class db-layer feature ([d57339d](https://github.com/proteinjs/db/commit/d57339d97bb00109b7db68ec76caad7be592d6e3))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Brent Bahry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../generated/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AAEpC,OAAO,eAAe,CAAC;AACvB,OAAO,mBAAmB,CAAC;AAuB3B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Load Dependency Source Graphs */
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
15
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
require("@proteinjs/db");
|
|
19
|
+
require("@proteinjs/logger");
|
|
20
|
+
/** Generate Source Graph */
|
|
21
|
+
var sourceGraph = "{\"options\":{\"directed\":true,\"multigraph\":false,\"compound\":false},\"nodes\":[{\"v\":\"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider\",\"value\":{\"packageName\":\"@proteinjs/db-encryption-driver-gcp-kms\",\"name\":\"GcpKmsMasterKeyProvider\",\"filePath\":\"src/GcpKmsMasterKeyProvider.ts\",\"qualifiedName\":\"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"config\",\"type\":{\"packageName\":\"@proteinjs/db-encryption-driver-gcp-kms\",\"name\":\"GcpKmsMasterKeyConfig\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyConfig\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"client\",\"type\":{\"packageName\":\"@google-cloud/kms\",\"name\":\"KeyManagementServiceClient\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/kms/KeyManagementServiceClient\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"}],\"methods\":[{\"name\":\"wrapDataKey\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<string>\",\"filePath\":null,\"qualifiedName\":\"/Promise<string>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"plaintextKeyMaterial\",\"type\":{\"packageName\":\"\",\"name\":\"Buffer\",\"filePath\":null,\"qualifiedName\":\"/Buffer\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"unwrapDataKey\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<Buffer>\",\"filePath\":null,\"qualifiedName\":\"/Promise<Buffer>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"wrappedKeyMaterial\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"getMasterKeyId\",\"returnType\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"MasterKeyProvider\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/MasterKeyProvider\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db/MasterKeyProvider\"}],\"edges\":[{\"v\":\"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider\",\"w\":\"@proteinjs/db/MasterKeyProvider\",\"value\":\"implements interface\"}]}";
|
|
22
|
+
/** Generate Source Links */
|
|
23
|
+
var GcpKmsMasterKeyProvider_1 = require("../src/GcpKmsMasterKeyProvider");
|
|
24
|
+
var sourceLinks = {
|
|
25
|
+
'@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider': GcpKmsMasterKeyProvider_1.GcpKmsMasterKeyProvider,
|
|
26
|
+
};
|
|
27
|
+
/** Load Source Graph and Links */
|
|
28
|
+
var reflection_1 = require("@proteinjs/reflection");
|
|
29
|
+
reflection_1.SourceRepository.merge(sourceGraph, sourceLinks);
|
|
30
|
+
__exportStar(require("../index"), exports);
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../generated/index.ts"],"names":[],"mappings":";AAAA,oCAAoC;;;;;;;;;;;;;;;;AAEpC,yBAAuB;AACvB,6BAA2B;AAG3B,4BAA4B;AAE5B,IAAM,WAAW,GAAG,o9FAAo9F,CAAC;AAGz+F,4BAA4B;AAE5B,0EAAyE;AAEzE,IAAM,WAAW,GAAG;IACnB,iEAAiE,EAAE,iDAAuB;CAC1F,CAAC;AAGF,kCAAkC;AAElC,oDAAyD;AACzD,6BAAgB,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAGjD,2CAAyB"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./src/GcpKmsMasterKeyProvider"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gEAA8C"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { KeyManagementServiceClient } from '@google-cloud/kms';
|
|
4
|
+
import { MasterKeyProvider } from '@proteinjs/db';
|
|
5
|
+
export interface GcpKmsMasterKeyConfig {
|
|
6
|
+
/**
|
|
7
|
+
* The Cloud KMS crypto key's full resource name:
|
|
8
|
+
* `projects/<project>/locations/<location>/keyRings/<ring>/cryptoKeys/<key>`
|
|
9
|
+
*/
|
|
10
|
+
keyName: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The production `MasterKeyProvider` for @proteinjs/db column encryption: the master key
|
|
14
|
+
* lives in Cloud KMS and never leaves the vault — KMS performs the wrap (encrypt) and
|
|
15
|
+
* unwrap (decrypt) of per-owner data keys for callers holding permission on the key.
|
|
16
|
+
*
|
|
17
|
+
* Operational shape (see the framework's `DataKeyStore`): the vault is called once per data
|
|
18
|
+
* key and cached in-process, never per row — a warm request makes zero KMS calls. Grant
|
|
19
|
+
* `roles/cloudkms.cryptoKeyEncrypterDecrypter` on the key to the identity the production
|
|
20
|
+
* server runs as (its service account) and to nothing else; granting anyone else is a
|
|
21
|
+
* configuration change Google records in admin logs that cannot be turned off.
|
|
22
|
+
*
|
|
23
|
+
* Key rotation note: KMS crypto keys can rotate their PRIMARY version automatically;
|
|
24
|
+
* `decrypt` transparently uses the version that encrypted each wrapped key, so stored
|
|
25
|
+
* wrapped data keys keep unwrapping across KMS rotations with no application involvement
|
|
26
|
+
* (per-owner DATA-key rotation is the framework's own `rotate-keys` walk, a separate
|
|
27
|
+
* concern).
|
|
28
|
+
*/
|
|
29
|
+
export declare class GcpKmsMasterKeyProvider implements MasterKeyProvider {
|
|
30
|
+
private config;
|
|
31
|
+
private client;
|
|
32
|
+
constructor(config: GcpKmsMasterKeyConfig, client?: KeyManagementServiceClient);
|
|
33
|
+
wrapDataKey(plaintextKeyMaterial: Buffer): Promise<string>;
|
|
34
|
+
unwrapDataKey(wrappedKeyMaterial: string): Promise<Buffer>;
|
|
35
|
+
getMasterKeyId(): string;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=GcpKmsMasterKeyProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GcpKmsMasterKeyProvider.d.ts","sourceRoot":"","sources":["../../src/GcpKmsMasterKeyProvider.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,MAAM,CAA6B;gBAE/B,MAAM,EAAE,qBAAqB,EAAE,MAAM,CAAC,EAAE,0BAA0B;IAKxE,WAAW,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAY1D,aAAa,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYhE,cAAc,IAAI,MAAM;CAGzB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.GcpKmsMasterKeyProvider = void 0;
|
|
40
|
+
var kms_1 = require("@google-cloud/kms");
|
|
41
|
+
/**
|
|
42
|
+
* The production `MasterKeyProvider` for @proteinjs/db column encryption: the master key
|
|
43
|
+
* lives in Cloud KMS and never leaves the vault — KMS performs the wrap (encrypt) and
|
|
44
|
+
* unwrap (decrypt) of per-owner data keys for callers holding permission on the key.
|
|
45
|
+
*
|
|
46
|
+
* Operational shape (see the framework's `DataKeyStore`): the vault is called once per data
|
|
47
|
+
* key and cached in-process, never per row — a warm request makes zero KMS calls. Grant
|
|
48
|
+
* `roles/cloudkms.cryptoKeyEncrypterDecrypter` on the key to the identity the production
|
|
49
|
+
* server runs as (its service account) and to nothing else; granting anyone else is a
|
|
50
|
+
* configuration change Google records in admin logs that cannot be turned off.
|
|
51
|
+
*
|
|
52
|
+
* Key rotation note: KMS crypto keys can rotate their PRIMARY version automatically;
|
|
53
|
+
* `decrypt` transparently uses the version that encrypted each wrapped key, so stored
|
|
54
|
+
* wrapped data keys keep unwrapping across KMS rotations with no application involvement
|
|
55
|
+
* (per-owner DATA-key rotation is the framework's own `rotate-keys` walk, a separate
|
|
56
|
+
* concern).
|
|
57
|
+
*/
|
|
58
|
+
var GcpKmsMasterKeyProvider = /** @class */ (function () {
|
|
59
|
+
function GcpKmsMasterKeyProvider(config, client) {
|
|
60
|
+
this.config = config;
|
|
61
|
+
this.client = client !== null && client !== void 0 ? client : new kms_1.KeyManagementServiceClient();
|
|
62
|
+
}
|
|
63
|
+
GcpKmsMasterKeyProvider.prototype.wrapDataKey = function (plaintextKeyMaterial) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
65
|
+
var result;
|
|
66
|
+
return __generator(this, function (_a) {
|
|
67
|
+
switch (_a.label) {
|
|
68
|
+
case 0: return [4 /*yield*/, this.client.encrypt({
|
|
69
|
+
name: this.config.keyName,
|
|
70
|
+
plaintext: plaintextKeyMaterial,
|
|
71
|
+
})];
|
|
72
|
+
case 1:
|
|
73
|
+
result = (_a.sent())[0];
|
|
74
|
+
if (!result.ciphertext) {
|
|
75
|
+
throw new Error("Cloud KMS encrypt returned no ciphertext for key ".concat(this.config.keyName));
|
|
76
|
+
}
|
|
77
|
+
return [2 /*return*/, Buffer.from(result.ciphertext).toString('base64')];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
GcpKmsMasterKeyProvider.prototype.unwrapDataKey = function (wrappedKeyMaterial) {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
84
|
+
var result;
|
|
85
|
+
return __generator(this, function (_a) {
|
|
86
|
+
switch (_a.label) {
|
|
87
|
+
case 0: return [4 /*yield*/, this.client.decrypt({
|
|
88
|
+
name: this.config.keyName,
|
|
89
|
+
ciphertext: Buffer.from(wrappedKeyMaterial, 'base64'),
|
|
90
|
+
})];
|
|
91
|
+
case 1:
|
|
92
|
+
result = (_a.sent())[0];
|
|
93
|
+
if (!result.plaintext) {
|
|
94
|
+
throw new Error("Cloud KMS decrypt returned no plaintext for key ".concat(this.config.keyName));
|
|
95
|
+
}
|
|
96
|
+
return [2 /*return*/, Buffer.from(result.plaintext)];
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
GcpKmsMasterKeyProvider.prototype.getMasterKeyId = function () {
|
|
102
|
+
return this.config.keyName;
|
|
103
|
+
};
|
|
104
|
+
return GcpKmsMasterKeyProvider;
|
|
105
|
+
}());
|
|
106
|
+
exports.GcpKmsMasterKeyProvider = GcpKmsMasterKeyProvider;
|
|
107
|
+
//# sourceMappingURL=GcpKmsMasterKeyProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GcpKmsMasterKeyProvider.js","sourceRoot":"","sources":["../../src/GcpKmsMasterKeyProvider.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yCAA+D;AAW/D;;;;;;;;;;;;;;;;GAgBG;AACH;IAIE,iCAAY,MAA6B,EAAE,MAAmC;QAC5E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,IAAI,gCAA0B,EAAE,CAAC;IAC3D,CAAC;IAEK,6CAAW,GAAjB,UAAkB,oBAA4B;;;;;4BAC3B,qBAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;4BACzC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;4BACzB,SAAS,EAAE,oBAAoB;yBAChC,CAAC,EAAA;;wBAHK,MAAM,GAAI,CAAA,SAGf,CAAA,GAHW;wBAIb,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE;4BACtB,MAAM,IAAI,KAAK,CAAC,2DAAoD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAE,CAAC,CAAC;yBAC5F;wBAED,sBAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAwB,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAC;;;;KACxE;IAEK,+CAAa,GAAnB,UAAoB,kBAA0B;;;;;4BAC3B,qBAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;4BACzC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;4BACzB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC;yBACtD,CAAC,EAAA;;wBAHK,MAAM,GAAI,CAAA,SAGf,CAAA,GAHW;wBAIb,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;4BACrB,MAAM,IAAI,KAAK,CAAC,0DAAmD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAE,CAAC,CAAC;yBAC3F;wBAED,sBAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAuB,CAAC,EAAC;;;;KACpD;IAED,gDAAc,GAAd;QACE,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;IAC7B,CAAC;IACH,8BAAC;AAAD,CAAC,AApCD,IAoCC;AApCY,0DAAuB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GcpKmsMasterKeyProvider.test.d.ts","sourceRoot":"","sources":["../../test/GcpKmsMasterKeyProvider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var crypto_1 = require("crypto");
|
|
40
|
+
var GcpKmsMasterKeyProvider_1 = require("../src/GcpKmsMasterKeyProvider");
|
|
41
|
+
var KEY_NAME = 'projects/p/locations/l/keyRings/r/cryptoKeys/k';
|
|
42
|
+
/**
|
|
43
|
+
* An in-memory stand-in for the KMS service (the adapter's only unmockable dependency is
|
|
44
|
+
* the network): encrypts/decrypts with a local AES key, and records the key resource names
|
|
45
|
+
* it was called with — so the adapter's contract (right key name, bytes-in/bytes-out,
|
|
46
|
+
* base64 at the storage boundary) is pinned without live GCP. The live path is exercised
|
|
47
|
+
* manually against a real key ring before production use (no cloud creds in CI).
|
|
48
|
+
*/
|
|
49
|
+
var FakeKmsClient = /** @class */ (function () {
|
|
50
|
+
function FakeKmsClient() {
|
|
51
|
+
this.encryptCalls = [];
|
|
52
|
+
this.decryptCalls = [];
|
|
53
|
+
this.key = (0, crypto_1.randomBytes)(32);
|
|
54
|
+
}
|
|
55
|
+
FakeKmsClient.prototype.encrypt = function (request) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
57
|
+
var iv, cipher, ciphertext;
|
|
58
|
+
return __generator(this, function (_a) {
|
|
59
|
+
this.encryptCalls.push(request.name);
|
|
60
|
+
iv = (0, crypto_1.randomBytes)(12);
|
|
61
|
+
cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', this.key, iv);
|
|
62
|
+
ciphertext = Buffer.concat([cipher.update(request.plaintext), cipher.final()]);
|
|
63
|
+
return [2 /*return*/, [{ ciphertext: Buffer.concat([iv, cipher.getAuthTag(), ciphertext]) }]];
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
FakeKmsClient.prototype.decrypt = function (request) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
69
|
+
var bytes, decipher;
|
|
70
|
+
return __generator(this, function (_a) {
|
|
71
|
+
this.decryptCalls.push(request.name);
|
|
72
|
+
bytes = request.ciphertext;
|
|
73
|
+
decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', this.key, bytes.subarray(0, 12));
|
|
74
|
+
decipher.setAuthTag(bytes.subarray(12, 28));
|
|
75
|
+
return [2 /*return*/, [
|
|
76
|
+
{
|
|
77
|
+
plaintext: Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]),
|
|
78
|
+
},
|
|
79
|
+
]];
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
return FakeKmsClient;
|
|
84
|
+
}());
|
|
85
|
+
describe('GcpKmsMasterKeyProvider', function () {
|
|
86
|
+
test('wrap/unwrap round-trips data-key material through the configured KMS key', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
87
|
+
var fakeClient, provider, material, wrapped, unwrapped;
|
|
88
|
+
return __generator(this, function (_a) {
|
|
89
|
+
switch (_a.label) {
|
|
90
|
+
case 0:
|
|
91
|
+
fakeClient = new FakeKmsClient();
|
|
92
|
+
provider = new GcpKmsMasterKeyProvider_1.GcpKmsMasterKeyProvider({ keyName: KEY_NAME }, fakeClient);
|
|
93
|
+
material = (0, crypto_1.randomBytes)(64);
|
|
94
|
+
return [4 /*yield*/, provider.wrapDataKey(material)];
|
|
95
|
+
case 1:
|
|
96
|
+
wrapped = _a.sent();
|
|
97
|
+
expect(typeof wrapped).toBe('string');
|
|
98
|
+
expect(wrapped).not.toContain(material.toString('base64'));
|
|
99
|
+
return [4 /*yield*/, provider.unwrapDataKey(wrapped)];
|
|
100
|
+
case 2:
|
|
101
|
+
unwrapped = _a.sent();
|
|
102
|
+
expect(unwrapped.equals(material)).toBe(true);
|
|
103
|
+
expect(fakeClient.encryptCalls).toEqual([KEY_NAME]);
|
|
104
|
+
expect(fakeClient.decryptCalls).toEqual([KEY_NAME]);
|
|
105
|
+
expect(provider.getMasterKeyId()).toBe(KEY_NAME);
|
|
106
|
+
return [2 /*return*/];
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}); });
|
|
110
|
+
test('an empty KMS response fails loudly, never returns empty key material', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
111
|
+
var emptyClient, provider;
|
|
112
|
+
return __generator(this, function (_a) {
|
|
113
|
+
switch (_a.label) {
|
|
114
|
+
case 0:
|
|
115
|
+
emptyClient = {
|
|
116
|
+
encrypt: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
117
|
+
return [2 /*return*/, [{}]];
|
|
118
|
+
}); }); },
|
|
119
|
+
decrypt: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
120
|
+
return [2 /*return*/, [{}]];
|
|
121
|
+
}); }); },
|
|
122
|
+
};
|
|
123
|
+
provider = new GcpKmsMasterKeyProvider_1.GcpKmsMasterKeyProvider({ keyName: KEY_NAME }, emptyClient);
|
|
124
|
+
return [4 /*yield*/, expect(provider.wrapDataKey((0, crypto_1.randomBytes)(64))).rejects.toThrow(/no ciphertext/)];
|
|
125
|
+
case 1:
|
|
126
|
+
_a.sent();
|
|
127
|
+
return [4 /*yield*/, expect(provider.unwrapDataKey('AAAA')).rejects.toThrow(/no plaintext/)];
|
|
128
|
+
case 2:
|
|
129
|
+
_a.sent();
|
|
130
|
+
return [2 /*return*/];
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}); });
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=GcpKmsMasterKeyProvider.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GcpKmsMasterKeyProvider.test.js","sourceRoot":"","sources":["../../test/GcpKmsMasterKeyProvider.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Load Dependency Source Graphs */
|
|
2
|
+
|
|
3
|
+
import '@proteinjs/db';
|
|
4
|
+
import '@proteinjs/logger';
|
|
5
|
+
|
|
6
|
+
/** Generate Source Graph */
|
|
7
|
+
|
|
8
|
+
const sourceGraph =
|
|
9
|
+
'{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider","value":{"packageName":"@proteinjs/db-encryption-driver-gcp-kms","name":"GcpKmsMasterKeyProvider","filePath":"src/GcpKmsMasterKeyProvider.ts","qualifiedName":"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"config","type":{"packageName":"@proteinjs/db-encryption-driver-gcp-kms","name":"GcpKmsMasterKeyConfig","filePath":null,"qualifiedName":"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyConfig","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"client","type":{"packageName":"@google-cloud/kms","name":"KeyManagementServiceClient","filePath":null,"qualifiedName":"@google-cloud/kms/KeyManagementServiceClient","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"wrapDataKey","returnType":{"packageName":"","name":"Promise<string>","filePath":null,"qualifiedName":"/Promise<string>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"plaintextKeyMaterial","type":{"packageName":"","name":"Buffer","filePath":null,"qualifiedName":"/Buffer","typeParameters":null,"directParents":null}}]},{"name":"unwrapDataKey","returnType":{"packageName":"","name":"Promise<Buffer>","filePath":null,"qualifiedName":"/Promise<Buffer>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"wrappedKeyMaterial","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"getMasterKeyId","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"MasterKeyProvider","filePath":null,"qualifiedName":"@proteinjs/db/MasterKeyProvider","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/MasterKeyProvider"}],"edges":[{"v":"@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider","w":"@proteinjs/db/MasterKeyProvider","value":"implements interface"}]}';
|
|
10
|
+
|
|
11
|
+
/** Generate Source Links */
|
|
12
|
+
|
|
13
|
+
import { GcpKmsMasterKeyProvider } from '../src/GcpKmsMasterKeyProvider';
|
|
14
|
+
|
|
15
|
+
const sourceLinks = {
|
|
16
|
+
'@proteinjs/db-encryption-driver-gcp-kms/GcpKmsMasterKeyProvider': GcpKmsMasterKeyProvider,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Load Source Graph and Links */
|
|
20
|
+
|
|
21
|
+
import { SourceRepository } from '@proteinjs/reflection';
|
|
22
|
+
SourceRepository.merge(sourceGraph, sourceLinks);
|
|
23
|
+
|
|
24
|
+
export * from '../index';
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/GcpKmsMasterKeyProvider';
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
roots: ['<rootDir>/test'],
|
|
3
|
+
transform: {
|
|
4
|
+
'^.+\\.tsx?$': 'ts-jest',
|
|
5
|
+
},
|
|
6
|
+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
|
7
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
8
|
+
testEnvironment: 'node',
|
|
9
|
+
testTimeout: 30000,
|
|
10
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@proteinjs/db-encryption-driver-gcp-kms",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Cloud KMS master-key provider for @proteinjs/db column encryption",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/proteinjs/db.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/proteinjs/db/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/proteinjs/db#readme",
|
|
16
|
+
"author": "Brent Bahry",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rm -rf dist/ node_modules/ generated/",
|
|
20
|
+
"build": "reflection-build && tsc",
|
|
21
|
+
"test": "jest --runInBand --passWithNoTests",
|
|
22
|
+
"watch": "reflection-watch"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@google-cloud/kms": "6.0.0",
|
|
26
|
+
"@proteinjs/db": "^1.36.0",
|
|
27
|
+
"@proteinjs/logger": "^1.0.21",
|
|
28
|
+
"@proteinjs/reflection": "^1.2.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@proteinjs/reflection-build": "^2.0.1",
|
|
32
|
+
"@types/jest": "29.5.5",
|
|
33
|
+
"@types/node": "14.0.27",
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "7.8.0",
|
|
35
|
+
"@typescript-eslint/parser": "7.8.0",
|
|
36
|
+
"eslint": "8.57.0",
|
|
37
|
+
"eslint-config-prettier": "9.1.0",
|
|
38
|
+
"eslint-plugin-prettier": "5.1.3",
|
|
39
|
+
"jest": "29.7.0",
|
|
40
|
+
"ts-jest": "29.1.1",
|
|
41
|
+
"typescript": "5.2.2"
|
|
42
|
+
},
|
|
43
|
+
"main": "./dist/generated/index.js",
|
|
44
|
+
"types": "./dist/generated/index.d.ts",
|
|
45
|
+
"gitHead": "b25c88ae26fffd1c784b4c7b1174825b52751987"
|
|
46
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { KeyManagementServiceClient } from '@google-cloud/kms';
|
|
2
|
+
import { MasterKeyProvider } from '@proteinjs/db';
|
|
3
|
+
|
|
4
|
+
export interface GcpKmsMasterKeyConfig {
|
|
5
|
+
/**
|
|
6
|
+
* The Cloud KMS crypto key's full resource name:
|
|
7
|
+
* `projects/<project>/locations/<location>/keyRings/<ring>/cryptoKeys/<key>`
|
|
8
|
+
*/
|
|
9
|
+
keyName: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The production `MasterKeyProvider` for @proteinjs/db column encryption: the master key
|
|
14
|
+
* lives in Cloud KMS and never leaves the vault — KMS performs the wrap (encrypt) and
|
|
15
|
+
* unwrap (decrypt) of per-owner data keys for callers holding permission on the key.
|
|
16
|
+
*
|
|
17
|
+
* Operational shape (see the framework's `DataKeyStore`): the vault is called once per data
|
|
18
|
+
* key and cached in-process, never per row — a warm request makes zero KMS calls. Grant
|
|
19
|
+
* `roles/cloudkms.cryptoKeyEncrypterDecrypter` on the key to the identity the production
|
|
20
|
+
* server runs as (its service account) and to nothing else; granting anyone else is a
|
|
21
|
+
* configuration change Google records in admin logs that cannot be turned off.
|
|
22
|
+
*
|
|
23
|
+
* Key rotation note: KMS crypto keys can rotate their PRIMARY version automatically;
|
|
24
|
+
* `decrypt` transparently uses the version that encrypted each wrapped key, so stored
|
|
25
|
+
* wrapped data keys keep unwrapping across KMS rotations with no application involvement
|
|
26
|
+
* (per-owner DATA-key rotation is the framework's own `rotate-keys` walk, a separate
|
|
27
|
+
* concern).
|
|
28
|
+
*/
|
|
29
|
+
export class GcpKmsMasterKeyProvider implements MasterKeyProvider {
|
|
30
|
+
private config: GcpKmsMasterKeyConfig;
|
|
31
|
+
private client: KeyManagementServiceClient;
|
|
32
|
+
|
|
33
|
+
constructor(config: GcpKmsMasterKeyConfig, client?: KeyManagementServiceClient) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
this.client = client ?? new KeyManagementServiceClient();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async wrapDataKey(plaintextKeyMaterial: Buffer): Promise<string> {
|
|
39
|
+
const [result] = await this.client.encrypt({
|
|
40
|
+
name: this.config.keyName,
|
|
41
|
+
plaintext: plaintextKeyMaterial,
|
|
42
|
+
});
|
|
43
|
+
if (!result.ciphertext) {
|
|
44
|
+
throw new Error(`Cloud KMS encrypt returned no ciphertext for key ${this.config.keyName}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return Buffer.from(result.ciphertext as Uint8Array).toString('base64');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async unwrapDataKey(wrappedKeyMaterial: string): Promise<Buffer> {
|
|
51
|
+
const [result] = await this.client.decrypt({
|
|
52
|
+
name: this.config.keyName,
|
|
53
|
+
ciphertext: Buffer.from(wrappedKeyMaterial, 'base64'),
|
|
54
|
+
});
|
|
55
|
+
if (!result.plaintext) {
|
|
56
|
+
throw new Error(`Cloud KMS decrypt returned no plaintext for key ${this.config.keyName}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Buffer.from(result.plaintext as Uint8Array);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getMasterKeyId(): string {
|
|
63
|
+
return this.config.keyName;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { KeyManagementServiceClient } from '@google-cloud/kms';
|
|
2
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
|
3
|
+
import { GcpKmsMasterKeyProvider } from '../src/GcpKmsMasterKeyProvider';
|
|
4
|
+
|
|
5
|
+
const KEY_NAME = 'projects/p/locations/l/keyRings/r/cryptoKeys/k';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An in-memory stand-in for the KMS service (the adapter's only unmockable dependency is
|
|
9
|
+
* the network): encrypts/decrypts with a local AES key, and records the key resource names
|
|
10
|
+
* it was called with — so the adapter's contract (right key name, bytes-in/bytes-out,
|
|
11
|
+
* base64 at the storage boundary) is pinned without live GCP. The live path is exercised
|
|
12
|
+
* manually against a real key ring before production use (no cloud creds in CI).
|
|
13
|
+
*/
|
|
14
|
+
class FakeKmsClient {
|
|
15
|
+
public encryptCalls: string[] = [];
|
|
16
|
+
public decryptCalls: string[] = [];
|
|
17
|
+
private key = randomBytes(32);
|
|
18
|
+
|
|
19
|
+
async encrypt(request: { name: string; plaintext: Buffer }): Promise<[{ ciphertext: Uint8Array }]> {
|
|
20
|
+
this.encryptCalls.push(request.name);
|
|
21
|
+
const iv = randomBytes(12);
|
|
22
|
+
const cipher = createCipheriv('aes-256-gcm', this.key, iv);
|
|
23
|
+
const ciphertext = Buffer.concat([cipher.update(request.plaintext), cipher.final()]);
|
|
24
|
+
return [{ ciphertext: Buffer.concat([iv, cipher.getAuthTag(), ciphertext]) }];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async decrypt(request: { name: string; ciphertext: Buffer }): Promise<[{ plaintext: Uint8Array }]> {
|
|
28
|
+
this.decryptCalls.push(request.name);
|
|
29
|
+
const bytes = request.ciphertext;
|
|
30
|
+
const decipher = createDecipheriv('aes-256-gcm', this.key, bytes.subarray(0, 12));
|
|
31
|
+
decipher.setAuthTag(bytes.subarray(12, 28));
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
plaintext: Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]),
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('GcpKmsMasterKeyProvider', () => {
|
|
41
|
+
test('wrap/unwrap round-trips data-key material through the configured KMS key', async () => {
|
|
42
|
+
const fakeClient = new FakeKmsClient();
|
|
43
|
+
const provider = new GcpKmsMasterKeyProvider(
|
|
44
|
+
{ keyName: KEY_NAME },
|
|
45
|
+
fakeClient as unknown as KeyManagementServiceClient
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const material = randomBytes(64);
|
|
49
|
+
const wrapped = await provider.wrapDataKey(material);
|
|
50
|
+
expect(typeof wrapped).toBe('string');
|
|
51
|
+
expect(wrapped).not.toContain(material.toString('base64'));
|
|
52
|
+
|
|
53
|
+
const unwrapped = await provider.unwrapDataKey(wrapped);
|
|
54
|
+
expect(unwrapped.equals(material)).toBe(true);
|
|
55
|
+
|
|
56
|
+
expect(fakeClient.encryptCalls).toEqual([KEY_NAME]);
|
|
57
|
+
expect(fakeClient.decryptCalls).toEqual([KEY_NAME]);
|
|
58
|
+
expect(provider.getMasterKeyId()).toBe(KEY_NAME);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('an empty KMS response fails loudly, never returns empty key material', async () => {
|
|
62
|
+
const emptyClient = {
|
|
63
|
+
encrypt: async () => [{}],
|
|
64
|
+
decrypt: async () => [{}],
|
|
65
|
+
};
|
|
66
|
+
const provider = new GcpKmsMasterKeyProvider(
|
|
67
|
+
{ keyName: KEY_NAME },
|
|
68
|
+
emptyClient as unknown as KeyManagementServiceClient
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
await expect(provider.wrapDataKey(randomBytes(64))).rejects.toThrow(/no ciphertext/);
|
|
72
|
+
await expect(provider.unwrapDataKey('AAAA')).rejects.toThrow(/no plaintext/);
|
|
73
|
+
});
|
|
74
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"noEmitOnError": true,
|
|
4
|
+
"rootDir": "./",
|
|
5
|
+
"target": "es5",
|
|
6
|
+
"module": "commonjs",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"outDir": "./dist/",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noImplicitAny": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"typeRoots": ["./node_modules/@types"],
|
|
18
|
+
"types": ["node", "jest"]
|
|
19
|
+
},
|
|
20
|
+
"include": ["./src/**/*.ts", "./test/**/*.ts", "./generated/**/*.ts", "./generated/index.ts"],
|
|
21
|
+
"exclude": ["./dist"]
|
|
22
|
+
}
|