quidproquo-actionprocessor-node 0.1.11 → 0.1.12

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.
Files changed (91) hide show
  1. package/lib/commonjs/index.d.ts +1 -0
  2. package/lib/commonjs/index.js +1 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/logic/cryptoEnvelope/CryptoBlob.d.ts +7 -0
  5. package/lib/commonjs/logic/cryptoEnvelope/CryptoBlob.js +3 -0
  6. package/lib/commonjs/logic/cryptoEnvelope/CryptoBlob.js.map +1 -0
  7. package/lib/commonjs/logic/cryptoEnvelope/CryptoContextMismatchError.d.ts +4 -0
  8. package/lib/commonjs/logic/cryptoEnvelope/CryptoContextMismatchError.js +15 -0
  9. package/lib/commonjs/logic/cryptoEnvelope/CryptoContextMismatchError.js.map +1 -0
  10. package/lib/commonjs/logic/cryptoEnvelope/CryptoMalformedCiphertextError.d.ts +4 -0
  11. package/lib/commonjs/logic/cryptoEnvelope/CryptoMalformedCiphertextError.js +15 -0
  12. package/lib/commonjs/logic/cryptoEnvelope/CryptoMalformedCiphertextError.js.map +1 -0
  13. package/lib/commonjs/logic/cryptoEnvelope/DataKeyProvider.d.ts +9 -0
  14. package/lib/commonjs/logic/cryptoEnvelope/DataKeyProvider.js +3 -0
  15. package/lib/commonjs/logic/cryptoEnvelope/DataKeyProvider.js.map +1 -0
  16. package/lib/commonjs/logic/cryptoEnvelope/canonicalizeCryptoContext.d.ts +2 -0
  17. package/lib/commonjs/logic/cryptoEnvelope/canonicalizeCryptoContext.js +18 -0
  18. package/lib/commonjs/logic/cryptoEnvelope/canonicalizeCryptoContext.js.map +1 -0
  19. package/lib/commonjs/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.d.ts +2 -0
  20. package/lib/commonjs/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.js +48 -0
  21. package/lib/commonjs/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.js.map +1 -0
  22. package/lib/commonjs/logic/cryptoEnvelope/cryptoBlobPrefix.d.ts +1 -0
  23. package/lib/commonjs/logic/cryptoEnvelope/cryptoBlobPrefix.js +8 -0
  24. package/lib/commonjs/logic/cryptoEnvelope/cryptoBlobPrefix.js.map +1 -0
  25. package/lib/commonjs/logic/cryptoEnvelope/cryptoContextHash.d.ts +1 -0
  26. package/lib/commonjs/logic/cryptoEnvelope/cryptoContextHash.js +13 -0
  27. package/lib/commonjs/logic/cryptoEnvelope/cryptoContextHash.js.map +1 -0
  28. package/lib/commonjs/logic/cryptoEnvelope/decodeCryptoBlob.d.ts +2 -0
  29. package/lib/commonjs/logic/cryptoEnvelope/decodeCryptoBlob.js +32 -0
  30. package/lib/commonjs/logic/cryptoEnvelope/decodeCryptoBlob.js.map +1 -0
  31. package/lib/commonjs/logic/cryptoEnvelope/encodeCryptoBlob.d.ts +2 -0
  32. package/lib/commonjs/logic/cryptoEnvelope/encodeCryptoBlob.js +16 -0
  33. package/lib/commonjs/logic/cryptoEnvelope/encodeCryptoBlob.js.map +1 -0
  34. package/lib/commonjs/logic/cryptoEnvelope/envelopeDecrypt.d.ts +3 -0
  35. package/lib/commonjs/logic/cryptoEnvelope/envelopeDecrypt.js +42 -0
  36. package/lib/commonjs/logic/cryptoEnvelope/envelopeDecrypt.js.map +1 -0
  37. package/lib/commonjs/logic/cryptoEnvelope/envelopeEncrypt.d.ts +3 -0
  38. package/lib/commonjs/logic/cryptoEnvelope/envelopeEncrypt.js +35 -0
  39. package/lib/commonjs/logic/cryptoEnvelope/envelopeEncrypt.js.map +1 -0
  40. package/lib/commonjs/logic/cryptoEnvelope/index.d.ts +12 -0
  41. package/lib/commonjs/logic/cryptoEnvelope/index.js +29 -0
  42. package/lib/commonjs/logic/cryptoEnvelope/index.js.map +1 -0
  43. package/lib/commonjs/logic/index.d.ts +1 -0
  44. package/lib/commonjs/logic/index.js +18 -0
  45. package/lib/commonjs/logic/index.js.map +1 -0
  46. package/lib/esm/index.d.ts +1 -0
  47. package/lib/esm/index.js +1 -0
  48. package/lib/esm/index.js.map +1 -1
  49. package/lib/esm/logic/cryptoEnvelope/CryptoBlob.d.ts +7 -0
  50. package/lib/esm/logic/cryptoEnvelope/CryptoBlob.js +2 -0
  51. package/lib/esm/logic/cryptoEnvelope/CryptoBlob.js.map +1 -0
  52. package/lib/esm/logic/cryptoEnvelope/CryptoContextMismatchError.d.ts +4 -0
  53. package/lib/esm/logic/cryptoEnvelope/CryptoContextMismatchError.js +11 -0
  54. package/lib/esm/logic/cryptoEnvelope/CryptoContextMismatchError.js.map +1 -0
  55. package/lib/esm/logic/cryptoEnvelope/CryptoMalformedCiphertextError.d.ts +4 -0
  56. package/lib/esm/logic/cryptoEnvelope/CryptoMalformedCiphertextError.js +11 -0
  57. package/lib/esm/logic/cryptoEnvelope/CryptoMalformedCiphertextError.js.map +1 -0
  58. package/lib/esm/logic/cryptoEnvelope/DataKeyProvider.d.ts +9 -0
  59. package/lib/esm/logic/cryptoEnvelope/DataKeyProvider.js +2 -0
  60. package/lib/esm/logic/cryptoEnvelope/DataKeyProvider.js.map +1 -0
  61. package/lib/esm/logic/cryptoEnvelope/canonicalizeCryptoContext.d.ts +2 -0
  62. package/lib/esm/logic/cryptoEnvelope/canonicalizeCryptoContext.js +14 -0
  63. package/lib/esm/logic/cryptoEnvelope/canonicalizeCryptoContext.js.map +1 -0
  64. package/lib/esm/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.d.ts +2 -0
  65. package/lib/esm/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.js +35 -0
  66. package/lib/esm/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.js.map +1 -0
  67. package/lib/esm/logic/cryptoEnvelope/cryptoBlobPrefix.d.ts +1 -0
  68. package/lib/esm/logic/cryptoEnvelope/cryptoBlobPrefix.js +5 -0
  69. package/lib/esm/logic/cryptoEnvelope/cryptoBlobPrefix.js.map +1 -0
  70. package/lib/esm/logic/cryptoEnvelope/cryptoContextHash.d.ts +1 -0
  71. package/lib/esm/logic/cryptoEnvelope/cryptoContextHash.js +9 -0
  72. package/lib/esm/logic/cryptoEnvelope/cryptoContextHash.js.map +1 -0
  73. package/lib/esm/logic/cryptoEnvelope/decodeCryptoBlob.d.ts +2 -0
  74. package/lib/esm/logic/cryptoEnvelope/decodeCryptoBlob.js +28 -0
  75. package/lib/esm/logic/cryptoEnvelope/decodeCryptoBlob.js.map +1 -0
  76. package/lib/esm/logic/cryptoEnvelope/encodeCryptoBlob.d.ts +2 -0
  77. package/lib/esm/logic/cryptoEnvelope/encodeCryptoBlob.js +12 -0
  78. package/lib/esm/logic/cryptoEnvelope/encodeCryptoBlob.js.map +1 -0
  79. package/lib/esm/logic/cryptoEnvelope/envelopeDecrypt.d.ts +3 -0
  80. package/lib/esm/logic/cryptoEnvelope/envelopeDecrypt.js +29 -0
  81. package/lib/esm/logic/cryptoEnvelope/envelopeDecrypt.js.map +1 -0
  82. package/lib/esm/logic/cryptoEnvelope/envelopeEncrypt.d.ts +3 -0
  83. package/lib/esm/logic/cryptoEnvelope/envelopeEncrypt.js +22 -0
  84. package/lib/esm/logic/cryptoEnvelope/envelopeEncrypt.js.map +1 -0
  85. package/lib/esm/logic/cryptoEnvelope/index.d.ts +12 -0
  86. package/lib/esm/logic/cryptoEnvelope/index.js +13 -0
  87. package/lib/esm/logic/cryptoEnvelope/index.js.map +1 -0
  88. package/lib/esm/logic/index.d.ts +1 -0
  89. package/lib/esm/logic/index.js +2 -0
  90. package/lib/esm/logic/index.js.map +1 -0
  91. package/package.json +5 -5
@@ -1,3 +1,4 @@
1
1
  export * from './actionProcessor';
2
2
  export * from './dynamicActionProcessor';
3
+ export * from './logic';
3
4
  export * from './traceStoryExecution';
@@ -16,5 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./actionProcessor"), exports);
18
18
  __exportStar(require("./dynamicActionProcessor"), exports);
19
+ __exportStar(require("./logic"), exports);
19
20
  __exportStar(require("./traceStoryExecution"), exports);
