@nextera.one/axis-server-sdk 2.2.1 → 2.2.3
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/axis-sensor-GBEI3Fab.d.mts +209 -0
- package/dist/axis-sensor-GBEI3Fab.d.ts +209 -0
- package/dist/cce/index.d.mts +162 -0
- package/dist/cce/index.d.ts +162 -0
- package/dist/cce/index.js +1502 -0
- package/dist/cce/index.js.map +1 -0
- package/dist/cce/index.mjs +1442 -0
- package/dist/cce/index.mjs.map +1 -0
- package/dist/cce-pipeline-B-zUBHo3.d.mts +294 -0
- package/dist/cce-pipeline-DbGBSsCG.d.ts +294 -0
- package/dist/idel/index.d.mts +24 -0
- package/dist/idel/index.d.ts +24 -0
- package/dist/idel/index.js +306 -0
- package/dist/idel/index.js.map +1 -0
- package/dist/idel/index.mjs +279 -0
- package/dist/idel/index.mjs.map +1 -0
- package/dist/idel.types-DuUAcOnQ.d.mts +83 -0
- package/dist/idel.types-DuUAcOnQ.d.ts +83 -0
- package/dist/index-B2G6cbRL.d.mts +824 -0
- package/dist/index-DbSxdR0f.d.ts +824 -0
- package/dist/index-_S4fmVUJ.d.mts +501 -0
- package/dist/index-l3Hhirqb.d.ts +501 -0
- package/dist/index.d.mts +77 -1894
- package/dist/index.d.ts +77 -1894
- package/dist/index.js +10087 -5970
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10302 -5897
- package/dist/index.mjs.map +1 -1
- package/dist/needle/index.d.mts +4 -0
- package/dist/needle/index.d.ts +4 -0
- package/dist/needle/index.js +3499 -0
- package/dist/needle/index.js.map +1 -0
- package/dist/needle/index.mjs +3528 -0
- package/dist/needle/index.mjs.map +1 -0
- package/dist/sensors/index.d.mts +5 -0
- package/dist/sensors/index.d.ts +5 -0
- package/dist/sensors/index.js +12767 -0
- package/dist/sensors/index.js.map +1 -0
- package/dist/sensors/index.mjs +12835 -0
- package/dist/sensors/index.mjs.map +1 -0
- package/dist/timeline/index.d.mts +54 -0
- package/dist/timeline/index.d.ts +54 -0
- package/dist/timeline/index.js +389 -0
- package/dist/timeline/index.js.map +1 -0
- package/dist/timeline/index.mjs +362 -0
- package/dist/timeline/index.mjs.map +1 -0
- package/dist/timeline.types-Cn0aqbUj.d.mts +125 -0
- package/dist/timeline.types-Cn0aqbUj.d.ts +125 -0
- package/package.json +28 -10
|
@@ -0,0 +1,1502 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/cce/index.ts
|
|
20
|
+
var cce_exports = {};
|
|
21
|
+
__export(cce_exports, {
|
|
22
|
+
CCE_AES_KEY_BYTES: () => CCE_AES_KEY_BYTES,
|
|
23
|
+
CCE_DERIVATION: () => CCE_DERIVATION,
|
|
24
|
+
CCE_ERROR: () => CCE_ERROR,
|
|
25
|
+
CCE_IV_BYTES: () => CCE_IV_BYTES,
|
|
26
|
+
CCE_NONCE_BYTES: () => CCE_NONCE_BYTES,
|
|
27
|
+
CCE_PROTOCOL_VERSION: () => CCE_PROTOCOL_VERSION,
|
|
28
|
+
CCE_TAG_BYTES: () => CCE_TAG_BYTES,
|
|
29
|
+
CceAudienceIntentBindingSensor: () => CceAudienceIntentBindingSensor,
|
|
30
|
+
CceCapsuleVerificationSensor: () => CceCapsuleVerificationSensor,
|
|
31
|
+
CceClientSignatureSensor: () => CceClientSignatureSensor,
|
|
32
|
+
CceEnvelopeValidationSensor: () => CceEnvelopeValidationSensor,
|
|
33
|
+
CceError: () => CceError,
|
|
34
|
+
CcePayloadDecryptionSensor: () => CcePayloadDecryptionSensor,
|
|
35
|
+
CceReplayProtectionSensor: () => CceReplayProtectionSensor,
|
|
36
|
+
CceTpsWindowSensor: () => CceTpsWindowSensor,
|
|
37
|
+
InMemoryCceReplayStore: () => InMemoryCceReplayStore,
|
|
38
|
+
InMemoryCceWitnessStore: () => InMemoryCceWitnessStore,
|
|
39
|
+
aesGcmDecrypt: () => aesGcmDecrypt,
|
|
40
|
+
aesGcmEncrypt: () => aesGcmEncrypt,
|
|
41
|
+
base64UrlDecode: () => base64UrlDecode,
|
|
42
|
+
base64UrlEncode: () => base64UrlEncode,
|
|
43
|
+
buildCceErrorResponse: () => buildCceErrorResponse,
|
|
44
|
+
buildCceResponse: () => buildCceResponse,
|
|
45
|
+
buildExecutionContext: () => buildExecutionContext,
|
|
46
|
+
buildWitnessRecord: () => buildWitnessRecord,
|
|
47
|
+
deriveRequestExecutionKey: () => deriveRequestExecutionKey,
|
|
48
|
+
deriveResponseExecutionKey: () => deriveResponseExecutionKey,
|
|
49
|
+
deriveWitnessKey: () => deriveWitnessKey,
|
|
50
|
+
executeCcePipeline: () => executeCcePipeline,
|
|
51
|
+
extractVerificationState: () => extractVerificationState,
|
|
52
|
+
generateAesKey: () => generateAesKey,
|
|
53
|
+
generateCceNonce: () => generateCceNonce,
|
|
54
|
+
generateIv: () => generateIv,
|
|
55
|
+
hashPayload: () => hashPayload,
|
|
56
|
+
nodeAesGcmProvider: () => nodeAesGcmProvider
|
|
57
|
+
});
|
|
58
|
+
module.exports = __toCommonJS(cce_exports);
|
|
59
|
+
|
|
60
|
+
// src/cce/cce.types.ts
|
|
61
|
+
var CCE_PROTOCOL_VERSION = "cce-v1";
|
|
62
|
+
var CCE_DERIVATION = {
|
|
63
|
+
/** Request execution context */
|
|
64
|
+
REQUEST: "axis:cce:req:v1",
|
|
65
|
+
/** Response execution context */
|
|
66
|
+
RESPONSE: "axis:cce:resp:v1",
|
|
67
|
+
/** Witness binding context */
|
|
68
|
+
WITNESS: "axis:cce:witness:v1"
|
|
69
|
+
};
|
|
70
|
+
var CCE_AES_KEY_BYTES = 32;
|
|
71
|
+
var CCE_IV_BYTES = 12;
|
|
72
|
+
var CCE_TAG_BYTES = 16;
|
|
73
|
+
var CCE_NONCE_BYTES = 32;
|
|
74
|
+
var CCE_ERROR = {
|
|
75
|
+
// Envelope errors
|
|
76
|
+
INVALID_ENVELOPE: "CCE_INVALID_ENVELOPE",
|
|
77
|
+
UNSUPPORTED_VERSION: "CCE_UNSUPPORTED_VERSION",
|
|
78
|
+
MISSING_CAPSULE: "CCE_MISSING_CAPSULE",
|
|
79
|
+
MISSING_ENCRYPTED_KEY: "CCE_MISSING_ENCRYPTED_KEY",
|
|
80
|
+
// Signature errors
|
|
81
|
+
CLIENT_SIG_INVALID: "CCE_CLIENT_SIG_INVALID",
|
|
82
|
+
CLIENT_KEY_NOT_FOUND: "CCE_CLIENT_KEY_NOT_FOUND",
|
|
83
|
+
// Capsule errors
|
|
84
|
+
CAPSULE_SIG_INVALID: "CCE_CAPSULE_SIG_INVALID",
|
|
85
|
+
CAPSULE_EXPIRED: "CCE_CAPSULE_EXPIRED",
|
|
86
|
+
CAPSULE_NOT_YET_VALID: "CCE_CAPSULE_NOT_YET_VALID",
|
|
87
|
+
CAPSULE_REVOKED: "CCE_CAPSULE_REVOKED",
|
|
88
|
+
CAPSULE_CONSUMED: "CCE_CAPSULE_CONSUMED",
|
|
89
|
+
// Binding errors
|
|
90
|
+
AUDIENCE_MISMATCH: "CCE_AUDIENCE_MISMATCH",
|
|
91
|
+
INTENT_MISMATCH: "CCE_INTENT_MISMATCH",
|
|
92
|
+
TPS_WINDOW_EXPIRED: "CCE_TPS_WINDOW_EXPIRED",
|
|
93
|
+
TPS_WINDOW_FUTURE: "CCE_TPS_WINDOW_FUTURE",
|
|
94
|
+
// Replay / nonce errors
|
|
95
|
+
REPLAY_DETECTED: "CCE_REPLAY_DETECTED",
|
|
96
|
+
NONCE_REUSED: "CCE_NONCE_REUSED",
|
|
97
|
+
// Decryption errors
|
|
98
|
+
DECRYPTION_FAILED: "CCE_DECRYPTION_FAILED",
|
|
99
|
+
KEY_UNWRAP_FAILED: "CCE_KEY_UNWRAP_FAILED",
|
|
100
|
+
AEAD_TAG_MISMATCH: "CCE_AEAD_TAG_MISMATCH",
|
|
101
|
+
PAYLOAD_TOO_LARGE: "CCE_PAYLOAD_TOO_LARGE",
|
|
102
|
+
// Schema / validation errors
|
|
103
|
+
PAYLOAD_SCHEMA_INVALID: "CCE_PAYLOAD_SCHEMA_INVALID",
|
|
104
|
+
INTENT_SCHEMA_MISMATCH: "CCE_INTENT_SCHEMA_MISMATCH",
|
|
105
|
+
// Policy errors
|
|
106
|
+
POLICY_DENIED: "CCE_POLICY_DENIED",
|
|
107
|
+
CONSTRAINT_VIOLATED: "CCE_CONSTRAINT_VIOLATED",
|
|
108
|
+
// Handler errors
|
|
109
|
+
HANDLER_NOT_FOUND: "CCE_HANDLER_NOT_FOUND",
|
|
110
|
+
HANDLER_EXECUTION_FAILED: "CCE_HANDLER_EXECUTION_FAILED",
|
|
111
|
+
HANDLER_TIMEOUT: "CCE_HANDLER_TIMEOUT",
|
|
112
|
+
// Response errors
|
|
113
|
+
RESPONSE_ENCRYPTION_FAILED: "CCE_RESPONSE_ENCRYPTION_FAILED"
|
|
114
|
+
};
|
|
115
|
+
var CceError = class extends Error {
|
|
116
|
+
constructor(code, message, metadata) {
|
|
117
|
+
super(`[${code}] ${message}`);
|
|
118
|
+
this.code = code;
|
|
119
|
+
this.metadata = metadata;
|
|
120
|
+
this.name = "CceError";
|
|
121
|
+
}
|
|
122
|
+
/** Whether this error is safe to expose to the client */
|
|
123
|
+
get clientSafe() {
|
|
124
|
+
const internal = [
|
|
125
|
+
CCE_ERROR.DECRYPTION_FAILED,
|
|
126
|
+
CCE_ERROR.KEY_UNWRAP_FAILED,
|
|
127
|
+
CCE_ERROR.AEAD_TAG_MISMATCH,
|
|
128
|
+
CCE_ERROR.HANDLER_EXECUTION_FAILED,
|
|
129
|
+
CCE_ERROR.RESPONSE_ENCRYPTION_FAILED
|
|
130
|
+
];
|
|
131
|
+
return !internal.includes(this.code);
|
|
132
|
+
}
|
|
133
|
+
/** Get client-safe representation */
|
|
134
|
+
toClientError() {
|
|
135
|
+
if (this.clientSafe) {
|
|
136
|
+
return { code: this.code, message: this.message };
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
code: CCE_ERROR.DECRYPTION_FAILED,
|
|
140
|
+
message: "Request processing failed"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/cce/cce-derivation.service.ts
|
|
146
|
+
var import_utils = require("@noble/hashes/utils.js");
|
|
147
|
+
var import_hkdf = require("@noble/hashes/hkdf.js");
|
|
148
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
149
|
+
function buildSalt(capsuleId, capsuleNonce, requestNonce) {
|
|
150
|
+
const encoder = new TextEncoder();
|
|
151
|
+
const data = encoder.encode(
|
|
152
|
+
capsuleId + "|" + capsuleNonce + "|" + requestNonce
|
|
153
|
+
);
|
|
154
|
+
return (0, import_sha2.sha256)(data);
|
|
155
|
+
}
|
|
156
|
+
function buildInfo(contextPrefix, capsule, extraNonce) {
|
|
157
|
+
const encoder = new TextEncoder();
|
|
158
|
+
const parts = [
|
|
159
|
+
contextPrefix,
|
|
160
|
+
capsule.sub,
|
|
161
|
+
capsule.kid,
|
|
162
|
+
capsule.intent,
|
|
163
|
+
capsule.aud,
|
|
164
|
+
String(capsule.tps_from),
|
|
165
|
+
String(capsule.tps_to),
|
|
166
|
+
capsule.policy_hash ?? "",
|
|
167
|
+
capsule.ver
|
|
168
|
+
];
|
|
169
|
+
if (extraNonce) {
|
|
170
|
+
parts.push(extraNonce);
|
|
171
|
+
}
|
|
172
|
+
return encoder.encode(parts.join("|"));
|
|
173
|
+
}
|
|
174
|
+
function deriveRequestExecutionKey(input) {
|
|
175
|
+
const ikm = (0, import_utils.hexToBytes)(input.axisLocalSecret);
|
|
176
|
+
const salt = buildSalt(
|
|
177
|
+
input.capsule.capsule_id,
|
|
178
|
+
input.capsule.capsule_nonce,
|
|
179
|
+
input.requestNonce
|
|
180
|
+
);
|
|
181
|
+
const info = buildInfo(CCE_DERIVATION.REQUEST, input.capsule);
|
|
182
|
+
return (0, import_hkdf.hkdf)(import_sha2.sha256, ikm, salt, info, CCE_AES_KEY_BYTES);
|
|
183
|
+
}
|
|
184
|
+
function deriveResponseExecutionKey(input) {
|
|
185
|
+
const ikm = (0, import_utils.hexToBytes)(input.axisLocalSecret);
|
|
186
|
+
const encoder = new TextEncoder();
|
|
187
|
+
const saltData = encoder.encode(
|
|
188
|
+
input.capsule.capsule_id + "|" + input.capsule.capsule_nonce + "|" + input.requestNonce + "|" + input.responseNonce
|
|
189
|
+
);
|
|
190
|
+
const salt = (0, import_sha2.sha256)(saltData);
|
|
191
|
+
const info = buildInfo(
|
|
192
|
+
CCE_DERIVATION.RESPONSE,
|
|
193
|
+
input.capsule,
|
|
194
|
+
input.responseNonce
|
|
195
|
+
);
|
|
196
|
+
return (0, import_hkdf.hkdf)(import_sha2.sha256, ikm, salt, info, CCE_AES_KEY_BYTES);
|
|
197
|
+
}
|
|
198
|
+
function deriveWitnessKey(input) {
|
|
199
|
+
const ikm = (0, import_utils.hexToBytes)(input.axisLocalSecret);
|
|
200
|
+
const salt = buildSalt(
|
|
201
|
+
input.capsule.capsule_id,
|
|
202
|
+
input.capsule.capsule_nonce,
|
|
203
|
+
input.requestNonce
|
|
204
|
+
);
|
|
205
|
+
const info = buildInfo(CCE_DERIVATION.WITNESS, input.capsule);
|
|
206
|
+
return (0, import_hkdf.hkdf)(import_sha2.sha256, ikm, salt, info, CCE_AES_KEY_BYTES);
|
|
207
|
+
}
|
|
208
|
+
function buildExecutionContext(input, requestId) {
|
|
209
|
+
const executionKey = deriveRequestExecutionKey(input);
|
|
210
|
+
const keyHash = (0, import_utils.bytesToHex)((0, import_sha2.sha256)(executionKey));
|
|
211
|
+
executionKey.fill(0);
|
|
212
|
+
return {
|
|
213
|
+
execution_key_hash: keyHash,
|
|
214
|
+
request_id: requestId,
|
|
215
|
+
capsule_id: input.capsule.capsule_id,
|
|
216
|
+
sub: input.capsule.sub,
|
|
217
|
+
kid: input.capsule.kid,
|
|
218
|
+
intent: input.capsule.intent,
|
|
219
|
+
aud: input.capsule.aud,
|
|
220
|
+
tps_from: input.capsule.tps_from,
|
|
221
|
+
tps_to: input.capsule.tps_to,
|
|
222
|
+
policy_hash: input.capsule.policy_hash,
|
|
223
|
+
derived_at: Math.floor(Date.now() / 1e3),
|
|
224
|
+
valid: true
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function generateCceNonce() {
|
|
228
|
+
const bytes = new Uint8Array(CCE_NONCE_BYTES);
|
|
229
|
+
crypto.getRandomValues(bytes);
|
|
230
|
+
return (0, import_utils.bytesToHex)(bytes);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/cce/cce-crypto.ts
|
|
234
|
+
var import_utils2 = require("@noble/hashes/utils.js");
|
|
235
|
+
var import_sha22 = require("@noble/hashes/sha2.js");
|
|
236
|
+
var import_crypto = require("crypto");
|
|
237
|
+
function aesGcmEncrypt(key, plaintext, aad) {
|
|
238
|
+
if (key.length !== CCE_AES_KEY_BYTES) {
|
|
239
|
+
throw new Error(`AES key must be ${CCE_AES_KEY_BYTES} bytes`);
|
|
240
|
+
}
|
|
241
|
+
const iv = (0, import_crypto.randomBytes)(CCE_IV_BYTES);
|
|
242
|
+
const cipher = (0, import_crypto.createCipheriv)("aes-256-gcm", key, iv);
|
|
243
|
+
if (aad) {
|
|
244
|
+
cipher.setAAD(aad);
|
|
245
|
+
}
|
|
246
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
247
|
+
const tag = cipher.getAuthTag();
|
|
248
|
+
return {
|
|
249
|
+
iv: new Uint8Array(iv),
|
|
250
|
+
ciphertext: new Uint8Array(encrypted),
|
|
251
|
+
tag: new Uint8Array(tag)
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function aesGcmDecrypt(key, iv, ciphertext, tag, aad) {
|
|
255
|
+
if (key.length !== CCE_AES_KEY_BYTES) {
|
|
256
|
+
throw new Error(`AES key must be ${CCE_AES_KEY_BYTES} bytes`);
|
|
257
|
+
}
|
|
258
|
+
if (iv.length !== CCE_IV_BYTES) {
|
|
259
|
+
throw new Error(`IV must be ${CCE_IV_BYTES} bytes`);
|
|
260
|
+
}
|
|
261
|
+
if (tag.length !== CCE_TAG_BYTES) {
|
|
262
|
+
throw new Error(`Tag must be ${CCE_TAG_BYTES} bytes`);
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const decipher = (0, import_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
266
|
+
decipher.setAuthTag(tag);
|
|
267
|
+
if (aad) {
|
|
268
|
+
decipher.setAAD(aad);
|
|
269
|
+
}
|
|
270
|
+
const decrypted = Buffer.concat([
|
|
271
|
+
decipher.update(ciphertext),
|
|
272
|
+
decipher.final()
|
|
273
|
+
]);
|
|
274
|
+
return new Uint8Array(decrypted);
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function generateAesKey() {
|
|
280
|
+
return new Uint8Array((0, import_crypto.randomBytes)(CCE_AES_KEY_BYTES));
|
|
281
|
+
}
|
|
282
|
+
function generateIv() {
|
|
283
|
+
return new Uint8Array((0, import_crypto.randomBytes)(CCE_IV_BYTES));
|
|
284
|
+
}
|
|
285
|
+
function base64UrlEncode(bytes) {
|
|
286
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
287
|
+
}
|
|
288
|
+
function base64UrlDecode(input) {
|
|
289
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
290
|
+
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
291
|
+
return new Uint8Array(Buffer.from(base64 + padding, "base64"));
|
|
292
|
+
}
|
|
293
|
+
function hashPayload(payload) {
|
|
294
|
+
return (0, import_utils2.bytesToHex)((0, import_sha22.sha256)(payload));
|
|
295
|
+
}
|
|
296
|
+
var nodeAesGcmProvider = {
|
|
297
|
+
async decrypt(key, iv, ciphertext, tag, aad) {
|
|
298
|
+
return aesGcmDecrypt(key, iv, ciphertext, tag, aad);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/cce/cce-response.service.ts
|
|
303
|
+
var import_utils3 = require("@noble/hashes/utils.js");
|
|
304
|
+
var import_crypto2 = require("crypto");
|
|
305
|
+
async function buildCceResponse(options, clientKeyEncryptor, axisSigner) {
|
|
306
|
+
const { request, capsule, status, body, clientPublicKeyHex, witnessRef } = options;
|
|
307
|
+
const responseNonce = (0, import_utils3.bytesToHex)(
|
|
308
|
+
new Uint8Array((0, import_crypto2.randomBytes)(CCE_NONCE_BYTES))
|
|
309
|
+
);
|
|
310
|
+
const responseId = generateResponseId();
|
|
311
|
+
const aesKey = generateAesKey();
|
|
312
|
+
const aad = buildResponseAad(
|
|
313
|
+
request.request_id,
|
|
314
|
+
responseId,
|
|
315
|
+
request.correlation_id,
|
|
316
|
+
capsule.capsule_id,
|
|
317
|
+
responseNonce
|
|
318
|
+
);
|
|
319
|
+
const { iv, ciphertext, tag } = aesGcmEncrypt(aesKey, body, aad);
|
|
320
|
+
const encryptedKey = await clientKeyEncryptor.wrapKey(
|
|
321
|
+
aesKey,
|
|
322
|
+
request.client_kid,
|
|
323
|
+
clientPublicKeyHex
|
|
324
|
+
);
|
|
325
|
+
aesKey.fill(0);
|
|
326
|
+
const encryptedPayload = {
|
|
327
|
+
alg: "AES-256-GCM",
|
|
328
|
+
iv: base64UrlEncode(iv),
|
|
329
|
+
ciphertext: base64UrlEncode(ciphertext),
|
|
330
|
+
tag: base64UrlEncode(tag)
|
|
331
|
+
};
|
|
332
|
+
const algorithms = {
|
|
333
|
+
kem: encryptedKey.alg,
|
|
334
|
+
enc: "AES-256-GCM",
|
|
335
|
+
kdf: "HKDF-SHA256",
|
|
336
|
+
sig: "EdDSA"
|
|
337
|
+
};
|
|
338
|
+
const unsignedResponse = {
|
|
339
|
+
ver: CCE_PROTOCOL_VERSION,
|
|
340
|
+
response_id: responseId,
|
|
341
|
+
request_id: request.request_id,
|
|
342
|
+
correlation_id: request.correlation_id,
|
|
343
|
+
capsule_id: capsule.capsule_id,
|
|
344
|
+
encrypted_key: encryptedKey,
|
|
345
|
+
encrypted_payload: encryptedPayload,
|
|
346
|
+
response_nonce: responseNonce,
|
|
347
|
+
algorithms,
|
|
348
|
+
status,
|
|
349
|
+
...witnessRef ? { witness_ref: witnessRef } : {}
|
|
350
|
+
};
|
|
351
|
+
const signPayload = new TextEncoder().encode(canonicalize(unsignedResponse));
|
|
352
|
+
const axisSig = await axisSigner.sign(signPayload);
|
|
353
|
+
const envelope = {
|
|
354
|
+
...unsignedResponse,
|
|
355
|
+
axis_sig: axisSig
|
|
356
|
+
};
|
|
357
|
+
return {
|
|
358
|
+
envelope,
|
|
359
|
+
responsePayloadHash: hashPayload(body)
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function buildCceErrorResponse(requestId, correlationId, status, errorCode, message) {
|
|
363
|
+
return {
|
|
364
|
+
ver: CCE_PROTOCOL_VERSION,
|
|
365
|
+
request_id: requestId,
|
|
366
|
+
correlation_id: correlationId,
|
|
367
|
+
status,
|
|
368
|
+
error: { code: errorCode, message }
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function generateResponseId() {
|
|
372
|
+
const bytes = (0, import_crypto2.randomBytes)(16);
|
|
373
|
+
return "resp_" + (0, import_utils3.bytesToHex)(new Uint8Array(bytes)).slice(0, 24);
|
|
374
|
+
}
|
|
375
|
+
function buildResponseAad(requestId, responseId, correlationId, capsuleId, responseNonce) {
|
|
376
|
+
const parts = [
|
|
377
|
+
requestId,
|
|
378
|
+
responseId,
|
|
379
|
+
correlationId,
|
|
380
|
+
capsuleId,
|
|
381
|
+
responseNonce
|
|
382
|
+
];
|
|
383
|
+
return new TextEncoder().encode(parts.join("|"));
|
|
384
|
+
}
|
|
385
|
+
function canonicalize(obj) {
|
|
386
|
+
if (Array.isArray(obj)) {
|
|
387
|
+
return "[" + obj.map(canonicalize).join(",") + "]";
|
|
388
|
+
}
|
|
389
|
+
if (obj !== null && typeof obj === "object") {
|
|
390
|
+
const sorted = Object.keys(obj).sort().map(
|
|
391
|
+
(k) => JSON.stringify(k) + ":" + canonicalize(obj[k])
|
|
392
|
+
);
|
|
393
|
+
return "{" + sorted.join(",") + "}";
|
|
394
|
+
}
|
|
395
|
+
return JSON.stringify(obj);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/cce/cce-witness.observer.ts
|
|
399
|
+
var import_utils4 = require("@noble/hashes/utils.js");
|
|
400
|
+
var import_hkdf2 = require("@noble/hashes/hkdf.js");
|
|
401
|
+
var import_sha23 = require("@noble/hashes/sha2.js");
|
|
402
|
+
var InMemoryCceWitnessStore = class {
|
|
403
|
+
constructor() {
|
|
404
|
+
this.records = [];
|
|
405
|
+
}
|
|
406
|
+
async record(witness) {
|
|
407
|
+
this.records.push(witness);
|
|
408
|
+
}
|
|
409
|
+
getByRequestId(requestId) {
|
|
410
|
+
return this.records.find((w) => w.request_id === requestId);
|
|
411
|
+
}
|
|
412
|
+
getByCapsuleId(capsuleId) {
|
|
413
|
+
return this.records.filter((w) => w.capsule_id === capsuleId);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function buildWitnessRecord(envelope, capsule, verification, execution, options) {
|
|
417
|
+
const witnessId = generateWitnessId(envelope.request_id, capsule.capsule_id);
|
|
418
|
+
const executionContextHash = computeExecutionContextHash(
|
|
419
|
+
options.axisLocalSecret,
|
|
420
|
+
capsule,
|
|
421
|
+
envelope.request_nonce
|
|
422
|
+
);
|
|
423
|
+
return {
|
|
424
|
+
witness_id: witnessId,
|
|
425
|
+
request_id: envelope.request_id,
|
|
426
|
+
capsule_id: capsule.capsule_id,
|
|
427
|
+
sub: capsule.sub,
|
|
428
|
+
intent: capsule.intent,
|
|
429
|
+
aud: capsule.aud,
|
|
430
|
+
tps_from: capsule.tps_from,
|
|
431
|
+
tps_to: capsule.tps_to,
|
|
432
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
433
|
+
verification: {
|
|
434
|
+
client_sig: verification.clientSigVerified,
|
|
435
|
+
capsule_sig: verification.capsuleSigVerified,
|
|
436
|
+
tps_valid: verification.tpsValid,
|
|
437
|
+
audience_match: verification.audienceMatch,
|
|
438
|
+
intent_match: verification.intentMatch,
|
|
439
|
+
replay_clean: verification.replayClean,
|
|
440
|
+
nonce_unique: verification.nonceUnique,
|
|
441
|
+
decryption_ok: verification.decryptionOk
|
|
442
|
+
},
|
|
443
|
+
execution: {
|
|
444
|
+
status: execution.status,
|
|
445
|
+
handler_duration_ms: execution.handlerDurationMs,
|
|
446
|
+
...execution.effect ? { effect: execution.effect } : {}
|
|
447
|
+
},
|
|
448
|
+
response_encrypted: options.responseEncrypted,
|
|
449
|
+
execution_context_hash: executionContextHash,
|
|
450
|
+
...options.requestPayload ? { request_payload_hash: hashPayload(options.requestPayload) } : {},
|
|
451
|
+
...options.responsePayload ? { response_payload_hash: hashPayload(options.responsePayload) } : {}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function extractVerificationState(metadata) {
|
|
455
|
+
return {
|
|
456
|
+
clientSigVerified: metadata.cceClientSigVerified === true,
|
|
457
|
+
capsuleSigVerified: metadata.cceCapsuleVerified === true,
|
|
458
|
+
tpsValid: metadata.cceTpsValid === true,
|
|
459
|
+
audienceMatch: metadata.cceBindingVerified === true,
|
|
460
|
+
intentMatch: metadata.cceBindingVerified === true,
|
|
461
|
+
replayClean: metadata.cceReplayClean === true,
|
|
462
|
+
nonceUnique: metadata.cceReplayClean === true,
|
|
463
|
+
decryptionOk: metadata.cceDecryptionOk === true
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function generateWitnessId(requestId, capsuleId) {
|
|
467
|
+
const input = `witness:${requestId}:${capsuleId}:${Date.now()}`;
|
|
468
|
+
const hash = (0, import_sha23.sha256)(new TextEncoder().encode(input));
|
|
469
|
+
return "wit_" + (0, import_utils4.bytesToHex)(hash).slice(0, 24);
|
|
470
|
+
}
|
|
471
|
+
function computeExecutionContextHash(axisLocalSecret, capsule, requestNonce) {
|
|
472
|
+
const encoder = new TextEncoder();
|
|
473
|
+
const ikm = hexToBytes2(axisLocalSecret);
|
|
474
|
+
const salt = (0, import_sha23.sha256)(
|
|
475
|
+
encoder.encode(
|
|
476
|
+
capsule.capsule_id + "|" + capsule.capsule_nonce + "|" + requestNonce
|
|
477
|
+
)
|
|
478
|
+
);
|
|
479
|
+
const info = encoder.encode(
|
|
480
|
+
[
|
|
481
|
+
CCE_DERIVATION.WITNESS,
|
|
482
|
+
capsule.sub,
|
|
483
|
+
capsule.kid,
|
|
484
|
+
capsule.intent,
|
|
485
|
+
capsule.aud,
|
|
486
|
+
String(capsule.tps_from),
|
|
487
|
+
String(capsule.tps_to),
|
|
488
|
+
capsule.policy_hash ?? "",
|
|
489
|
+
capsule.ver
|
|
490
|
+
].join("|")
|
|
491
|
+
);
|
|
492
|
+
const witnessKey = (0, import_hkdf2.hkdf)(import_sha23.sha256, ikm, salt, info, 32);
|
|
493
|
+
const hash = (0, import_utils4.bytesToHex)((0, import_sha23.sha256)(witnessKey));
|
|
494
|
+
witnessKey.fill(0);
|
|
495
|
+
return hash;
|
|
496
|
+
}
|
|
497
|
+
function hexToBytes2(hex) {
|
|
498
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
499
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
500
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
501
|
+
}
|
|
502
|
+
return bytes;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/sensor/axis-sensor.ts
|
|
506
|
+
function normalizeSensorDecision(sensorDecision) {
|
|
507
|
+
if ("action" in sensorDecision) {
|
|
508
|
+
switch (sensorDecision.action) {
|
|
509
|
+
case "ALLOW":
|
|
510
|
+
return {
|
|
511
|
+
allow: true,
|
|
512
|
+
riskScore: 0,
|
|
513
|
+
reasons: [],
|
|
514
|
+
meta: sensorDecision.meta
|
|
515
|
+
};
|
|
516
|
+
case "DENY":
|
|
517
|
+
return {
|
|
518
|
+
allow: false,
|
|
519
|
+
riskScore: 100,
|
|
520
|
+
reasons: [sensorDecision.code, sensorDecision.reason].filter(
|
|
521
|
+
Boolean
|
|
522
|
+
),
|
|
523
|
+
meta: sensorDecision.meta,
|
|
524
|
+
retryAfterMs: sensorDecision.retryAfterMs
|
|
525
|
+
};
|
|
526
|
+
case "THROTTLE":
|
|
527
|
+
return {
|
|
528
|
+
allow: false,
|
|
529
|
+
riskScore: 50,
|
|
530
|
+
reasons: ["RATE_LIMIT"],
|
|
531
|
+
retryAfterMs: sensorDecision.retryAfterMs,
|
|
532
|
+
meta: sensorDecision.meta
|
|
533
|
+
};
|
|
534
|
+
case "FLAG":
|
|
535
|
+
return {
|
|
536
|
+
allow: true,
|
|
537
|
+
riskScore: sensorDecision.scoreDelta,
|
|
538
|
+
reasons: sensorDecision.reasons,
|
|
539
|
+
meta: sensorDecision.meta
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
allow: sensorDecision.allow,
|
|
545
|
+
riskScore: sensorDecision.riskScore,
|
|
546
|
+
reasons: sensorDecision.reasons,
|
|
547
|
+
tags: sensorDecision.tags,
|
|
548
|
+
meta: sensorDecision.meta,
|
|
549
|
+
tighten: sensorDecision.tighten,
|
|
550
|
+
retryAfterMs: sensorDecision.retryAfterMs
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/cce/cce-pipeline.ts
|
|
555
|
+
async function executeCcePipeline(envelope, config) {
|
|
556
|
+
const startTime = Date.now();
|
|
557
|
+
if (envelope.ver !== CCE_PROTOCOL_VERSION) {
|
|
558
|
+
return {
|
|
559
|
+
ok: false,
|
|
560
|
+
error: {
|
|
561
|
+
code: CCE_ERROR.UNSUPPORTED_VERSION,
|
|
562
|
+
message: `Unsupported version: ${envelope.ver}`
|
|
563
|
+
},
|
|
564
|
+
status: "ERROR"
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const sensorInput = {
|
|
568
|
+
intent: envelope.capsule.intent,
|
|
569
|
+
metadata: {
|
|
570
|
+
cce: true,
|
|
571
|
+
cceEnvelope: envelope,
|
|
572
|
+
contentType: "application/axis-cce"
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
const sortedSensors = [...config.sensors].sort(
|
|
576
|
+
(a, b) => (a.order ?? 999) - (b.order ?? 999)
|
|
577
|
+
);
|
|
578
|
+
for (const sensor of sortedSensors) {
|
|
579
|
+
if (sensor.supports && !sensor.supports(sensorInput)) {
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
let decision;
|
|
583
|
+
try {
|
|
584
|
+
decision = await sensor.run(sensorInput);
|
|
585
|
+
} catch (err) {
|
|
586
|
+
return {
|
|
587
|
+
ok: false,
|
|
588
|
+
error: {
|
|
589
|
+
code: CCE_ERROR.DECRYPTION_FAILED,
|
|
590
|
+
message: `Sensor ${sensor.name} failed`
|
|
591
|
+
},
|
|
592
|
+
status: "ERROR"
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const normalized = normalizeSensorDecision(decision);
|
|
596
|
+
if (!normalized.allow) {
|
|
597
|
+
const code = normalized.reasons[0]?.split(":")[0] ?? CCE_ERROR.DECRYPTION_FAILED;
|
|
598
|
+
return {
|
|
599
|
+
ok: false,
|
|
600
|
+
error: { code, message: normalized.reasons.join("; ") },
|
|
601
|
+
status: "DENIED"
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const capsule = sensorInput.metadata?.cceCapsule;
|
|
606
|
+
const decryptedPayload = sensorInput.metadata?.cceDecryptedPayload;
|
|
607
|
+
const clientKey = sensorInput.metadata?.cceClientKey;
|
|
608
|
+
if (!capsule || !decryptedPayload || !clientKey) {
|
|
609
|
+
return {
|
|
610
|
+
ok: false,
|
|
611
|
+
error: {
|
|
612
|
+
code: CCE_ERROR.DECRYPTION_FAILED,
|
|
613
|
+
message: "Sensor chain did not produce required outputs"
|
|
614
|
+
},
|
|
615
|
+
status: "ERROR"
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
const derivationInput = {
|
|
619
|
+
axisLocalSecret: config.axisLocalSecret,
|
|
620
|
+
capsule,
|
|
621
|
+
requestNonce: envelope.request_nonce
|
|
622
|
+
};
|
|
623
|
+
const executionContext = buildExecutionContext(
|
|
624
|
+
derivationInput,
|
|
625
|
+
envelope.request_id
|
|
626
|
+
);
|
|
627
|
+
if (config.policyEvaluator) {
|
|
628
|
+
try {
|
|
629
|
+
const policyDecision = await config.policyEvaluator.evaluate({
|
|
630
|
+
envelope,
|
|
631
|
+
capsule,
|
|
632
|
+
executionContext,
|
|
633
|
+
decryptedPayload,
|
|
634
|
+
clientPublicKeyHex: clientKey.publicKeyHex
|
|
635
|
+
});
|
|
636
|
+
if (!policyDecision.allow) {
|
|
637
|
+
const verification2 = extractVerificationState(sensorInput.metadata ?? {});
|
|
638
|
+
const witness2 = buildWitnessRecord(
|
|
639
|
+
envelope,
|
|
640
|
+
capsule,
|
|
641
|
+
verification2,
|
|
642
|
+
{
|
|
643
|
+
status: "DENIED",
|
|
644
|
+
handlerDurationMs: 0,
|
|
645
|
+
effect: "policy_denied"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
axisLocalSecret: config.axisLocalSecret,
|
|
649
|
+
requestPayload: decryptedPayload,
|
|
650
|
+
responseEncrypted: false
|
|
651
|
+
}
|
|
652
|
+
);
|
|
653
|
+
await config.witnessStore.record(witness2);
|
|
654
|
+
return {
|
|
655
|
+
ok: false,
|
|
656
|
+
error: {
|
|
657
|
+
code: policyDecision.code ?? CCE_ERROR.POLICY_DENIED,
|
|
658
|
+
message: policyDecision.message ?? "Request denied by policy evaluator"
|
|
659
|
+
},
|
|
660
|
+
status: "DENIED"
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
} catch (err) {
|
|
664
|
+
return {
|
|
665
|
+
ok: false,
|
|
666
|
+
error: {
|
|
667
|
+
code: CCE_ERROR.POLICY_DENIED,
|
|
668
|
+
message: "Policy evaluator failed"
|
|
669
|
+
},
|
|
670
|
+
status: "ERROR"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const handler = config.handlers.get(capsule.intent);
|
|
675
|
+
if (!handler) {
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
error: {
|
|
679
|
+
code: CCE_ERROR.HANDLER_NOT_FOUND,
|
|
680
|
+
message: `No handler for intent: ${capsule.intent}`
|
|
681
|
+
},
|
|
682
|
+
status: "ERROR"
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
const handlerContext = {
|
|
686
|
+
capsule,
|
|
687
|
+
executionContext,
|
|
688
|
+
envelope,
|
|
689
|
+
clientPublicKeyHex: clientKey.publicKeyHex,
|
|
690
|
+
intent: capsule.intent,
|
|
691
|
+
sub: capsule.sub
|
|
692
|
+
};
|
|
693
|
+
let result;
|
|
694
|
+
const handlerStart = Date.now();
|
|
695
|
+
try {
|
|
696
|
+
result = await handler(decryptedPayload, handlerContext);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
const handlerDuration2 = Date.now() - handlerStart;
|
|
699
|
+
const verification2 = extractVerificationState(sensorInput.metadata ?? {});
|
|
700
|
+
const witness2 = buildWitnessRecord(
|
|
701
|
+
envelope,
|
|
702
|
+
capsule,
|
|
703
|
+
verification2,
|
|
704
|
+
{ status: "FAILED", handlerDurationMs: handlerDuration2 },
|
|
705
|
+
{
|
|
706
|
+
axisLocalSecret: config.axisLocalSecret,
|
|
707
|
+
requestPayload: decryptedPayload,
|
|
708
|
+
responseEncrypted: false
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
await config.witnessStore.record(witness2);
|
|
712
|
+
return {
|
|
713
|
+
ok: false,
|
|
714
|
+
error: {
|
|
715
|
+
code: CCE_ERROR.HANDLER_EXECUTION_FAILED,
|
|
716
|
+
message: "Handler execution failed"
|
|
717
|
+
},
|
|
718
|
+
status: "FAILED"
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const handlerDuration = Date.now() - handlerStart;
|
|
722
|
+
let responseEnvelope;
|
|
723
|
+
let responsePayloadHash;
|
|
724
|
+
try {
|
|
725
|
+
const responseResult = await buildCceResponse(
|
|
726
|
+
{
|
|
727
|
+
request: envelope,
|
|
728
|
+
capsule,
|
|
729
|
+
status: result.status,
|
|
730
|
+
body: result.body,
|
|
731
|
+
clientPublicKeyHex: clientKey.publicKeyHex
|
|
732
|
+
},
|
|
733
|
+
config.clientKeyEncryptor,
|
|
734
|
+
config.axisSigner
|
|
735
|
+
);
|
|
736
|
+
responseEnvelope = responseResult.envelope;
|
|
737
|
+
responsePayloadHash = responseResult.responsePayloadHash;
|
|
738
|
+
} catch (err) {
|
|
739
|
+
return {
|
|
740
|
+
ok: false,
|
|
741
|
+
error: {
|
|
742
|
+
code: CCE_ERROR.RESPONSE_ENCRYPTION_FAILED,
|
|
743
|
+
message: "Response encryption failed"
|
|
744
|
+
},
|
|
745
|
+
status: "ERROR"
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const verification = extractVerificationState(sensorInput.metadata ?? {});
|
|
749
|
+
const witness = buildWitnessRecord(
|
|
750
|
+
envelope,
|
|
751
|
+
capsule,
|
|
752
|
+
verification,
|
|
753
|
+
{
|
|
754
|
+
status: result.status,
|
|
755
|
+
handlerDurationMs: handlerDuration,
|
|
756
|
+
effect: result.effect
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
axisLocalSecret: config.axisLocalSecret,
|
|
760
|
+
requestPayload: decryptedPayload,
|
|
761
|
+
responsePayload: result.body,
|
|
762
|
+
responseEncrypted: true
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
await config.witnessStore.record(witness);
|
|
766
|
+
return {
|
|
767
|
+
ok: true,
|
|
768
|
+
response: responseEnvelope,
|
|
769
|
+
witnessId: witness.witness_id
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/cce/sensors/cce-envelope-validation.sensor.ts
|
|
774
|
+
var REQUIRED_FIELDS = [
|
|
775
|
+
"ver",
|
|
776
|
+
"request_id",
|
|
777
|
+
"correlation_id",
|
|
778
|
+
"client_kid",
|
|
779
|
+
"capsule",
|
|
780
|
+
"encrypted_key",
|
|
781
|
+
"encrypted_payload",
|
|
782
|
+
"request_nonce",
|
|
783
|
+
"client_sig",
|
|
784
|
+
"content_type",
|
|
785
|
+
"algorithms"
|
|
786
|
+
];
|
|
787
|
+
var CceEnvelopeValidationSensor = class {
|
|
788
|
+
constructor() {
|
|
789
|
+
this.name = "cce.envelope.validation";
|
|
790
|
+
this.order = 5;
|
|
791
|
+
this.phase = "PRE_DECODE";
|
|
792
|
+
}
|
|
793
|
+
supports(input) {
|
|
794
|
+
return input.metadata?.cce === true || input.metadata?.contentType === "application/axis-cce";
|
|
795
|
+
}
|
|
796
|
+
async run(input) {
|
|
797
|
+
const envelope = input.metadata?.cceEnvelope;
|
|
798
|
+
if (!envelope) {
|
|
799
|
+
return {
|
|
800
|
+
allow: false,
|
|
801
|
+
riskScore: 100,
|
|
802
|
+
reasons: [CCE_ERROR.INVALID_ENVELOPE],
|
|
803
|
+
code: CCE_ERROR.INVALID_ENVELOPE
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
for (const field of REQUIRED_FIELDS) {
|
|
807
|
+
if (envelope[field] === void 0 || envelope[field] === null) {
|
|
808
|
+
return {
|
|
809
|
+
allow: false,
|
|
810
|
+
riskScore: 100,
|
|
811
|
+
reasons: [`${CCE_ERROR.INVALID_ENVELOPE}: missing ${field}`],
|
|
812
|
+
code: CCE_ERROR.INVALID_ENVELOPE
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (envelope.ver !== CCE_PROTOCOL_VERSION) {
|
|
817
|
+
return {
|
|
818
|
+
allow: false,
|
|
819
|
+
riskScore: 100,
|
|
820
|
+
reasons: [`${CCE_ERROR.UNSUPPORTED_VERSION}: ${envelope.ver}`],
|
|
821
|
+
code: CCE_ERROR.UNSUPPORTED_VERSION
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (!/^[0-9a-f]+$/i.test(envelope.request_nonce)) {
|
|
825
|
+
return {
|
|
826
|
+
allow: false,
|
|
827
|
+
riskScore: 100,
|
|
828
|
+
reasons: [
|
|
829
|
+
`${CCE_ERROR.INVALID_ENVELOPE}: invalid request_nonce format`
|
|
830
|
+
],
|
|
831
|
+
code: CCE_ERROR.INVALID_ENVELOPE
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
if (envelope.request_nonce.length !== CCE_NONCE_BYTES * 2) {
|
|
835
|
+
return {
|
|
836
|
+
allow: false,
|
|
837
|
+
riskScore: 100,
|
|
838
|
+
reasons: [`${CCE_ERROR.INVALID_ENVELOPE}: request_nonce wrong length`],
|
|
839
|
+
code: CCE_ERROR.INVALID_ENVELOPE
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const capsule = envelope.capsule;
|
|
843
|
+
if (!capsule.capsule_id || !capsule.ver || !capsule.sub || !capsule.kid || !capsule.intent || !capsule.aud || !capsule.issuer_sig) {
|
|
844
|
+
return {
|
|
845
|
+
allow: false,
|
|
846
|
+
riskScore: 100,
|
|
847
|
+
reasons: [`${CCE_ERROR.MISSING_CAPSULE}: incomplete capsule claims`],
|
|
848
|
+
code: CCE_ERROR.MISSING_CAPSULE
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
if (!envelope.encrypted_key.ciphertext || !envelope.encrypted_key.alg) {
|
|
852
|
+
return {
|
|
853
|
+
allow: false,
|
|
854
|
+
riskScore: 100,
|
|
855
|
+
reasons: [
|
|
856
|
+
`${CCE_ERROR.MISSING_ENCRYPTED_KEY}: incomplete encrypted_key`
|
|
857
|
+
],
|
|
858
|
+
code: CCE_ERROR.MISSING_ENCRYPTED_KEY
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
input.metadata = input.metadata ?? {};
|
|
862
|
+
input.metadata.cceEnvelopeValid = true;
|
|
863
|
+
return {
|
|
864
|
+
decision: "ALLOW" /* ALLOW */,
|
|
865
|
+
allow: true,
|
|
866
|
+
riskScore: 0,
|
|
867
|
+
reasons: []
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
// src/cce/sensors/cce-client-signature.sensor.ts
|
|
873
|
+
var CceClientSignatureSensor = class {
|
|
874
|
+
constructor(keyResolver, signatureVerifier) {
|
|
875
|
+
this.keyResolver = keyResolver;
|
|
876
|
+
this.signatureVerifier = signatureVerifier;
|
|
877
|
+
this.name = "cce.client.signature";
|
|
878
|
+
this.order = 45;
|
|
879
|
+
this.phase = "POST_DECODE";
|
|
880
|
+
}
|
|
881
|
+
supports(input) {
|
|
882
|
+
return input.metadata?.cceEnvelopeValid === true;
|
|
883
|
+
}
|
|
884
|
+
async run(input) {
|
|
885
|
+
const envelope = input.metadata?.cceEnvelope;
|
|
886
|
+
if (!envelope) {
|
|
887
|
+
return {
|
|
888
|
+
allow: false,
|
|
889
|
+
riskScore: 100,
|
|
890
|
+
reasons: [CCE_ERROR.INVALID_ENVELOPE],
|
|
891
|
+
code: CCE_ERROR.INVALID_ENVELOPE
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
const keyRecord = await this.keyResolver.resolve(envelope.client_kid);
|
|
895
|
+
if (!keyRecord) {
|
|
896
|
+
return {
|
|
897
|
+
allow: false,
|
|
898
|
+
riskScore: 100,
|
|
899
|
+
reasons: [
|
|
900
|
+
`${CCE_ERROR.CLIENT_KEY_NOT_FOUND}: kid=${envelope.client_kid}`
|
|
901
|
+
],
|
|
902
|
+
code: CCE_ERROR.CLIENT_KEY_NOT_FOUND
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
const { client_sig, ...signable } = envelope;
|
|
906
|
+
const canonical = canonicalize2(signable);
|
|
907
|
+
const message = new TextEncoder().encode(canonical);
|
|
908
|
+
const valid = await this.signatureVerifier.verify(
|
|
909
|
+
message,
|
|
910
|
+
client_sig.value,
|
|
911
|
+
keyRecord.publicKeyHex,
|
|
912
|
+
keyRecord.alg
|
|
913
|
+
);
|
|
914
|
+
if (!valid) {
|
|
915
|
+
return {
|
|
916
|
+
allow: false,
|
|
917
|
+
riskScore: 100,
|
|
918
|
+
reasons: [CCE_ERROR.CLIENT_SIG_INVALID],
|
|
919
|
+
code: CCE_ERROR.CLIENT_SIG_INVALID
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
input.metadata = input.metadata ?? {};
|
|
923
|
+
input.metadata.cceClientKey = keyRecord;
|
|
924
|
+
input.metadata.cceClientSigVerified = true;
|
|
925
|
+
return {
|
|
926
|
+
decision: "ALLOW" /* ALLOW */,
|
|
927
|
+
allow: true,
|
|
928
|
+
riskScore: 0,
|
|
929
|
+
reasons: [],
|
|
930
|
+
meta: { kid: envelope.client_kid }
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
function canonicalize2(obj) {
|
|
935
|
+
if (Array.isArray(obj)) {
|
|
936
|
+
return "[" + obj.map(canonicalize2).join(",") + "]";
|
|
937
|
+
}
|
|
938
|
+
if (obj !== null && typeof obj === "object") {
|
|
939
|
+
const sorted = Object.keys(obj).sort().map(
|
|
940
|
+
(k) => JSON.stringify(k) + ":" + canonicalize2(obj[k])
|
|
941
|
+
);
|
|
942
|
+
return "{" + sorted.join(",") + "}";
|
|
943
|
+
}
|
|
944
|
+
return JSON.stringify(obj);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/cce/sensors/cce-capsule-verification.sensor.ts
|
|
948
|
+
var import_blake3 = require("@noble/hashes/blake3.js");
|
|
949
|
+
var import_utils5 = require("@noble/hashes/utils.js");
|
|
950
|
+
var CceCapsuleVerificationSensor = class {
|
|
951
|
+
constructor(issuerKeyResolver, capsuleVerifier) {
|
|
952
|
+
this.issuerKeyResolver = issuerKeyResolver;
|
|
953
|
+
this.capsuleVerifier = capsuleVerifier;
|
|
954
|
+
this.name = "cce.capsule.verification";
|
|
955
|
+
this.order = 50;
|
|
956
|
+
this.phase = "POST_DECODE";
|
|
957
|
+
}
|
|
958
|
+
supports(input) {
|
|
959
|
+
return input.metadata?.cceEnvelopeValid === true;
|
|
960
|
+
}
|
|
961
|
+
async run(input) {
|
|
962
|
+
const capsule = input.metadata?.cceEnvelope?.capsule;
|
|
963
|
+
if (!capsule) {
|
|
964
|
+
return {
|
|
965
|
+
allow: false,
|
|
966
|
+
riskScore: 100,
|
|
967
|
+
reasons: [CCE_ERROR.MISSING_CAPSULE],
|
|
968
|
+
code: CCE_ERROR.MISSING_CAPSULE
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
if (capsule.ver !== CCE_PROTOCOL_VERSION) {
|
|
972
|
+
return {
|
|
973
|
+
allow: false,
|
|
974
|
+
riskScore: 100,
|
|
975
|
+
reasons: [
|
|
976
|
+
`${CCE_ERROR.CAPSULE_SIG_INVALID}: wrong version ${capsule.ver}`
|
|
977
|
+
],
|
|
978
|
+
code: CCE_ERROR.CAPSULE_SIG_INVALID
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
const { capsule_id, issuer_sig, ...claimsBody } = capsule;
|
|
982
|
+
const expectedId = computeCceCapsuleId(claimsBody);
|
|
983
|
+
if (capsule_id !== expectedId) {
|
|
984
|
+
return {
|
|
985
|
+
allow: false,
|
|
986
|
+
riskScore: 100,
|
|
987
|
+
reasons: [`${CCE_ERROR.CAPSULE_SIG_INVALID}: content hash mismatch`],
|
|
988
|
+
code: CCE_ERROR.CAPSULE_SIG_INVALID
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
const issuerKey = await this.issuerKeyResolver.resolve(
|
|
992
|
+
capsule.issuer_sig.kid
|
|
993
|
+
);
|
|
994
|
+
if (!issuerKey) {
|
|
995
|
+
return {
|
|
996
|
+
allow: false,
|
|
997
|
+
riskScore: 100,
|
|
998
|
+
reasons: [`${CCE_ERROR.CAPSULE_SIG_INVALID}: issuer key not found`],
|
|
999
|
+
code: CCE_ERROR.CAPSULE_SIG_INVALID
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
const { issuer_sig: sig, ...rest } = capsule;
|
|
1003
|
+
const sigValid = await this.capsuleVerifier.verify(
|
|
1004
|
+
rest,
|
|
1005
|
+
sig,
|
|
1006
|
+
issuerKey.publicKeyHex
|
|
1007
|
+
);
|
|
1008
|
+
if (!sigValid) {
|
|
1009
|
+
return {
|
|
1010
|
+
allow: false,
|
|
1011
|
+
riskScore: 100,
|
|
1012
|
+
reasons: [CCE_ERROR.CAPSULE_SIG_INVALID],
|
|
1013
|
+
code: CCE_ERROR.CAPSULE_SIG_INVALID
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
1017
|
+
if (capsule.exp < nowSeconds) {
|
|
1018
|
+
return {
|
|
1019
|
+
allow: false,
|
|
1020
|
+
riskScore: 100,
|
|
1021
|
+
reasons: [`${CCE_ERROR.CAPSULE_EXPIRED}: exp=${capsule.exp}`],
|
|
1022
|
+
code: CCE_ERROR.CAPSULE_EXPIRED
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
if (capsule.iat > nowSeconds + 5) {
|
|
1026
|
+
return {
|
|
1027
|
+
allow: false,
|
|
1028
|
+
riskScore: 100,
|
|
1029
|
+
reasons: [`${CCE_ERROR.CAPSULE_NOT_YET_VALID}: iat=${capsule.iat}`],
|
|
1030
|
+
code: CCE_ERROR.CAPSULE_NOT_YET_VALID
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
input.metadata = input.metadata ?? {};
|
|
1034
|
+
input.metadata.cceCapsuleVerified = true;
|
|
1035
|
+
input.metadata.cceCapsule = capsule;
|
|
1036
|
+
return {
|
|
1037
|
+
decision: "ALLOW" /* ALLOW */,
|
|
1038
|
+
allow: true,
|
|
1039
|
+
riskScore: 0,
|
|
1040
|
+
reasons: [],
|
|
1041
|
+
meta: { capsule_id: capsule.capsule_id }
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
function canonicalize3(obj) {
|
|
1046
|
+
if (Array.isArray(obj)) {
|
|
1047
|
+
return "[" + obj.map(canonicalize3).join(",") + "]";
|
|
1048
|
+
}
|
|
1049
|
+
if (obj !== null && typeof obj === "object") {
|
|
1050
|
+
const sorted = Object.keys(obj).sort().map(
|
|
1051
|
+
(k) => JSON.stringify(k) + ":" + canonicalize3(obj[k])
|
|
1052
|
+
);
|
|
1053
|
+
return "{" + sorted.join(",") + "}";
|
|
1054
|
+
}
|
|
1055
|
+
return JSON.stringify(obj);
|
|
1056
|
+
}
|
|
1057
|
+
function computeCceCapsuleId(claims) {
|
|
1058
|
+
const canonical = canonicalize3(claims);
|
|
1059
|
+
const hash = (0, import_blake3.blake3)(new TextEncoder().encode(canonical));
|
|
1060
|
+
return "cce_b3_" + (0, import_utils5.bytesToHex)(hash).slice(0, 32);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/cce/sensors/cce-tps-window.sensor.ts
|
|
1064
|
+
var DEFAULT_SKEW_MS = 5e3;
|
|
1065
|
+
var CceTpsWindowSensor = class {
|
|
1066
|
+
constructor(skewMs = DEFAULT_SKEW_MS) {
|
|
1067
|
+
this.skewMs = skewMs;
|
|
1068
|
+
this.name = "cce.tps.window";
|
|
1069
|
+
this.order = 92;
|
|
1070
|
+
this.phase = "POST_DECODE";
|
|
1071
|
+
}
|
|
1072
|
+
supports(input) {
|
|
1073
|
+
return input.metadata?.cceCapsuleVerified === true;
|
|
1074
|
+
}
|
|
1075
|
+
async run(input) {
|
|
1076
|
+
const capsule = input.metadata?.cceCapsule;
|
|
1077
|
+
if (!capsule) {
|
|
1078
|
+
return {
|
|
1079
|
+
allow: false,
|
|
1080
|
+
riskScore: 100,
|
|
1081
|
+
reasons: [CCE_ERROR.MISSING_CAPSULE],
|
|
1082
|
+
code: CCE_ERROR.MISSING_CAPSULE
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const nowMs = Date.now();
|
|
1086
|
+
if (nowMs > capsule.tps_to + this.skewMs) {
|
|
1087
|
+
return {
|
|
1088
|
+
allow: false,
|
|
1089
|
+
riskScore: 100,
|
|
1090
|
+
reasons: [
|
|
1091
|
+
`${CCE_ERROR.TPS_WINDOW_EXPIRED}: window ended at ${capsule.tps_to}, now=${nowMs}`
|
|
1092
|
+
],
|
|
1093
|
+
code: CCE_ERROR.TPS_WINDOW_EXPIRED
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
if (nowMs < capsule.tps_from - this.skewMs) {
|
|
1097
|
+
return {
|
|
1098
|
+
allow: false,
|
|
1099
|
+
riskScore: 100,
|
|
1100
|
+
reasons: [
|
|
1101
|
+
`${CCE_ERROR.TPS_WINDOW_FUTURE}: window starts at ${capsule.tps_from}, now=${nowMs}`
|
|
1102
|
+
],
|
|
1103
|
+
code: CCE_ERROR.TPS_WINDOW_FUTURE
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
input.metadata = input.metadata ?? {};
|
|
1107
|
+
input.metadata.cceTpsValid = true;
|
|
1108
|
+
return {
|
|
1109
|
+
decision: "ALLOW" /* ALLOW */,
|
|
1110
|
+
allow: true,
|
|
1111
|
+
riskScore: 0,
|
|
1112
|
+
reasons: []
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
// src/cce/sensors/cce-audience-intent-binding.sensor.ts
|
|
1118
|
+
var CceAudienceIntentBindingSensor = class {
|
|
1119
|
+
constructor(axisAudience) {
|
|
1120
|
+
this.axisAudience = axisAudience;
|
|
1121
|
+
this.name = "cce.audience.intent.binding";
|
|
1122
|
+
this.order = 95;
|
|
1123
|
+
this.phase = "POST_DECODE";
|
|
1124
|
+
}
|
|
1125
|
+
supports(input) {
|
|
1126
|
+
return input.metadata?.cceCapsuleVerified === true;
|
|
1127
|
+
}
|
|
1128
|
+
async run(input) {
|
|
1129
|
+
const capsule = input.metadata?.cceCapsule;
|
|
1130
|
+
const envelope = input.metadata?.cceEnvelope;
|
|
1131
|
+
if (!capsule || !envelope) {
|
|
1132
|
+
return {
|
|
1133
|
+
allow: false,
|
|
1134
|
+
riskScore: 100,
|
|
1135
|
+
reasons: [CCE_ERROR.MISSING_CAPSULE],
|
|
1136
|
+
code: CCE_ERROR.MISSING_CAPSULE
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
if (capsule.aud !== this.axisAudience) {
|
|
1140
|
+
return {
|
|
1141
|
+
allow: false,
|
|
1142
|
+
riskScore: 100,
|
|
1143
|
+
reasons: [
|
|
1144
|
+
`${CCE_ERROR.AUDIENCE_MISMATCH}: capsule.aud=${capsule.aud}, expected=${this.axisAudience}`
|
|
1145
|
+
],
|
|
1146
|
+
code: CCE_ERROR.AUDIENCE_MISMATCH
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
const requestIntent = input.intent ?? input.metadata?.cceRequestIntent;
|
|
1150
|
+
if (requestIntent && capsule.intent !== requestIntent) {
|
|
1151
|
+
return {
|
|
1152
|
+
allow: false,
|
|
1153
|
+
riskScore: 100,
|
|
1154
|
+
reasons: [
|
|
1155
|
+
`${CCE_ERROR.INTENT_MISMATCH}: capsule.intent=${capsule.intent}, request=${requestIntent}`
|
|
1156
|
+
],
|
|
1157
|
+
code: CCE_ERROR.INTENT_MISMATCH
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
if (envelope.client_kid !== capsule.kid) {
|
|
1161
|
+
return {
|
|
1162
|
+
allow: false,
|
|
1163
|
+
riskScore: 100,
|
|
1164
|
+
reasons: [
|
|
1165
|
+
`${CCE_ERROR.INTENT_MISMATCH}: envelope.kid=${envelope.client_kid}, capsule.kid=${capsule.kid}`
|
|
1166
|
+
],
|
|
1167
|
+
code: CCE_ERROR.INTENT_MISMATCH
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
input.metadata = input.metadata ?? {};
|
|
1171
|
+
input.metadata.cceBindingVerified = true;
|
|
1172
|
+
return {
|
|
1173
|
+
decision: "ALLOW" /* ALLOW */,
|
|
1174
|
+
allow: true,
|
|
1175
|
+
riskScore: 0,
|
|
1176
|
+
reasons: []
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// src/cce/sensors/cce-replay-protection.sensor.ts
|
|
1182
|
+
var InMemoryCceReplayStore = class {
|
|
1183
|
+
constructor() {
|
|
1184
|
+
this.nonces = /* @__PURE__ */ new Map();
|
|
1185
|
+
this.consumed = /* @__PURE__ */ new Set();
|
|
1186
|
+
this.revoked = /* @__PURE__ */ new Set();
|
|
1187
|
+
}
|
|
1188
|
+
async checkAndMark(key, ttlMs) {
|
|
1189
|
+
this.cleanup();
|
|
1190
|
+
if (this.nonces.has(key)) return false;
|
|
1191
|
+
this.nonces.set(key, Date.now() + ttlMs);
|
|
1192
|
+
return true;
|
|
1193
|
+
}
|
|
1194
|
+
async isCapsuleConsumed(capsuleId) {
|
|
1195
|
+
return this.consumed.has(capsuleId);
|
|
1196
|
+
}
|
|
1197
|
+
async markCapsuleConsumed(capsuleId, _ttlMs) {
|
|
1198
|
+
this.consumed.add(capsuleId);
|
|
1199
|
+
}
|
|
1200
|
+
async isCapsuleRevoked(capsuleId) {
|
|
1201
|
+
return this.revoked.has(capsuleId);
|
|
1202
|
+
}
|
|
1203
|
+
/** Revoke a capsule (for testing/admin) */
|
|
1204
|
+
revoke(capsuleId) {
|
|
1205
|
+
this.revoked.add(capsuleId);
|
|
1206
|
+
}
|
|
1207
|
+
cleanup() {
|
|
1208
|
+
const now = Date.now();
|
|
1209
|
+
for (const [key, expiresAt] of this.nonces) {
|
|
1210
|
+
if (expiresAt < now) this.nonces.delete(key);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
var CceReplayProtectionSensor = class {
|
|
1215
|
+
constructor(replayStore, options) {
|
|
1216
|
+
this.replayStore = replayStore;
|
|
1217
|
+
this.name = "cce.replay.protection";
|
|
1218
|
+
this.order = 98;
|
|
1219
|
+
this.phase = "POST_DECODE";
|
|
1220
|
+
this.nonceTtlMs = options?.nonceTtlMs ?? 5 * 60 * 1e3;
|
|
1221
|
+
}
|
|
1222
|
+
supports(input) {
|
|
1223
|
+
return input.metadata?.cceCapsuleVerified === true;
|
|
1224
|
+
}
|
|
1225
|
+
async run(input) {
|
|
1226
|
+
const capsule = input.metadata?.cceCapsule;
|
|
1227
|
+
const envelope = input.metadata?.cceEnvelope;
|
|
1228
|
+
if (!capsule || !envelope) {
|
|
1229
|
+
return {
|
|
1230
|
+
allow: false,
|
|
1231
|
+
riskScore: 100,
|
|
1232
|
+
reasons: [CCE_ERROR.MISSING_CAPSULE],
|
|
1233
|
+
code: CCE_ERROR.MISSING_CAPSULE
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
const revoked = await this.replayStore.isCapsuleRevoked(capsule.capsule_id);
|
|
1237
|
+
if (revoked) {
|
|
1238
|
+
return {
|
|
1239
|
+
allow: false,
|
|
1240
|
+
riskScore: 100,
|
|
1241
|
+
reasons: [`${CCE_ERROR.CAPSULE_REVOKED}: ${capsule.capsule_id}`],
|
|
1242
|
+
code: CCE_ERROR.CAPSULE_REVOKED
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
if (capsule.mode === "SINGLE_USE") {
|
|
1246
|
+
const consumed = await this.replayStore.isCapsuleConsumed(
|
|
1247
|
+
capsule.capsule_id
|
|
1248
|
+
);
|
|
1249
|
+
if (consumed) {
|
|
1250
|
+
return {
|
|
1251
|
+
allow: false,
|
|
1252
|
+
riskScore: 100,
|
|
1253
|
+
reasons: [`${CCE_ERROR.CAPSULE_CONSUMED}: ${capsule.capsule_id}`],
|
|
1254
|
+
code: CCE_ERROR.CAPSULE_CONSUMED
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
const nonceKey = `cce:nonce:${capsule.sub}:${capsule.aud}:${capsule.intent}:${envelope.request_nonce}`;
|
|
1259
|
+
const nonceValid = await this.replayStore.checkAndMark(
|
|
1260
|
+
nonceKey,
|
|
1261
|
+
this.nonceTtlMs
|
|
1262
|
+
);
|
|
1263
|
+
if (!nonceValid) {
|
|
1264
|
+
return {
|
|
1265
|
+
allow: false,
|
|
1266
|
+
riskScore: 100,
|
|
1267
|
+
reasons: [
|
|
1268
|
+
`${CCE_ERROR.NONCE_REUSED}: ${envelope.request_nonce.slice(0, 16)}...`
|
|
1269
|
+
],
|
|
1270
|
+
code: CCE_ERROR.NONCE_REUSED
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
if (capsule.mode === "SINGLE_USE") {
|
|
1274
|
+
const capsuleTtl = (capsule.exp - capsule.iat) * 1e3 + 6e4;
|
|
1275
|
+
await this.replayStore.markCapsuleConsumed(
|
|
1276
|
+
capsule.capsule_id,
|
|
1277
|
+
capsuleTtl
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
input.metadata = input.metadata ?? {};
|
|
1281
|
+
input.metadata.cceReplayClean = true;
|
|
1282
|
+
return {
|
|
1283
|
+
decision: "ALLOW" /* ALLOW */,
|
|
1284
|
+
allow: true,
|
|
1285
|
+
riskScore: 0,
|
|
1286
|
+
reasons: []
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
// src/cce/sensors/cce-payload-decryption.sensor.ts
|
|
1292
|
+
var CcePayloadDecryptionSensor = class {
|
|
1293
|
+
constructor(keyProvider, aesProvider, maxPayloadBytes = 64 * 1024, payloadValidator) {
|
|
1294
|
+
this.keyProvider = keyProvider;
|
|
1295
|
+
this.aesProvider = aesProvider;
|
|
1296
|
+
this.maxPayloadBytes = maxPayloadBytes;
|
|
1297
|
+
this.payloadValidator = payloadValidator;
|
|
1298
|
+
this.name = "cce.payload.decryption";
|
|
1299
|
+
this.order = 145;
|
|
1300
|
+
this.phase = "POST_DECODE";
|
|
1301
|
+
}
|
|
1302
|
+
supports(input) {
|
|
1303
|
+
return input.metadata?.cceEnvelopeValid === true && input.metadata?.cceClientSigVerified === true && input.metadata?.cceCapsuleVerified === true && input.metadata?.cceReplayClean === true;
|
|
1304
|
+
}
|
|
1305
|
+
async run(input) {
|
|
1306
|
+
const envelope = input.metadata?.cceEnvelope;
|
|
1307
|
+
if (!envelope) {
|
|
1308
|
+
return {
|
|
1309
|
+
allow: false,
|
|
1310
|
+
riskScore: 100,
|
|
1311
|
+
reasons: [CCE_ERROR.INVALID_ENVELOPE],
|
|
1312
|
+
code: CCE_ERROR.INVALID_ENVELOPE
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
let aesKey;
|
|
1316
|
+
try {
|
|
1317
|
+
aesKey = await this.keyProvider.unwrapKey(
|
|
1318
|
+
envelope.encrypted_key.ciphertext,
|
|
1319
|
+
envelope.encrypted_key.alg,
|
|
1320
|
+
envelope.encrypted_key.axis_kid,
|
|
1321
|
+
envelope.encrypted_key.ephemeral_pk
|
|
1322
|
+
);
|
|
1323
|
+
} catch {
|
|
1324
|
+
return {
|
|
1325
|
+
allow: false,
|
|
1326
|
+
riskScore: 100,
|
|
1327
|
+
reasons: [CCE_ERROR.KEY_UNWRAP_FAILED],
|
|
1328
|
+
code: CCE_ERROR.KEY_UNWRAP_FAILED
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
if (!aesKey) {
|
|
1332
|
+
return {
|
|
1333
|
+
allow: false,
|
|
1334
|
+
riskScore: 100,
|
|
1335
|
+
reasons: [CCE_ERROR.KEY_UNWRAP_FAILED],
|
|
1336
|
+
code: CCE_ERROR.KEY_UNWRAP_FAILED
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
let iv;
|
|
1340
|
+
let ciphertext;
|
|
1341
|
+
let tag;
|
|
1342
|
+
try {
|
|
1343
|
+
iv = base64UrlDecode2(envelope.encrypted_payload.iv);
|
|
1344
|
+
ciphertext = base64UrlDecode2(envelope.encrypted_payload.ciphertext);
|
|
1345
|
+
tag = base64UrlDecode2(envelope.encrypted_payload.tag);
|
|
1346
|
+
} catch {
|
|
1347
|
+
return {
|
|
1348
|
+
allow: false,
|
|
1349
|
+
riskScore: 100,
|
|
1350
|
+
reasons: [`${CCE_ERROR.DECRYPTION_FAILED}: invalid base64url encoding`],
|
|
1351
|
+
code: CCE_ERROR.DECRYPTION_FAILED
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
if (ciphertext.length > this.maxPayloadBytes) {
|
|
1355
|
+
return {
|
|
1356
|
+
allow: false,
|
|
1357
|
+
riskScore: 100,
|
|
1358
|
+
reasons: [
|
|
1359
|
+
`${CCE_ERROR.PAYLOAD_TOO_LARGE}: ${ciphertext.length} > ${this.maxPayloadBytes}`
|
|
1360
|
+
],
|
|
1361
|
+
code: CCE_ERROR.PAYLOAD_TOO_LARGE
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
const aad = buildAad(envelope);
|
|
1365
|
+
let plaintext;
|
|
1366
|
+
try {
|
|
1367
|
+
plaintext = await this.aesProvider.decrypt(
|
|
1368
|
+
aesKey,
|
|
1369
|
+
iv,
|
|
1370
|
+
ciphertext,
|
|
1371
|
+
tag,
|
|
1372
|
+
aad
|
|
1373
|
+
);
|
|
1374
|
+
} catch {
|
|
1375
|
+
plaintext = null;
|
|
1376
|
+
} finally {
|
|
1377
|
+
aesKey.fill(0);
|
|
1378
|
+
}
|
|
1379
|
+
if (!plaintext) {
|
|
1380
|
+
return {
|
|
1381
|
+
allow: false,
|
|
1382
|
+
riskScore: 100,
|
|
1383
|
+
reasons: [CCE_ERROR.AEAD_TAG_MISMATCH],
|
|
1384
|
+
code: CCE_ERROR.AEAD_TAG_MISMATCH
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
const capsule = input.metadata?.cceCapsule;
|
|
1388
|
+
if (capsule && isJsonContentType(envelope.content_type)) {
|
|
1389
|
+
const parsed = tryParseJsonObject(plaintext);
|
|
1390
|
+
if (parsed && typeof parsed.intent === "string") {
|
|
1391
|
+
if (parsed.intent !== capsule.intent) {
|
|
1392
|
+
return {
|
|
1393
|
+
allow: false,
|
|
1394
|
+
riskScore: 100,
|
|
1395
|
+
reasons: [
|
|
1396
|
+
`${CCE_ERROR.INTENT_SCHEMA_MISMATCH}: payload.intent=${parsed.intent}, capsule.intent=${capsule.intent}`
|
|
1397
|
+
],
|
|
1398
|
+
code: CCE_ERROR.INTENT_SCHEMA_MISMATCH
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
input.metadata = input.metadata ?? {};
|
|
1402
|
+
input.metadata.cceRequestIntent = parsed.intent;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (this.payloadValidator) {
|
|
1406
|
+
const verdict = await this.payloadValidator.validate(plaintext, envelope);
|
|
1407
|
+
if (!verdict.ok) {
|
|
1408
|
+
const code = verdict.code ?? CCE_ERROR.PAYLOAD_SCHEMA_INVALID;
|
|
1409
|
+
return {
|
|
1410
|
+
allow: false,
|
|
1411
|
+
riskScore: 100,
|
|
1412
|
+
reasons: [verdict.reason ?? code],
|
|
1413
|
+
code
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
if (verdict.intent) {
|
|
1417
|
+
input.metadata = input.metadata ?? {};
|
|
1418
|
+
input.metadata.cceRequestIntent = verdict.intent;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
input.metadata = input.metadata ?? {};
|
|
1422
|
+
input.metadata.cceDecryptedPayload = plaintext;
|
|
1423
|
+
input.metadata.cceDecryptionOk = true;
|
|
1424
|
+
return {
|
|
1425
|
+
decision: "ALLOW" /* ALLOW */,
|
|
1426
|
+
allow: true,
|
|
1427
|
+
riskScore: 0,
|
|
1428
|
+
reasons: []
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
function buildAad(envelope) {
|
|
1433
|
+
const parts = [
|
|
1434
|
+
envelope.ver,
|
|
1435
|
+
envelope.request_id,
|
|
1436
|
+
envelope.correlation_id,
|
|
1437
|
+
envelope.client_kid,
|
|
1438
|
+
envelope.capsule.capsule_id,
|
|
1439
|
+
envelope.capsule.intent,
|
|
1440
|
+
envelope.capsule.aud,
|
|
1441
|
+
envelope.request_nonce
|
|
1442
|
+
];
|
|
1443
|
+
return new TextEncoder().encode(parts.join("|"));
|
|
1444
|
+
}
|
|
1445
|
+
function isJsonContentType(contentType) {
|
|
1446
|
+
return typeof contentType === "string" && contentType.toLowerCase().includes("application/json");
|
|
1447
|
+
}
|
|
1448
|
+
function tryParseJsonObject(payload) {
|
|
1449
|
+
try {
|
|
1450
|
+
const parsed = JSON.parse(new TextDecoder().decode(payload));
|
|
1451
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1452
|
+
return parsed;
|
|
1453
|
+
}
|
|
1454
|
+
return null;
|
|
1455
|
+
} catch {
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function base64UrlDecode2(input) {
|
|
1460
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
1461
|
+
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
1462
|
+
return new Uint8Array(Buffer.from(base64 + padding, "base64"));
|
|
1463
|
+
}
|
|
1464
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1465
|
+
0 && (module.exports = {
|
|
1466
|
+
CCE_AES_KEY_BYTES,
|
|
1467
|
+
CCE_DERIVATION,
|
|
1468
|
+
CCE_ERROR,
|
|
1469
|
+
CCE_IV_BYTES,
|
|
1470
|
+
CCE_NONCE_BYTES,
|
|
1471
|
+
CCE_PROTOCOL_VERSION,
|
|
1472
|
+
CCE_TAG_BYTES,
|
|
1473
|
+
CceAudienceIntentBindingSensor,
|
|
1474
|
+
CceCapsuleVerificationSensor,
|
|
1475
|
+
CceClientSignatureSensor,
|
|
1476
|
+
CceEnvelopeValidationSensor,
|
|
1477
|
+
CceError,
|
|
1478
|
+
CcePayloadDecryptionSensor,
|
|
1479
|
+
CceReplayProtectionSensor,
|
|
1480
|
+
CceTpsWindowSensor,
|
|
1481
|
+
InMemoryCceReplayStore,
|
|
1482
|
+
InMemoryCceWitnessStore,
|
|
1483
|
+
aesGcmDecrypt,
|
|
1484
|
+
aesGcmEncrypt,
|
|
1485
|
+
base64UrlDecode,
|
|
1486
|
+
base64UrlEncode,
|
|
1487
|
+
buildCceErrorResponse,
|
|
1488
|
+
buildCceResponse,
|
|
1489
|
+
buildExecutionContext,
|
|
1490
|
+
buildWitnessRecord,
|
|
1491
|
+
deriveRequestExecutionKey,
|
|
1492
|
+
deriveResponseExecutionKey,
|
|
1493
|
+
deriveWitnessKey,
|
|
1494
|
+
executeCcePipeline,
|
|
1495
|
+
extractVerificationState,
|
|
1496
|
+
generateAesKey,
|
|
1497
|
+
generateCceNonce,
|
|
1498
|
+
generateIv,
|
|
1499
|
+
hashPayload,
|
|
1500
|
+
nodeAesGcmProvider
|
|
1501
|
+
});
|
|
1502
|
+
//# sourceMappingURL=index.js.map
|