@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.
- package/dist/config/auth-policy.d.ts.map +1 -1
- package/dist/config/auth-policy.js +18 -1
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +7 -1
- package/dist/lib/key-store.d.ts +8 -1
- package/dist/lib/key-store.d.ts.map +1 -1
- package/dist/lib/key-store.js +102 -5
- package/dist/lib/opaque-store.d.ts +34 -0
- package/dist/lib/opaque-store.d.ts.map +1 -0
- package/dist/lib/opaque-store.js +72 -0
- package/dist/lib/problem.d.ts +1 -0
- package/dist/lib/problem.d.ts.map +1 -1
- package/dist/lib/problem.js +10 -0
- package/dist/routes/v1/decrypt.d.ts.map +1 -1
- package/dist/routes/v1/decrypt.js +4 -1
- package/dist/routes/v1/verify.d.ts.map +1 -1
- package/dist/routes/v1/verify.js +4 -1
- package/dist/routes/v2/hpke.d.ts +3 -0
- package/dist/routes/v2/hpke.d.ts.map +1 -0
- package/dist/routes/v2/hpke.js +168 -0
- package/dist/routes/v2/index.d.ts.map +1 -1
- package/dist/routes/v2/index.js +6 -0
- package/dist/routes/v2/kms.d.ts +7 -0
- package/dist/routes/v2/kms.d.ts.map +1 -0
- package/dist/routes/v2/kms.js +310 -0
- package/dist/routes/v2/mac.d.ts.map +1 -1
- package/dist/routes/v2/mac.js +1 -0
- package/dist/routes/v2/opaque.d.ts +4 -0
- package/dist/routes/v2/opaque.d.ts.map +1 -0
- package/dist/routes/v2/opaque.js +174 -0
- package/dist/routes/v2/pq-hash-sign.d.ts.map +1 -1
- package/dist/routes/v2/pq-hash-sign.js +3 -0
- package/dist/routes/v2/pq-sign.d.ts.map +1 -1
- package/dist/routes/v2/pq-sign.js +1 -0
- package/dist/routes/v2/signing.d.ts.map +1 -1
- package/dist/routes/v2/signing.js +1 -0
- package/dist/routes/v2/stream-schemas.d.ts +296 -0
- package/dist/routes/v2/stream-schemas.d.ts.map +1 -0
- package/dist/routes/v2/stream-schemas.js +207 -0
- package/dist/routes/v2/stream.d.ts.map +1 -1
- package/dist/routes/v2/stream.js +117 -129
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -0
- package/dist/utils/trace.d.ts +12 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +62 -0
- 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,
|
|
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,
|
|
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"}
|
package/dist/config/constants.js
CHANGED
|
@@ -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: [
|
|
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([
|
package/dist/lib/key-store.d.ts
CHANGED
|
@@ -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
|
-
|
|
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":"
|
|
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"}
|
package/dist/lib/key-store.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
package/dist/lib/problem.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/lib/problem.js
CHANGED
|
@@ -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,
|
|
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", {
|
|
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,
|
|
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"}
|
package/dist/routes/v1/verify.js
CHANGED
|
@@ -29,7 +29,10 @@ const verifySchema = {
|
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
exports.default = (app) => {
|
|
32
|
-
app.post("/v1/verify", {
|
|
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 @@
|
|
|
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;
|
|
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"}
|
package/dist/routes/v2/index.js
CHANGED
|
@@ -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
|