@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,12 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
/**
|
|
2
|
+
* Working Nitro Attestation validation utilities
|
|
3
|
+
*/
|
|
4
|
+
import { AsnParser } from '@peculiar/asn1-schema';
|
|
5
|
+
import { SubjectPublicKeyInfo } from '@peculiar/asn1-x509';
|
|
6
|
+
import { Crypto } from '@peculiar/webcrypto';
|
|
7
|
+
import { X509Certificate, X509ChainBuilder } from '@peculiar/x509';
|
|
8
|
+
import { sign } from 'cose-js';
|
|
9
|
+
// Helper function to dynamically import cbor-x
|
|
6
10
|
async function getCborDecode() {
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
const { decode } = await import('cbor-x');
|
|
12
|
+
return decode;
|
|
9
13
|
}
|
|
14
|
+
// AWS Nitro root certificate (from nitrite)
|
|
10
15
|
const AWS_NITRO_ROOT_CERT = `-----BEGIN CERTIFICATE-----
|
|
11
16
|
MIICETCCAZagAwIBAgIRAPkxdWgbkK/hHUbMtOTn+FYwCgYIKoZIzj0EAwMwSTEL
|
|
12
17
|
MAkGA1UEBhMCVVMxDzANBgNVBAoMBkFtYXpvbjEMMAoGA1UECwwDQVdTMRswGQYD
|
|
@@ -21,229 +26,299 @@ MQCjfy+Rocm9Xue4YnwWmNJVA44fA0P5W2OpYow9OYCVRaEevL8uO1XYru5xtMPW
|
|
|
21
26
|
rfMCMQCi85sWBbJwKKXdS6BptQFuZbT73o/gBh1qUxl/nNr12UO8Yfwr6wPLb+6N
|
|
22
27
|
IwLz3/Y=
|
|
23
28
|
-----END CERTIFICATE-----`;
|
|
29
|
+
// Expected PCR values (replace with actual values)
|
|
30
|
+
// const EXPECTED_PCRS = {
|
|
31
|
+
// //0: Buffer.from('000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'hex'),
|
|
32
|
+
// }
|
|
33
|
+
//
|
|
34
|
+
// // Secure buffer comparison to prevent timing attacks
|
|
35
|
+
// function secureBufferCompare(a: Buffer, b: Buffer): boolean {
|
|
36
|
+
// if(a.length !== b.length) {
|
|
37
|
+
// return false
|
|
38
|
+
// }
|
|
39
|
+
//
|
|
40
|
+
// let result = 0
|
|
41
|
+
// for(const [i, element] of a.entries()) {
|
|
42
|
+
// result |= element ^ b[i]
|
|
43
|
+
// }
|
|
44
|
+
//
|
|
45
|
+
// return result === 0
|
|
46
|
+
// }
|
|
47
|
+
// Enhanced certificate chain validation
|
|
24
48
|
async function validateCertificateChain(targetCert, intermediateCerts, rootCert, crypto) {
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const rootSubject = rootCert.subject;
|
|
28
|
-
const rootIssuer = rootCert.issuer;
|
|
29
|
-
if (rootSubject !== rootIssuer) {
|
|
30
|
-
errors.push("Root certificate is not self-signed");
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const isRootValid = await rootCert.verify(void 0, crypto);
|
|
34
|
-
if (!isRootValid) {
|
|
35
|
-
errors.push("Root certificate signature verification failed");
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
errors.push(`Root certificate verification failed: ${error.message}`);
|
|
39
|
-
}
|
|
40
|
-
const chainBuilder = new X509ChainBuilder({
|
|
41
|
-
certificates: [rootCert, ...intermediateCerts]
|
|
42
|
-
});
|
|
43
|
-
let chain;
|
|
49
|
+
const errors = [];
|
|
44
50
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { isValid: false, errors, chain: [] };
|
|
53
|
-
}
|
|
54
|
-
const now = /* @__PURE__ */ new Date();
|
|
55
|
-
for (let i = 0; i < chain.length; i++) {
|
|
56
|
-
const cert = chain[i];
|
|
57
|
-
if (now < cert.notBefore) {
|
|
58
|
-
errors.push(`Certificate ${i} (${cert.subject}) is not yet valid`);
|
|
59
|
-
}
|
|
60
|
-
if (now > cert.notAfter) {
|
|
61
|
-
errors.push(`Certificate ${i} (${cert.subject}) has expired`);
|
|
62
|
-
}
|
|
63
|
-
if (i < chain.length - 1) {
|
|
51
|
+
// Validate root certificate is self-signed and trusted
|
|
52
|
+
const rootSubject = rootCert.subject;
|
|
53
|
+
const rootIssuer = rootCert.issuer;
|
|
54
|
+
if (rootSubject !== rootIssuer) {
|
|
55
|
+
errors.push('Root certificate is not self-signed');
|
|
56
|
+
}
|
|
57
|
+
// Verify root certificate signature (self-verification)
|
|
64
58
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
59
|
+
const isRootValid = await rootCert.verify(undefined, crypto);
|
|
60
|
+
if (!isRootValid) {
|
|
61
|
+
errors.push('Root certificate signature verification failed');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
errors.push(`Root certificate verification failed: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
// Build the certificate chain
|
|
68
|
+
const chainBuilder = new X509ChainBuilder({
|
|
69
|
+
certificates: [rootCert, ...intermediateCerts]
|
|
70
|
+
});
|
|
71
|
+
let chain;
|
|
72
|
+
try {
|
|
73
|
+
chain = await chainBuilder.build(targetCert, crypto);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
errors.push(`Certificate chain building failed: ${error.message}`);
|
|
77
|
+
return { isValid: false, errors, chain: [] };
|
|
78
|
+
}
|
|
79
|
+
if (!chain || chain.length === 0) {
|
|
80
|
+
errors.push('No valid certificate chain could be built');
|
|
81
|
+
return { isValid: false, errors, chain: [] };
|
|
82
|
+
}
|
|
83
|
+
// Validate each certificate in the chain
|
|
84
|
+
const now = new Date();
|
|
85
|
+
for (let i = 0; i < chain.length; i++) {
|
|
86
|
+
const cert = chain[i];
|
|
87
|
+
// Check expiration dates
|
|
88
|
+
if (now < cert.notBefore) {
|
|
89
|
+
errors.push(`Certificate ${i} (${cert.subject}) is not yet valid`);
|
|
90
|
+
}
|
|
91
|
+
if (now > cert.notAfter) {
|
|
92
|
+
errors.push(`Certificate ${i} (${cert.subject}) has expired`);
|
|
93
|
+
}
|
|
94
|
+
// Verify each certificate's signature (except root which is self-signed)
|
|
95
|
+
if (i < chain.length - 1) {
|
|
96
|
+
try {
|
|
97
|
+
const issuer = chain[i + 1];
|
|
98
|
+
const isValid = await cert.verify(issuer, crypto);
|
|
99
|
+
if (!isValid) {
|
|
100
|
+
errors.push(`Certificate ${i} signature verification failed`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
errors.push(`Certificate ${i} verification failed: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
isValid: errors.length === 0,
|
|
110
|
+
errors,
|
|
111
|
+
chain
|
|
112
|
+
};
|
|
97
113
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
ethAddress: teeTMatch[1],
|
|
102
|
-
// Store the full ETH address with 0x prefix
|
|
103
|
-
pcr0: ""
|
|
104
|
-
};
|
|
114
|
+
catch (error) {
|
|
115
|
+
errors.push(`Certificate chain validation error: ${error.message}`);
|
|
116
|
+
return { isValid: false, errors, chain: [] };
|
|
105
117
|
}
|
|
106
|
-
return null;
|
|
107
|
-
} catch {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
118
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const crypto = new Crypto();
|
|
116
|
-
const decode = await getCborDecode();
|
|
117
|
-
let decoded;
|
|
118
|
-
try {
|
|
119
|
-
decoded = decode(Buffer.from(attestationBytes));
|
|
120
|
-
} catch (error) {
|
|
121
|
-
errors.push(`CBOR decoding failed: ${error.message}`);
|
|
122
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
123
|
-
}
|
|
124
|
-
if (!Array.isArray(decoded) || decoded.length < 4) {
|
|
125
|
-
errors.push("Invalid COSE_Sign1 structure: expected array with 4 elements");
|
|
126
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
127
|
-
}
|
|
128
|
-
const [, , payload] = decoded;
|
|
129
|
-
if (!payload || payload.length === 0) {
|
|
130
|
-
errors.push("Empty or missing payload in COSE_Sign1 structure");
|
|
131
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
132
|
-
}
|
|
133
|
-
let doc;
|
|
134
|
-
try {
|
|
135
|
-
doc = decode(payload);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
errors.push(`Payload decoding failed: ${error.message}`);
|
|
138
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
139
|
-
}
|
|
140
|
-
if (doc.module_id.length === 0) {
|
|
141
|
-
errors.push("Missing or invalid module_id");
|
|
142
|
-
}
|
|
143
|
-
if (doc.digest.length === 0) {
|
|
144
|
-
errors.push("Missing or invalid digest");
|
|
145
|
-
}
|
|
146
|
-
if (!doc.pcrs || typeof doc.pcrs !== "object") {
|
|
147
|
-
errors.push("Missing or invalid pcrs");
|
|
148
|
-
}
|
|
149
|
-
if (!Buffer.isBuffer(doc.certificate) || doc.certificate.length === 0) {
|
|
150
|
-
errors.push("Missing or invalid certificate");
|
|
151
|
-
}
|
|
152
|
-
if (!Array.isArray(doc.cabundle) || doc.cabundle.length === 0) {
|
|
153
|
-
errors.push("Missing or invalid cabundle");
|
|
154
|
-
}
|
|
155
|
-
if (errors.length > 0) {
|
|
156
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
157
|
-
}
|
|
158
|
-
const pcr0 = doc.pcrs[0].toString("hex");
|
|
159
|
-
const intermediateCerts = [];
|
|
160
|
-
for (let i = 0; i < doc.cabundle.length; i++) {
|
|
161
|
-
try {
|
|
162
|
-
const cert = new X509Certificate(doc.cabundle[i].toString("base64"));
|
|
163
|
-
intermediateCerts.push(cert);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
errors.push(`Failed to parse cabundle certificate ${i}: ${error.message}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
let targetCert;
|
|
169
|
-
try {
|
|
170
|
-
targetCert = new X509Certificate(doc.certificate.toString("base64"));
|
|
171
|
-
} catch (error) {
|
|
172
|
-
errors.push(`Failed to parse target certificate: ${error.message}`);
|
|
173
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
174
|
-
}
|
|
175
|
-
let rootCert;
|
|
176
|
-
try {
|
|
177
|
-
rootCert = new X509Certificate(AWS_NITRO_ROOT_CERT);
|
|
178
|
-
} catch (error) {
|
|
179
|
-
errors.push(`Failed to parse AWS Nitro root certificate: ${error.message}`);
|
|
180
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
181
|
-
}
|
|
182
|
-
const chainResult = await validateCertificateChain(targetCert, intermediateCerts, rootCert, crypto);
|
|
183
|
-
if (!chainResult.isValid) {
|
|
184
|
-
errors.push(...chainResult.errors);
|
|
185
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
186
|
-
}
|
|
187
|
-
let publicKeyRaw;
|
|
188
|
-
try {
|
|
189
|
-
publicKeyRaw = Buffer.from(targetCert.publicKey.rawData);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
errors.push(`Failed to extract public key: ${error.message}`);
|
|
192
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
193
|
-
}
|
|
194
|
-
if (publicKeyRaw.length !== 120 || publicKeyRaw[0] !== 48) {
|
|
195
|
-
errors.push(`Invalid public key format: expected 120-byte DER-encoded key, got ${publicKeyRaw.length} bytes`);
|
|
196
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
197
|
-
}
|
|
198
|
-
let spki;
|
|
119
|
+
/**
|
|
120
|
+
* Extract public key from user_data field in attestation document
|
|
121
|
+
*/
|
|
122
|
+
function extractPublicKeyFromUserData(userDataBuffer) {
|
|
199
123
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
124
|
+
const userDataString = userDataBuffer.toString('utf-8');
|
|
125
|
+
// Parse new format: "tee_k_public_key:0xETH_ADDRESS" or "tee_t_public_key:0xETH_ADDRESS"
|
|
126
|
+
const teeKMatch = userDataString.match(/^tee_k_public_key:(0x[0-9a-fA-F]{40})$/);
|
|
127
|
+
const teeTMatch = userDataString.match(/^tee_t_public_key:(0x[0-9a-fA-F]{40})$/);
|
|
128
|
+
if (teeKMatch) {
|
|
129
|
+
return {
|
|
130
|
+
teeType: 'tee_k',
|
|
131
|
+
ethAddress: teeKMatch[1], // Store the full ETH address with 0x prefix
|
|
132
|
+
pcr0: ''
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (teeTMatch) {
|
|
136
|
+
return {
|
|
137
|
+
teeType: 'tee_t',
|
|
138
|
+
ethAddress: teeTMatch[1], // Store the full ETH address with 0x prefix
|
|
139
|
+
pcr0: ''
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
204
143
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
errors.push("Invalid EC point: expected 97-byte uncompressed P-384 key");
|
|
208
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
209
146
|
}
|
|
210
|
-
|
|
211
|
-
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Working validation function copied from nitroattestor
|
|
150
|
+
*/
|
|
151
|
+
export async function validateNitroAttestationAndExtractKey(attestationBytes) {
|
|
152
|
+
const errors = [];
|
|
153
|
+
const warnings = [];
|
|
212
154
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
155
|
+
// Set up WebCrypto
|
|
156
|
+
const crypto = new Crypto();
|
|
157
|
+
// Decode CBOR - use exact same approach as working nitroattestor
|
|
158
|
+
const decode = await getCborDecode();
|
|
159
|
+
let decoded;
|
|
160
|
+
try {
|
|
161
|
+
decoded = decode(Buffer.from(attestationBytes));
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
errors.push(`CBOR decoding failed: ${error.message}`);
|
|
165
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
166
|
+
}
|
|
167
|
+
// Extract COSE_Sign1 structure
|
|
168
|
+
if (!Array.isArray(decoded) || decoded.length < 4) {
|
|
169
|
+
errors.push('Invalid COSE_Sign1 structure: expected array with 4 elements');
|
|
170
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
171
|
+
}
|
|
172
|
+
const [, , payload] = decoded;
|
|
173
|
+
// Validate payload exists and is not empty
|
|
174
|
+
if (!payload || payload.length === 0) {
|
|
175
|
+
errors.push('Empty or missing payload in COSE_Sign1 structure');
|
|
176
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
177
|
+
}
|
|
178
|
+
// Decode payload - use exact same approach as working code
|
|
179
|
+
let doc;
|
|
180
|
+
try {
|
|
181
|
+
doc = decode(payload);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
errors.push(`Payload decoding failed: ${error.message}`);
|
|
185
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
186
|
+
}
|
|
187
|
+
// Validate mandatory fields with strict type checking
|
|
188
|
+
if (doc.module_id.length === 0) {
|
|
189
|
+
errors.push('Missing or invalid module_id');
|
|
190
|
+
}
|
|
191
|
+
if (doc.digest.length === 0) {
|
|
192
|
+
errors.push('Missing or invalid digest');
|
|
193
|
+
}
|
|
194
|
+
if (!doc.pcrs || typeof doc.pcrs !== 'object') {
|
|
195
|
+
errors.push('Missing or invalid pcrs');
|
|
196
|
+
}
|
|
197
|
+
if (!Buffer.isBuffer(doc.certificate) || doc.certificate.length === 0) {
|
|
198
|
+
errors.push('Missing or invalid certificate');
|
|
199
|
+
}
|
|
200
|
+
if (!Array.isArray(doc.cabundle) || doc.cabundle.length === 0) {
|
|
201
|
+
errors.push('Missing or invalid cabundle');
|
|
202
|
+
}
|
|
203
|
+
// Early return if basic validation fails
|
|
204
|
+
if (errors.length > 0) {
|
|
205
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
206
|
+
}
|
|
207
|
+
const pcr0 = doc.pcrs[0].toString('hex');
|
|
208
|
+
// Validate PCRs with secure comparison
|
|
209
|
+
// for(const [index, expected] of Object.entries(EXPECTED_PCRS)) {
|
|
210
|
+
// const pcrIndex = parseInt(index)
|
|
211
|
+
// const actualPcr = doc.pcrs[pcrIndex]
|
|
212
|
+
//
|
|
213
|
+
// if(!Buffer.isBuffer(actualPcr)) {
|
|
214
|
+
// errors.push(`PCR${index} is not a Buffer`)
|
|
215
|
+
// continue
|
|
216
|
+
// }
|
|
217
|
+
//
|
|
218
|
+
// if(!secureBufferCompare(expected, actualPcr)) {
|
|
219
|
+
// errors.push(`PCR${index} mismatch`)
|
|
220
|
+
// }
|
|
221
|
+
// }
|
|
222
|
+
// Parse certificates with better error handling
|
|
223
|
+
const intermediateCerts = [];
|
|
224
|
+
for (let i = 0; i < doc.cabundle.length; i++) {
|
|
225
|
+
try {
|
|
226
|
+
const cert = new X509Certificate(doc.cabundle[i].toString('base64'));
|
|
227
|
+
intermediateCerts.push(cert);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
errors.push(`Failed to parse cabundle certificate ${i}: ${error.message}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Parse target certificate
|
|
234
|
+
let targetCert;
|
|
235
|
+
try {
|
|
236
|
+
targetCert = new X509Certificate(doc.certificate.toString('base64'));
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
errors.push(`Failed to parse target certificate: ${error.message}`);
|
|
240
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
241
|
+
}
|
|
242
|
+
// Parse root certificate
|
|
243
|
+
let rootCert;
|
|
244
|
+
try {
|
|
245
|
+
rootCert = new X509Certificate(AWS_NITRO_ROOT_CERT);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
errors.push(`Failed to parse AWS Nitro root certificate: ${error.message}`);
|
|
249
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
250
|
+
}
|
|
251
|
+
// Enhanced certificate chain validation
|
|
252
|
+
const chainResult = await validateCertificateChain(targetCert, intermediateCerts, rootCert, crypto);
|
|
253
|
+
if (!chainResult.isValid) {
|
|
254
|
+
errors.push(...chainResult.errors);
|
|
255
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
256
|
+
}
|
|
257
|
+
// Parse and validate public key
|
|
258
|
+
let publicKeyRaw;
|
|
259
|
+
try {
|
|
260
|
+
publicKeyRaw = Buffer.from(targetCert.publicKey.rawData);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
errors.push(`Failed to extract public key: ${error.message}`);
|
|
264
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
265
|
+
}
|
|
266
|
+
// Validate public key format (P-384 ECDSA)
|
|
267
|
+
if (publicKeyRaw.length !== 120 || publicKeyRaw[0] !== 0x30) {
|
|
268
|
+
errors.push(`Invalid public key format: expected 120-byte DER-encoded key, got ${publicKeyRaw.length} bytes`);
|
|
269
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
270
|
+
}
|
|
271
|
+
let spki;
|
|
272
|
+
try {
|
|
273
|
+
spki = AsnParser.parse(publicKeyRaw, SubjectPublicKeyInfo);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
errors.push(`Failed to parse SubjectPublicKeyInfo: ${error.message}`);
|
|
277
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
278
|
+
}
|
|
279
|
+
const ecPoint = Buffer.from(spki.subjectPublicKey);
|
|
280
|
+
if (ecPoint.length !== 97 || ecPoint[0] !== 0x04) {
|
|
281
|
+
errors.push('Invalid EC point: expected 97-byte uncompressed P-384 key');
|
|
282
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
283
|
+
}
|
|
284
|
+
const x = ecPoint.subarray(1, 49); // 48-byte x coordinate
|
|
285
|
+
const y = ecPoint.subarray(49, 97); // 48-byte y coordinate
|
|
286
|
+
// Validate ECDSA signature using cose-js
|
|
287
|
+
try {
|
|
288
|
+
const verifier = {
|
|
289
|
+
key: {
|
|
290
|
+
x: x,
|
|
291
|
+
y: y,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
const options = { defaultType: 18 }; // cose.sign.Sign1Tag
|
|
295
|
+
await sign.verify(Buffer.from(attestationBytes), verifier, options);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
errors.push(`COSE signature verification failed: ${error.message}`);
|
|
299
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
300
|
+
}
|
|
301
|
+
// Extract public key from user_data if present
|
|
302
|
+
let userDataType;
|
|
303
|
+
let ethAddress;
|
|
304
|
+
if (doc.user_data) {
|
|
305
|
+
const keyInfo = extractPublicKeyFromUserData(doc.user_data);
|
|
306
|
+
if (keyInfo) {
|
|
307
|
+
userDataType = keyInfo.teeType;
|
|
308
|
+
ethAddress = keyInfo.ethAddress;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
isValid: errors.length === 0,
|
|
313
|
+
errors,
|
|
314
|
+
warnings,
|
|
315
|
+
userDataType,
|
|
316
|
+
ethAddress,
|
|
317
|
+
pcr0: pcr0
|
|
318
|
+
};
|
|
224
319
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const keyInfo = extractPublicKeyFromUserData(doc.user_data);
|
|
229
|
-
if (keyInfo) {
|
|
230
|
-
userDataType = keyInfo.teeType;
|
|
231
|
-
ethAddress = keyInfo.ethAddress;
|
|
232
|
-
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
errors.push(`Unexpected error during validation: ${error.message}`);
|
|
322
|
+
return { isValid: false, errors, warnings, pcr0: '' };
|
|
233
323
|
}
|
|
234
|
-
return {
|
|
235
|
-
isValid: errors.length === 0,
|
|
236
|
-
errors,
|
|
237
|
-
warnings,
|
|
238
|
-
userDataType,
|
|
239
|
-
ethAddress,
|
|
240
|
-
pcr0
|
|
241
|
-
};
|
|
242
|
-
} catch (error) {
|
|
243
|
-
errors.push(`Unexpected error during validation: ${error.message}`);
|
|
244
|
-
return { isValid: false, errors, warnings, pcr0: "" };
|
|
245
|
-
}
|
|
246
324
|
}
|
|
247
|
-
export {
|
|
248
|
-
validateNitroAttestationAndExtractKey
|
|
249
|
-
};
|
|
@@ -1,61 +1,54 @@
|
|
|
1
|
-
import { getBytes } from
|
|
1
|
+
import { getBytes } from 'ethers';
|
|
2
2
|
import { TOPRF_DOMAIN_SEPARATOR } from "../../config/index.js";
|
|
3
3
|
import { getEnvVariable } from "../../utils/env.js";
|
|
4
4
|
import { makeDefaultOPRFOperator } from "../../utils/zk.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const results = [];
|
|
18
|
-
for (const marker of markers) {
|
|
19
|
-
const { dataLocation } = marker;
|
|
20
|
-
if (!dataLocation) {
|
|
21
|
-
logger.warn("oprf-raw marker missing dataLocation, skipping");
|
|
22
|
-
continue;
|
|
5
|
+
/**
|
|
6
|
+
* Compute OPRF for plaintext data marked with oprf-raw.
|
|
7
|
+
* This runs server-side since the attestor has access to the revealed plaintext.
|
|
8
|
+
*
|
|
9
|
+
* @param plaintext - The revealed plaintext from the TLS transcript
|
|
10
|
+
* @param markers - Positions in the plaintext to compute OPRF for
|
|
11
|
+
* @param logger - Logger instance
|
|
12
|
+
* @returns Array of OPRF results with nullifiers
|
|
13
|
+
*/
|
|
14
|
+
export async function computeOPRFRaw(plaintext, markers, logger) {
|
|
15
|
+
if (!markers.length) {
|
|
16
|
+
return [];
|
|
23
17
|
}
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
`oprf-raw marker out of bounds: fromIndex=${fromIndex}, length=${length}, plaintextLength=${plaintext.length}`
|
|
29
|
-
);
|
|
18
|
+
const PRIVATE_KEY_STR = getEnvVariable('TOPRF_SHARE_PRIVATE_KEY');
|
|
19
|
+
const PUBLIC_KEY_STR = getEnvVariable('TOPRF_SHARE_PUBLIC_KEY');
|
|
20
|
+
if (!PRIVATE_KEY_STR || !PUBLIC_KEY_STR) {
|
|
21
|
+
throw new Error('TOPRF keys not configured. Cannot compute oprf-raw.');
|
|
30
22
|
}
|
|
31
|
-
const
|
|
32
|
-
const
|
|
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
|
-
|
|
23
|
+
const privateKey = getBytes(PRIVATE_KEY_STR);
|
|
24
|
+
const publicKey = getBytes(PUBLIC_KEY_STR);
|
|
25
|
+
// Use gnark engine for server-side OPRF (same as TOPRF handler)
|
|
26
|
+
const operator = makeDefaultOPRFOperator('chacha20', 'gnark', logger);
|
|
27
|
+
const results = [];
|
|
28
|
+
for (const marker of markers) {
|
|
29
|
+
const { dataLocation } = marker;
|
|
30
|
+
if (!dataLocation) {
|
|
31
|
+
logger.warn('oprf-raw marker missing dataLocation, skipping');
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const { fromIndex, length } = dataLocation;
|
|
35
|
+
const endIndex = fromIndex + length;
|
|
36
|
+
if (endIndex > plaintext.length) {
|
|
37
|
+
throw new Error(`oprf-raw marker out of bounds: fromIndex=${fromIndex}, length=${length}, plaintextLength=${plaintext.length}`);
|
|
38
|
+
}
|
|
39
|
+
// Extract the data to OPRF
|
|
40
|
+
const data = plaintext.slice(fromIndex, endIndex);
|
|
41
|
+
// Generate OPRF request (server-side, we do the full flow)
|
|
42
|
+
const request = await operator.generateOPRFRequestData(data, TOPRF_DOMAIN_SEPARATOR, logger);
|
|
43
|
+
// Evaluate OPRF with server's private key
|
|
44
|
+
const response = await operator.evaluateOPRF(privateKey, request.maskedData, logger);
|
|
45
|
+
// Finalize to get the nullifier
|
|
46
|
+
const nullifier = await operator.finaliseOPRF(publicKey, request, [{ ...response, publicKeyShare: publicKey }], logger);
|
|
47
|
+
results.push({
|
|
48
|
+
dataLocation: { fromIndex, length },
|
|
49
|
+
nullifier
|
|
50
|
+
});
|
|
51
|
+
logger.debug({ fromIndex, length, nullifierHex: Buffer.from(nullifier).toString('hex').slice(0, 16) + '...' }, 'computed oprf-raw nullifier');
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
58
54
|
}
|
|
59
|
-
export {
|
|
60
|
-
computeOPRFRaw
|
|
61
|
-
};
|