@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.
Files changed (145) hide show
  1. package/browser/resources/attestor-browser.min.mjs +4512 -0
  2. package/lib/avs/abis/avsDirectoryABI.js +338 -341
  3. package/lib/avs/abis/delegationABI.js +1 -4
  4. package/lib/avs/abis/registryABI.js +719 -722
  5. package/lib/avs/client/create-claim-on-avs.js +129 -157
  6. package/lib/avs/config.js +18 -24
  7. package/lib/avs/contracts/ReclaimServiceManager.js +1 -0
  8. package/lib/avs/contracts/common.js +1 -0
  9. package/lib/avs/contracts/factories/ReclaimServiceManager__factory.js +1139 -1156
  10. package/lib/avs/contracts/factories/index.js +4 -4
  11. package/lib/avs/contracts/index.js +2 -6
  12. package/lib/avs/types/index.js +1 -0
  13. package/lib/avs/utils/contracts.js +30 -50
  14. package/lib/avs/utils/register.js +75 -70
  15. package/lib/avs/utils/tasks.js +38 -45
  16. package/lib/client/create-claim.js +402 -431
  17. package/lib/client/tunnels/make-rpc-tcp-tunnel.js +46 -48
  18. package/lib/client/tunnels/make-rpc-tls-tunnel.js +125 -121
  19. package/lib/client/utils/attestor-pool.js +23 -22
  20. package/lib/client/utils/client-socket.js +86 -109
  21. package/lib/client/utils/message-handler.js +79 -89
  22. package/lib/config/index.js +40 -58
  23. package/lib/external-rpc/benchmark.js +61 -74
  24. package/lib/external-rpc/event-bus.js +12 -15
  25. package/lib/external-rpc/handle-incoming-msg.js +216 -225
  26. package/lib/external-rpc/jsc-polyfills/1.js +70 -68
  27. package/lib/external-rpc/jsc-polyfills/2.js +17 -12
  28. package/lib/external-rpc/jsc-polyfills/event.js +10 -15
  29. package/lib/external-rpc/jsc-polyfills/index.js +2 -2
  30. package/lib/external-rpc/jsc-polyfills/ws.js +77 -79
  31. package/lib/external-rpc/setup-browser.js +28 -28
  32. package/lib/external-rpc/setup-jsc.js +17 -17
  33. package/lib/external-rpc/types.js +1 -0
  34. package/lib/external-rpc/utils.js +89 -89
  35. package/lib/external-rpc/zk.js +55 -50
  36. package/lib/index.js +2 -6
  37. package/lib/mechain/abis/governanceABI.js +457 -460
  38. package/lib/mechain/abis/taskABI.js +502 -505
  39. package/lib/mechain/client/create-claim-on-mechain.js +24 -29
  40. package/lib/mechain/constants/index.js +3 -8
  41. package/lib/mechain/types/index.js +1 -0
  42. package/lib/proto/api.js +4200 -4087
  43. package/lib/proto/tee-bundle.js +1261 -1241
  44. package/lib/providers/http/index.js +616 -603
  45. package/lib/providers/http/patch-parse5-tree.js +27 -29
  46. package/lib/providers/http/utils.js +289 -248
  47. package/lib/providers/index.js +3 -6
  48. package/lib/server/create-server.js +89 -91
  49. package/lib/server/handlers/claimTeeBundle.js +231 -211
  50. package/lib/server/handlers/claimTunnel.js +66 -73
  51. package/lib/server/handlers/completeClaimOnChain.js +20 -25
  52. package/lib/server/handlers/createClaimOnChain.js +21 -27
  53. package/lib/server/handlers/createTaskOnMechain.js +40 -50
  54. package/lib/server/handlers/createTunnel.js +85 -90
  55. package/lib/server/handlers/disconnectTunnel.js +4 -7
  56. package/lib/server/handlers/fetchCertificateBytes.js +37 -53
  57. package/lib/server/handlers/index.js +21 -24
  58. package/lib/server/handlers/init.js +27 -28
  59. package/lib/server/handlers/toprf.js +13 -16
  60. package/lib/server/socket.js +97 -100
  61. package/lib/server/tunnels/make-tcp-tunnel.js +161 -186
  62. package/lib/server/utils/apm.js +32 -25
  63. package/lib/server/utils/assert-valid-claim-request.js +305 -334
  64. package/lib/server/utils/config-env.js +2 -2
  65. package/lib/server/utils/dns.js +12 -18
  66. package/lib/server/utils/gcp-attestation.js +233 -181
  67. package/lib/server/utils/generics.d.ts +1 -1
  68. package/lib/server/utils/generics.js +43 -37
  69. package/lib/server/utils/iso.js +253 -256
  70. package/lib/server/utils/keep-alive.js +36 -36
  71. package/lib/server/utils/nitro-attestation.js +295 -220
  72. package/lib/server/utils/oprf-raw.js +48 -55
  73. package/lib/server/utils/process-handshake.js +200 -218
  74. package/lib/server/utils/proxy-session.js +5 -5
  75. package/lib/server/utils/tee-oprf-mpc-verification.js +82 -78
  76. package/lib/server/utils/tee-oprf-verification.js +165 -142
  77. package/lib/server/utils/tee-transcript-reconstruction.js +176 -129
  78. package/lib/server/utils/tee-verification.js +397 -334
  79. package/lib/server/utils/validation.js +30 -37
  80. package/lib/types/bgp.js +1 -0
  81. package/lib/types/claims.js +1 -0
  82. package/lib/types/client.js +1 -0
  83. package/lib/types/general.js +1 -0
  84. package/lib/types/handlers.js +1 -0
  85. package/lib/types/providers.d.ts +3 -2
  86. package/lib/types/providers.gen.js +9 -15
  87. package/lib/types/providers.js +1 -0
  88. package/lib/types/rpc.js +1 -0
  89. package/lib/types/signatures.d.ts +1 -2
  90. package/lib/types/signatures.js +1 -0
  91. package/lib/types/tunnel.js +1 -0
  92. package/lib/types/zk.js +1 -0
  93. package/lib/utils/auth.js +54 -66
  94. package/lib/utils/b64-json.js +15 -15
  95. package/lib/utils/bgp-listener.js +107 -111
  96. package/lib/utils/claims.js +89 -80
  97. package/lib/utils/env.js +13 -17
  98. package/lib/utils/error.js +43 -47
  99. package/lib/utils/generics.js +284 -235
  100. package/lib/utils/http-parser.js +232 -187
  101. package/lib/utils/logger.js +80 -71
  102. package/lib/utils/prepare-packets.js +69 -67
  103. package/lib/utils/redactions.js +163 -121
  104. package/lib/utils/retries.js +22 -24
  105. package/lib/utils/signatures/eth.js +29 -28
  106. package/lib/utils/signatures/index.js +5 -10
  107. package/lib/utils/socket-base.js +84 -88
  108. package/lib/utils/tls.js +28 -28
  109. package/lib/utils/ws.js +19 -19
  110. package/lib/utils/zk.js +542 -582
  111. package/package.json +12 -5
  112. package/lib/external-rpc/global.d.js +0 -0
  113. package/lib/scripts/build-browser.d.ts +0 -1
  114. package/lib/scripts/build-jsc.d.ts +0 -1
  115. package/lib/scripts/build-lib.d.ts +0 -1
  116. package/lib/scripts/check-avs-registration.d.ts +0 -1
  117. package/lib/scripts/check-avs-registration.js +0 -28
  118. package/lib/scripts/fallbacks/crypto.d.ts +0 -1
  119. package/lib/scripts/fallbacks/crypto.js +0 -4
  120. package/lib/scripts/fallbacks/empty.d.ts +0 -3
  121. package/lib/scripts/fallbacks/empty.js +0 -4
  122. package/lib/scripts/fallbacks/re2.d.ts +0 -1
  123. package/lib/scripts/fallbacks/re2.js +0 -7
  124. package/lib/scripts/fallbacks/snarkjs.d.ts +0 -1
  125. package/lib/scripts/fallbacks/snarkjs.js +0 -10
  126. package/lib/scripts/fallbacks/stwo.d.ts +0 -6
  127. package/lib/scripts/fallbacks/stwo.js +0 -159
  128. package/lib/scripts/generate-provider-types.d.ts +0 -5
  129. package/lib/scripts/generate-provider-types.js +0 -101
  130. package/lib/scripts/generate-receipt.d.ts +0 -9
  131. package/lib/scripts/generate-receipt.js +0 -101
  132. package/lib/scripts/generate-toprf-keys.d.ts +0 -1
  133. package/lib/scripts/generate-toprf-keys.js +0 -24
  134. package/lib/scripts/jsc-cli-rpc.d.ts +0 -1
  135. package/lib/scripts/jsc-cli-rpc.js +0 -35
  136. package/lib/scripts/register-avs-operator.d.ts +0 -1
  137. package/lib/scripts/register-avs-operator.js +0 -3
  138. package/lib/scripts/start-server.d.ts +0 -1
  139. package/lib/scripts/start-server.js +0 -11
  140. package/lib/scripts/update-avs-metadata.d.ts +0 -1
  141. package/lib/scripts/update-avs-metadata.js +0 -20
  142. package/lib/scripts/utils.d.ts +0 -1
  143. package/lib/scripts/utils.js +0 -10
  144. package/lib/scripts/whitelist-operator.d.ts +0 -1
  145. package/lib/scripts/whitelist-operator.js +0 -16
