@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,135 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
4
+ const streaming_1 = require("@sebastienrousseau/crypto-lib/streaming");
4
5
  const protocols_1 = require("@sebastienrousseau/crypto-lib/protocols");
5
6
  const route_helpers_1 = require("../../utils/route-helpers");
6
7
  const keys_1 = require("../../utils/keys");
7
- const SIGN_SCHEMA = {
8
- tags: ["Streaming"],
9
- summary: "Batch sign messages with sub-millisecond throughput",
10
- description: "Process high-throughput batch digital signing operations over multiple payloads.",
11
- body: {
12
- type: "object",
13
- required: ["items"],
14
- additionalProperties: false,
15
- properties: {
16
- items: {
17
- type: "array",
18
- minItems: 1,
19
- maxItems: 1000,
20
- items: {
21
- type: "object",
22
- required: ["id", "message", "keyId"],
23
- additionalProperties: false,
24
- properties: {
25
- id: { type: "string" },
26
- message: { type: "string", minLength: 1 },
27
- keyId: keys_1.KEY_ID_SCHEMA,
28
- },
29
- },
30
- },
31
- },
32
- },
33
- };
34
- const VERIFY_SCHEMA = {
35
- tags: ["Streaming"],
36
- summary: "Batch verify digital signatures",
37
- description: "Batch verification pipeline for high-concurrency payment and transaction verification.",
38
- body: {
39
- type: "object",
40
- required: ["items"],
41
- additionalProperties: false,
42
- properties: {
43
- items: {
44
- type: "array",
45
- minItems: 1,
46
- maxItems: 1000,
47
- items: {
48
- type: "object",
49
- required: ["id", "message", "signature", "publicKey"],
50
- additionalProperties: false,
51
- properties: {
52
- id: { type: "string" },
53
- message: { type: "string", minLength: 1 },
54
- signature: { type: "string", minLength: 1 },
55
- publicKey: { type: "string", minLength: 64, maxLength: 64 },
56
- },
57
- },
58
- },
59
- },
60
- },
61
- };
62
- const ISO20022_SCHEMA = {
63
- tags: ["Wholesale Payments"],
64
- summary: "Verify ISO 20022 post-quantum dual-signature envelope",
65
- description: "Validates the payload digest and both the Ed25519 and ML-DSA signatures of a pacs.008, pain.001 or camt.053 envelope. Signatures are checked against trustedKeys, the signer's public keys from the caller's own registry; the public keys inside the envelope are ignored.",
66
- body: {
67
- type: "object",
68
- required: ["envelope", "payload", "trustedKeys"],
69
- additionalProperties: false,
70
- properties: {
71
- trustedKeys: {
72
- type: "object",
73
- required: ["classicalPublicKey", "postQuantumPublicKey"],
74
- additionalProperties: false,
75
- properties: {
76
- classicalPublicKey: { type: "string", minLength: 1 },
77
- postQuantumPublicKey: { type: "string", minLength: 1 },
78
- },
79
- },
80
- envelope: {
81
- type: "object",
82
- required: [
83
- "messageId",
84
- "messageType",
85
- "payloadDigest",
86
- "digestAlgorithm",
87
- "timestamp",
88
- "classical",
89
- "postQuantum",
90
- ],
91
- additionalProperties: false,
92
- properties: {
93
- messageId: { type: "string" },
94
- messageType: {
95
- type: "string",
96
- enum: ["pacs.008", "pain.001", "camt.053", "generic"],
97
- },
98
- payloadDigest: { type: "string" },
99
- digestAlgorithm: {
100
- type: "string",
101
- enum: ["sha256", "sha384", "sha512"],
102
- },
103
- timestamp: { type: "string" },
104
- classical: {
105
- type: "object",
106
- required: ["algorithm", "signature", "publicKey"],
107
- additionalProperties: false,
108
- properties: {
109
- algorithm: { type: "string" },
110
- signature: { type: "string" },
111
- publicKey: { type: "string" },
112
- },
113
- },
114
- postQuantum: {
115
- type: "object",
116
- required: ["algorithm", "signature", "publicKey"],
117
- additionalProperties: false,
118
- properties: {
119
- algorithm: {
120
- type: "string",
121
- enum: ["ml-dsa-44", "ml-dsa-65", "ml-dsa-87"],
122
- },
123
- signature: { type: "string" },
124
- publicKey: { type: "string" },
125
- },
126
- },
127
- },
128
- },
129
- payload: {},
130
- },
131
- },
132
- };
8
+ const stream_schemas_1 = require("./stream-schemas");
133
9
  async function resolveBatchKeys(request, items) {
134
10
  const keys = new Map();
135
11
  for (const keyId of new Set(items.map((i) => i.keyId))) {
@@ -138,7 +14,7 @@ async function resolveBatchKeys(request, items) {
138
14
  return keys;
139
15
  }
140
16
  function registerBatchSign(app) {
141
- app.post("/v2/stream/sign", { schema: SIGN_SCHEMA }, async (request, reply) => {
17
+ app.post("/v2/stream/sign", { schema: stream_schemas_1.SIGN_SCHEMA }, async (request, reply) => {
142
18
  const { items } = request.body;
143
19
  const keys = await resolveBatchKeys(request, items);
144
20
  const signatures = items.map((item) => {
@@ -159,7 +35,10 @@ function registerBatchSign(app) {
159
35
  });
160
36
  }
161
37
  function registerBatchVerify(app) {
162
- app.post("/v2/stream/verify", { schema: VERIFY_SCHEMA }, async (request, reply) => {
38
+ app.post("/v2/stream/verify", {
39
+ schema: stream_schemas_1.VERIFY_SCHEMA,
40
+ config: { rateLimit: { max: 100, timeWindow: "1 minute" } },
41
+ }, async (request, reply) => {
163
42
  try {
164
43
  const { items } = request.body;
165
44
  let validCount = 0;
@@ -189,7 +68,10 @@ function registerBatchVerify(app) {
189
68
  });
190
69
  }
191
70
  function registerIso20022Verify(app) {
192
- app.post("/v2/stream/iso20022", { schema: ISO20022_SCHEMA }, async (request, reply) => {
71
+ app.post("/v2/stream/iso20022", {
72
+ schema: stream_schemas_1.ISO20022_SCHEMA,
73
+ config: { rateLimit: { max: 100, timeWindow: "1 minute" } },
74
+ }, async (request, reply) => {
193
75
  try {
194
76
  const { envelope, payload, trustedKeys } = request.body;
195
77
  const result = (0, protocols_1.verifyIso20022Payment)(envelope, payload, {
@@ -203,9 +85,115 @@ function registerIso20022Verify(app) {
203
85
  }
204
86
  });
205
87
  }
88
+ function registerStreamPqEncrypt(app) {
89
+ app.post("/v2/stream/pq-encrypt", { schema: stream_schemas_1.STREAM_PQ_ENCRYPT_SCHEMA }, async (request, reply) => {
90
+ try {
91
+ const { x25519PublicKey, mlKemPublicKey, plaintext, chunkSize } = request.body;
92
+ const ptBytes = Buffer.from(plaintext, "utf8");
93
+ const res = (0, streaming_1.streamPqEncrypt)({
94
+ recipientX25519Public: x25519PublicKey,
95
+ recipientMlKemPublic: mlKemPublicKey,
96
+ plaintext: ptBytes,
97
+ ...(chunkSize !== undefined ? { chunkSize } : {}),
98
+ });
99
+ return reply.send({
100
+ data: {
101
+ ciphertext: Buffer.from(res.ciphertext).toString("base64"),
102
+ algorithm: res.algorithm,
103
+ },
104
+ });
105
+ }
106
+ catch (error) {
107
+ return (0, route_helpers_1.classifyCryptoError)(error, request, reply, "PQ stream encryption");
108
+ }
109
+ });
110
+ }
111
+ function registerStreamPqDecrypt(app) {
112
+ app.post("/v2/stream/pq-decrypt", { schema: stream_schemas_1.STREAM_PQ_DECRYPT_SCHEMA }, async (request, reply) => {
113
+ const { keyId, ciphertext, chunkSize } = request.body;
114
+ const key = await (0, keys_1.resolveKey)(request, keyId, ["x25519-ml-kem-768"]);
115
+ try {
116
+ const ctBytes = Buffer.from(ciphertext, "base64");
117
+ const decrypted = (0, streaming_1.streamPqDecrypt)({
118
+ recipientX25519Secret: key.privateParts["x25519PrivateKey"],
119
+ recipientMlKemSecret: key.privateParts["mlKemSecretKey"],
120
+ ciphertext: ctBytes,
121
+ ...(chunkSize !== undefined ? { chunkSize } : {}),
122
+ });
123
+ const plaintext = Buffer.from(decrypted).toString("utf8");
124
+ (0, crypto_lib_1.wipeMemory)(decrypted);
125
+ return reply.send({ data: { plaintext } });
126
+ }
127
+ catch (error) {
128
+ return (0, route_helpers_1.classifyCryptoError)(error, request, reply, "PQ stream decryption");
129
+ }
130
+ });
131
+ }
132
+ function registerStreamMultiPqEncrypt(app) {
133
+ app.post("/v2/stream/multi-pq-encrypt", { schema: stream_schemas_1.STREAM_MULTI_PQ_ENCRYPT_SCHEMA }, async (request, reply) => {
134
+ try {
135
+ const { recipients, plaintext, chunkSize } = request.body;
136
+ const ptBytes = Buffer.from(plaintext, "utf8");
137
+ const res = (0, streaming_1.streamMultiPqEncrypt)({
138
+ recipients: recipients.map((r) => ({
139
+ recipientId: r.recipientId,
140
+ recipientX25519Public: r.x25519PublicKey,
141
+ recipientMlKemPublic: r.mlKemPublicKey,
142
+ })),
143
+ plaintext: ptBytes,
144
+ ...(chunkSize !== undefined ? { chunkSize } : {}),
145
+ });
146
+ (0, crypto_lib_1.wipeMemory)(ptBytes);
147
+ return reply.send({
148
+ data: {
149
+ ciphertext: Buffer.from(res.ciphertext).toString("base64"),
150
+ algorithm: res.algorithm,
151
+ recipientCount: res.recipientCount,
152
+ },
153
+ });
154
+ }
155
+ catch (error) {
156
+ return (0, route_helpers_1.classifyCryptoError)(error, request, reply, "Multi-PQ stream encryption");
157
+ }
158
+ });
159
+ }
160
+ function registerStreamMultiPqDecrypt(app) {
161
+ app.post("/v2/stream/multi-pq-decrypt", {
162
+ schema: stream_schemas_1.STREAM_MULTI_PQ_DECRYPT_SCHEMA,
163
+ config: { rateLimit: { max: 100, timeWindow: "1 minute" } },
164
+ }, async (request, reply) => {
165
+ const { keyId, recipientId, ciphertext, chunkSize } = request.body;
166
+ const key = await (0, keys_1.resolveKey)(request, keyId, ["x25519-ml-kem-768"]);
167
+ try {
168
+ const ctBytes = Buffer.from(ciphertext, "base64");
169
+ const decrypted = (0, streaming_1.streamMultiPqDecrypt)({
170
+ ...(recipientId !== undefined ? { recipientId } : {}),
171
+ recipientX25519Secret: key.privateParts["x25519PrivateKey"],
172
+ recipientMlKemSecret: key.privateParts["mlKemSecretKey"],
173
+ ciphertext: ctBytes,
174
+ ...(chunkSize !== undefined ? { chunkSize } : {}),
175
+ });
176
+ const plaintext = Buffer.from(decrypted.plaintext).toString("utf8");
177
+ (0, crypto_lib_1.wipeMemory)(decrypted.plaintext);
178
+ return reply.send({
179
+ data: {
180
+ plaintext,
181
+ recipientId: decrypted.recipientId,
182
+ },
183
+ });
184
+ }
185
+ catch (error) {
186
+ return (0, route_helpers_1.classifyCryptoError)(error, request, reply, "Multi-PQ stream decryption");
187
+ }
188
+ });
189
+ }
206
190
  exports.default = (app) => {
207
191
  registerBatchSign(app);
208
192
  registerBatchVerify(app);
209
193
  registerIso20022Verify(app);
194
+ registerStreamPqEncrypt(app);
195
+ registerStreamPqDecrypt(app);
196
+ registerStreamMultiPqEncrypt(app);
197
+ registerStreamMultiPqDecrypt(app);
210
198
  };
211
199
  //# sourceMappingURL=stream.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAuCnC,iBAAe,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CA6EtD;AAED,OAAO,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAuCnC,iBAAe,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAgFtD;AAED,OAAO,EAAE,IAAI,EAAE,CAAC"}
package/dist/server.js CHANGED
@@ -55,6 +55,8 @@ const metering_1 = require("./enterprise/metering");
55
55
  const key_store_1 = require("./lib/key-store");
56
56
  const kdf_runner_1 = require("./lib/kdf-runner");
57
57
  const problem_1 = require("./lib/problem");
58
+ const trace_1 = require("./utils/trace");
59
+ const opaque_store_1 = require("./lib/opaque-store");
58
60
  const routes_1 = __importDefault(require("./routes"));
59
61
  const fastify = __importStar(require("fastify"));
60
62
  async function registerDocs(app) {
@@ -92,6 +94,7 @@ async function init() {
92
94
  app.addHook("onRequest", async (request, reply) => {
93
95
  const reqId = request.headers["x-request-id"] ?? (0, crypto_1.randomUUID)();
94
96
  reply.header("x-request-id", reqId);
97
+ (0, trace_1.propagateTraceContext)(request, reply);
95
98
  });
96
99
  app.addHook("onSend", async (request, reply) => {
97
100
  if (request.url.startsWith("/v2/")) {
@@ -122,6 +125,7 @@ async function init() {
122
125
  });
123
126
  app.decorate("keyStore", (0, key_store_1.keyStoreFromEnv)());
124
127
  app.decorate("kdf", new kdf_runner_1.KdfRunner());
128
+ app.decorate("opaqueStore", new opaque_store_1.OpaqueStore());
125
129
  app.addHook("onClose", () => app.kdf.close());
126
130
  (0, metering_1.registerMetering)(app);
127
131
  await app.register(async (scope) => {
@@ -0,0 +1,12 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ export interface TraceparentComponents {
3
+ version: string;
4
+ traceId: string;
5
+ parentId: string;
6
+ flags: string;
7
+ }
8
+ export declare function isValidTraceparent(header: unknown): header is string;
9
+ export declare function parseTraceparent(header: string): TraceparentComponents | null;
10
+ export declare function generateTraceparent(sampled?: boolean): string;
11
+ export declare function propagateTraceContext(request: FastifyRequest, reply: FastifyReply): void;
12
+ //# sourceMappingURL=trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/utils/trace.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAa5D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,MAAM,CAUpE;AAQD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAS7E;AAQD,wBAAgB,mBAAmB,CAAC,OAAO,UAAO,GAAG,MAAM,CAK1D;AASD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,IAAI,CAqBN"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidTraceparent = isValidTraceparent;
4
+ exports.parseTraceparent = parseTraceparent;
5
+ exports.generateTraceparent = generateTraceparent;
6
+ exports.propagateTraceContext = propagateTraceContext;
7
+ const crypto_1 = require("crypto");
8
+ const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
9
+ const ALL_ZERO_TRACE_ID = "0".repeat(32);
10
+ const ALL_ZERO_PARENT_ID = "0".repeat(16);
11
+ function isValidTraceparent(header) {
12
+ if (typeof header !== "string")
13
+ return false;
14
+ const match = TRACEPARENT_REGEX.exec(header.trim());
15
+ if (!match)
16
+ return false;
17
+ const traceId = match[1];
18
+ const parentId = match[2];
19
+ if (traceId === ALL_ZERO_TRACE_ID || parentId === ALL_ZERO_PARENT_ID) {
20
+ return false;
21
+ }
22
+ return true;
23
+ }
24
+ function parseTraceparent(header) {
25
+ if (!isValidTraceparent(header))
26
+ return null;
27
+ const match = TRACEPARENT_REGEX.exec(header.trim());
28
+ return {
29
+ version: "00",
30
+ traceId: match[1],
31
+ parentId: match[2],
32
+ flags: match[3],
33
+ };
34
+ }
35
+ function generateTraceparent(sampled = true) {
36
+ const traceId = (0, crypto_1.randomBytes)(16).toString("hex");
37
+ const parentId = (0, crypto_1.randomBytes)(8).toString("hex");
38
+ const flags = sampled ? "01" : "00";
39
+ return `00-${traceId}-${parentId}-${flags}`;
40
+ }
41
+ function propagateTraceContext(request, reply) {
42
+ const incoming = request.headers["traceparent"];
43
+ let traceparentValue;
44
+ if (isValidTraceparent(incoming)) {
45
+ traceparentValue = incoming.trim();
46
+ const tracestate = request.headers["tracestate"];
47
+ if (typeof tracestate === "string" && tracestate.trim().length > 0) {
48
+ reply.header("tracestate", tracestate.trim());
49
+ }
50
+ }
51
+ else {
52
+ traceparentValue = generateTraceparent();
53
+ }
54
+ reply.header("traceparent", traceparentValue);
55
+ const parsed = parseTraceparent(traceparentValue);
56
+ if (parsed) {
57
+ request.traceId = parsed.traceId;
58
+ }
59
+ request.traceparent =
60
+ traceparentValue;
61
+ }
62
+ //# sourceMappingURL=trace.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebastienrousseau/crypto-server",
3
- "version": "0.0.9",
3
+ "version": "0.0.24",
4
4
  "description": "Crypto Server is a Fastify web server that exposes easy consumable REST APIs to perform low-level cryptographic operations.",
5
5
  "author": "Sebastien Rousseau <sebastienrousseau@users.noreply.github.com>",
6
6
  "license": "MIT OR Apache-2.0",
@@ -60,7 +60,8 @@
60
60
  "fastify-healthcheck": "^5.1.0",
61
61
  "uuid": "^14.0.2",
62
62
  "winston": "^3.17.0",
63
- "@sebastienrousseau/crypto-lib": "0.0.9"
63
+ "@sebastienrousseau/crypto-kms": "0.0.24",
64
+ "@sebastienrousseau/crypto-lib": "0.0.24"
64
65
  },
65
66
  "devDependencies": {
66
67
  "@types/chai": "^5.2.3",
@@ -73,7 +74,7 @@
73
74
  "chai-as-promised": "^8.0.2",
74
75
  "fast-check": "^4.10.2",
75
76
  "mocha": "^12.0.3",
76
- "openpgp": "^6.3.1",
77
+ "openpgp": "^6.3.2",
77
78
  "prettier": "^3.9.9",
78
79
  "rimraf": "^6.1.3",
79
80
  "ts-node": "^10.9.2",