@sebastienrousseau/crypto-server 0.0.9 → 0.0.24

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 (47) hide show
  1. package/dist/config/auth-policy.d.ts.map +1 -1
  2. package/dist/config/auth-policy.js +18 -1
  3. package/dist/config/constants.d.ts.map +1 -1
  4. package/dist/config/constants.js +7 -1
  5. package/dist/lib/key-store.d.ts +8 -1
  6. package/dist/lib/key-store.d.ts.map +1 -1
  7. package/dist/lib/key-store.js +102 -5
  8. package/dist/lib/opaque-store.d.ts +34 -0
  9. package/dist/lib/opaque-store.d.ts.map +1 -0
  10. package/dist/lib/opaque-store.js +72 -0
  11. package/dist/lib/problem.d.ts +1 -0
  12. package/dist/lib/problem.d.ts.map +1 -1
  13. package/dist/lib/problem.js +10 -0
  14. package/dist/routes/v1/decrypt.d.ts.map +1 -1
  15. package/dist/routes/v1/decrypt.js +4 -1
  16. package/dist/routes/v1/verify.d.ts.map +1 -1
  17. package/dist/routes/v1/verify.js +4 -1
  18. package/dist/routes/v2/hpke.d.ts +3 -0
  19. package/dist/routes/v2/hpke.d.ts.map +1 -0
  20. package/dist/routes/v2/hpke.js +168 -0
  21. package/dist/routes/v2/index.d.ts.map +1 -1
  22. package/dist/routes/v2/index.js +6 -0
  23. package/dist/routes/v2/kms.d.ts +7 -0
  24. package/dist/routes/v2/kms.d.ts.map +1 -0
  25. package/dist/routes/v2/kms.js +310 -0
  26. package/dist/routes/v2/mac.d.ts.map +1 -1
  27. package/dist/routes/v2/mac.js +1 -0
  28. package/dist/routes/v2/opaque.d.ts +4 -0
  29. package/dist/routes/v2/opaque.d.ts.map +1 -0
  30. package/dist/routes/v2/opaque.js +174 -0
  31. package/dist/routes/v2/pq-hash-sign.d.ts.map +1 -1
  32. package/dist/routes/v2/pq-hash-sign.js +3 -0
  33. package/dist/routes/v2/pq-sign.d.ts.map +1 -1
  34. package/dist/routes/v2/pq-sign.js +1 -0
  35. package/dist/routes/v2/signing.d.ts.map +1 -1
  36. package/dist/routes/v2/signing.js +1 -0
  37. package/dist/routes/v2/stream-schemas.d.ts +296 -0
  38. package/dist/routes/v2/stream-schemas.d.ts.map +1 -0
  39. package/dist/routes/v2/stream-schemas.js +207 -0
  40. package/dist/routes/v2/stream.d.ts.map +1 -1
  41. package/dist/routes/v2/stream.js +117 -129
  42. package/dist/server.d.ts.map +1 -1
  43. package/dist/server.js +4 -0
  44. package/dist/utils/trace.d.ts +12 -0
  45. package/dist/utils/trace.d.ts.map +1 -0
  46. package/dist/utils/trace.js +62 -0
  47. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"auth-policy.d.ts","sourceRoot":"","sources":["../../src/config/auth-policy.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,MAAM,WAAW,SAAS;IAMxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAEpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAQD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CAUpE;AAMD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAO3D;AAUD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CAgBrE;AAOD,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAY7C,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6DxD,CAAC;AAMH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD;AAMD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAGpB;AAGD,MAAM,WAAW,eAAe;IAE9B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE1B,GAAG,EAAE,MAAM,CAAC;CACb;AAMD,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,EAAE,CAY3E;AAOD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,IAAI,CAO5E"}