@@ -1,232 +1,252 @@
1
+ /**
2
+ * TEE Bundle Claim Handler
3
+ * Handles ClaimTeeBundleRequest by verifying TEE attestations and reconstructing TLS transcript
4
+ */
1
5
  import { ClaimTeeBundleResponse } from "../../proto/api.js";
2
6
  import { VerificationBundle } from "../../proto/tee-bundle.js";
3
7
  import { substituteParamValues } from "../../providers/http/index.js";
4
- import { assertValidProviderTranscript } from "../../server/utils/assert-valid-claim-request.js";
5
- import { getAttestorAddress, niceParseJsonObject, signAsAttestor } from "../../server/utils/generics.js";
6
- import { verifyOprfMpcOutputs } from "../../server/utils/tee-oprf-mpc-verification.js";
7
- import { verifyOprfProofs } from "../../server/utils/tee-oprf-verification.js";
8
- import { reconstructTlsTranscript } from "../../server/utils/tee-transcript-reconstruction.js";
9
- import { verifyTeeBundle } from "../../server/utils/tee-verification.js";
8
+ import { assertValidProviderTranscript } from "../utils/assert-valid-claim-request.js";
9
+ import { getAttestorAddress, niceParseJsonObject, signAsAttestor } from "../utils/generics.js";
10
+ import { verifyOprfMpcOutputs } from "../utils/tee-oprf-mpc-verification.js";
11
+ import { verifyOprfProofs } from "../utils/tee-oprf-verification.js";
12
+ import { reconstructTlsTranscript } from "../utils/tee-transcript-reconstruction.js";
13
+ import { verifyTeeBundle } from "../utils/tee-verification.js";
10
14
  import { AttestorError } from "../../utils/error.js";
