@reclaimprotocol/attestor-core 5.0.1-beta.2 → 5.0.1-beta.22
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/browser/resources/attestor-browser.min.mjs +4512 -0
- package/lib/avs/abis/avsDirectoryABI.js +338 -341
- package/lib/avs/abis/delegationABI.js +1 -4
- package/lib/avs/abis/registryABI.js +719 -722
- package/lib/avs/client/create-claim-on-avs.js +129 -157
- package/lib/avs/config.js +18 -24
- package/lib/avs/contracts/ReclaimServiceManager.js +1 -0
- package/lib/avs/contracts/common.js +1 -0
- package/lib/avs/contracts/factories/ReclaimServiceManager__factory.js +1139 -1156
- package/lib/avs/contracts/factories/index.js +4 -4
- package/lib/avs/contracts/index.js +2 -6
- package/lib/avs/types/index.js +1 -0
- package/lib/avs/utils/contracts.js +30 -50
- package/lib/avs/utils/register.js +75 -70
- package/lib/avs/utils/tasks.js +38 -45
- package/lib/client/create-claim.js +402 -431
- package/lib/client/tunnels/make-rpc-tcp-tunnel.js +46 -48
- package/lib/client/tunnels/make-rpc-tls-tunnel.js +125 -121
- package/lib/client/utils/attestor-pool.js +23 -22
- package/lib/client/utils/client-socket.js +86 -109
- package/lib/client/utils/message-handler.js +79 -89
- package/lib/config/index.js +40 -58
- package/lib/external-rpc/benchmark.js +61 -74
- package/lib/external-rpc/event-bus.js +12 -15
- package/lib/external-rpc/handle-incoming-msg.js +216 -225
- package/lib/external-rpc/jsc-polyfills/1.js +70 -68
- package/lib/external-rpc/jsc-polyfills/2.js +17 -12
- package/lib/external-rpc/jsc-polyfills/event.js +10 -15
- package/lib/external-rpc/jsc-polyfills/index.js +2 -2
- package/lib/external-rpc/jsc-polyfills/ws.js +77 -79
- package/lib/external-rpc/setup-browser.js +28 -28
- package/lib/external-rpc/setup-jsc.js +17 -17
- package/lib/external-rpc/types.js +1 -0
- package/lib/external-rpc/utils.js +89 -89
- package/lib/external-rpc/zk.js +55 -50
- package/lib/index.js +2 -6
- package/lib/mechain/abis/governanceABI.js +457 -460
- package/lib/mechain/abis/taskABI.js +502 -505
- package/lib/mechain/client/create-claim-on-mechain.js +24 -29
- package/lib/mechain/constants/index.js +3 -8
- package/lib/mechain/types/index.js +1 -0
- package/lib/proto/api.js +4200 -4087
- package/lib/proto/tee-bundle.js +1261 -1241
- package/lib/providers/http/index.js +616 -603
- package/lib/providers/http/patch-parse5-tree.js +27 -29
- package/lib/providers/http/utils.js +289 -248
- package/lib/providers/index.js +3 -6
- package/lib/server/create-server.js +89 -91
- package/lib/server/handlers/claimTeeBundle.js +231 -211
- package/lib/server/handlers/claimTunnel.js +66 -73
- package/lib/server/handlers/completeClaimOnChain.js +20 -25
- package/lib/server/handlers/createClaimOnChain.js +21 -27
- package/lib/server/handlers/createTaskOnMechain.js +40 -50
- package/lib/server/handlers/createTunnel.js +85 -90
- package/lib/server/handlers/disconnectTunnel.js +4 -7
- package/lib/server/handlers/fetchCertificateBytes.js +37 -53
- package/lib/server/handlers/index.js +21 -24
- package/lib/server/handlers/init.js +27 -28
- package/lib/server/handlers/toprf.js +13 -16
- package/lib/server/socket.js +97 -100
- package/lib/server/tunnels/make-tcp-tunnel.js +161 -186
- package/lib/server/utils/apm.js +32 -25
- package/lib/server/utils/assert-valid-claim-request.js +305 -334
- package/lib/server/utils/config-env.js +2 -2
- package/lib/server/utils/dns.js +12 -18
- package/lib/server/utils/gcp-attestation.js +233 -181
- package/lib/server/utils/generics.d.ts +1 -1
- package/lib/server/utils/generics.js +43 -37
- package/lib/server/utils/iso.js +253 -256
- package/lib/server/utils/keep-alive.js +36 -36
- package/lib/server/utils/nitro-attestation.js +295 -220
- package/lib/server/utils/oprf-raw.js +48 -55
- package/lib/server/utils/process-handshake.js +200 -218
- package/lib/server/utils/proxy-session.js +5 -5
- package/lib/server/utils/tee-oprf-mpc-verification.js +82 -78
- package/lib/server/utils/tee-oprf-verification.js +165 -142
- package/lib/server/utils/tee-transcript-reconstruction.js +176 -129
- package/lib/server/utils/tee-verification.js +397 -334
- package/lib/server/utils/validation.js +30 -37
- package/lib/types/bgp.js +1 -0
- package/lib/types/claims.js +1 -0
- package/lib/types/client.js +1 -0
- package/lib/types/general.js +1 -0
- package/lib/types/handlers.js +1 -0
- package/lib/types/providers.d.ts +3 -2
- package/lib/types/providers.gen.js +9 -15
- package/lib/types/providers.js +1 -0
- package/lib/types/rpc.js +1 -0
- package/lib/types/signatures.d.ts +1 -2
- package/lib/types/signatures.js +1 -0
- package/lib/types/tunnel.js +1 -0
- package/lib/types/zk.js +1 -0
- package/lib/utils/auth.js +54 -66
- package/lib/utils/b64-json.js +15 -15
- package/lib/utils/bgp-listener.js +107 -111
- package/lib/utils/claims.js +89 -80
- package/lib/utils/env.js +13 -17
- package/lib/utils/error.js +43 -47
- package/lib/utils/generics.js +284 -235
- package/lib/utils/http-parser.js +232 -187
- package/lib/utils/logger.js +80 -71
- package/lib/utils/prepare-packets.js +69 -67
- package/lib/utils/redactions.js +163 -121
- package/lib/utils/retries.js +22 -24
- package/lib/utils/signatures/eth.js +29 -28
- package/lib/utils/signatures/index.js +5 -10
- package/lib/utils/socket-base.js +84 -88
- package/lib/utils/tls.js +28 -28
- package/lib/utils/ws.js +19 -19
- package/lib/utils/zk.js +542 -582
- package/package.json +12 -5
- package/lib/external-rpc/global.d.js +0 -0
- package/lib/scripts/build-browser.d.ts +0 -1
- package/lib/scripts/build-jsc.d.ts +0 -1
- package/lib/scripts/build-lib.d.ts +0 -1
- package/lib/scripts/check-avs-registration.d.ts +0 -1
- package/lib/scripts/check-avs-registration.js +0 -28
- package/lib/scripts/fallbacks/crypto.d.ts +0 -1
- package/lib/scripts/fallbacks/crypto.js +0 -4
- package/lib/scripts/fallbacks/empty.d.ts +0 -3
- package/lib/scripts/fallbacks/empty.js +0 -4
- package/lib/scripts/fallbacks/re2.d.ts +0 -1
- package/lib/scripts/fallbacks/re2.js +0 -7
- package/lib/scripts/fallbacks/snarkjs.d.ts +0 -1
- package/lib/scripts/fallbacks/snarkjs.js +0 -10
- package/lib/scripts/fallbacks/stwo.d.ts +0 -6
- package/lib/scripts/fallbacks/stwo.js +0 -159
- package/lib/scripts/generate-provider-types.d.ts +0 -5
- package/lib/scripts/generate-provider-types.js +0 -101
- package/lib/scripts/generate-receipt.d.ts +0 -9
- package/lib/scripts/generate-receipt.js +0 -101
- package/lib/scripts/generate-toprf-keys.d.ts +0 -1
- package/lib/scripts/generate-toprf-keys.js +0 -24
- package/lib/scripts/jsc-cli-rpc.d.ts +0 -1
- package/lib/scripts/jsc-cli-rpc.js +0 -35
- package/lib/scripts/register-avs-operator.d.ts +0 -1
- package/lib/scripts/register-avs-operator.js +0 -3
- package/lib/scripts/start-server.d.ts +0 -1
- package/lib/scripts/start-server.js +0 -11
- package/lib/scripts/update-avs-metadata.d.ts +0 -1
- package/lib/scripts/update-avs-metadata.js +0 -20
- package/lib/scripts/utils.d.ts +0 -1
- package/lib/scripts/utils.js +0 -10
- package/lib/scripts/whitelist-operator.d.ts +0 -1
- package/lib/scripts/whitelist-operator.js +0 -16
|
@@ -1,358 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TEE Bundle verification utilities
|
|
3
|
+
* Handles validation of TEE verification bundles including attestations and signatures
|
|
4
|
+
*/
|
|
1
5
|
import { ServiceSignatureType } from "../../proto/api.js";
|
|
2
6
|
import { BodyType, KOutputPayload, TOutputPayload, VerificationBundle } from "../../proto/tee-bundle.js";
|
|
3
|
-
import { validateGcpAttestationAndExtractKey } from "
|
|
4
|
-
import { validateNitroAttestationAndExtractKey } from "
|
|
7
|
+
import { validateGcpAttestationAndExtractKey } from "./gcp-attestation.js";
|
|
8
|
+
import { validateNitroAttestationAndExtractKey } from "./nitro-attestation.js";
|
|
5
9
|
import { AttestorError } from "../../utils/error.js";
|
|
6
10
|
import { SIGNATURES } from "../../utils/signatures/index.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Verifies a complete TEE verification bundle
|
|
13
|
+
* @param bundleBytes - Raw protobuf-encoded verification bundle
|
|
14
|
+
* @param logger - Logger instance
|
|
15
|
+
* @returns Validated TEE bundle data
|
|
16
|
+
*/
|
|
17
|
+
export async function verifyTeeBundle(bundleBytes, logger) {
|
|
18
|
+
// Parse the verification bundle protobuf
|
|
19
|
+
const bundle = parseVerificationBundle(bundleBytes);
|
|
20
|
+
// Validate required components are present
|
|
21
|
+
validateBundleCompleteness(bundle);
|
|
22
|
+
// Extract public keys (from attestations or embedded keys)
|
|
23
|
+
const { teekKeyResult, teetKeyResult } = await extractPublicKeys(bundle, logger);
|
|
24
|
+
// Verify TEE signatures using extracted public keys
|
|
25
|
+
await verifyTeeSignatures(bundle, teekKeyResult, teetKeyResult, logger);
|
|
26
|
+
// Ensure signed messages are present
|
|
27
|
+
if (!bundle.teekSigned || !bundle.teetSigned) {
|
|
28
|
+
throw new AttestorError('ERROR_INVALID_CLAIM', 'Missing TEE signed messages');
|
|
29
|
+
}
|
|
30
|
+
// Parse TEE payloads
|
|
31
|
+
const kOutputPayload = parseKOutputPayload(bundle.teekSigned);
|
|
32
|
+
const tOutputPayload = parseTOutputPayload(bundle.teetSigned);
|
|
33
|
+
// Validate timestamps
|
|
34
|
+
validateTimestamps(kOutputPayload, tOutputPayload, logger);
|
|
35
|
+
// Validate session IDs match (prevents cross-session component substitution)
|
|
36
|
+
const teeSessionId = validateSessionIds(kOutputPayload, tOutputPayload, logger);
|
|
37
|
+
logger.info('TEE bundle verification successful');
|
|
38
|
+
return {
|
|
39
|
+
teekSigned: bundle.teekSigned,
|
|
40
|
+
teetSigned: bundle.teetSigned,
|
|
41
|
+
kOutputPayload,
|
|
42
|
+
tOutputPayload,
|
|
43
|
+
teekPcr0: teekKeyResult.pcr0,
|
|
44
|
+
teetPcr0: teetKeyResult.pcr0,
|
|
45
|
+
teeSessionId,
|
|
46
|
+
};
|
|
29
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Parses the raw verification bundle bytes into structured data
|
|
50
|
+
*/
|
|
30
51
|
function parseVerificationBundle(bundleBytes) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
try {
|
|
53
|
+
// Use the actual protobuf decoder for the TEE bundle format
|
|
54
|
+
return VerificationBundle.decode(bundleBytes);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new Error(`Failed to parse verification bundle: ${error.message}`);
|
|
58
|
+
}
|
|
36
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Validates that all required bundle components are present
|
|
62
|
+
*/
|
|
37
63
|
function validateBundleCompleteness(bundle) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
if (!bundle.teekSigned) {
|
|
65
|
+
throw new Error('SECURITY ERROR: missing TEE_K signed message - verification bundle incomplete');
|
|
66
|
+
}
|
|
67
|
+
if (!bundle.teetSigned) {
|
|
68
|
+
throw new Error('SECURITY ERROR: missing TEE_T signed message - verification bundle incomplete');
|
|
69
|
+
}
|
|
70
|
+
// Check if we're in standalone mode (development/testing) or attestation mode (production)
|
|
71
|
+
// Attestations are now embedded in SignedMessage.attestationReport
|
|
72
|
+
const hasAttestations = (bundle.teekSigned.attestationReport?.report && bundle.teekSigned.attestationReport.report.length > 0) ||
|
|
73
|
+
(bundle.teetSigned.attestationReport?.report && bundle.teetSigned.attestationReport.report.length > 0);
|
|
74
|
+
const hasPublicKeys = (bundle.teekSigned.ethAddress && bundle.teekSigned.ethAddress.length > 0) ||
|
|
75
|
+
(bundle.teetSigned.ethAddress && bundle.teetSigned.ethAddress.length > 0);
|
|
76
|
+
if (!hasAttestations && !hasPublicKeys) {
|
|
77
|
+
throw new Error('SECURITY ERROR: bundle must have either Nitro attestations (production) or embedded public keys (development)');
|
|
78
|
+
}
|
|
79
|
+
// Validate signed message structure
|
|
80
|
+
if (bundle.teekSigned.bodyType !== BodyType.BODY_TYPE_K_OUTPUT) {
|
|
81
|
+
throw new Error('Invalid TEE_K signed message: wrong body type');
|
|
82
|
+
}
|
|
83
|
+
if (bundle.teetSigned.bodyType !== BodyType.BODY_TYPE_T_OUTPUT) {
|
|
84
|
+
throw new Error('Invalid TEE_T signed message: wrong body type');
|
|
85
|
+
}
|
|
86
|
+
if (!bundle.teekSigned.body || bundle.teekSigned.body.length === 0) {
|
|
87
|
+
throw new Error('Invalid TEE_K signed message: empty body');
|
|
88
|
+
}
|
|
89
|
+
if (!bundle.teetSigned.body || bundle.teetSigned.body.length === 0) {
|
|
90
|
+
throw new Error('Invalid TEE_T signed message: empty body');
|
|
91
|
+
}
|
|
92
|
+
if (!bundle.teekSigned.signature || bundle.teekSigned.signature.length === 0) {
|
|
93
|
+
throw new Error('Invalid TEE_K signed message: missing signature');
|
|
94
|
+
}
|
|
95
|
+
if (!bundle.teetSigned.signature || bundle.teetSigned.signature.length === 0) {
|
|
96
|
+
throw new Error('Invalid TEE_T signed message: missing signature');
|
|
97
|
+
}
|
|
67
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Extracts public keys from either Nitro attestations or embedded keys (standalone mode)
|
|
101
|
+
*/
|
|
68
102
|
async function extractPublicKeys(bundle, logger) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
103
|
+
// Check if we have attestations (production mode) or embedded keys (standalone mode)
|
|
104
|
+
// Attestations are now embedded in SignedMessage.attestationReport
|
|
105
|
+
const hasEmbeddedAttestations = (bundle.teekSigned.attestationReport?.report && bundle.teekSigned.attestationReport.report.length > 0) &&
|
|
106
|
+
(bundle.teetSigned.attestationReport?.report && bundle.teetSigned.attestationReport.report.length > 0);
|
|
107
|
+
let teekKeyResult;
|
|
108
|
+
let teetKeyResult;
|
|
109
|
+
if (hasEmbeddedAttestations) {
|
|
110
|
+
// Production mode: Extract from attestations (Nitro or GCP)
|
|
111
|
+
logger.info('Using production mode: extracting keys from attestations');
|
|
112
|
+
if (!bundle.teekSigned?.attestationReport?.report) {
|
|
113
|
+
throw new Error('TEE_K embedded attestation report missing');
|
|
114
|
+
}
|
|
115
|
+
if (!bundle.teetSigned?.attestationReport?.report) {
|
|
116
|
+
throw new Error('TEE_T embedded attestation report missing');
|
|
117
|
+
}
|
|
118
|
+
const teekAttestationType = bundle.teekSigned.attestationReport.type || 'nitro';
|
|
119
|
+
const teetAttestationType = bundle.teetSigned.attestationReport.type || 'nitro';
|
|
120
|
+
logger.info(`TEE_K attestation type: ${teekAttestationType}`);
|
|
121
|
+
logger.info(`TEE_T attestation type: ${teetAttestationType}`);
|
|
122
|
+
const teekAttestationBytes = bundle.teekSigned.attestationReport.report;
|
|
123
|
+
const teetAttestationBytes = bundle.teetSigned.attestationReport.report;
|
|
124
|
+
// Validate TEE_K attestation based on type
|
|
125
|
+
if (teekAttestationType === 'gcp') {
|
|
126
|
+
const gcpResult = await validateGcpAttestationAndExtractKey(teekAttestationBytes, logger);
|
|
127
|
+
if (!gcpResult.isValid) {
|
|
128
|
+
throw new Error(`TEE_K GCP attestation validation failed: ${gcpResult.errors.join(', ')}`);
|
|
129
|
+
}
|
|
130
|
+
if (!gcpResult.ethAddress) {
|
|
131
|
+
throw new Error('TEE_K GCP attestation validation failed: no address');
|
|
132
|
+
}
|
|
133
|
+
if (gcpResult.userDataType !== 'tee_k') {
|
|
134
|
+
throw new Error(`TEE_K GCP attestation validation failed: wrong TEE type, expected tee_k, got ${gcpResult.userDataType}`);
|
|
135
|
+
}
|
|
136
|
+
teekKeyResult = {
|
|
137
|
+
teeType: gcpResult.userDataType,
|
|
138
|
+
ethAddress: '0x' + Buffer.from(gcpResult.ethAddress).toString('hex'),
|
|
139
|
+
pcr0: gcpResult.pcr0 || 'gcp-no-digest'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const nitroResult = await validateNitroAttestationAndExtractKey(teekAttestationBytes);
|
|
144
|
+
if (!nitroResult.isValid) {
|
|
145
|
+
throw new Error(`TEE_K Nitro attestation validation failed: ${nitroResult.errors.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
if (!nitroResult.ethAddress) {
|
|
148
|
+
throw new Error('TEE_K Nitro attestation validation failed: no address');
|
|
149
|
+
}
|
|
150
|
+
if (nitroResult.userDataType !== 'tee_k') {
|
|
151
|
+
throw new Error(`TEE_K Nitro attestation validation failed: wrong TEE type, expected tee_k, got ${nitroResult.userDataType}`);
|
|
152
|
+
}
|
|
153
|
+
teekKeyResult = {
|
|
154
|
+
teeType: nitroResult.userDataType,
|
|
155
|
+
ethAddress: nitroResult.ethAddress,
|
|
156
|
+
pcr0: nitroResult.pcr0
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Validate TEE_T attestation based on type
|
|
160
|
+
if (teetAttestationType === 'gcp') {
|
|
161
|
+
const gcpResult = await validateGcpAttestationAndExtractKey(teetAttestationBytes, logger);
|
|
162
|
+
if (!gcpResult.isValid) {
|
|
163
|
+
throw new Error(`TEE_T GCP attestation validation failed: ${gcpResult.errors.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
if (!gcpResult.ethAddress) {
|
|
166
|
+
throw new Error('TEE_T GCP attestation validation failed: no address');
|
|
167
|
+
}
|
|
168
|
+
if (gcpResult.userDataType !== 'tee_t') {
|
|
169
|
+
throw new Error(`TEE_T GCP attestation validation failed: wrong TEE type, expected tee_t, got ${gcpResult.userDataType}`);
|
|
170
|
+
}
|
|
171
|
+
teetKeyResult = {
|
|
172
|
+
teeType: gcpResult.userDataType,
|
|
173
|
+
ethAddress: '0x' + Buffer.from(gcpResult.ethAddress).toString('hex'),
|
|
174
|
+
pcr0: gcpResult.pcr0 || 'gcp-no-digest'
|
|
175
|
+
};
|
|
176
|
+
// Cross-validate: TEE_T must have EXPECTED_TEEK_PCR0 env var matching TEE_K's PCR0
|
|
177
|
+
if (!gcpResult.envVars?.EXPECTED_TEEK_PCR0) {
|
|
178
|
+
throw new Error('TEE_T GCP attestation missing required EXPECTED_TEEK_PCR0 environment variable');
|
|
179
|
+
}
|
|
180
|
+
const expectedPcr0 = gcpResult.envVars.EXPECTED_TEEK_PCR0;
|
|
181
|
+
const actualPcr0 = teekKeyResult.pcr0;
|
|
182
|
+
logger.info(`Cross-validating TEE_K PCR0: expected=${expectedPcr0}, actual=${actualPcr0}`);
|
|
183
|
+
if (expectedPcr0 !== actualPcr0) {
|
|
184
|
+
throw new Error(`TEE cross-validation failed: TEE_T expects TEE_K PCR0 "${expectedPcr0}" but got "${actualPcr0}"`);
|
|
185
|
+
}
|
|
186
|
+
logger.info('TEE cross-validation successful: TEE_K PCR0 matches TEE_T expectation');
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const nitroResult = await validateNitroAttestationAndExtractKey(teetAttestationBytes);
|
|
190
|
+
if (!nitroResult.isValid) {
|
|
191
|
+
throw new Error(`TEE_T Nitro attestation validation failed: ${nitroResult.errors.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
if (!nitroResult.ethAddress) {
|
|
194
|
+
throw new Error('TEE_T Nitro attestation validation failed: no address');
|
|
195
|
+
}
|
|
196
|
+
if (nitroResult.userDataType !== 'tee_t') {
|
|
197
|
+
throw new Error(`TEE_T Nitro attestation validation failed: wrong TEE type, expected tee_t, got ${nitroResult.userDataType}`);
|
|
198
|
+
}
|
|
199
|
+
teetKeyResult = {
|
|
200
|
+
teeType: nitroResult.userDataType,
|
|
201
|
+
ethAddress: nitroResult.ethAddress,
|
|
202
|
+
pcr0: nitroResult.pcr0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
logger.info('Attestations validated successfully');
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Standalone/Development mode: Extract from embedded ETH addresses
|
|
209
|
+
// SECURITY: Only allow standalone mode when explicitly enabled via environment variable
|
|
210
|
+
const standaloneEnabled = process.env.TEE_STANDALONE === 'true' || process.env.TEE_STANDALONE === '1';
|
|
211
|
+
if (!standaloneEnabled) {
|
|
212
|
+
throw new Error('Missing attestation reports and standalone mode is not enabled (set TEE_STANDALONE=true to enable)');
|
|
213
|
+
}
|
|
214
|
+
const hasEmbeddedKeys = (bundle.teekSigned.ethAddress && bundle.teekSigned.ethAddress.length > 0) &&
|
|
215
|
+
(bundle.teetSigned.ethAddress && bundle.teetSigned.ethAddress.length > 0);
|
|
216
|
+
if (!hasEmbeddedKeys) {
|
|
217
|
+
throw new Error('Missing attestation and no embedded ETH addresses for standalone mode');
|
|
218
|
+
}
|
|
219
|
+
logger.warn('STANDALONE MODE ENABLED: Using embedded ETH addresses without attestation verification');
|
|
220
|
+
// Extract TEE_K address (stored as UTF-8 string like "0xe3c8d66...")
|
|
221
|
+
const teekAddress = Buffer.from(bundle.teekSigned.ethAddress).toString('utf8');
|
|
222
|
+
teekKeyResult = {
|
|
223
|
+
teeType: 'tee_k',
|
|
224
|
+
ethAddress: teekAddress,
|
|
225
|
+
pcr0: 'standalone-mode' // No PCR0 in standalone mode
|
|
226
|
+
};
|
|
227
|
+
logger.info(`TEE_K standalone address: ${teekAddress}`);
|
|
228
|
+
// Extract TEE_T address (stored as UTF-8 string like "0x3b8ad67...")
|
|
229
|
+
const teetAddress = Buffer.from(bundle.teetSigned.ethAddress).toString('utf8');
|
|
230
|
+
teetKeyResult = {
|
|
231
|
+
teeType: 'tee_t',
|
|
232
|
+
ethAddress: teetAddress,
|
|
233
|
+
pcr0: 'standalone-mode' // No PCR0 in standalone mode
|
|
234
|
+
};
|
|
235
|
+
logger.info(`TEE_T standalone address: ${teetAddress}`);
|
|
236
|
+
logger.info('Standalone mode key extraction successful');
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
teekKeyResult,
|
|
240
|
+
teetKeyResult
|
|
187
241
|
};
|
|
188
|
-
logger.info(`TEE_T standalone address: ${teetAddress}`);
|
|
189
|
-
logger.info("Standalone mode key extraction successful");
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
teekKeyResult,
|
|
193
|
-
teetKeyResult
|
|
194
|
-
};
|
|
195
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Verifies TEE signatures using extracted key results
|
|
245
|
+
*/
|
|
196
246
|
async function verifyTeeSignatures(bundle, teekKeyResult, teetKeyResult, logger) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
bundle.teekSigned,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
teetKeyResult,
|
|
215
|
-
"TEE_T",
|
|
216
|
-
logger
|
|
217
|
-
);
|
|
218
|
-
if (!teetResult.isValid) {
|
|
219
|
-
throw new Error(`TEE_T signature verification failed: ${teetResult.errors.join(", ")}`);
|
|
220
|
-
}
|
|
221
|
-
logger.info("TEE signatures verified successfully");
|
|
247
|
+
// Verify TEE_K signature
|
|
248
|
+
if (!bundle.teekSigned) {
|
|
249
|
+
throw new Error('TEE_K signed message is missing');
|
|
250
|
+
}
|
|
251
|
+
const teekResult = await verifyTeeSignature(bundle.teekSigned, teekKeyResult, 'TEE_K', logger);
|
|
252
|
+
if (!teekResult.isValid) {
|
|
253
|
+
throw new Error(`TEE_K signature verification failed: ${teekResult.errors.join(', ')}`);
|
|
254
|
+
}
|
|
255
|
+
// Verify TEE_T signature
|
|
256
|
+
if (!bundle.teetSigned) {
|
|
257
|
+
throw new Error('TEE_T signed message is missing');
|
|
258
|
+
}
|
|
259
|
+
const teetResult = await verifyTeeSignature(bundle.teetSigned, teetKeyResult, 'TEE_T', logger);
|
|
260
|
+
if (!teetResult.isValid) {
|
|
261
|
+
throw new Error(`TEE_T signature verification failed: ${teetResult.errors.join(', ')}`);
|
|
262
|
+
}
|
|
263
|
+
logger.info('TEE signatures verified successfully');
|
|
222
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Verifies a single TEE signature using ETH address format
|
|
267
|
+
*/
|
|
223
268
|
async function verifyTeeSignature(signedMessage, extractedKey, teeType, logger) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
const errors = [];
|
|
270
|
+
if (!signedMessage) {
|
|
271
|
+
return {
|
|
272
|
+
isValid: false,
|
|
273
|
+
errors: ['Signed message is null or undefined']
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
let ethAddress;
|
|
278
|
+
if (extractedKey.ethAddress) {
|
|
279
|
+
ethAddress = extractedKey.ethAddress;
|
|
280
|
+
logger.debug(`${teeType} using ETH address from attestation: ${ethAddress}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
return {
|
|
284
|
+
isValid: false,
|
|
285
|
+
errors: ['eth address is null'],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
// Use the ETH signature verification from the existing system
|
|
289
|
+
const { verify: verifySig } = SIGNATURES[ServiceSignatureType.SERVICE_SIGNATURE_TYPE_ETH];
|
|
290
|
+
// Verify signature over the body bytes
|
|
291
|
+
const isValid = await verifySig(signedMessage.body, signedMessage.signature, ethAddress);
|
|
292
|
+
if (!isValid) {
|
|
293
|
+
errors.push(`${teeType} signature verification failed for address ${ethAddress}`);
|
|
294
|
+
}
|
|
295
|
+
logger.debug(`${teeType} signature verification result: ${isValid} for address ${ethAddress}`);
|
|
296
|
+
return {
|
|
297
|
+
isValid: errors.length === 0,
|
|
298
|
+
errors,
|
|
299
|
+
address: extractedKey.ethAddress
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
errors.push(`${teeType} signature verification error: ${error.message}`);
|
|
304
|
+
return {
|
|
305
|
+
isValid: false,
|
|
306
|
+
errors
|
|
307
|
+
};
|
|
308
|
+
}
|
|
264
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Parses TEE_K output payload
|
|
312
|
+
*/
|
|
265
313
|
function parseKOutputPayload(signedMessage) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
314
|
+
// Use actual protobuf decoding
|
|
315
|
+
const payload = KOutputPayload.decode(signedMessage.body);
|
|
316
|
+
// Validate required fields
|
|
317
|
+
if (!payload.redactedRequest) {
|
|
318
|
+
throw new Error('Missing redacted request in TEE_K payload');
|
|
319
|
+
}
|
|
320
|
+
if (!payload.consolidatedResponseKeystream || payload.consolidatedResponseKeystream.length === 0) {
|
|
321
|
+
throw new Error('Missing consolidated response keystream in TEE_K payload');
|
|
322
|
+
}
|
|
323
|
+
if (!payload.certificateInfo) {
|
|
324
|
+
throw new Error('Missing certificate info in TEE_K payload');
|
|
325
|
+
}
|
|
326
|
+
return payload;
|
|
277
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Parses TEE_T output payload
|
|
330
|
+
*/
|
|
278
331
|
function parseTOutputPayload(signedMessage) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
332
|
+
// Use actual protobuf decoding
|
|
333
|
+
const payload = TOutputPayload.decode(signedMessage.body);
|
|
334
|
+
// Validate required fields
|
|
335
|
+
if (!payload.consolidatedResponseCiphertext || payload.consolidatedResponseCiphertext.length === 0) {
|
|
336
|
+
throw new Error('Missing consolidated response ciphertext in TEE_T payload');
|
|
337
|
+
}
|
|
338
|
+
return payload;
|
|
284
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Validates that both K and T timestamps are within acceptable range
|
|
342
|
+
* @param kPayload - K output payload with timestamp
|
|
343
|
+
* @param tPayload - T output payload with timestamp
|
|
344
|
+
* @param logger - Logger instance
|
|
345
|
+
*/
|
|
285
346
|
function validateTimestamps(kPayload, tPayload, logger) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
timestampDiffMs
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
347
|
+
const now = Date.now(); // Current time in milliseconds
|
|
348
|
+
const kTimestamp = kPayload.timestampMs;
|
|
349
|
+
const tTimestamp = tPayload.timestampMs;
|
|
350
|
+
// Convert to seconds for logging consistency with existing code
|
|
351
|
+
const kTimestampS = Math.floor(kTimestamp / 1000);
|
|
352
|
+
const tTimestampS = Math.floor(tTimestamp / 1000);
|
|
353
|
+
const nowS = Math.floor(now / 1000);
|
|
354
|
+
logger.info('Validating TEE timestamps', {
|
|
355
|
+
kTimestampMs: kTimestamp,
|
|
356
|
+
tTimestampMs: tTimestamp,
|
|
357
|
+
kTimestampS,
|
|
358
|
+
tTimestampS,
|
|
359
|
+
nowS
|
|
360
|
+
});
|
|
361
|
+
// 1. Check that both timestamps are not earlier than 10 minutes in the past
|
|
362
|
+
const maxAgeMs = 10 * 60 * 1000; // 10 minutes in milliseconds
|
|
363
|
+
const oldestAllowed = now - maxAgeMs;
|
|
364
|
+
if (kTimestamp < oldestAllowed) {
|
|
365
|
+
throw new Error(`TEE_K timestamp ${kTimestamp} is too old. Must be within 10 minutes of current time ${now}`);
|
|
366
|
+
}
|
|
367
|
+
if (tTimestamp < oldestAllowed) {
|
|
368
|
+
throw new Error(`TEE_T timestamp ${tTimestamp} is too old. Must be within 10 minutes of current time ${now}`);
|
|
369
|
+
}
|
|
370
|
+
// 1b. Check that timestamps are not in the future (with small tolerance for clock skew)
|
|
371
|
+
const maxFutureMs = 60 * 1000; // 1 minute tolerance for clock skew
|
|
372
|
+
const maxAllowed = now + maxFutureMs;
|
|
373
|
+
if (kTimestamp > maxAllowed) {
|
|
374
|
+
throw new Error(`TEE_K timestamp ${kTimestamp} is in the future. Current time is ${now}`);
|
|
375
|
+
}
|
|
376
|
+
if (tTimestamp > maxAllowed) {
|
|
377
|
+
throw new Error(`TEE_T timestamp ${tTimestamp} is in the future. Current time is ${now}`);
|
|
378
|
+
}
|
|
379
|
+
// 2. Check that both timestamps are within 1 minute of each other
|
|
380
|
+
const timestampDiffMs = Math.abs(kTimestamp - tTimestamp);
|
|
381
|
+
const maxDiffMs = 60 * 1000; // 1 minute
|
|
382
|
+
if (timestampDiffMs > maxDiffMs) {
|
|
383
|
+
throw new Error(`TEE timestamps differ by ${timestampDiffMs}ms, which exceeds maximum allowed difference of ${maxDiffMs}ms (1 minute)`);
|
|
384
|
+
}
|
|
385
|
+
logger.info('TEE timestamp validation successful', {
|
|
386
|
+
timestampDiffMs,
|
|
387
|
+
ageKMs: now - kTimestamp,
|
|
388
|
+
ageTMs: now - tTimestamp
|
|
389
|
+
});
|
|
325
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Validates that both K and T payloads have matching session IDs
|
|
393
|
+
* SECURITY: Prevents cross-session component substitution attacks
|
|
394
|
+
* @param kPayload - K output payload with session_id
|
|
395
|
+
* @param tPayload - T output payload with session_id
|
|
396
|
+
* @param logger - Logger instance
|
|
397
|
+
* @returns The validated session ID
|
|
398
|
+
*/
|
|
326
399
|
function validateSessionIds(kPayload, tPayload, logger) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
`Session ID mismatch: TEE_K session_id "${kSessionId}" does not match TEE_T session_id "${tSessionId}".`
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
logger.info("TEE session ID validation successful", {
|
|
352
|
-
sessionId: kSessionId
|
|
353
|
-
});
|
|
354
|
-
return kSessionId;
|
|
400
|
+
const kSessionId = kPayload.sessionId;
|
|
401
|
+
const tSessionId = tPayload.sessionId;
|
|
402
|
+
logger.info('Validating TEE session IDs', {
|
|
403
|
+
kSessionId,
|
|
404
|
+
tSessionId
|
|
405
|
+
});
|
|
406
|
+
// Both session IDs must be present
|
|
407
|
+
if (!kSessionId || kSessionId.length === 0) {
|
|
408
|
+
throw new AttestorError('ERROR_INVALID_CLAIM', 'Missing session_id in TEE_K payload - required for cross-TEE binding');
|
|
409
|
+
}
|
|
410
|
+
if (!tSessionId || tSessionId.length === 0) {
|
|
411
|
+
throw new AttestorError('ERROR_INVALID_CLAIM', 'Missing session_id in TEE_T payload - required for cross-TEE binding');
|
|
412
|
+
}
|
|
413
|
+
// Session IDs must match
|
|
414
|
+
if (kSessionId !== tSessionId) {
|
|
415
|
+
throw new AttestorError('ERROR_INVALID_CLAIM', `Session ID mismatch: TEE_K session_id "${kSessionId}" does not match TEE_T session_id "${tSessionId}".`);
|
|
416
|
+
}
|
|
417
|
+
logger.info('TEE session ID validation successful', {
|
|
418
|
+
sessionId: kSessionId
|
|
419
|
+
});
|
|
420
|
+
return kSessionId;
|
|
355
421
|
}
|
|
356
|
-
export {
|
|
357
|
-
verifyTeeBundle
|
|
358
|
-
};
|