20
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,wDAAsC","sourcesContent":["export * from './actionProcessor';\nexport * from './dynamicActionProcessor';\nexport * from './traceStoryExecution';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,0CAAwB;AACxB,wDAAsC","sourcesContent":["export * from './actionProcessor';\nexport * from './dynamicActionProcessor';\nexport * from './logic';\nexport * from './traceStoryExecution';\n"]}
@@ -0,0 +1,7 @@
1
+ export type CryptoBlob = {
2
+ wrappedKey: Buffer;
3
+ iv: Buffer;
4
+ authTag: Buffer;
5
+ ciphertext: Buffer;
6
+ contextHash: string;
7
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=CryptoBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CryptoBlob.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/CryptoBlob.ts"],"names":[],"mappings":"","sourcesContent":["// Decoded form of a qpqcrypto:v1 ciphertext blob.\nexport type CryptoBlob = {\n wrappedKey: Buffer;\n iv: Buffer;\n authTag: Buffer;\n ciphertext: Buffer;\n contextHash: string;\n};\n"]}
@@ -0,0 +1,4 @@
1
+ export declare class CryptoContextMismatchError extends Error {
2
+ readonly code = "QpqCryptoContextMismatch";
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CryptoContextMismatchError = void 0;
4
+ // Thrown when the context supplied at decrypt differs from the context the
5
+ // blob was encrypted under. The `code` is what actionResultErrorFromCaughtError
6
+ // keys on, so processors can map this to a distinguishable action error.
7
+ class CryptoContextMismatchError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.code = 'QpqCryptoContextMismatch';
11
+ this.name = 'CryptoContextMismatchError';
12
+ }
13
+ }
14
+ exports.CryptoContextMismatchError = CryptoContextMismatchError;
15
+ //# sourceMappingURL=CryptoContextMismatchError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CryptoContextMismatchError.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/CryptoContextMismatchError.ts"],"names":[],"mappings":";;;AAAA,2EAA2E;AAC3E,gFAAgF;AAChF,yEAAyE;AACzE,MAAa,0BAA2B,SAAQ,KAAK;IAGnD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,SAAI,GAAG,0BAA0B,CAAC;QAIzC,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AAPD,gEAOC","sourcesContent":["// Thrown when the context supplied at decrypt differs from the context the\n// blob was encrypted under. The `code` is what actionResultErrorFromCaughtError\n// keys on, so processors can map this to a distinguishable action error.\nexport class CryptoContextMismatchError extends Error {\n readonly code = 'QpqCryptoContextMismatch';\n\n constructor(message: string) {\n super(message);\n this.name = 'CryptoContextMismatchError';\n }\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export declare class CryptoMalformedCiphertextError extends Error {
2
+ readonly code = "QpqCryptoMalformedCiphertext";
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CryptoMalformedCiphertextError = void 0;
4
+ // Thrown when a ciphertext is not a valid qpq crypto blob, or fails
5
+ // authenticated decryption with the correct context (i.e. corruption or
6
+ // truncation). The `code` is what actionResultErrorFromCaughtError keys on.
7
+ class CryptoMalformedCiphertextError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.code = 'QpqCryptoMalformedCiphertext';
11
+ this.name = 'CryptoMalformedCiphertextError';
12
+ }
13
+ }
14
+ exports.CryptoMalformedCiphertextError = CryptoMalformedCiphertextError;
15
+ //# sourceMappingURL=CryptoMalformedCiphertextError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CryptoMalformedCiphertextError.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/CryptoMalformedCiphertextError.ts"],"names":[],"mappings":";;;AAAA,oEAAoE;AACpE,wEAAwE;AACxE,4EAA4E;AAC5E,MAAa,8BAA+B,SAAQ,KAAK;IAGvD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,SAAI,GAAG,8BAA8B,CAAC;QAI7C,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF;AAPD,wEAOC","sourcesContent":["// Thrown when a ciphertext is not a valid qpq crypto blob, or fails\n// authenticated decryption with the correct context (i.e. corruption or\n// truncation). The `code` is what actionResultErrorFromCaughtError keys on.\nexport class CryptoMalformedCiphertextError extends Error {\n readonly code = 'QpqCryptoMalformedCiphertext';\n\n constructor(message: string) {\n super(message);\n this.name = 'CryptoMalformedCiphertextError';\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ export type GeneratedDataKey = {
3
+ plaintextKey: Buffer;
4
+ wrappedKey: Buffer;
5
+ };
6
+ export type DataKeyProvider = {
7
+ generateDataKey: (context: CryptoContext) => Promise<GeneratedDataKey>;
8
+ unwrapDataKey: (wrappedKey: Buffer, context: CryptoContext) => Promise<Buffer>;
9
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=DataKeyProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataKeyProvider.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/DataKeyProvider.ts"],"names":[],"mappings":"","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\nexport type GeneratedDataKey = {\n plaintextKey: Buffer;\n wrappedKey: Buffer;\n};\n\n// The only part of envelope encryption that differs per runtime: how a\n// one-off data key is created and wrapped (KMS in prod, a local master key in\n// the dev server). The context is passed through so the wrapping layer can\n// bind it too (KMS EncryptionContext, which also lands in CloudTrail).\nexport type DataKeyProvider = {\n generateDataKey: (context: CryptoContext) => Promise<GeneratedDataKey>;\n unwrapDataKey: (wrappedKey: Buffer, context: CryptoContext) => Promise<Buffer>;\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ export declare const canonicalizeCryptoContext: (context?: CryptoContext) => string;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canonicalizeCryptoContext = void 0;
4
+ // Netstring-style encoding (byteLength:value) so { ab: 'c' } and { a: 'bc' }
5
+ // cannot canonicalize to the same bytes.
6
+ const encodePart = (part) => `${Buffer.byteLength(part, 'utf8')}:${part}`;
7
+ // Deterministic byte encoding of a crypto context, used as the AAD for
8
+ // envelope encryption. Keys are sorted by code unit (NOT localeCompare, which
9
+ // varies by environment). undefined and {} are equivalent: both canonicalize
10
+ // to the empty string. Every runtime (prod KMS, dev stub) MUST canonicalize
11
+ // through this one function, or dev and prod disagree about what a valid
12
+ // context is and it only surfaces after deploy.
13
+ const canonicalizeCryptoContext = (context) => {
14
+ const sortedEntries = Object.entries(context || {}).sort(([keyA], [keyB]) => (keyA < keyB ? -1 : keyA > keyB ? 1 : 0));
15
+ return sortedEntries.map(([key, value]) => encodePart(key) + encodePart(value)).join('');
16
+ };
17
+ exports.canonicalizeCryptoContext = canonicalizeCryptoContext;
18
+ //# sourceMappingURL=canonicalizeCryptoContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalizeCryptoContext.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/canonicalizeCryptoContext.ts"],"names":[],"mappings":";;;AAEA,6EAA6E;AAC7E,yCAAyC;AACzC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;AAE1F,uEAAuE;AACvE,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,yEAAyE;AACzE,gDAAgD;AACzC,MAAM,yBAAyB,GAAG,CAAC,OAAuB,EAAU,EAAE;IAC3E,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvH,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3F,CAAC,CAAC;AAJW,QAAA,yBAAyB,6BAIpC","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\n// Netstring-style encoding (byteLength:value) so { ab: 'c' } and { a: 'bc' }\n// cannot canonicalize to the same bytes.\nconst encodePart = (part: string): string => `${Buffer.byteLength(part, 'utf8')}:${part}`;\n\n// Deterministic byte encoding of a crypto context, used as the AAD for\n// envelope encryption. Keys are sorted by code unit (NOT localeCompare, which\n// varies by environment). undefined and {} are equivalent: both canonicalize\n// to the empty string. Every runtime (prod KMS, dev stub) MUST canonicalize\n// through this one function, or dev and prod disagree about what a valid\n// context is and it only surfaces after deploy.\nexport const canonicalizeCryptoContext = (context?: CryptoContext): string => {\n const sortedEntries = Object.entries(context || {}).sort(([keyA], [keyB]) => (keyA < keyB ? -1 : keyA > keyB ? 1 : 0));\n\n return sortedEntries.map(([key, value]) => encodePart(key) + encodePart(value)).join('');\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { DataKeyProvider } from './DataKeyProvider';
2
+ export declare const createLocalMasterKeyDataKeyProvider: (masterKey: Buffer) => DataKeyProvider;
@@ -0,0 +1,48 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createLocalMasterKeyDataKeyProvider = void 0;
13
+ const crypto_1 = require("crypto");
14
+ const canonicalizeCryptoContext_1 = require("./canonicalizeCryptoContext");
15
+ const CryptoMalformedCiphertextError_1 = require("./CryptoMalformedCiphertextError");
16
+ // A DataKeyProvider that wraps data keys with AES-256-GCM under a local
17
+ // 32-byte master key, with the canonical context as AAD (mirroring KMS
18
+ // EncryptionContext binding). Used by the dev server so envelope encryption
19
+ // runs the identical code path offline. Wrapped key layout: iv(12) tag(16) key.
20
+ const createLocalMasterKeyDataKeyProvider = (masterKey) => ({
21
+ generateDataKey: (context) => __awaiter(void 0, void 0, void 0, function* () {
22
+ const plaintextKey = (0, crypto_1.randomBytes)(32);
23
+ const iv = (0, crypto_1.randomBytes)(12);
24
+ const cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', masterKey, iv);
25
+ cipher.setAAD(Buffer.from((0, canonicalizeCryptoContext_1.canonicalizeCryptoContext)(context), 'utf8'));
26
+ const wrappedKeyBytes = Buffer.concat([cipher.update(plaintextKey), cipher.final()]);
27
+ return {
28
+ plaintextKey,
29
+ wrappedKey: Buffer.concat([iv, cipher.getAuthTag(), wrappedKeyBytes]),
30
+ };
31
+ }),
32
+ unwrapDataKey: (wrappedKey, context) => __awaiter(void 0, void 0, void 0, function* () {
33
+ if (wrappedKey.length < 28) {
34
+ throw new CryptoMalformedCiphertextError_1.CryptoMalformedCiphertextError('Wrapped data key is truncated');
35
+ }
36
+ const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', masterKey, wrappedKey.subarray(0, 12));
37
+ decipher.setAAD(Buffer.from((0, canonicalizeCryptoContext_1.canonicalizeCryptoContext)(context), 'utf8'));
38
+ decipher.setAuthTag(wrappedKey.subarray(12, 28));
39
+ try {
40
+ return Buffer.concat([decipher.update(wrappedKey.subarray(28)), decipher.final()]);
41
+ }
42
+ catch (_a) {
43
+ throw new CryptoMalformedCiphertextError_1.CryptoMalformedCiphertextError('Wrapped data key failed authenticated decryption');
44
+ }
45
+ }),
46
+ });
47
+ exports.createLocalMasterKeyDataKeyProvider = createLocalMasterKeyDataKeyProvider;
48
+ //# sourceMappingURL=createLocalMasterKeyDataKeyProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createLocalMasterKeyDataKeyProvider.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mCAAuE;AAEvE,2EAAwE;AACxE,qFAAkF;AAGlF,wEAAwE;AACxE,uEAAuE;AACvE,4EAA4E;AAC5E,gFAAgF;AACzE,MAAM,mCAAmC,GAAG,CAAC,SAAiB,EAAmB,EAAE,CAAC,CAAC;IAC1F,eAAe,EAAE,CAAO,OAAO,EAAE,EAAE;QACjC,MAAM,YAAY,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC;QAErC,MAAM,EAAE,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,IAAA,uBAAc,EAAC,aAAa,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAA,qDAAyB,EAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAEvE,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAErF,OAAO;YACL,YAAY;YACZ,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,eAAe,CAAC,CAAC;SACtE,CAAC;IACJ,CAAC,CAAA;IAED,aAAa,EAAE,CAAO,UAAU,EAAE,OAAO,EAAE,EAAE;QAC3C,IAAI,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAC3B,MAAM,IAAI,+DAA8B,CAAC,+BAA+B,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,QAAQ,GAAG,IAAA,yBAAgB,EAAC,aAAa,EAAE,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACxF,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAA,qDAAyB,EAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACzE,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAEjD,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC;QAAC,WAAM,CAAC;YACP,MAAM,IAAI,+DAA8B,CAAC,kDAAkD,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC,CAAA;CACF,CAAC,CAAC;AA/BU,QAAA,mCAAmC,uCA+B7C","sourcesContent":["import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';\n\nimport { canonicalizeCryptoContext } from './canonicalizeCryptoContext';\nimport { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';\nimport { DataKeyProvider } from './DataKeyProvider';\n\n// A DataKeyProvider that wraps data keys with AES-256-GCM under a local\n// 32-byte master key, with the canonical context as AAD (mirroring KMS\n// EncryptionContext binding). Used by the dev server so envelope encryption\n// runs the identical code path offline. Wrapped key layout: iv(12) tag(16) key.\nexport const createLocalMasterKeyDataKeyProvider = (masterKey: Buffer): DataKeyProvider => ({\n generateDataKey: async (context) => {\n const plaintextKey = randomBytes(32);\n\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', masterKey, iv);\n cipher.setAAD(Buffer.from(canonicalizeCryptoContext(context), 'utf8'));\n\n const wrappedKeyBytes = Buffer.concat([cipher.update(plaintextKey), cipher.final()]);\n\n return {\n plaintextKey,\n wrappedKey: Buffer.concat([iv, cipher.getAuthTag(), wrappedKeyBytes]),\n };\n },\n\n unwrapDataKey: async (wrappedKey, context) => {\n if (wrappedKey.length < 28) {\n throw new CryptoMalformedCiphertextError('Wrapped data key is truncated');\n }\n\n const decipher = createDecipheriv('aes-256-gcm', masterKey, wrappedKey.subarray(0, 12));\n decipher.setAAD(Buffer.from(canonicalizeCryptoContext(context), 'utf8'));\n decipher.setAuthTag(wrappedKey.subarray(12, 28));\n\n try {\n return Buffer.concat([decipher.update(wrappedKey.subarray(28)), decipher.final()]);\n } catch {\n throw new CryptoMalformedCiphertextError('Wrapped data key failed authenticated decryption');\n }\n },\n});\n"]}
@@ -0,0 +1 @@
1
+ export declare const CRYPTO_BLOB_PREFIX_V1 = "qpqcrypto:v1:";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CRYPTO_BLOB_PREFIX_V1 = void 0;
4
+ // Version prefix on every ciphertext this capability produces. Bump the
5
+ // version (qpqcrypto:v2:...) to change the blob format; decrypt dispatches on
6
+ // the prefix, so old blobs keep working without a data migration.
7
+ exports.CRYPTO_BLOB_PREFIX_V1 = 'qpqcrypto:v1:';
8
+ //# sourceMappingURL=cryptoBlobPrefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptoBlobPrefix.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/cryptoBlobPrefix.ts"],"names":[],"mappings":";;;AAAA,wEAAwE;AACxE,8EAA8E;AAC9E,kEAAkE;AACrD,QAAA,qBAAqB,GAAG,eAAe,CAAC","sourcesContent":["// Version prefix on every ciphertext this capability produces. Bump the\n// version (qpqcrypto:v2:...) to change the blob format; decrypt dispatches on\n// the prefix, so old blobs keep working without a data migration.\nexport const CRYPTO_BLOB_PREFIX_V1 = 'qpqcrypto:v1:';\n"]}
@@ -0,0 +1 @@
1
+ export declare const cryptoContextHash: (canonicalContext: string) => string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cryptoContextHash = void 0;
4
+ const crypto_1 = require("crypto");
5
+ // Hash of the canonical context stored inside the crypto blob. Lets decrypt
6
+ // distinguish "wrong context supplied" (hash differs) from "corrupt
7
+ // ciphertext" (hash matches, GCM tag fails). The context is not secret, so
8
+ // storing its hash leaks nothing new.
9
+ const cryptoContextHash = (canonicalContext) => {
10
+ return (0, crypto_1.createHash)('sha256').update(canonicalContext, 'utf8').digest('base64');
11
+ };
12
+ exports.cryptoContextHash = cryptoContextHash;
13
+ //# sourceMappingURL=cryptoContextHash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptoContextHash.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/cryptoContextHash.ts"],"names":[],"mappings":";;;AAAA,mCAAoC;AAEpC,4EAA4E;AAC5E,oEAAoE;AACpE,2EAA2E;AAC3E,sCAAsC;AAC/B,MAAM,iBAAiB,GAAG,CAAC,gBAAwB,EAAU,EAAE;IACpE,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAChF,CAAC,CAAC;AAFW,QAAA,iBAAiB,qBAE5B","sourcesContent":["import { createHash } from 'crypto';\n\n// Hash of the canonical context stored inside the crypto blob. Lets decrypt\n// distinguish \"wrong context supplied\" (hash differs) from \"corrupt\n// ciphertext\" (hash matches, GCM tag fails). The context is not secret, so\n// storing its hash leaks nothing new.\nexport const cryptoContextHash = (canonicalContext: string): string => {\n return createHash('sha256').update(canonicalContext, 'utf8').digest('base64');\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { CryptoBlob } from './CryptoBlob';
2
+ export declare const decodeCryptoBlob: (ciphertext: string) => CryptoBlob;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeCryptoBlob = void 0;
4
+ const cryptoBlobPrefix_1 = require("./cryptoBlobPrefix");
5
+ const CryptoMalformedCiphertextError_1 = require("./CryptoMalformedCiphertextError");
6
+ // Parsing failures all collapse to CryptoMalformedCiphertextError: anything
7
+ // that is not a well-formed v1 blob is corruption as far as the caller cares.
8
+ const decodeCryptoBlob = (ciphertext) => {
9
+ if (!ciphertext.startsWith(cryptoBlobPrefix_1.CRYPTO_BLOB_PREFIX_V1)) {
10
+ throw new CryptoMalformedCiphertextError_1.CryptoMalformedCiphertextError('Ciphertext is not a qpq crypto blob (missing/unknown version prefix)');
11
+ }
12
+ let fields;
13
+ try {
14
+ fields = JSON.parse(Buffer.from(ciphertext.slice(cryptoBlobPrefix_1.CRYPTO_BLOB_PREFIX_V1.length), 'base64').toString('utf8'));
15
+ }
16
+ catch (_a) {
17
+ throw new CryptoMalformedCiphertextError_1.CryptoMalformedCiphertextError('Ciphertext blob payload is not decodable');
18
+ }
19
+ const { k, iv, tg, ct, ch } = fields;
20
+ if (typeof k !== 'string' || typeof iv !== 'string' || typeof tg !== 'string' || typeof ct !== 'string' || typeof ch !== 'string') {
21
+ throw new CryptoMalformedCiphertextError_1.CryptoMalformedCiphertextError('Ciphertext blob is missing required fields');
22
+ }
23
+ return {
24
+ wrappedKey: Buffer.from(k, 'base64'),
25
+ iv: Buffer.from(iv, 'base64'),
26
+ authTag: Buffer.from(tg, 'base64'),
27
+ ciphertext: Buffer.from(ct, 'base64'),
28
+ contextHash: ch,
29
+ };
30
+ };
31
+ exports.decodeCryptoBlob = decodeCryptoBlob;
32
+ //# sourceMappingURL=decodeCryptoBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeCryptoBlob.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/decodeCryptoBlob.ts"],"names":[],"mappings":";;;AACA,yDAA2D;AAC3D,qFAAkF;AAElF,4EAA4E;AAC5E,8EAA8E;AACvE,MAAM,gBAAgB,GAAG,CAAC,UAAkB,EAAc,EAAE;IACjE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,wCAAqB,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,+DAA8B,CAAC,sEAAsE,CAAC,CAAC;IACnH,CAAC;IAED,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,wCAAqB,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9G,CAAC;IAAC,WAAM,CAAC;QACP,MAAM,IAAI,+DAA8B,CAAC,0CAA0C,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;IACrC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAClI,MAAM,IAAI,+DAA8B,CAAC,4CAA4C,CAAC,CAAC;IACzF,CAAC;IAED,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;QACpC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC;QAC7B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC;QAClC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC;QACrC,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC,CAAC;AAxBW,QAAA,gBAAgB,oBAwB3B","sourcesContent":["import { CryptoBlob } from './CryptoBlob';\nimport { CRYPTO_BLOB_PREFIX_V1 } from './cryptoBlobPrefix';\nimport { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';\n\n// Parsing failures all collapse to CryptoMalformedCiphertextError: anything\n// that is not a well-formed v1 blob is corruption as far as the caller cares.\nexport const decodeCryptoBlob = (ciphertext: string): CryptoBlob => {\n if (!ciphertext.startsWith(CRYPTO_BLOB_PREFIX_V1)) {\n throw new CryptoMalformedCiphertextError('Ciphertext is not a qpq crypto blob (missing/unknown version prefix)');\n }\n\n let fields: Record<string, unknown>;\n try {\n fields = JSON.parse(Buffer.from(ciphertext.slice(CRYPTO_BLOB_PREFIX_V1.length), 'base64').toString('utf8'));\n } catch {\n throw new CryptoMalformedCiphertextError('Ciphertext blob payload is not decodable');\n }\n\n const { k, iv, tg, ct, ch } = fields;\n if (typeof k !== 'string' || typeof iv !== 'string' || typeof tg !== 'string' || typeof ct !== 'string' || typeof ch !== 'string') {\n throw new CryptoMalformedCiphertextError('Ciphertext blob is missing required fields');\n }\n\n return {\n wrappedKey: Buffer.from(k, 'base64'),\n iv: Buffer.from(iv, 'base64'),\n authTag: Buffer.from(tg, 'base64'),\n ciphertext: Buffer.from(ct, 'base64'),\n contextHash: ch,\n };\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { CryptoBlob } from './CryptoBlob';
2
+ export declare const encodeCryptoBlob: (blob: CryptoBlob) => string;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeCryptoBlob = void 0;
4
+ const cryptoBlobPrefix_1 = require("./cryptoBlobPrefix");
5
+ const encodeCryptoBlob = (blob) => {
6
+ const json = JSON.stringify({
7
+ k: blob.wrappedKey.toString('base64'),
8
+ iv: blob.iv.toString('base64'),
9
+ tg: blob.authTag.toString('base64'),
10
+ ct: blob.ciphertext.toString('base64'),
11
+ ch: blob.contextHash,
12
+ });
13
+ return `${cryptoBlobPrefix_1.CRYPTO_BLOB_PREFIX_V1}${Buffer.from(json, 'utf8').toString('base64')}`;
14
+ };
15
+ exports.encodeCryptoBlob = encodeCryptoBlob;
16
+ //# sourceMappingURL=encodeCryptoBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encodeCryptoBlob.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/encodeCryptoBlob.ts"],"names":[],"mappings":";;;AACA,yDAA2D;AAEpD,MAAM,gBAAgB,GAAG,CAAC,IAAgB,EAAU,EAAE;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC9B,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACnC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACtC,EAAE,EAAE,IAAI,CAAC,WAAW;KACrB,CAAC,CAAC;IAEH,OAAO,GAAG,wCAAqB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AACnF,CAAC,CAAC;AAVW,QAAA,gBAAgB,oBAU3B","sourcesContent":["import { CryptoBlob } from './CryptoBlob';\nimport { CRYPTO_BLOB_PREFIX_V1 } from './cryptoBlobPrefix';\n\nexport const encodeCryptoBlob = (blob: CryptoBlob): string => {\n const json = JSON.stringify({\n k: blob.wrappedKey.toString('base64'),\n iv: blob.iv.toString('base64'),\n tg: blob.authTag.toString('base64'),\n ct: blob.ciphertext.toString('base64'),\n ch: blob.contextHash,\n });\n\n return `${CRYPTO_BLOB_PREFIX_V1}${Buffer.from(json, 'utf8').toString('base64')}`;\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ import { DataKeyProvider } from './DataKeyProvider';
3
+ export declare const envelopeDecrypt: (ciphertext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider) => Promise<string>;
@@ -0,0 +1,42 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.envelopeDecrypt = void 0;
13
+ const crypto_1 = require("crypto");
14
+ const canonicalizeCryptoContext_1 = require("./canonicalizeCryptoContext");
15
+ const cryptoContextHash_1 = require("./cryptoContextHash");
16
+ const CryptoContextMismatchError_1 = require("./CryptoContextMismatchError");
17
+ const CryptoMalformedCiphertextError_1 = require("./CryptoMalformedCiphertextError");
18
+ const decodeCryptoBlob_1 = require("./decodeCryptoBlob");
19
+ const envelopeDecrypt = (ciphertext, context, dataKeyProvider) => __awaiter(void 0, void 0, void 0, function* () {
20
+ const blob = (0, decodeCryptoBlob_1.decodeCryptoBlob)(ciphertext);
21
+ // Context is checked against the stored hash BEFORE any key unwrap or GCM
22
+ // work: a mismatch fails as ContextMismatch (probable scoping bug) rather
23
+ // than a bare tag failure that reads as corruption.
24
+ const canonicalContext = (0, canonicalizeCryptoContext_1.canonicalizeCryptoContext)(context);
25
+ if (blob.contextHash !== (0, cryptoContextHash_1.cryptoContextHash)(canonicalContext)) {
26
+ throw new CryptoContextMismatchError_1.CryptoContextMismatchError('Supplied crypto context does not match the context this value was encrypted under');
27
+ }
28
+ const plaintextKey = yield dataKeyProvider.unwrapDataKey(blob.wrappedKey, context || {});
29
+ const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', plaintextKey, blob.iv);
30
+ decipher.setAAD(Buffer.from(canonicalContext, 'utf8'));
31
+ decipher.setAuthTag(blob.authTag);
32
+ // Context hash already matched, so a GCM auth failure here means the
33
+ // ciphertext bytes themselves are corrupt or truncated.
34
+ try {
35
+ return Buffer.concat([decipher.update(blob.ciphertext), decipher.final()]).toString('utf8');
36
+ }
37
+ catch (_a) {
38
+ throw new CryptoMalformedCiphertextError_1.CryptoMalformedCiphertextError('Ciphertext failed authenticated decryption; the stored value is corrupt or truncated');
39
+ }
40
+ });
41
+ exports.envelopeDecrypt = envelopeDecrypt;
42
+ //# sourceMappingURL=envelopeDecrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelopeDecrypt.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/envelopeDecrypt.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,mCAA0C;AAE1C,2EAAwE;AACxE,2DAAwD;AACxD,6EAA0E;AAC1E,qFAAkF;AAElF,yDAAsD;AAE/C,MAAM,eAAe,GAAG,CAAO,UAAkB,EAAE,OAAkC,EAAE,eAAgC,EAAmB,EAAE;IACjJ,MAAM,IAAI,GAAG,IAAA,mCAAgB,EAAC,UAAU,CAAC,CAAC;IAE1C,0EAA0E;IAC1E,0EAA0E;IAC1E,oDAAoD;IACpD,MAAM,gBAAgB,GAAG,IAAA,qDAAyB,EAAC,OAAO,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,WAAW,KAAK,IAAA,qCAAiB,EAAC,gBAAgB,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,uDAA0B,CAAC,mFAAmF,CAAC,CAAC;IAC5H,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAEzF,MAAM,QAAQ,GAAG,IAAA,yBAAgB,EAAC,aAAa,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACxE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAElC,qEAAqE;IACrE,wDAAwD;IACxD,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC;IAAC,WAAM,CAAC;QACP,MAAM,IAAI,+DAA8B,CAAC,sFAAsF,CAAC,CAAC;IACnI,CAAC;AACH,CAAC,CAAA,CAAC;AAxBW,QAAA,eAAe,mBAwB1B","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\nimport { createDecipheriv } from 'crypto';\n\nimport { canonicalizeCryptoContext } from './canonicalizeCryptoContext';\nimport { cryptoContextHash } from './cryptoContextHash';\nimport { CryptoContextMismatchError } from './CryptoContextMismatchError';\nimport { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';\nimport { DataKeyProvider } from './DataKeyProvider';\nimport { decodeCryptoBlob } from './decodeCryptoBlob';\n\nexport const envelopeDecrypt = async (ciphertext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider): Promise<string> => {\n const blob = decodeCryptoBlob(ciphertext);\n\n // Context is checked against the stored hash BEFORE any key unwrap or GCM\n // work: a mismatch fails as ContextMismatch (probable scoping bug) rather\n // than a bare tag failure that reads as corruption.\n const canonicalContext = canonicalizeCryptoContext(context);\n if (blob.contextHash !== cryptoContextHash(canonicalContext)) {\n throw new CryptoContextMismatchError('Supplied crypto context does not match the context this value was encrypted under');\n }\n\n const plaintextKey = await dataKeyProvider.unwrapDataKey(blob.wrappedKey, context || {});\n\n const decipher = createDecipheriv('aes-256-gcm', plaintextKey, blob.iv);\n decipher.setAAD(Buffer.from(canonicalContext, 'utf8'));\n decipher.setAuthTag(blob.authTag);\n\n // Context hash already matched, so a GCM auth failure here means the\n // ciphertext bytes themselves are corrupt or truncated.\n try {\n return Buffer.concat([decipher.update(blob.ciphertext), decipher.final()]).toString('utf8');\n } catch {\n throw new CryptoMalformedCiphertextError('Ciphertext failed authenticated decryption; the stored value is corrupt or truncated');\n }\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ import { DataKeyProvider } from './DataKeyProvider';
3
+ export declare const envelopeEncrypt: (plaintext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider) => Promise<string>;
@@ -0,0 +1,35 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.envelopeEncrypt = void 0;
13
+ const crypto_1 = require("crypto");
14
+ const canonicalizeCryptoContext_1 = require("./canonicalizeCryptoContext");
15
+ const cryptoContextHash_1 = require("./cryptoContextHash");
16
+ const encodeCryptoBlob_1 = require("./encodeCryptoBlob");
17
+ // Envelope encryption: a fresh data key from the provider, AES-256-GCM locally
18
+ // with the canonical context as AAD, wrapped key stored inside the blob.
19
+ const envelopeEncrypt = (plaintext, context, dataKeyProvider) => __awaiter(void 0, void 0, void 0, function* () {
20
+ const canonicalContext = (0, canonicalizeCryptoContext_1.canonicalizeCryptoContext)(context);
21
+ const { plaintextKey, wrappedKey } = yield dataKeyProvider.generateDataKey(context || {});
22
+ const iv = (0, crypto_1.randomBytes)(12);
23
+ const cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', plaintextKey, iv);
24
+ cipher.setAAD(Buffer.from(canonicalContext, 'utf8'));
25
+ const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
26
+ return (0, encodeCryptoBlob_1.encodeCryptoBlob)({
27
+ wrappedKey,
28
+ iv,
29
+ authTag: cipher.getAuthTag(),
30
+ ciphertext,
31
+ contextHash: (0, cryptoContextHash_1.cryptoContextHash)(canonicalContext),
32
+ });
33
+ });
34
+ exports.envelopeEncrypt = envelopeEncrypt;
35
+ //# sourceMappingURL=envelopeEncrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelopeEncrypt.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/envelopeEncrypt.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,mCAAqD;AAErD,2EAAwE;AACxE,2DAAwD;AAExD,yDAAsD;AAEtD,+EAA+E;AAC/E,yEAAyE;AAClE,MAAM,eAAe,GAAG,CAAO,SAAiB,EAAE,OAAkC,EAAE,eAAgC,EAAmB,EAAE;IAChJ,MAAM,gBAAgB,GAAG,IAAA,qDAAyB,EAAC,OAAO,CAAC,CAAC;IAE5D,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,eAAe,CAAC,eAAe,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAE1F,MAAM,EAAE,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAA,uBAAc,EAAC,aAAa,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IAErD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAErF,OAAO,IAAA,mCAAgB,EAAC;QACtB,UAAU;QACV,EAAE;QACF,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE;QAC5B,UAAU;QACV,WAAW,EAAE,IAAA,qCAAiB,EAAC,gBAAgB,CAAC;KACjD,CAAC,CAAC;AACL,CAAC,CAAA,CAAC;AAlBW,QAAA,eAAe,mBAkB1B","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\nimport { createCipheriv, randomBytes } from 'crypto';\n\nimport { canonicalizeCryptoContext } from './canonicalizeCryptoContext';\nimport { cryptoContextHash } from './cryptoContextHash';\nimport { DataKeyProvider } from './DataKeyProvider';\nimport { encodeCryptoBlob } from './encodeCryptoBlob';\n\n// Envelope encryption: a fresh data key from the provider, AES-256-GCM locally\n// with the canonical context as AAD, wrapped key stored inside the blob.\nexport const envelopeEncrypt = async (plaintext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider): Promise<string> => {\n const canonicalContext = canonicalizeCryptoContext(context);\n\n const { plaintextKey, wrappedKey } = await dataKeyProvider.generateDataKey(context || {});\n\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', plaintextKey, iv);\n cipher.setAAD(Buffer.from(canonicalContext, 'utf8'));\n\n const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n\n return encodeCryptoBlob({\n wrappedKey,\n iv,\n authTag: cipher.getAuthTag(),\n ciphertext,\n contextHash: cryptoContextHash(canonicalContext),\n });\n};\n"]}
@@ -0,0 +1,12 @@
1
+ export * from './canonicalizeCryptoContext';
2
+ export * from './createLocalMasterKeyDataKeyProvider';
3
+ export * from './CryptoBlob';
4
+ export * from './cryptoBlobPrefix';
5
+ export * from './cryptoContextHash';
6
+ export * from './CryptoContextMismatchError';
7
+ export * from './CryptoMalformedCiphertextError';
8
+ export * from './DataKeyProvider';
9
+ export * from './decodeCryptoBlob';
10
+ export * from './encodeCryptoBlob';
11
+ export * from './envelopeDecrypt';
12
+ export * from './envelopeEncrypt';
@@ -0,0 +1,29 @@
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("./canonicalizeCryptoContext"), exports);
18
+ __exportStar(require("./createLocalMasterKeyDataKeyProvider"), exports);
19
+ __exportStar(require("./CryptoBlob"), exports);
20
+ __exportStar(require("./cryptoBlobPrefix"), exports);
21
+ __exportStar(require("./cryptoContextHash"), exports);
22
+ __exportStar(require("./CryptoContextMismatchError"), exports);
23
+ __exportStar(require("./CryptoMalformedCiphertextError"), exports);
24
+ __exportStar(require("./DataKeyProvider"), exports);
25
+ __exportStar(require("./decodeCryptoBlob"), exports);
26
+ __exportStar(require("./encodeCryptoBlob"), exports);
27
+ __exportStar(require("./envelopeDecrypt"), exports);
28
+ __exportStar(require("./envelopeEncrypt"), exports);
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8DAA4C;AAC5C,wEAAsD;AACtD,+CAA6B;AAC7B,qDAAmC;AACnC,sDAAoC;AACpC,+DAA6C;AAC7C,mEAAiD;AACjD,oDAAkC;AAClC,qDAAmC;AACnC,qDAAmC;AACnC,oDAAkC;AAClC,oDAAkC","sourcesContent":["export * from './canonicalizeCryptoContext';\nexport * from './createLocalMasterKeyDataKeyProvider';\nexport * from './CryptoBlob';\nexport * from './cryptoBlobPrefix';\nexport * from './cryptoContextHash';\nexport * from './CryptoContextMismatchError';\nexport * from './CryptoMalformedCiphertextError';\nexport * from './DataKeyProvider';\nexport * from './decodeCryptoBlob';\nexport * from './encodeCryptoBlob';\nexport * from './envelopeDecrypt';\nexport * from './envelopeEncrypt';\n"]}
@@ -0,0 +1 @@
1
+ export * from './cryptoEnvelope';
@@ -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("./cryptoEnvelope"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/logic/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAiC","sourcesContent":["export * from './cryptoEnvelope';\n"]}
@@ -1,3 +1,4 @@
1
1
  export * from './actionProcessor';
2
2
  export * from './dynamicActionProcessor';
3
+ export * from './logic';
3
4
  export * from './traceStoryExecution';
package/lib/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './actionProcessor';
2
2
  export * from './dynamicActionProcessor';
3
+ export * from './logic';
3
4
  export * from './traceStoryExecution';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC","sourcesContent":["export * from './actionProcessor';\nexport * from './dynamicActionProcessor';\nexport * from './traceStoryExecution';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC","sourcesContent":["export * from './actionProcessor';\nexport * from './dynamicActionProcessor';\nexport * from './logic';\nexport * from './traceStoryExecution';\n"]}
@@ -0,0 +1,7 @@
1
+ export type CryptoBlob = {
2
+ wrappedKey: Buffer;
3
+ iv: Buffer;
4
+ authTag: Buffer;
5
+ ciphertext: Buffer;
6
+ contextHash: string;
7
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=CryptoBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CryptoBlob.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/CryptoBlob.ts"],"names":[],"mappings":"","sourcesContent":["// Decoded form of a qpqcrypto:v1 ciphertext blob.\nexport type CryptoBlob = {\n wrappedKey: Buffer;\n iv: Buffer;\n authTag: Buffer;\n ciphertext: Buffer;\n contextHash: string;\n};\n"]}
@@ -0,0 +1,4 @@
1
+ export declare class CryptoContextMismatchError extends Error {
2
+ readonly code = "QpqCryptoContextMismatch";
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,11 @@
1
+ // Thrown when the context supplied at decrypt differs from the context the
2
+ // blob was encrypted under. The `code` is what actionResultErrorFromCaughtError
3
+ // keys on, so processors can map this to a distinguishable action error.
4
+ export class CryptoContextMismatchError extends Error {
5
+ code = 'QpqCryptoContextMismatch';
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'CryptoContextMismatchError';
9
+ }
10
+ }
11
+ //# sourceMappingURL=CryptoContextMismatchError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CryptoContextMismatchError.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/CryptoContextMismatchError.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,gFAAgF;AAChF,yEAAyE;AACzE,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IAC1C,IAAI,GAAG,0BAA0B,CAAC;IAE3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF","sourcesContent":["// Thrown when the context supplied at decrypt differs from the context the\n// blob was encrypted under. The `code` is what actionResultErrorFromCaughtError\n// keys on, so processors can map this to a distinguishable action error.\nexport class CryptoContextMismatchError extends Error {\n readonly code = 'QpqCryptoContextMismatch';\n\n constructor(message: string) {\n super(message);\n this.name = 'CryptoContextMismatchError';\n }\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export declare class CryptoMalformedCiphertextError extends Error {
2
+ readonly code = "QpqCryptoMalformedCiphertext";
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,11 @@
1
+ // Thrown when a ciphertext is not a valid qpq crypto blob, or fails
2
+ // authenticated decryption with the correct context (i.e. corruption or
3
+ // truncation). The `code` is what actionResultErrorFromCaughtError keys on.
4
+ export class CryptoMalformedCiphertextError extends Error {
5
+ code = 'QpqCryptoMalformedCiphertext';
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'CryptoMalformedCiphertextError';
9
+ }
10
+ }
11
+ //# sourceMappingURL=CryptoMalformedCiphertextError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CryptoMalformedCiphertextError.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/CryptoMalformedCiphertextError.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,wEAAwE;AACxE,4EAA4E;AAC5E,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IAC9C,IAAI,GAAG,8BAA8B,CAAC;IAE/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF","sourcesContent":["// Thrown when a ciphertext is not a valid qpq crypto blob, or fails\n// authenticated decryption with the correct context (i.e. corruption or\n// truncation). The `code` is what actionResultErrorFromCaughtError keys on.\nexport class CryptoMalformedCiphertextError extends Error {\n readonly code = 'QpqCryptoMalformedCiphertext';\n\n constructor(message: string) {\n super(message);\n this.name = 'CryptoMalformedCiphertextError';\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ export type GeneratedDataKey = {
3
+ plaintextKey: Buffer;
4
+ wrappedKey: Buffer;
5
+ };
6
+ export type DataKeyProvider = {
7
+ generateDataKey: (context: CryptoContext) => Promise<GeneratedDataKey>;
8
+ unwrapDataKey: (wrappedKey: Buffer, context: CryptoContext) => Promise<Buffer>;
9
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DataKeyProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataKeyProvider.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/DataKeyProvider.ts"],"names":[],"mappings":"","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\nexport type GeneratedDataKey = {\n plaintextKey: Buffer;\n wrappedKey: Buffer;\n};\n\n// The only part of envelope encryption that differs per runtime: how a\n// one-off data key is created and wrapped (KMS in prod, a local master key in\n// the dev server). The context is passed through so the wrapping layer can\n// bind it too (KMS EncryptionContext, which also lands in CloudTrail).\nexport type DataKeyProvider = {\n generateDataKey: (context: CryptoContext) => Promise<GeneratedDataKey>;\n unwrapDataKey: (wrappedKey: Buffer, context: CryptoContext) => Promise<Buffer>;\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ export declare const canonicalizeCryptoContext: (context?: CryptoContext) => string;
@@ -0,0 +1,14 @@
1
+ // Netstring-style encoding (byteLength:value) so { ab: 'c' } and { a: 'bc' }
2
+ // cannot canonicalize to the same bytes.
3
+ const encodePart = (part) => `${Buffer.byteLength(part, 'utf8')}:${part}`;
4
+ // Deterministic byte encoding of a crypto context, used as the AAD for
5
+ // envelope encryption. Keys are sorted by code unit (NOT localeCompare, which
6
+ // varies by environment). undefined and {} are equivalent: both canonicalize
7
+ // to the empty string. Every runtime (prod KMS, dev stub) MUST canonicalize
8
+ // through this one function, or dev and prod disagree about what a valid
9
+ // context is and it only surfaces after deploy.
10
+ export const canonicalizeCryptoContext = (context) => {
11
+ const sortedEntries = Object.entries(context || {}).sort(([keyA], [keyB]) => (keyA < keyB ? -1 : keyA > keyB ? 1 : 0));
12
+ return sortedEntries.map(([key, value]) => encodePart(key) + encodePart(value)).join('');
13
+ };
14
+ //# sourceMappingURL=canonicalizeCryptoContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalizeCryptoContext.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/canonicalizeCryptoContext.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,yCAAyC;AACzC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;AAE1F,uEAAuE;AACvE,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,yEAAyE;AACzE,gDAAgD;AAChD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,OAAuB,EAAU,EAAE;IAC3E,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvH,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3F,CAAC,CAAC","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\n// Netstring-style encoding (byteLength:value) so { ab: 'c' } and { a: 'bc' }\n// cannot canonicalize to the same bytes.\nconst encodePart = (part: string): string => `${Buffer.byteLength(part, 'utf8')}:${part}`;\n\n// Deterministic byte encoding of a crypto context, used as the AAD for\n// envelope encryption. Keys are sorted by code unit (NOT localeCompare, which\n// varies by environment). undefined and {} are equivalent: both canonicalize\n// to the empty string. Every runtime (prod KMS, dev stub) MUST canonicalize\n// through this one function, or dev and prod disagree about what a valid\n// context is and it only surfaces after deploy.\nexport const canonicalizeCryptoContext = (context?: CryptoContext): string => {\n const sortedEntries = Object.entries(context || {}).sort(([keyA], [keyB]) => (keyA < keyB ? -1 : keyA > keyB ? 1 : 0));\n\n return sortedEntries.map(([key, value]) => encodePart(key) + encodePart(value)).join('');\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { DataKeyProvider } from './DataKeyProvider';
2
+ export declare const createLocalMasterKeyDataKeyProvider: (masterKey: Buffer) => DataKeyProvider;
@@ -0,0 +1,35 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
2
+ import { canonicalizeCryptoContext } from './canonicalizeCryptoContext';
3
+ import { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';
4
+ // A DataKeyProvider that wraps data keys with AES-256-GCM under a local
5
+ // 32-byte master key, with the canonical context as AAD (mirroring KMS
6
+ // EncryptionContext binding). Used by the dev server so envelope encryption
7
+ // runs the identical code path offline. Wrapped key layout: iv(12) tag(16) key.
8
+ export const createLocalMasterKeyDataKeyProvider = (masterKey) => ({
9
+ generateDataKey: async (context) => {
10
+ const plaintextKey = randomBytes(32);
11
+ const iv = randomBytes(12);
12
+ const cipher = createCipheriv('aes-256-gcm', masterKey, iv);
13
+ cipher.setAAD(Buffer.from(canonicalizeCryptoContext(context), 'utf8'));
14
+ const wrappedKeyBytes = Buffer.concat([cipher.update(plaintextKey), cipher.final()]);
15
+ return {
16
+ plaintextKey,
17
+ wrappedKey: Buffer.concat([iv, cipher.getAuthTag(), wrappedKeyBytes]),
18
+ };
19
+ },
20
+ unwrapDataKey: async (wrappedKey, context) => {
21
+ if (wrappedKey.length < 28) {
22
+ throw new CryptoMalformedCiphertextError('Wrapped data key is truncated');
23
+ }
24
+ const decipher = createDecipheriv('aes-256-gcm', masterKey, wrappedKey.subarray(0, 12));
25
+ decipher.setAAD(Buffer.from(canonicalizeCryptoContext(context), 'utf8'));
26
+ decipher.setAuthTag(wrappedKey.subarray(12, 28));
27
+ try {
28
+ return Buffer.concat([decipher.update(wrappedKey.subarray(28)), decipher.final()]);
29
+ }
30
+ catch {
31
+ throw new CryptoMalformedCiphertextError('Wrapped data key failed authenticated decryption');
32
+ }
33
+ },
34
+ });
35
+ //# sourceMappingURL=createLocalMasterKeyDataKeyProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createLocalMasterKeyDataKeyProvider.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/createLocalMasterKeyDataKeyProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAEvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAGlF,wEAAwE;AACxE,uEAAuE;AACvE,4EAA4E;AAC5E,gFAAgF;AAChF,MAAM,CAAC,MAAM,mCAAmC,GAAG,CAAC,SAAiB,EAAmB,EAAE,CAAC,CAAC;IAC1F,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACjC,MAAM,YAAY,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAErC,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAEvE,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAErF,OAAO;YACL,YAAY;YACZ,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,eAAe,CAAC,CAAC;SACtE,CAAC;IACJ,CAAC;IAED,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE;QAC3C,IAAI,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAC3B,MAAM,IAAI,8BAA8B,CAAC,+BAA+B,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACxF,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACzE,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAEjD,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,8BAA8B,CAAC,kDAAkD,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';\n\nimport { canonicalizeCryptoContext } from './canonicalizeCryptoContext';\nimport { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';\nimport { DataKeyProvider } from './DataKeyProvider';\n\n// A DataKeyProvider that wraps data keys with AES-256-GCM under a local\n// 32-byte master key, with the canonical context as AAD (mirroring KMS\n// EncryptionContext binding). Used by the dev server so envelope encryption\n// runs the identical code path offline. Wrapped key layout: iv(12) tag(16) key.\nexport const createLocalMasterKeyDataKeyProvider = (masterKey: Buffer): DataKeyProvider => ({\n generateDataKey: async (context) => {\n const plaintextKey = randomBytes(32);\n\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', masterKey, iv);\n cipher.setAAD(Buffer.from(canonicalizeCryptoContext(context), 'utf8'));\n\n const wrappedKeyBytes = Buffer.concat([cipher.update(plaintextKey), cipher.final()]);\n\n return {\n plaintextKey,\n wrappedKey: Buffer.concat([iv, cipher.getAuthTag(), wrappedKeyBytes]),\n };\n },\n\n unwrapDataKey: async (wrappedKey, context) => {\n if (wrappedKey.length < 28) {\n throw new CryptoMalformedCiphertextError('Wrapped data key is truncated');\n }\n\n const decipher = createDecipheriv('aes-256-gcm', masterKey, wrappedKey.subarray(0, 12));\n decipher.setAAD(Buffer.from(canonicalizeCryptoContext(context), 'utf8'));\n decipher.setAuthTag(wrappedKey.subarray(12, 28));\n\n try {\n return Buffer.concat([decipher.update(wrappedKey.subarray(28)), decipher.final()]);\n } catch {\n throw new CryptoMalformedCiphertextError('Wrapped data key failed authenticated decryption');\n }\n },\n});\n"]}
@@ -0,0 +1 @@
1
+ export declare const CRYPTO_BLOB_PREFIX_V1 = "qpqcrypto:v1:";
@@ -0,0 +1,5 @@
1
+ // Version prefix on every ciphertext this capability produces. Bump the
2
+ // version (qpqcrypto:v2:...) to change the blob format; decrypt dispatches on
3
+ // the prefix, so old blobs keep working without a data migration.
4
+ export const CRYPTO_BLOB_PREFIX_V1 = 'qpqcrypto:v1:';
5
+ //# sourceMappingURL=cryptoBlobPrefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptoBlobPrefix.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/cryptoBlobPrefix.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,8EAA8E;AAC9E,kEAAkE;AAClE,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC","sourcesContent":["// Version prefix on every ciphertext this capability produces. Bump the\n// version (qpqcrypto:v2:...) to change the blob format; decrypt dispatches on\n// the prefix, so old blobs keep working without a data migration.\nexport const CRYPTO_BLOB_PREFIX_V1 = 'qpqcrypto:v1:';\n"]}
@@ -0,0 +1 @@
1
+ export declare const cryptoContextHash: (canonicalContext: string) => string;
@@ -0,0 +1,9 @@
1
+ import { createHash } from 'crypto';
2
+ // Hash of the canonical context stored inside the crypto blob. Lets decrypt
3
+ // distinguish "wrong context supplied" (hash differs) from "corrupt
4
+ // ciphertext" (hash matches, GCM tag fails). The context is not secret, so
5
+ // storing its hash leaks nothing new.
6
+ export const cryptoContextHash = (canonicalContext) => {
7
+ return createHash('sha256').update(canonicalContext, 'utf8').digest('base64');
8
+ };
9
+ //# sourceMappingURL=cryptoContextHash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptoContextHash.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/cryptoContextHash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,4EAA4E;AAC5E,oEAAoE;AACpE,2EAA2E;AAC3E,sCAAsC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,gBAAwB,EAAU,EAAE;IACpE,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAChF,CAAC,CAAC","sourcesContent":["import { createHash } from 'crypto';\n\n// Hash of the canonical context stored inside the crypto blob. Lets decrypt\n// distinguish \"wrong context supplied\" (hash differs) from \"corrupt\n// ciphertext\" (hash matches, GCM tag fails). The context is not secret, so\n// storing its hash leaks nothing new.\nexport const cryptoContextHash = (canonicalContext: string): string => {\n return createHash('sha256').update(canonicalContext, 'utf8').digest('base64');\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { CryptoBlob } from './CryptoBlob';
2
+ export declare const decodeCryptoBlob: (ciphertext: string) => CryptoBlob;
@@ -0,0 +1,28 @@
1
+ import { CRYPTO_BLOB_PREFIX_V1 } from './cryptoBlobPrefix';
2
+ import { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';
3
+ // Parsing failures all collapse to CryptoMalformedCiphertextError: anything
4
+ // that is not a well-formed v1 blob is corruption as far as the caller cares.
5
+ export const decodeCryptoBlob = (ciphertext) => {
6
+ if (!ciphertext.startsWith(CRYPTO_BLOB_PREFIX_V1)) {
7
+ throw new CryptoMalformedCiphertextError('Ciphertext is not a qpq crypto blob (missing/unknown version prefix)');
8
+ }
9
+ let fields;
10
+ try {
11
+ fields = JSON.parse(Buffer.from(ciphertext.slice(CRYPTO_BLOB_PREFIX_V1.length), 'base64').toString('utf8'));
12
+ }
13
+ catch {
14
+ throw new CryptoMalformedCiphertextError('Ciphertext blob payload is not decodable');
15
+ }
16
+ const { k, iv, tg, ct, ch } = fields;
17
+ if (typeof k !== 'string' || typeof iv !== 'string' || typeof tg !== 'string' || typeof ct !== 'string' || typeof ch !== 'string') {
18
+ throw new CryptoMalformedCiphertextError('Ciphertext blob is missing required fields');
19
+ }
20
+ return {
21
+ wrappedKey: Buffer.from(k, 'base64'),
22
+ iv: Buffer.from(iv, 'base64'),
23
+ authTag: Buffer.from(tg, 'base64'),
24
+ ciphertext: Buffer.from(ct, 'base64'),
25
+ contextHash: ch,
26
+ };
27
+ };
28
+ //# sourceMappingURL=decodeCryptoBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeCryptoBlob.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/decodeCryptoBlob.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF,4EAA4E;AAC5E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,UAAkB,EAAc,EAAE;IACjE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,8BAA8B,CAAC,sEAAsE,CAAC,CAAC;IACnH,CAAC;IAED,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9G,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,8BAA8B,CAAC,0CAA0C,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;IACrC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAClI,MAAM,IAAI,8BAA8B,CAAC,4CAA4C,CAAC,CAAC;IACzF,CAAC;IAED,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;QACpC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC;QAC7B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC;QAClC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC;QACrC,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { CryptoBlob } from './CryptoBlob';\nimport { CRYPTO_BLOB_PREFIX_V1 } from './cryptoBlobPrefix';\nimport { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';\n\n// Parsing failures all collapse to CryptoMalformedCiphertextError: anything\n// that is not a well-formed v1 blob is corruption as far as the caller cares.\nexport const decodeCryptoBlob = (ciphertext: string): CryptoBlob => {\n if (!ciphertext.startsWith(CRYPTO_BLOB_PREFIX_V1)) {\n throw new CryptoMalformedCiphertextError('Ciphertext is not a qpq crypto blob (missing/unknown version prefix)');\n }\n\n let fields: Record<string, unknown>;\n try {\n fields = JSON.parse(Buffer.from(ciphertext.slice(CRYPTO_BLOB_PREFIX_V1.length), 'base64').toString('utf8'));\n } catch {\n throw new CryptoMalformedCiphertextError('Ciphertext blob payload is not decodable');\n }\n\n const { k, iv, tg, ct, ch } = fields;\n if (typeof k !== 'string' || typeof iv !== 'string' || typeof tg !== 'string' || typeof ct !== 'string' || typeof ch !== 'string') {\n throw new CryptoMalformedCiphertextError('Ciphertext blob is missing required fields');\n }\n\n return {\n wrappedKey: Buffer.from(k, 'base64'),\n iv: Buffer.from(iv, 'base64'),\n authTag: Buffer.from(tg, 'base64'),\n ciphertext: Buffer.from(ct, 'base64'),\n contextHash: ch,\n };\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { CryptoBlob } from './CryptoBlob';
2
+ export declare const encodeCryptoBlob: (blob: CryptoBlob) => string;
@@ -0,0 +1,12 @@
1
+ import { CRYPTO_BLOB_PREFIX_V1 } from './cryptoBlobPrefix';
2
+ export const encodeCryptoBlob = (blob) => {
3
+ const json = JSON.stringify({
4
+ k: blob.wrappedKey.toString('base64'),
5
+ iv: blob.iv.toString('base64'),
6
+ tg: blob.authTag.toString('base64'),
7
+ ct: blob.ciphertext.toString('base64'),
8
+ ch: blob.contextHash,
9
+ });
10
+ return `${CRYPTO_BLOB_PREFIX_V1}${Buffer.from(json, 'utf8').toString('base64')}`;
11
+ };
12
+ //# sourceMappingURL=encodeCryptoBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encodeCryptoBlob.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/encodeCryptoBlob.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAgB,EAAU,EAAE;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC9B,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACnC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACtC,EAAE,EAAE,IAAI,CAAC,WAAW;KACrB,CAAC,CAAC;IAEH,OAAO,GAAG,qBAAqB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AACnF,CAAC,CAAC","sourcesContent":["import { CryptoBlob } from './CryptoBlob';\nimport { CRYPTO_BLOB_PREFIX_V1 } from './cryptoBlobPrefix';\n\nexport const encodeCryptoBlob = (blob: CryptoBlob): string => {\n const json = JSON.stringify({\n k: blob.wrappedKey.toString('base64'),\n iv: blob.iv.toString('base64'),\n tg: blob.authTag.toString('base64'),\n ct: blob.ciphertext.toString('base64'),\n ch: blob.contextHash,\n });\n\n return `${CRYPTO_BLOB_PREFIX_V1}${Buffer.from(json, 'utf8').toString('base64')}`;\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ import { DataKeyProvider } from './DataKeyProvider';
3
+ export declare const envelopeDecrypt: (ciphertext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider) => Promise<string>;
@@ -0,0 +1,29 @@
1
+ import { createDecipheriv } from 'crypto';
2
+ import { canonicalizeCryptoContext } from './canonicalizeCryptoContext';
3
+ import { cryptoContextHash } from './cryptoContextHash';
4
+ import { CryptoContextMismatchError } from './CryptoContextMismatchError';
5
+ import { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';
6
+ import { decodeCryptoBlob } from './decodeCryptoBlob';
7
+ export const envelopeDecrypt = async (ciphertext, context, dataKeyProvider) => {
8
+ const blob = decodeCryptoBlob(ciphertext);
9
+ // Context is checked against the stored hash BEFORE any key unwrap or GCM
10
+ // work: a mismatch fails as ContextMismatch (probable scoping bug) rather
11
+ // than a bare tag failure that reads as corruption.
12
+ const canonicalContext = canonicalizeCryptoContext(context);
13
+ if (blob.contextHash !== cryptoContextHash(canonicalContext)) {
14
+ throw new CryptoContextMismatchError('Supplied crypto context does not match the context this value was encrypted under');
15
+ }
16
+ const plaintextKey = await dataKeyProvider.unwrapDataKey(blob.wrappedKey, context || {});
17
+ const decipher = createDecipheriv('aes-256-gcm', plaintextKey, blob.iv);
18
+ decipher.setAAD(Buffer.from(canonicalContext, 'utf8'));
19
+ decipher.setAuthTag(blob.authTag);
20
+ // Context hash already matched, so a GCM auth failure here means the
21
+ // ciphertext bytes themselves are corrupt or truncated.
22
+ try {
23
+ return Buffer.concat([decipher.update(blob.ciphertext), decipher.final()]).toString('utf8');
24
+ }
25
+ catch {
26
+ throw new CryptoMalformedCiphertextError('Ciphertext failed authenticated decryption; the stored value is corrupt or truncated');
27
+ }
28
+ };
29
+ //# sourceMappingURL=envelopeDecrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelopeDecrypt.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/envelopeDecrypt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAE1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,UAAkB,EAAE,OAAkC,EAAE,eAAgC,EAAmB,EAAE;IACjJ,MAAM,IAAI,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE1C,0EAA0E;IAC1E,0EAA0E;IAC1E,oDAAoD;IACpD,MAAM,gBAAgB,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,WAAW,KAAK,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,0BAA0B,CAAC,mFAAmF,CAAC,CAAC;IAC5H,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAEzF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACxE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAElC,qEAAqE;IACrE,wDAAwD;IACxD,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,8BAA8B,CAAC,sFAAsF,CAAC,CAAC;IACnI,CAAC;AACH,CAAC,CAAC","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\nimport { createDecipheriv } from 'crypto';\n\nimport { canonicalizeCryptoContext } from './canonicalizeCryptoContext';\nimport { cryptoContextHash } from './cryptoContextHash';\nimport { CryptoContextMismatchError } from './CryptoContextMismatchError';\nimport { CryptoMalformedCiphertextError } from './CryptoMalformedCiphertextError';\nimport { DataKeyProvider } from './DataKeyProvider';\nimport { decodeCryptoBlob } from './decodeCryptoBlob';\n\nexport const envelopeDecrypt = async (ciphertext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider): Promise<string> => {\n const blob = decodeCryptoBlob(ciphertext);\n\n // Context is checked against the stored hash BEFORE any key unwrap or GCM\n // work: a mismatch fails as ContextMismatch (probable scoping bug) rather\n // than a bare tag failure that reads as corruption.\n const canonicalContext = canonicalizeCryptoContext(context);\n if (blob.contextHash !== cryptoContextHash(canonicalContext)) {\n throw new CryptoContextMismatchError('Supplied crypto context does not match the context this value was encrypted under');\n }\n\n const plaintextKey = await dataKeyProvider.unwrapDataKey(blob.wrappedKey, context || {});\n\n const decipher = createDecipheriv('aes-256-gcm', plaintextKey, blob.iv);\n decipher.setAAD(Buffer.from(canonicalContext, 'utf8'));\n decipher.setAuthTag(blob.authTag);\n\n // Context hash already matched, so a GCM auth failure here means the\n // ciphertext bytes themselves are corrupt or truncated.\n try {\n return Buffer.concat([decipher.update(blob.ciphertext), decipher.final()]).toString('utf8');\n } catch {\n throw new CryptoMalformedCiphertextError('Ciphertext failed authenticated decryption; the stored value is corrupt or truncated');\n }\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import { CryptoContext } from 'quidproquo-core';
2
+ import { DataKeyProvider } from './DataKeyProvider';
3
+ export declare const envelopeEncrypt: (plaintext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider) => Promise<string>;
@@ -0,0 +1,22 @@
1
+ import { createCipheriv, randomBytes } from 'crypto';
2
+ import { canonicalizeCryptoContext } from './canonicalizeCryptoContext';
3
+ import { cryptoContextHash } from './cryptoContextHash';
4
+ import { encodeCryptoBlob } from './encodeCryptoBlob';
5
+ // Envelope encryption: a fresh data key from the provider, AES-256-GCM locally
6
+ // with the canonical context as AAD, wrapped key stored inside the blob.
7
+ export const envelopeEncrypt = async (plaintext, context, dataKeyProvider) => {
8
+ const canonicalContext = canonicalizeCryptoContext(context);
9
+ const { plaintextKey, wrappedKey } = await dataKeyProvider.generateDataKey(context || {});
10
+ const iv = randomBytes(12);
11
+ const cipher = createCipheriv('aes-256-gcm', plaintextKey, iv);
12
+ cipher.setAAD(Buffer.from(canonicalContext, 'utf8'));
13
+ const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
14
+ return encodeCryptoBlob({
15
+ wrappedKey,
16
+ iv,
17
+ authTag: cipher.getAuthTag(),
18
+ ciphertext,
19
+ contextHash: cryptoContextHash(canonicalContext),
20
+ });
21
+ };
22
+ //# sourceMappingURL=envelopeEncrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelopeEncrypt.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/envelopeEncrypt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErD,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,+EAA+E;AAC/E,yEAAyE;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,SAAiB,EAAE,OAAkC,EAAE,eAAgC,EAAmB,EAAE;IAChJ,MAAM,gBAAgB,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAE5D,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,eAAe,CAAC,eAAe,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAE1F,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IAErD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAErF,OAAO,gBAAgB,CAAC;QACtB,UAAU;QACV,EAAE;QACF,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE;QAC5B,UAAU;QACV,WAAW,EAAE,iBAAiB,CAAC,gBAAgB,CAAC;KACjD,CAAC,CAAC;AACL,CAAC,CAAC","sourcesContent":["import { CryptoContext } from 'quidproquo-core';\n\nimport { createCipheriv, randomBytes } from 'crypto';\n\nimport { canonicalizeCryptoContext } from './canonicalizeCryptoContext';\nimport { cryptoContextHash } from './cryptoContextHash';\nimport { DataKeyProvider } from './DataKeyProvider';\nimport { encodeCryptoBlob } from './encodeCryptoBlob';\n\n// Envelope encryption: a fresh data key from the provider, AES-256-GCM locally\n// with the canonical context as AAD, wrapped key stored inside the blob.\nexport const envelopeEncrypt = async (plaintext: string, context: CryptoContext | undefined, dataKeyProvider: DataKeyProvider): Promise<string> => {\n const canonicalContext = canonicalizeCryptoContext(context);\n\n const { plaintextKey, wrappedKey } = await dataKeyProvider.generateDataKey(context || {});\n\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', plaintextKey, iv);\n cipher.setAAD(Buffer.from(canonicalContext, 'utf8'));\n\n const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n\n return encodeCryptoBlob({\n wrappedKey,\n iv,\n authTag: cipher.getAuthTag(),\n ciphertext,\n contextHash: cryptoContextHash(canonicalContext),\n });\n};\n"]}
@@ -0,0 +1,12 @@
1
+ export * from './canonicalizeCryptoContext';
2
+ export * from './createLocalMasterKeyDataKeyProvider';
3
+ export * from './CryptoBlob';
4
+ export * from './cryptoBlobPrefix';
5
+ export * from './cryptoContextHash';
6
+ export * from './CryptoContextMismatchError';
7
+ export * from './CryptoMalformedCiphertextError';
8
+ export * from './DataKeyProvider';
9
+ export * from './decodeCryptoBlob';
10
+ export * from './encodeCryptoBlob';
11
+ export * from './envelopeDecrypt';
12
+ export * from './envelopeEncrypt';
@@ -0,0 +1,13 @@
1
+ export * from './canonicalizeCryptoContext';
2
+ export * from './createLocalMasterKeyDataKeyProvider';
3
+ export * from './CryptoBlob';
4
+ export * from './cryptoBlobPrefix';
5
+ export * from './cryptoContextHash';
6
+ export * from './CryptoContextMismatchError';
7
+ export * from './CryptoMalformedCiphertextError';
8
+ export * from './DataKeyProvider';
9
+ export * from './decodeCryptoBlob';
10
+ export * from './encodeCryptoBlob';
11
+ export * from './envelopeDecrypt';
12
+ export * from './envelopeEncrypt';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/logic/cryptoEnvelope/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC","sourcesContent":["export * from './canonicalizeCryptoContext';\nexport * from './createLocalMasterKeyDataKeyProvider';\nexport * from './CryptoBlob';\nexport * from './cryptoBlobPrefix';\nexport * from './cryptoContextHash';\nexport * from './CryptoContextMismatchError';\nexport * from './CryptoMalformedCiphertextError';\nexport * from './DataKeyProvider';\nexport * from './decodeCryptoBlob';\nexport * from './encodeCryptoBlob';\nexport * from './envelopeDecrypt';\nexport * from './envelopeEncrypt';\n"]}
@@ -0,0 +1 @@
1
+ export * from './cryptoEnvelope';
@@ -0,0 +1,2 @@
1
+ export * from './cryptoEnvelope';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/logic/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC","sourcesContent":["export * from './cryptoEnvelope';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quidproquo-actionprocessor-node",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -34,15 +34,15 @@
34
34
  "dependencies": {
35
35
  "@anthropic-ai/sdk": "^0.19.1",
36
36
  "@jridgewell/trace-mapping": "^0.3.25",
37
- "quidproquo-actionprocessor-js": "0.1.11",
38
- "quidproquo-core": "0.1.11",
39
- "quidproquo-webserver": "0.1.11",
37
+ "quidproquo-actionprocessor-js": "0.1.12",
38
+ "quidproquo-core": "0.1.12",
39
+ "quidproquo-webserver": "0.1.12",
40
40
  "uuidv7": "^1.0.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@jridgewell/gen-mapping": "^0.3.13",
44
44
  "@types/node": "^22.13.13",
45
- "quidproquo-tsconfig": "0.1.11",
45
+ "quidproquo-tsconfig": "0.1.12",
46
46
  "typescript": "^5.8.2"
47
47
  }
48
48
  }