11
15
  import { createSignDataForClaim, getIdentifierFromClaimInfo } from "../../utils/index.js";
12
- const claimTeeBundle = async (teeBundleRequest, { logger, client }) => {
13
- const {
14
- verificationBundle,
15
- data
16
- } = teeBundleRequest;
17
- const res = ClaimTeeBundleResponse.create({ request: teeBundleRequest });
18
- logger.info("Starting TEE bundle verification");
19
- const teeData = await verifyTeeBundle(verificationBundle, logger);
20
- const timestampS = Math.floor(teeData.kOutputPayload.timestampMs / 1e3);
21
- logger.info("Verifying OPRF proofs");
22
- const bundle = VerificationBundle.decode(verificationBundle);
23
- const zkOprfResults = await verifyOprfProofs(
24
- { ...teeData, oprfVerifications: bundle.oprfVerifications },
25
- logger
26
- );
27
- logger.info("Verifying OPRF MPC outputs");
28
- const oprfMpcResults = verifyOprfMpcOutputs(
29
- teeData.kOutputPayload,
30
- teeData.tOutputPayload,
31
- logger
32
- );
33
- const allOprfResults = validateAndCombineOprfResults(zkOprfResults, oprfMpcResults, logger);
34
- logger.info("Starting TLS transcript reconstruction with OPRF replacements");
35
- const transcriptData = await reconstructTlsTranscript(teeData, logger, allOprfResults);
36
- logger.info("Creating plaintext transcript from TEE data");
37
- const plaintextTranscript = createPlaintextTranscriptFromTeeData(transcriptData, logger);
38
- logger.info("Running direct provider validation on TEE reconstructed data");
39
- if (!data) {
40
- throw new AttestorError("ERROR_INVALID_CLAIM", "No claim data provided in TEE bundle request");
41
- }
42
- const validatedClaim = await validateTeeProviderReceipt(
43
- plaintextTranscript,
44
- data,
45
- logger,
46
- { version: client.metadata.clientVersion },
47
- transcriptData.certificateInfo
48
- );
49
- const ctx = niceParseJsonObject(validatedClaim.context, "context");
50
- ctx.pcr0_k = teeData.teekPcr0;
51
- ctx.pcr0_t = teeData.teetPcr0;
52
- ctx.tee_session_id = teeData.teeSessionId;
53
- validatedClaim.context = JSON.stringify(ctx);
54
- res.claim = {
55
- ...validatedClaim,
56
- identifier: getIdentifierFromClaimInfo(validatedClaim),
57
- // Use timestampS from TEE_K bundle for claim signing
58
- timestampS,
59
- // hardcode for compatibility with V1 claims
60
- epoch: 1
61
- };
62
- logger.info({ claim: res.claim }, "TEE bundle claim validation successful");
63
- res.signatures = {
64
- attestorAddress: getAttestorAddress(
65
- client.metadata.signatureType
66
- ),
67
- claimSignature: res.claim ? await signAsAttestor(
68
- createSignDataForClaim(res.claim),
69
- client.metadata.signatureType
70
- ) : new Uint8Array(),
71
- resultSignature: await signAsAttestor(
72
- ClaimTeeBundleResponse.encode(res).finish(),
73
- client.metadata.signatureType
74
- )
75
- };
76
- logger.info("TEE bundle claim processing completed");
77
- return res;
16
+ export const claimTeeBundle = async (teeBundleRequest, { logger, client }) => {
17
+ const { verificationBundle, data } = teeBundleRequest;
18
+ // Initialize response
19
+ const res = ClaimTeeBundleResponse.create({ request: teeBundleRequest });
20
+ // 1. Verify TEE bundle (attestations + signatures) - this includes timestamp validation
21
+ logger.info('Starting TEE bundle verification');
22
+ const teeData = await verifyTeeBundle(verificationBundle, logger);
23
+ // 2. Extract timestampS from TEE_K bundle for claim signing
24
+ const timestampS = Math.floor(teeData.kOutputPayload.timestampMs / 1000);
25
+ // 3. Verify OPRF proofs first (before transcript reconstruction)
26
+ logger.info('Verifying OPRF proofs');
27
+ // Parse the verification bundle to get OPRF verifications
28
+ const bundle = VerificationBundle.decode(verificationBundle);
29
+ const zkOprfResults = await verifyOprfProofs({ ...teeData, oprfVerifications: bundle.oprfVerifications }, logger);
30
+ // 4. Verify OPRF MPC outputs (TEE-to-TEE computed OPRF)
31
+ logger.info('Verifying OPRF MPC outputs');
32
+ const oprfMpcResults = verifyOprfMpcOutputs(teeData.kOutputPayload, teeData.tOutputPayload, logger);
33
+ // 5. Combine ZK and OPRF MPC results for transcript reconstruction
34
+ const allOprfResults = validateAndCombineOprfResults(zkOprfResults, oprfMpcResults, logger);
35
+ // 6. Reconstruct TLS transcript with all OPRF replacements applied
36
+ logger.info('Starting TLS transcript reconstruction with OPRF replacements');
37
+ const transcriptData = await reconstructTlsTranscript(teeData, logger, allOprfResults);
38
+ // 7. Create plaintext transcript for provider validation (OPRF already applied)
39
+ logger.info('Creating plaintext transcript from TEE data');
40
+ const plaintextTranscript = createPlaintextTranscriptFromTeeData(transcriptData, logger);
41
+ // 8. Direct provider validation
42
+ logger.info('Running direct provider validation on TEE reconstructed data');
43
+ if (!data) {
44
+ throw new AttestorError('ERROR_INVALID_CLAIM', 'No claim data provided in TEE bundle request');
45
+ }
46
+ const validatedClaim = await validateTeeProviderReceipt(plaintextTranscript, data, logger, { version: client.metadata.clientVersion }, transcriptData.certificateInfo);
47
+ const ctx = niceParseJsonObject(validatedClaim.context, 'context');
48
+ // eslint-disable-next-line camelcase
49
+ ctx.pcr0_k = teeData.teekPcr0;
50
+ // eslint-disable-next-line camelcase
51
+ ctx.pcr0_t = teeData.teetPcr0;
52
+ // eslint-disable-next-line camelcase
53
+ ctx.tee_session_id = teeData.teeSessionId;
54
+ validatedClaim.context = JSON.stringify(ctx);
55
+ res.claim = {
56
+ ...validatedClaim,
57
+ identifier: getIdentifierFromClaimInfo(validatedClaim),
58
+ // Use timestampS from TEE_K bundle for claim signing
59
+ timestampS,
60
+ // hardcode for compatibility with V1 claims
61
+ epoch: 1
62
+ };
63
+ logger.info({ claim: res.claim }, 'TEE bundle claim validation successful');
64
+ // 9. Sign the response
65
+ res.signatures = {
66
+ attestorAddress: getAttestorAddress(client.metadata.signatureType),
67
+ claimSignature: res.claim
68
+ ? await signAsAttestor(createSignDataForClaim(res.claim), client.metadata.signatureType)
69
+ : new Uint8Array(),
70
+ resultSignature: await signAsAttestor(ClaimTeeBundleResponse.encode(res).finish(), client.metadata.signatureType)
71
+ };
72
+ logger.info('TEE bundle claim processing completed');
73
+ return res;
78
74
  };
75
+ /**
76
+ * Creates a plaintext transcript from TEE reconstructed data
77
+ * This converts the TEE transcript data into the format expected by provider validation
78
+ * NEW: Uses consolidated response instead of individual packets for simplicity
79
+ */
79
80
  function createPlaintextTranscriptFromTeeData(transcriptData, logger) {
80
- const transcript = [];
81
- if (transcriptData.revealedRequest && transcriptData.revealedRequest.length > 0) {
82
- transcript.push({
83
- sender: "client",
84
- message: transcriptData.revealedRequest
85
- });
86
- logger.debug("Added TEE revealed request to plaintext transcript", {
87
- length: transcriptData.revealedRequest.length
88
- });
89
- }
90
- if (transcriptData.reconstructedResponse && transcriptData.reconstructedResponse.length > 0) {
91
- transcript.push({
92
- sender: "server",
93
- message: transcriptData.reconstructedResponse
94
- });
95
- logger.debug("Added TEE consolidated response to plaintext transcript", {
96
- length: transcriptData.reconstructedResponse.length
97
- });
98
- }
99
- if (transcriptData.certificateInfo) {
100
- logger.info("Certificate information available for validation", {
101
- commonName: transcriptData.certificateInfo.commonName,
102
- issuerCommonName: transcriptData.certificateInfo.issuerCommonName,
103
- dnsNames: transcriptData.certificateInfo.dnsNames,
104
- notBefore: new Date(transcriptData.certificateInfo.notBeforeUnix * 1e3).toISOString(),
105
- notAfter: new Date(transcriptData.certificateInfo.notAfterUnix * 1e3).toISOString()
81
+ const transcript = [];
82
+ // Add reconstructed request (client -> server)
83
+ if (transcriptData.revealedRequest && transcriptData.revealedRequest.length > 0) {
84
+ transcript.push({
85
+ sender: 'client',
86
+ message: transcriptData.revealedRequest
87
+ });
88
+ logger.debug('Added TEE revealed request to plaintext transcript', {
89
+ length: transcriptData.revealedRequest.length
90
+ });
91
+ }
92
+ // Add consolidated reconstructed response (server -> client)
93
+ if (transcriptData.reconstructedResponse && transcriptData.reconstructedResponse.length > 0) {
94
+ transcript.push({
95
+ sender: 'server',
96
+ message: transcriptData.reconstructedResponse
97
+ });
98
+ logger.debug('Added TEE consolidated response to plaintext transcript', {
99
+ length: transcriptData.reconstructedResponse.length
100
+ });
101
+ }
102
+ // Log certificate validation info if available
103
+ if (transcriptData.certificateInfo) {
104
+ logger.info('Certificate information available for validation', {
105
+ commonName: transcriptData.certificateInfo.commonName,
106
+ issuerCommonName: transcriptData.certificateInfo.issuerCommonName,
107
+ dnsNames: transcriptData.certificateInfo.dnsNames,
108
+ notBefore: new Date(transcriptData.certificateInfo.notBeforeUnix * 1000).toISOString(),
109
+ notAfter: new Date(transcriptData.certificateInfo.notAfterUnix * 1000).toISOString()
110
+ });
111
+ }
112
+ logger.info('Created plaintext transcript from TEE data', {
113
+ totalMessages: transcript.length,
114
+ hasRequest: !!transcriptData.revealedRequest?.length,
115
+ hasResponse: !!transcriptData.reconstructedResponse?.length,
116
+ hasCertificateInfo: !!transcriptData.certificateInfo
106
117
  });
107
- }
108
- logger.info("Created plaintext transcript from TEE data", {
109
- totalMessages: transcript.length,
110
- hasRequest: !!transcriptData.revealedRequest?.length,
111
- hasResponse: !!transcriptData.reconstructedResponse?.length,
112
- hasCertificateInfo: !!transcriptData.certificateInfo
113
- });
114
- return transcript;
118
+ return transcript;
115
119
  }
120
+ /**
121
+ * Validates TEE provider receipt directly without signature validation
122
+ * This is essentially assertValidProviderTranscript but for TEE data
123
+ * NEW: Includes certificate validation for domain authentication
124
+ */
116
125
  async function validateTeeProviderReceipt(plaintextTranscript, claimInfo, logger, providerCtx, certificateInfo) {
117
- logger.info("Starting direct TEE provider validation", {
118
- provider: claimInfo.provider,
119
- transcriptMessages: plaintextTranscript.length,
120
- hasCertificateInfo: !!certificateInfo
121
- });
122
- if (certificateInfo) {
123
- validateTlsCertificate(claimInfo, certificateInfo, logger);
124
- }
125
- const validatedClaim = await assertValidProviderTranscript(
126
- plaintextTranscript,
127
- claimInfo,
128
- logger,
129
- providerCtx
130
- );
131
- logger.info("TEE provider validation completed successfully", {
132
- provider: validatedClaim.provider,
133
- owner: validatedClaim.owner || "unknown"
134
- });
135
- return validatedClaim;
126
+ logger.info('Starting direct TEE provider validation', {
127
+ provider: claimInfo.provider,
128
+ transcriptMessages: plaintextTranscript.length,
129
+ hasCertificateInfo: !!certificateInfo
130
+ });
131
+ // Validate certificate if available
132
+ if (certificateInfo) {
133
+ validateTlsCertificate(claimInfo, certificateInfo, logger);
134
+ }
135
+ // Use the existing provider validation logic directly
136
+ const validatedClaim = await assertValidProviderTranscript(plaintextTranscript, claimInfo, logger, providerCtx);
137
+ logger.info('TEE provider validation completed successfully', {
138
+ provider: validatedClaim.provider,
139
+ owner: validatedClaim.owner || 'unknown'
140
+ });
141
+ return validatedClaim;
136
142
  }
143
+ /**
144
+ * Checks if a hostname matches a certificate name (with wildcard support)
145
+ * @param hostname - The hostname to check
146
+ * @param certName - The certificate name
147
+ * @returns true if the hostname is valid for this certificate name
148
+ */
137
149
  function isHostnameValidForCertificate(hostname, certName) {
138
- if (hostname === certName) {
139
- return true;
140
- }
141
- if (certName.startsWith("*.")) {
142
- const wildcardDomain = certName.slice(2);
143
- if (hostname.endsWith(wildcardDomain)) {
144
- const subdomainPart = hostname.slice(0, -wildcardDomain.length);
145
- if (subdomainPart.endsWith(".")) {
146
- const subdomain = subdomainPart.slice(0, -1);
147
- return !subdomain.includes(".");
148
- }
150
+ // Exact match
151
+ if (hostname === certName) {
152
+ return true;
149
153
  }
150
- }
151
- return false;
154
+ // Wildcard match
155
+ if (certName.startsWith('*.')) {
156
+ // Extract the domain from wildcard
157
+ const wildcardDomain = certName.slice(2);
158
+ // Check if hostname ends with the wildcard domain
159
+ if (hostname.endsWith(wildcardDomain)) {
160
+ // Ensure we're matching a subdomain, not partial domain
161
+ const subdomainPart = hostname.slice(0, -(wildcardDomain.length));
162
+ // Valid if:
163
+ // 1. The subdomain part ends with a dot (proper subdomain boundary)
164
+ // 2. The subdomain part doesn't contain additional dots (single-level wildcard)
165
+ if (subdomainPart.endsWith('.')) {
166
+ const subdomain = subdomainPart.slice(0, -1);
167
+ // Wildcard only matches single level, not multiple subdomains
168
+ return !subdomain.includes('.');
169
+ }
170
+ }
171
+ }
172
+ return false;
152
173
  }
174
+ /**
175
+ * Validates that the TLS certificate is valid for the domain being claimed
176
+ */
153
177
  function validateTlsCertificate(claimInfo, certificateInfo, logger) {
154
- let claimedHostname;
155
- const paramsWithTemplates = niceParseJsonObject(claimInfo.parameters, "params");
156
- const params = substituteParamValues(paramsWithTemplates, void 0, true).newParams;
157
- if ("url" in params && typeof params.url === "string") {
158
- claimedHostname = new URL(params.url).hostname;
159
- }
160
- if (!claimedHostname) {
161
- logger.warn("Could not extract hostname from claim for certificate validation", {
162
- provider: claimInfo.provider
178
+ // Extract hostname from the claim (this varies by provider)
179
+ let claimedHostname;
180
+ const paramsWithTemplates = niceParseJsonObject(claimInfo.parameters, 'params');
181
+ const params = substituteParamValues(paramsWithTemplates, undefined, true).newParams;
182
+ // Different providers store hostname in different places
183
+ if ('url' in params && typeof params.url === 'string') {
184
+ claimedHostname = new URL(params.url).hostname;
185
+ }
186
+ if (!claimedHostname) {
187
+ logger.warn('Could not extract hostname from claim for certificate validation', {
188
+ provider: claimInfo.provider
189
+ });
190
+ throw new AttestorError('ERROR_INVALID_CLAIM', 'Certificate validation failed: hostname not found');
191
+ }
192
+ logger.info('Validating TLS certificate for claimed hostname', {
193
+ claimedHostname,
194
+ certificateCommonName: certificateInfo.commonName,
195
+ certificateDnsNames: certificateInfo.dnsNames
196
+ });
197
+ // Check if claimed hostname matches certificate (including wildcard support)
198
+ const isValidForHostname = isHostnameValidForCertificate(claimedHostname, certificateInfo.commonName) ||
199
+ certificateInfo.dnsNames.some(name => isHostnameValidForCertificate(claimedHostname, name));
200
+ if (!isValidForHostname) {
201
+ throw new AttestorError('ERROR_INVALID_CLAIM', `Certificate validation failed: hostname '${claimedHostname}' not valid for certificate (CN: ${certificateInfo.commonName}, SANs: ${certificateInfo.dnsNames.join(', ')})`);
202
+ }
203
+ // Check certificate validity period
204
+ const now = Date.now() / 1000; // Current time in Unix seconds
205
+ if (now < certificateInfo.notBeforeUnix || now > certificateInfo.notAfterUnix) {
206
+ throw new AttestorError('ERROR_INVALID_CLAIM', `Certificate validation failed: certificate not valid at current time (valid from ${new Date(certificateInfo.notBeforeUnix * 1000).toISOString()} to ${new Date(certificateInfo.notAfterUnix * 1000).toISOString()})`);
207
+ }
208
+ logger.info('TLS certificate validation passed', {
209
+ claimedHostname,
210
+ validatedAgainst: isHostnameValidForCertificate(claimedHostname, certificateInfo.commonName) ?
211
+ `CommonName: ${certificateInfo.commonName}` :
212
+ `SAN: ${certificateInfo.dnsNames.find(name => isHostnameValidForCertificate(claimedHostname, name))}`
163
213
  });
164
- throw new AttestorError(
165
- "ERROR_INVALID_CLAIM",
166
- "Certificate validation failed: hostname not found"
167
- );
168
- }
169
- logger.info("Validating TLS certificate for claimed hostname", {
170
- claimedHostname,
171
- certificateCommonName: certificateInfo.commonName,
172
- certificateDnsNames: certificateInfo.dnsNames
173
- });
174
- const isValidForHostname = isHostnameValidForCertificate(claimedHostname, certificateInfo.commonName) || certificateInfo.dnsNames.some((name) => isHostnameValidForCertificate(claimedHostname, name));
175
- if (!isValidForHostname) {
176
- throw new AttestorError(
177
- "ERROR_INVALID_CLAIM",
178
- `Certificate validation failed: hostname '${claimedHostname}' not valid for certificate (CN: ${certificateInfo.commonName}, SANs: ${certificateInfo.dnsNames.join(", ")})`
179
- );
180
- }
181
- const now = Date.now() / 1e3;
182
- if (now < certificateInfo.notBeforeUnix || now > certificateInfo.notAfterUnix) {
183
- throw new AttestorError(
184
- "ERROR_INVALID_CLAIM",
185
- `Certificate validation failed: certificate not valid at current time (valid from ${new Date(certificateInfo.notBeforeUnix * 1e3).toISOString()} to ${new Date(certificateInfo.notAfterUnix * 1e3).toISOString()})`
186
- );
187
- }
188
- logger.info("TLS certificate validation passed", {
189
- claimedHostname,
190
- validatedAgainst: isHostnameValidForCertificate(claimedHostname, certificateInfo.commonName) ? `CommonName: ${certificateInfo.commonName}` : `SAN: ${certificateInfo.dnsNames.find((name) => isHostnameValidForCertificate(claimedHostname, name))}`
191
- });
192
214
  }
215
+ /**
216
+ * Validates OPRF results have no overlapping ranges and combines them
217
+ * SECURITY: Prevents position collisions between ZK and OPRF MPC results
218
+ */
193
219
  function validateAndCombineOprfResults(zkOprfResults, oprfMpcResults, logger) {
194
- const allOprfResults = [...zkOprfResults, ...oprfMpcResults];
195
- if (allOprfResults.length === 0) {
196
- return allOprfResults;
197
- }
198
- logger.info(`Combined ${zkOprfResults.length} ZK OPRF + ${oprfMpcResults.length} OPRF MPC results`);
199
- const seen = {};
200
- for (const result of zkOprfResults) {
201
- seen[result.position] = { length: result.length, source: "zk" };
202
- }
203
- for (const result of oprfMpcResults) {
204
- const existing = seen[result.position];
205
- if (existing) {
206
- if (existing.length !== result.length) {
207
- throw new AttestorError(
208
- "ERROR_INVALID_CLAIM",
209
- `OPRF range conflict at position ${result.position}: ZK length ${existing.length} vs MPC length ${result.length}`
210
- );
211
- }
212
- logger.warn(`Duplicate OPRF range at position ${result.position} from both ZK and MPC - using MPC result`);
220
+ const allOprfResults = [...zkOprfResults, ...oprfMpcResults];
221
+ if (allOprfResults.length === 0) {
222
+ return allOprfResults;
223
+ }
224
+ logger.info(`Combined ${zkOprfResults.length} ZK OPRF + ${oprfMpcResults.length} OPRF MPC results`);
225
+ // Check for overlapping ranges (position collision detection)
226
+ const seen = {};
227
+ for (const result of zkOprfResults) {
228
+ seen[result.position] = { length: result.length, source: 'zk' };
213
229
  }
214
- for (const [pos, data] of Object.entries(seen)) {
215
- const position = Number(pos);
216
- const existingEnd = position + data.length;
217
- const newEnd = result.position + result.length;
218
- const overlaps = result.position < existingEnd && newEnd > position && result.position !== position;
219
- if (overlaps) {
220
- throw new AttestorError(
221
- "ERROR_INVALID_CLAIM",
222
- `Overlapping OPRF ranges: [${position}:${existingEnd}] (${data.source}) and [${result.position}:${newEnd}] (mpc)`
223
- );
224
- }
230
+ for (const result of oprfMpcResults) {
231
+ const existing = seen[result.position];
232
+ if (existing) {
233
+ // Exact duplicate at same position - verify they match
234
+ if (existing.length !== result.length) {
235
+ throw new AttestorError('ERROR_INVALID_CLAIM', `OPRF range conflict at position ${result.position}: ZK length ${existing.length} vs MPC length ${result.length}`);
236
+ }
237
+ logger.warn(`Duplicate OPRF range at position ${result.position} from both ZK and MPC - using MPC result`);
238
+ }
239
+ // Check for overlapping (but not identical) ranges
240
+ for (const [pos, data] of Object.entries(seen)) {
241
+ const position = Number(pos);
242
+ const existingEnd = position + data.length;
243
+ const newEnd = result.position + result.length;
244
+ const overlaps = (result.position < existingEnd && newEnd > position) && result.position !== position;
245
+ if (overlaps) {
246
+ throw new AttestorError('ERROR_INVALID_CLAIM', `Overlapping OPRF ranges: [${position}:${existingEnd}] (${data.source}) and [${result.position}:${newEnd}] (mpc)`);
247
+ }
248
+ }
249
+ seen[result.position] = { length: result.length, source: 'mpc' };
225
250
  }
226
- seen[result.position] = { length: result.length, source: "mpc" };
227
- }
228
- return allOprfResults;
251
+ return allOprfResults;
229
252
  }
230
- export {
231
- claimTeeBundle
232
- };
@@ -1,80 +1,73 @@
1
1
  import { MAX_CLAIM_TIMESTAMP_DIFF_S } from "../../config/index.js";
2
2
  import { ClaimTunnelResponse } from "../../proto/api.js";
3
- import { getApm } from "../../server/utils/apm.js";
4
- import { assertTranscriptsMatch, assertValidClaimRequest } from "../../server/utils/assert-valid-claim-request.js";
5
- import { getAttestorAddress, signAsAttestor } from "../../server/utils/generics.js";
3
+ import { getApm } from "../utils/apm.js";
4
+ import { assertTranscriptsMatch, assertValidClaimRequest } from "../utils/assert-valid-claim-request.js";
5
+ import { getAttestorAddress, signAsAttestor } from "../utils/generics.js";
6
6
  import { AttestorError, createSignDataForClaim, getIdentifierFromClaimInfo, unixTimestampSeconds } from "../../utils/index.js";
7
- const claimTunnel = async (claimRequest, { tx, logger, client }) => {
8
- const {
9
- request,
10
- data: { timestampS } = {}
11
- } = claimRequest;
12
- const tunnel = client.getTunnel(request?.id);
13
- try {
14
- await tunnel.close();
15
- } catch (err) {
16
- logger.debug({ err }, "error closing tunnel");
17
- }
18
- if (tx) {
19
- const transcriptBytes = tunnel.transcript.reduce(
20
- (acc, { message }) => acc + message.length,
21
- 0
22
- );
23
- tx?.setLabel("transcriptBytes", transcriptBytes.toString());
24
- }
25
- if (tunnel.createRequest?.host !== request?.host || tunnel.createRequest?.port !== request?.port || tunnel.createRequest?.geoLocation !== request?.geoLocation || tunnel.createRequest?.proxySessionId !== request?.proxySessionId) {
26
- throw AttestorError.badRequest("Tunnel request does not match");
27
- }
28
- assertTranscriptsMatch(claimRequest.transcript, tunnel.transcript);
29
- const res = ClaimTunnelResponse.create({ request: claimRequest });
30
- try {
31
- const now = unixTimestampSeconds();
32
- if (Math.floor(timestampS - now) > MAX_CLAIM_TIMESTAMP_DIFF_S) {
33
- throw new AttestorError(
34
- "ERROR_INVALID_CLAIM",
35
- `Timestamp provided ${timestampS} is too far off. Current time is ${now}`
36
- );
7
+ export const claimTunnel = async (claimRequest, { tx, logger, client }) => {
8
+ const { request, data: { timestampS } = {}, } = claimRequest;
9
+ const tunnel = client.getTunnel(request?.id);
10
+ try {
11
+ await tunnel.close();
12
+ }
13
+ catch (err) {
14
+ logger.debug({ err }, 'error closing tunnel');
15
+ }
16
+ if (tx) {
17
+ const transcriptBytes = tunnel.transcript.reduce((acc, { message }) => acc + message.length, 0);
18
+ tx?.setLabel('transcriptBytes', transcriptBytes.toString());
37
19
  }
38
- const assertTx = getApm()?.startTransaction("assertValidClaimRequest", { childOf: tx });
20
+ // we throw an error for cases where the attestor cannot prove
21
+ // the user's request is faulty. For eg. if the user sends a
22
+ // "createRequest" that does not match the tunnel's actual
23
+ // create request -- the attestor cannot prove that the user
24
+ // is lying. In such cases, we throw a bad request error.
25
+ // Same goes for matching the transcript.
26
+ if (tunnel.createRequest?.host !== request?.host
27
+ || tunnel.createRequest?.port !== request?.port
28
+ || tunnel.createRequest?.geoLocation !== request?.geoLocation
29
+ || tunnel.createRequest?.proxySessionId !== request?.proxySessionId) {
30
+ throw AttestorError.badRequest('Tunnel request does not match');
31
+ }
32
+ assertTranscriptsMatch(claimRequest.transcript, tunnel.transcript);
33
+ const res = ClaimTunnelResponse.create({ request: claimRequest });
39
34
  try {
40
- const claim = await assertValidClaimRequest(
41
- claimRequest,
42
- client.metadata,
43
- logger
44
- );
45
- res.claim = {
46
- ...claim,
47
- identifier: getIdentifierFromClaimInfo(claim),
48
- // hardcode for compatibility with V1 claims
49
- epoch: 1
50
- };
51
- } catch (err) {
52
- assertTx?.setOutcome("failure");
53
- throw err;
54
- } finally {
55
- assertTx?.end();
35
+ const now = unixTimestampSeconds();
36
+ if (Math.floor(timestampS - now) > MAX_CLAIM_TIMESTAMP_DIFF_S) {
37
+ throw new AttestorError('ERROR_INVALID_CLAIM', `Timestamp provided ${timestampS} is too far off. Current time is ${now}`);
38
+ }
39
+ const assertTx = getApm()
40
+ ?.startTransaction('assertValidClaimRequest', { childOf: tx });
41
+ try {
42
+ const claim = await assertValidClaimRequest(claimRequest, client.metadata, logger);
43
+ res.claim = {
44
+ ...claim,
45
+ identifier: getIdentifierFromClaimInfo(claim),
46
+ // hardcode for compatibility with V1 claims
47
+ epoch: 1
48
+ };
49
+ }
50
+ catch (err) {
51
+ assertTx?.setOutcome('failure');
52
+ throw err;
53
+ }
54
+ finally {
55
+ assertTx?.end();
56
+ }
56
57
  }
57
- } catch (err) {
58
- logger.error({ err }, "invalid claim request");
59
- const attestorErr = AttestorError.fromError(err, "ERROR_INVALID_CLAIM");
60
- res.error = attestorErr.toProto();
61
- }
62
- res.signatures = {
63
- attestorAddress: getAttestorAddress(
64
- client.metadata.signatureType
65
- ),
66
- claimSignature: res.claim ? await signAsAttestor(
67
- createSignDataForClaim(res.claim),
68
- client.metadata.signatureType
69
- ) : new Uint8Array(),
70
- resultSignature: await signAsAttestor(
71
- ClaimTunnelResponse.encode(res).finish(),
72
- client.metadata.signatureType
73
- )
74
- };
75
- client.removeTunnel(request.id);
76
- return res;
77
- };
78
- export {
79
- claimTunnel
58
+ catch (err) {
59
+ logger.error({ err }, 'invalid claim request');
60
+ const attestorErr = AttestorError.fromError(err, 'ERROR_INVALID_CLAIM');
61
+ res.error = attestorErr.toProto();
62
+ }
63
+ res.signatures = {
64
+ attestorAddress: getAttestorAddress(client.metadata.signatureType),
65
+ claimSignature: res.claim
66
+ ? await signAsAttestor(createSignDataForClaim(res.claim), client.metadata.signatureType)
67
+ : new Uint8Array(),
68
+ resultSignature: await signAsAttestor(ClaimTunnelResponse.encode(res).finish(), client.metadata.signatureType)
69
+ };
70
+ // remove tunnel from client -- to free up our mem
71
+ client.removeTunnel(request.id);
72
+ return res;
80
73
  };