@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,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 "
|
|
5
|
-
import { getAttestorAddress, niceParseJsonObject, signAsAttestor } from "
|
|
6
|
-
import { verifyOprfMpcOutputs } from "
|
|
7
|
-
import { verifyOprfProofs } from "
|
|
8
|
-
import { reconstructTlsTranscript } from "
|
|
9
|
-
import { verifyTeeBundle } from "
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
logger
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
claimInfo,
|
|
128
|
-
logger,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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 "
|
|
4
|
-
import { assertTranscriptsMatch, assertValidClaimRequest } from "
|
|
5
|
-
import { getAttestorAddress, signAsAttestor } from "
|
|
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
|
-
|
|
9
|
-
request
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
};
|