1
+ {"version":3,"file":"auth-policy.d.ts","sourceRoot":"","sources":["../../src/config/auth-policy.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,MAAM,WAAW,SAAS;IAMxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAEpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAQD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CAUpE;AAMD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAO3D;AAUD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CAgBrE;AAOD,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAY7C,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiFxD,CAAC;AAOH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIlD;AAMD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAGpB;AAGD,MAAM,WAAW,eAAe;IAE9B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE1B,GAAG,EAAE,MAAM,CAAC;CACb;AAMD,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,EAAE,CAY3E;AAOD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,IAAI,CAO5E"}
@@ -66,6 +66,8 @@ exports.ROUTE_SCOPES = Object.freeze({
66
66
  "POST /v2/multi-recipient/encrypt": "crypto:encrypt",
67
67
  "POST /v2/pq/encapsulate": "crypto:encrypt",
68
68
  "POST /v2/pq/hybrid/encapsulate": "crypto:encrypt",
69
+ "POST /v2/stream/pq-encrypt": "crypto:encrypt",
70
+ "POST /v2/stream/multi-pq-encrypt": "crypto:encrypt",
69
71
  "POST /v2/decrypt": "crypto:decrypt",
70
72
  "POST /v2/secretbox/open": "crypto:decrypt",
71
73
  "POST /v2/sealedbox/open": "crypto:decrypt",
@@ -73,6 +75,8 @@ exports.ROUTE_SCOPES = Object.freeze({
73
75
  "POST /v2/password/decrypt": "crypto:decrypt",
74
76
  "POST /v2/pq/decapsulate": "crypto:decrypt",
75
77
  "POST /v2/pq/hybrid/decapsulate": "crypto:decrypt",
78
+ "POST /v2/stream/pq-decrypt": "crypto:decrypt",
79
+ "POST /v2/stream/multi-pq-decrypt": "crypto:decrypt",
76
80
  "POST /v2/sign": "crypto:sign",
77
81
  "POST /v2/stream/sign": "crypto:sign",
78
82
  "POST /v2/pq/dsa/sign": "crypto:sign",
@@ -96,9 +100,22 @@ exports.ROUTE_SCOPES = Object.freeze({
96
100
  "POST /v2/pq/hybrid/keygen": "crypto:keys",
97
101
  "POST /v2/pq/dsa/keygen": "crypto:keys",
98
102
  "POST /v2/pq/slh-dsa/keygen": "crypto:keys",
103
+ "POST /v2/opaque/register/init": exports.AUTHENTICATED,
104
+ "POST /v2/opaque/register/finish": exports.AUTHENTICATED,
105
+ "POST /v2/opaque/login/init": exports.AUTHENTICATED,
106
+ "POST /v2/opaque/login/finish": exports.AUTHENTICATED,
107
+ "POST /v2/hpke/keygen": "crypto:keys",
108
+ "POST /v2/hpke/seal": "crypto:encrypt",
109
+ "POST /v2/hpke/open": "crypto:decrypt",
110
+ "POST /v2/kms/create-key": "crypto:keys",
111
+ "POST /v2/kms/wrap": "crypto:keys",
112
+ "POST /v2/kms/unwrap": "crypto:keys",
113
+ "POST /v2/kms/generate-data-key": "crypto:keys",
114
+ "POST /v2/kms/encrypt": "crypto:encrypt",
115
+ "POST /v2/kms/decrypt": "crypto:decrypt",
99
116
  });
100
117
  function isPublicRoute(url) {
101
- return (0, constants_1.isProbePath)(url) || url.startsWith("/docs");
118
+ return ((0, constants_1.isProbePath)(url) || url.startsWith("/docs") || url.startsWith("/v2/opaque"));
102
119
  }
103
120
  function routeRequirement(method, url) {
104
121
  const verb = method === "HEAD" ? "GET" : method;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/config/constants.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAQ5D,eAAO,MAAM,WAAW,QAA+B,CAAC;AAMxD,eAAO,MAAM,IAAI,QAAqC,CAAC;AAMvD,eAAO,MAAM,IAAI,iBAA8B,CAAC;AAMhD,eAAO,MAAM,QAAQ,QAAoC,CAAC;AAsB1D,eAAO,MAAM,aAAa,UAMzB,CAAC;AAMF,eAAO,MAAM,cAAc,EAAE,oBAa5B,CAAC;AAOF,eAAO,MAAM,eAAe,EAAE,sBAM7B,CAAC;AAOF,eAAO,MAAM,aAAa,EAAE,oBAG3B,CAAC;AAOF,eAAO,MAAM,WAAW,EAAE,kBAOzB,CAAC;AAOF,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAK1C,CAAC;AAKH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OACN,CAAC;AAMrC,eAAO,MAAM,gBAAgB;;;;qBAgBV;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO;;;;;;;;8BAiBnC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE;YAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;SAAE,CAAA;KAAE,WAChD;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;;;;;;;;CAoB3E,CAAC;AAOF,eAAO,MAAM,mBAAmB,QAAO,MAKtC,CAAC;AAMF,eAAO,MAAM,kBAAkB;;;CAK9B,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/config/constants.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAQ5D,eAAO,MAAM,WAAW,QAA+B,CAAC;AAMxD,eAAO,MAAM,IAAI,QAAqC,CAAC;AAMvD,eAAO,MAAM,IAAI,iBAA8B,CAAC;AAMhD,eAAO,MAAM,QAAQ,QAAoC,CAAC;AAsB1D,eAAO,MAAM,aAAa,UAMzB,CAAC;AAMF,eAAO,MAAM,cAAc,EAAE,oBAa5B,CAAC;AAOF,eAAO,MAAM,eAAe,EAAE,sBAM7B,CAAC;AAOF,eAAO,MAAM,aAAa,EAAE,oBAG3B,CAAC;AAOF,eAAO,MAAM,WAAW,EAAE,kBAazB,CAAC;AAOF,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAK1C,CAAC;AAKH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OACN,CAAC;AAMrC,eAAO,MAAM,gBAAgB;;;;qBAgBV;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO;;;;;;;;8BAiBnC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE;YAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;SAAE,CAAA;KAAE,WAChD;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;;;;;;;;CAoB3E,CAAC;AAOF,eAAO,MAAM,mBAAmB,QAAO,MAKtC,CAAC;AAMF,eAAO,MAAM,kBAAkB;;;CAK9B,CAAC"}
@@ -52,7 +52,13 @@ exports.corsOptions = {
52
52
  ? process.env["CORS_ORIGIN"].split(",").map((o) => o.trim())
53
53
  : false,
54
54
  methods: ["GET", "POST"],
55
- allowedHeaders: ["Content-Type", "x-api-key"],
55
+ allowedHeaders: [
56
+ "Content-Type",
57
+ "x-api-key",
58
+ "Authorization",
59
+ "x-request-id",
60
+ "traceparent",
61
+ ],
56
62
  credentials: true,
57
63
  };
58
64
  exports.PROBE_PATHS = new Set([
@@ -15,11 +15,18 @@ export declare class KeyStoreError extends Error {
15
15
  readonly code: string;
16
16
  constructor(message: string, statusCode: number, code: string);
17
17
  }
18
+ export interface EncryptedPartsPayload {
19
+ readonly ciphertext: string;
20
+ readonly iv: string;
21
+ readonly tag: string;
22
+ readonly alg: "aes-256-gcm";
23
+ }
18
24
  export declare class KeyStore {
19
25
  private readonly dir;
20
26
  private readonly capacity;
21
27
  private readonly keys;
22
- constructor(dir: string | undefined, capacity?: number);
28
+ private readonly storageKey;
29
+ constructor(dir: string | undefined, capacity?: number, storageKey?: string | Uint8Array);
23
30
  put(key: NewKey): Promise<StoredKey>;
24
31
  get(keyId: string, owner: string, algorithms?: readonly string[]): Promise<StoredKey>;
25
32
  private lookup;
@@ -1 +1 @@
1
- {"version":3,"file":"key-store.d.ts","sourceRoot":"","sources":["../../src/lib/key-store.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAGxD,MAAM,WAAW,SAAS;IAExB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC;IAE/B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC;CACjC;AAGD,MAAM,MAAM,MAAM,GAAG,IAAI,CACvB,SAAS,EACT,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,CACvD,CAAC;AAGF,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAKtD,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAG3C,qBAAa,aAAc,SAAQ,KAAK;IAEtC,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM9D;AAOD,qBAAa,QAAQ;IAUjB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAV3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;gBASlC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,GAAE,MAA6B;IAIpD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IA6BpC,GAAG,CACP,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,SAAS,CAAC;YAcP,MAAM;IAiBpB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,IAAI;CAGb;AAGD,wBAAgB,eAAe,IAAI,QAAQ,CAE1C"}
1
+ {"version":3,"file":"key-store.d.ts","sourceRoot":"","sources":["../../src/lib/key-store.ts"],"names":[],"mappings":"AAmCA,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAGxD,MAAM,WAAW,SAAS;IAExB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC;IAE/B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC;CACjC;AAGD,MAAM,MAAM,MAAM,GAAG,IAAI,CACvB,SAAS,EACT,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,cAAc,CACvD,CAAC;AAGF,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAKtD,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAG3C,qBAAa,aAAc,SAAQ,KAAK;IAEtC,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM9D;AAGD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;CAC7B;AAmFD,qBAAa,QAAQ;IAajB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAb3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;gBAW7B,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,GAAE,MAA6B,EACxD,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU;IAM5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAgDpC,GAAG,CACP,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,SAAS,CAAC;YAcP,MAAM;IAwDpB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,IAAI;CAGb;AAGD,wBAAgB,eAAe,IAAI,QAAQ,CAM1C"}
@@ -38,6 +38,7 @@ exports.keyStoreFromEnv = keyStoreFromEnv;
38
38
  const crypto_1 = require("crypto");
39
39
  const promises_1 = require("fs/promises");
40
40
  const path = __importStar(require("path"));
41
+ const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
41
42
  exports.KEY_ID_PATTERN = "^k_[A-Za-z0-9_-]{22}$";
42
43
  const KEY_ID_RE = new RegExp(exports.KEY_ID_PATTERN);
43
44
  exports.DEFAULT_KEY_CAPACITY = 10_000;
@@ -52,14 +53,70 @@ class KeyStoreError extends Error {
52
53
  }
53
54
  }
54
55
  exports.KeyStoreError = KeyStoreError;
56
+ function resolveStorageKey(key) {
57
+ if (!key)
58
+ return undefined;
59
+ if (typeof key !== "string") {
60
+ if (key.length === 32)
61
+ return Buffer.from(key);
62
+ return (0, crypto_1.createHash)("sha256").update(key).digest();
63
+ }
64
+ const trimmed = key.trim();
65
+ if (!trimmed)
66
+ return undefined;
67
+ if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
68
+ return Buffer.from(trimmed, "hex");
69
+ }
70
+ return (0, crypto_1.createHash)("sha256").update(trimmed, "utf8").digest();
71
+ }
72
+ function encryptPrivateParts(privateParts, keyId, owner, key) {
73
+ const iv = (0, crypto_1.randomBytes)(12);
74
+ const cipher = (0, crypto_1.createCipheriv)("aes-256-gcm", key, iv);
75
+ cipher.setAAD(Buffer.from(`${keyId}:${owner}`, "utf8"));
76
+ const plaintext = Buffer.from(JSON.stringify(privateParts), "utf8");
77
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
78
+ const tag = cipher.getAuthTag();
79
+ (0, crypto_lib_1.wipeMemory)(plaintext);
80
+ return {
81
+ ciphertext: ciphertext.toString("base64"),
82
+ iv: iv.toString("base64"),
83
+ tag: tag.toString("base64"),
84
+ alg: "aes-256-gcm",
85
+ };
86
+ }
87
+ function decryptPrivateParts(encrypted, keyId, owner, key) {
88
+ if (encrypted.alg !== "aes-256-gcm") {
89
+ throw new KeyStoreError(`Unsupported encryption algorithm: ${encrypted.alg}`, 500, "UNSUPPORTED_STORAGE_ALGORITHM");
90
+ }
91
+ try {
92
+ const iv = Buffer.from(encrypted.iv, "base64");
93
+ const tag = Buffer.from(encrypted.tag, "base64");
94
+ const ciphertext = Buffer.from(encrypted.ciphertext, "base64");
95
+ const decipher = (0, crypto_1.createDecipheriv)("aes-256-gcm", key, iv);
96
+ decipher.setAAD(Buffer.from(`${keyId}:${owner}`, "utf8"));
97
+ decipher.setAuthTag(tag);
98
+ const decrypted = Buffer.concat([
99
+ decipher.update(ciphertext),
100
+ decipher.final(),
101
+ ]);
102
+ const parsed = JSON.parse(decrypted.toString("utf8"));
103
+ (0, crypto_lib_1.wipeMemory)(decrypted);
104
+ return parsed;
105
+ }
106
+ catch {
107
+ throw new KeyStoreError("Failed to decrypt key material: invalid key or corrupted data", 500, "KEY_INTEGRITY_FAILED");
108
+ }
109
+ }
55
110
  const notFound = () => new KeyStoreError("Key not found", 404, "KEY_NOT_FOUND");
56
111
  class KeyStore {
57
112
  dir;
58
113
  capacity;
59
114
  keys = new Map();
60
- constructor(dir, capacity = exports.DEFAULT_KEY_CAPACITY) {
115
+ storageKey;
116
+ constructor(dir, capacity = exports.DEFAULT_KEY_CAPACITY, storageKey) {
61
117
  this.dir = dir;
62
118
  this.capacity = capacity;
119
+ this.storageKey = resolveStorageKey(storageKey);
63
120
  }
64
121
  async put(key) {
65
122
  if (!this.dir && this.keys.size >= this.capacity) {
@@ -71,7 +128,20 @@ class KeyStore {
71
128
  createdAt: new Date().toISOString(),
72
129
  });
73
130
  if (this.dir) {
74
- await (0, promises_1.writeFile)(this.file(stored.keyId), JSON.stringify(stored), {
131
+ const diskRecord = {
132
+ keyId: stored.keyId,
133
+ algorithm: stored.algorithm,
134
+ owner: stored.owner,
135
+ createdAt: stored.createdAt,
136
+ publicParts: stored.publicParts,
137
+ };
138
+ if (this.storageKey) {
139
+ diskRecord.encryptedPrivateParts = encryptPrivateParts(stored.privateParts, stored.keyId, stored.owner, this.storageKey);
140
+ }
141
+ else {
142
+ diskRecord.privateParts = stored.privateParts;
143
+ }
144
+ await (0, promises_1.writeFile)(this.file(stored.keyId), JSON.stringify(diskRecord), {
75
145
  encoding: "utf8",
76
146
  mode: 0o600,
77
147
  flag: "wx",
@@ -102,9 +172,36 @@ class KeyStore {
102
172
  catch {
103
173
  return undefined;
104
174
  }
105
- const stored = Object.freeze(JSON.parse(raw));
106
- if (stored.keyId !== keyId)
175
+ let record;
176
+ try {
177
+ record = JSON.parse(raw);
178
+ }
179
+ catch {
107
180
  return undefined;
181
+ }
182
+ if (record.keyId !== keyId)
183
+ return undefined;
184
+ let privateParts;
185
+ if (record.encryptedPrivateParts) {
186
+ if (!this.storageKey) {
187
+ throw new KeyStoreError("Key is encrypted at rest; storage key required", 500, "STORAGE_KEY_REQUIRED");
188
+ }
189
+ privateParts = decryptPrivateParts(record.encryptedPrivateParts, record.keyId, record.owner, this.storageKey);
190
+ }
191
+ else if (record.privateParts) {
192
+ privateParts = record.privateParts;
193
+ }
194
+ else {
195
+ throw new KeyStoreError("Key file contains no private key material", 500, "KEY_CORRUPTED");
196
+ }
197
+ const stored = Object.freeze({
198
+ keyId: record.keyId,
199
+ algorithm: record.algorithm,
200
+ owner: record.owner,
201
+ createdAt: record.createdAt,
202
+ publicParts: record.publicParts,
203
+ privateParts,
204
+ });
108
205
  this.remember(stored);
109
206
  return stored;
110
207
  }
@@ -121,6 +218,6 @@ class KeyStore {
121
218
  }
122
219
  exports.KeyStore = KeyStore;
123
220
  function keyStoreFromEnv() {
124
- return new KeyStore(process.env["CRYPTO_KEY_OUT_DIR"] || undefined);
221
+ return new KeyStore(process.env["CRYPTO_KEY_OUT_DIR"] || undefined, exports.DEFAULT_KEY_CAPACITY, process.env["CRYPTO_KEY_STORAGE_KEY"] || undefined);
125
222
  }
126
223
  //# sourceMappingURL=key-store.js.map
@@ -0,0 +1,34 @@
1
+ import { protocols } from "@sebastienrousseau/crypto-lib";
2
+ type SuiteId = protocols.pake.SuiteId;
3
+ type ServerSetup = protocols.pake.ServerSetup;
4
+ type RegistrationRecord = protocols.pake.RegistrationRecord;
5
+ type ServerLoginState = protocols.pake.ServerLoginState;
6
+ export declare const DEFAULT_OPAQUE_SESSION_TTL_MS: number;
7
+ export interface OpaqueActiveSession {
8
+ readonly serverState: ServerLoginState;
9
+ readonly credentialIdentifier: string;
10
+ readonly suite: SuiteId;
11
+ readonly expiresAt: number;
12
+ }
13
+ export declare class OpaqueStore {
14
+ private readonly setups;
15
+ private readonly fakeRecords;
16
+ private readonly records;
17
+ private readonly sessions;
18
+ getSetup(suite?: SuiteId): ServerSetup;
19
+ getFakeRecord(suite?: SuiteId): RegistrationRecord;
20
+ setRecord(credentialIdentifier: string, record: RegistrationRecord): void;
21
+ getRecord(credentialIdentifier: string): RegistrationRecord | undefined;
22
+ hasRecord(credentialIdentifier: string): boolean;
23
+ createSession(credentialIdentifier: string, serverState: ServerLoginState, suite?: SuiteId, ttlMs?: number): string;
24
+ consumeSession(sessionId: string): OpaqueActiveSession | undefined;
25
+ cleanExpiredSessions(): void;
26
+ clear(): void;
27
+ }
28
+ declare module "fastify" {
29
+ interface FastifyInstance {
30
+ opaqueStore: OpaqueStore;
31
+ }
32
+ }
33
+ export {};
34
+ //# sourceMappingURL=opaque-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque-store.d.ts","sourceRoot":"","sources":["../../src/lib/opaque-store.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE1D,KAAK,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;AACtC,KAAK,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC;AAC9C,KAAK,kBAAkB,GAAG,SAAS,CAAC,IAAI,CAAC,kBAAkB,CAAC;AAC5D,KAAK,gBAAgB,GAAG,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC;AAKxD,eAAO,MAAM,6BAA6B,QAAgB,CAAC;AAG3D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAGD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IACjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IAMnE,QAAQ,CAAC,KAAK,GAAE,OAAuB,GAAG,WAAW;IAarD,aAAa,CAAC,KAAK,GAAE,OAAuB,GAAG,kBAAkB;IAUjE,SAAS,CAAC,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAKzE,SAAS,CAAC,oBAAoB,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAKvE,SAAS,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO;IAKhD,aAAa,CACX,oBAAoB,EAAE,MAAM,EAC5B,WAAW,EAAE,gBAAgB,EAC7B,KAAK,GAAE,OAAuB,EAC9B,KAAK,GAAE,MAAsC,GAC5C,MAAM;IAgBT,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IASlE,oBAAoB,IAAI,IAAI;IAU5B,KAAK,IAAI,IAAI;CAId;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,eAAe;QAEvB,WAAW,EAAE,WAAW,CAAC;KAC1B;CACF"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpaqueStore = exports.DEFAULT_OPAQUE_SESSION_TTL_MS = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
6
+ const { pake } = crypto_lib_1.protocols;
7
+ exports.DEFAULT_OPAQUE_SESSION_TTL_MS = 5 * 60 * 1000;
8
+ class OpaqueStore {
9
+ setups = new Map();
10
+ fakeRecords = new Map();
11
+ records = new Map();
12
+ sessions = new Map();
13
+ getSetup(suite = "P256-SHA256") {
14
+ let setup = this.setups.get(suite);
15
+ if (!setup) {
16
+ setup = pake.createServerSetup({ suite });
17
+ this.setups.set(suite, setup);
18
+ }
19
+ return setup;
20
+ }
21
+ getFakeRecord(suite = "P256-SHA256") {
22
+ let fake = this.fakeRecords.get(suite);
23
+ if (!fake) {
24
+ fake = pake.createFakeRecord({ suite });
25
+ this.fakeRecords.set(suite, fake);
26
+ }
27
+ return fake;
28
+ }
29
+ setRecord(credentialIdentifier, record) {
30
+ this.records.set(credentialIdentifier, record);
31
+ }
32
+ getRecord(credentialIdentifier) {
33
+ return this.records.get(credentialIdentifier);
34
+ }
35
+ hasRecord(credentialIdentifier) {
36
+ return this.records.has(credentialIdentifier);
37
+ }
38
+ createSession(credentialIdentifier, serverState, suite = "P256-SHA256", ttlMs = exports.DEFAULT_OPAQUE_SESSION_TTL_MS) {
39
+ this.cleanExpiredSessions();
40
+ const sessionId = (0, node_crypto_1.randomUUID)();
41
+ this.sessions.set(sessionId, {
42
+ serverState,
43
+ credentialIdentifier,
44
+ suite,
45
+ expiresAt: Date.now() + ttlMs,
46
+ });
47
+ return sessionId;
48
+ }
49
+ consumeSession(sessionId) {
50
+ const session = this.sessions.get(sessionId);
51
+ if (!session)
52
+ return undefined;
53
+ this.sessions.delete(sessionId);
54
+ if (Date.now() > session.expiresAt)
55
+ return undefined;
56
+ return session;
57
+ }
58
+ cleanExpiredSessions() {
59
+ const now = Date.now();
60
+ for (const [id, session] of this.sessions) {
61
+ if (now > session.expiresAt) {
62
+ this.sessions.delete(id);
63
+ }
64
+ }
65
+ }
66
+ clear() {
67
+ this.records.clear();
68
+ this.sessions.clear();
69
+ }
70
+ }
71
+ exports.OpaqueStore = OpaqueStore;
72
+ //# sourceMappingURL=opaque-store.js.map
@@ -15,6 +15,7 @@ export declare const PROBLEM_TITLES: Readonly<{
15
15
  "rate-limited": "Too Many Requests";
16
16
  "request-error": "Bad Request";
17
17
  "internal-error": "Internal Server Error";
18
+ conflict: "Conflict";
18
19
  }>;
19
20
  export type ProblemSlug = keyof typeof PROBLEM_TITLES;
20
21
  export interface Problem {
@@ -1 +1 @@
1
- {"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../../src/lib/problem.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAkB,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAI5D,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAG/D,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAGjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;EAazB,CAAC;AAGH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,cAAc,CAAC;AAGtD,MAAM,WAAW,OAAO;IAEtB,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,EAAE,MAAM,CAAC;IAEf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAGD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;CAWjB,CAAC;AAGX,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACxC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAST;AAOD,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,YAAY,CAGd;AAaD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAUD,MAAM,WAAW,WAAW;IAE1B,MAAM,EAAE,MAAM,CAAC;IAEf,IAAI,EAAE,WAAW,CAAC;IAElB,MAAM,EAAE,MAAM,CAAC;IAEf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAwFD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAU1D;AAOD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAoBlE"}
1
+ {"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../../src/lib/problem.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAkB,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAI5D,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAG/D,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAGjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;EAczB,CAAC;AAGH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,cAAc,CAAC;AAGtD,MAAM,WAAW,OAAO;IAEtB,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,EAAE,MAAM,CAAC;IAEf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAGD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;CAWjB,CAAC;AAGX,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACxC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAST;AAOD,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,YAAY,CAGd;AAkBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAcD,MAAM,WAAW,WAAW;IAE1B,MAAM,EAAE,MAAM,CAAC;IAEf,IAAI,EAAE,WAAW,CAAC;IAElB,MAAM,EAAE,MAAM,CAAC;IAEf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAwFD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAU1D;AAOD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAoBlE"}
@@ -25,6 +25,7 @@ exports.PROBLEM_TITLES = Object.freeze({
25
25
  "rate-limited": "Too Many Requests",
26
26
  "request-error": "Bad Request",
27
27
  "internal-error": "Internal Server Error",
28
+ conflict: "Conflict",
28
29
  });
29
30
  exports.PROBLEM_SCHEMA = {
30
31
  type: "object",
@@ -60,6 +61,11 @@ const CRYPTO_INPUT_CODES = new Set([
60
61
  "INVALID_CIPHERTEXT",
61
62
  "AUTH_FAILED",
62
63
  "INVALID_INPUT",
64
+ "OPAQUE_ENVELOPE_RECOVERY",
65
+ "OPAQUE_SERVER_AUTHENTICATION",
66
+ "OPAQUE_CLIENT_AUTHENTICATION",
67
+ "OPAQUE_DESERIALIZE",
68
+ "OPAQUE_INVALID_INPUT",
63
69
  ]);
64
70
  function cryptoErrorStatus(code) {
65
71
  return CRYPTO_INPUT_CODES.has(code) ? 400 : 500;
@@ -71,6 +77,10 @@ const KEY_STORE_SLUGS = {
71
77
  KEY_NOT_FOUND: "key-not-found",
72
78
  KEY_ALGORITHM_MISMATCH: "key-algorithm-mismatch",
73
79
  KEY_STORE_FULL: "key-store-full",
80
+ STORAGE_KEY_REQUIRED: "internal-error",
81
+ KEY_INTEGRITY_FAILED: "internal-error",
82
+ KEY_CORRUPTED: "internal-error",
83
+ UNSUPPORTED_STORAGE_ALGORITHM: "internal-error",
74
84
  };
75
85
  function fieldOf(issue) {
76
86
  const missing = issue.params.missingProperty;
@@ -1 +1 @@
1
- {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/decrypt.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;yBA2C/B,KAAK,eAAe,KAAG,IAAI;AAA3C,wBA+BE"}
1
+ {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/decrypt.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;yBA2C/B,KAAK,eAAe,KAAG,IAAI;AAA3C,wBAkCE"}
@@ -37,7 +37,10 @@ const decryptSchema = {
37
37
  },
38
38
  };
39
39
  exports.default = (app) => {
40
- app.post("/v1/decrypt", { schema: decryptSchema }, async (request, reply) => {
40
+ app.post("/v1/decrypt", {
41
+ schema: decryptSchema,
42
+ config: { rateLimit: { max: 100, timeWindow: "1 minute" } },
43
+ }, async (request, reply) => {
41
44
  try {
42
45
  const body = request.body;
43
46
  const v = (0, route_helpers_1.collectValidation)({
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/verify.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;yBAuC/B,KAAK,eAAe,KAAG,IAAI;AAA3C,wBAiCE"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/verify.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;yBAuC/B,KAAK,eAAe,KAAG,IAAI;AAA3C,wBAoCE"}
@@ -29,7 +29,10 @@ const verifySchema = {
29
29
  },
30
30
  };
31
31
  exports.default = (app) => {
32
- app.post("/v1/verify", { schema: verifySchema }, async (request, reply) => {
32
+ app.post("/v1/verify", {
33
+ schema: verifySchema,
34
+ config: { rateLimit: { max: 100, timeWindow: "1 minute" } },
35
+ }, async (request, reply) => {
33
36
  try {
34
37
  const body = request.body;
35
38
  const v = (0, route_helpers_1.collectValidation)({
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function hpkeRoute(app: FastifyInstance): void;
3
+ //# sourceMappingURL=hpke.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hpke.d.ts","sourceRoot":"","sources":["../../../src/routes/v2/hpke.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA0L/C,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAI5D"}
@@ -0,0 +1,168 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = hpkeRoute;
37
+ const route_helpers_1 = require("../../utils/route-helpers");
38
+ const keys_1 = require("../../utils/keys");
39
+ const loadHpke = async () => await Promise.resolve().then(() => __importStar(require("@sebastienrousseau/crypto-lib/modern")));
40
+ function ensureHex(text) {
41
+ const isHex = /^[0-9a-fA-F]*$/.test(text) && text.length % 2 === 0;
42
+ return isHex ? text : Buffer.from(text, "utf8").toString("hex");
43
+ }
44
+ const KEYGEN_SCHEMA = {
45
+ tags: ["HPKE"],
46
+ summary: "Generate a server-held HPKE key pair (classical or post-quantum hybrid)",
47
+ body: {
48
+ type: "object",
49
+ additionalProperties: false,
50
+ properties: {
51
+ kem: {
52
+ type: "string",
53
+ enum: ["x25519", "p256", "x25519-ml-kem-768"],
54
+ default: "x25519-ml-kem-768",
55
+ },
56
+ },
57
+ },
58
+ };
59
+ const SEAL_SCHEMA = {
60
+ tags: ["HPKE"],
61
+ summary: "Encrypt (seal) a message using HPKE (RFC 9180)",
62
+ body: {
63
+ type: "object",
64
+ required: ["recipientPublicKey", "plaintext"],
65
+ additionalProperties: false,
66
+ properties: {
67
+ recipientPublicKey: { type: "string", minLength: 1 },
68
+ plaintext: { type: "string", minLength: 1 },
69
+ kem: { type: "string", enum: ["x25519", "p256", "x25519-ml-kem-768"] },
70
+ aead: { type: "string", enum: ["chacha20-poly1305", "aes-128-gcm"] },
71
+ info: { type: "string" },
72
+ aad: { type: "string" },
73
+ psk: { type: "string" },
74
+ pskId: { type: "string" },
75
+ },
76
+ },
77
+ };
78
+ const OPEN_SCHEMA = {
79
+ tags: ["HPKE"],
80
+ summary: "Decrypt (open) an HPKE ciphertext with a server-held key",
81
+ body: {
82
+ type: "object",
83
+ required: ["keyId", "encapsulatedKey", "ciphertext"],
84
+ additionalProperties: false,
85
+ properties: {
86
+ keyId: keys_1.KEY_ID_SCHEMA,
87
+ encapsulatedKey: { type: "string", minLength: 1 },
88
+ ciphertext: { type: "string", minLength: 1 },
89
+ aead: { type: "string", enum: ["chacha20-poly1305", "aes-128-gcm"] },
90
+ info: { type: "string" },
91
+ aad: { type: "string" },
92
+ psk: { type: "string" },
93
+ pskId: { type: "string" },
94
+ },
95
+ },
96
+ };
97
+ function registerKeygen(app) {
98
+ app.post("/v2/hpke/keygen", { schema: KEYGEN_SCHEMA }, async (request, reply) => {
99
+ const { hpkeGenerateKeyPair } = await loadHpke();
100
+ const { kem } = request.body;
101
+ const kp = hpkeGenerateKeyPair(kem);
102
+ const stored = await (0, keys_1.storeKey)(request, kem, { publicKey: kp.publicKey }, { privateKey: kp.privateKey });
103
+ return reply.send({ data: (0, keys_1.publicView)(stored) });
104
+ });
105
+ }
106
+ function registerSeal(app) {
107
+ app.post("/v2/hpke/seal", { schema: SEAL_SCHEMA }, async (request, reply) => {
108
+ try {
109
+ const { hpkeSeal } = await loadHpke();
110
+ const b = request.body;
111
+ const hexPlaintext = ensureHex(b.plaintext);
112
+ const res = hpkeSeal({
113
+ recipientPublicKey: b.recipientPublicKey,
114
+ plaintext: hexPlaintext,
115
+ suite: {
116
+ kem: b.kem ?? "x25519-ml-kem-768",
117
+ aead: b.aead ?? "chacha20-poly1305",
118
+ },
119
+ ...(b.info ? { info: ensureHex(b.info) } : {}),
120
+ ...(b.aad ? { aad: ensureHex(b.aad) } : {}),
121
+ ...(b.psk && b.pskId ? { psk: { psk: b.psk, pskId: b.pskId } } : {}),
122
+ });
123
+ return reply.send({ data: res });
124
+ }
125
+ catch (error) {
126
+ return (0, route_helpers_1.classifyCryptoError)(error, request, reply, "Encryption");
127
+ }
128
+ });
129
+ }
130
+ function registerOpen(app) {
131
+ app.post("/v2/hpke/open", {
132
+ config: { rateLimit: { max: 100, timeWindow: "1 minute" } },
133
+ schema: OPEN_SCHEMA,
134
+ }, async (request, reply) => {
135
+ const b = request.body;
136
+ const key = await (0, keys_1.resolveKey)(request, b.keyId, [
137
+ "x25519-ml-kem-768",
138
+ "x25519",
139
+ "p256",
140
+ ]);
141
+ try {
142
+ const { hpkeOpen } = await loadHpke();
143
+ const res = hpkeOpen({
144
+ recipientPrivateKey: key.privateParts["privateKey"],
145
+ encapsulatedKey: b.encapsulatedKey,
146
+ ciphertext: b.ciphertext,
147
+ suite: {
148
+ kem: key.algorithm,
149
+ aead: b.aead ?? "chacha20-poly1305",
150
+ },
151
+ ...(b.info ? { info: ensureHex(b.info) } : {}),
152
+ ...(b.aad ? { aad: ensureHex(b.aad) } : {}),
153
+ ...(b.psk && b.pskId ? { psk: { psk: b.psk, pskId: b.pskId } } : {}),
154
+ });
155
+ const utf8 = Buffer.from(res.plaintext, "hex").toString("utf8");
156
+ return reply.send({ data: { plaintext: utf8, hex: res.plaintext } });
157
+ }
158
+ catch (error) {
159
+ return (0, route_helpers_1.classifyCryptoError)(error, request, reply, "Decryption");
160
+ }
161
+ });
162
+ }
163
+ function hpkeRoute(app) {
164
+ registerKeygen(app);
165
+ registerSeal(app);
166
+ registerOpen(app);
167
+ }
168
+ //# sourceMappingURL=hpke.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/v2/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;yBAqB/B,KAAK,eAAe,KAAG,IAAI;AAA3C,wBAmBE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/v2/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;yBAwB/B,KAAK,eAAe,KAAG,IAAI;AAA3C,wBAsBE"}
@@ -21,6 +21,9 @@ const keys_1 = __importDefault(require("./keys"));
21
21
  const multi_recipient_1 = __importDefault(require("./multi-recipient"));
22
22
  const compliance_1 = __importDefault(require("./compliance"));
23
23
  const stream_1 = __importDefault(require("./stream"));
24
+ const opaque_1 = __importDefault(require("./opaque"));
25
+ const hpke_1 = __importDefault(require("./hpke"));
26
+ const kms_1 = __importDefault(require("./kms"));
24
27
  exports.default = (app) => {
25
28
  (0, algorithms_1.default)(app);
26
29
  (0, compliance_1.default)(app);
@@ -40,5 +43,8 @@ exports.default = (app) => {
40
43
  (0, key_wrap_1.default)(app);
41
44
  (0, keys_1.default)(app);
42
45
  (0, multi_recipient_1.default)(app);
46
+ (0, opaque_1.default)(app);
47
+ (0, hpke_1.default)(app);
48
+ (0, kms_1.default)(app);
43
49
  };
44
50
  //# sourceMappingURL=index.js.map