@calimero-network/mero-js 19.25.0 → 19.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-api/admin-client.d.ts +4 -0
- package/dist/admin-api/admin-client.js +4 -0
- package/dist/admin-api/admin-types.d.ts +18 -0
- package/dist/admin-api/admin-types.js +0 -2
- package/dist/cloud/connect.d.ts +19 -0
- package/dist/cloud/connect.js +17 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +222 -91
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +222 -91
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -2
- package/dist/sealed/index.js +2 -1
- package/dist/sealed/noise.js +11 -3
- package/dist/sealed/sealed.d.ts +46 -6
- package/dist/sealed/sealed.js +57 -1
- package/dist/sealed/verify.d.ts +109 -0
- package/dist/sealed/verify.js +116 -0
- package/package.json +2 -1
package/dist/sealed/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export { HANDSHAKE_PATH, SEALED_CONTENT_TYPE, SEALED_PATH, SealedTransportError, StaleTransportKeyError, createSealedFetch, fetchAttestedTransportKey, transportKeyBinding, } from './sealed.js';
|
|
2
|
-
export type { SealedFetchOptions, VerifyTransportQuote } from './sealed.js';
|
|
1
|
+
export { HANDSHAKE_PATH, SEALED_CONTENT_TYPE, SEALED_PATH, SealedTransportError, StaleTransportKeyError, createAttestedSealedFetch, createSealedFetch, fetchAttestedTransportKey, transportKeyBinding, } from './sealed.js';
|
|
2
|
+
export type { AttestedSealedFetchOptions, SealedFetchOptions, VerifyTransportQuote } from './sealed.js';
|
|
3
|
+
export { createQuoteVerifier } from './verify.js';
|
|
4
|
+
export type { DcapCollateral, DcapVerifiedReport, DcapVerify, QuoteVerifier, QuoteVerifierOptions, } from './verify.js';
|
package/dist/sealed/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { HANDSHAKE_PATH, SEALED_CONTENT_TYPE, SEALED_PATH, SealedTransportError, StaleTransportKeyError, createSealedFetch, fetchAttestedTransportKey, transportKeyBinding, } from './sealed.js';
|
|
1
|
+
export { HANDSHAKE_PATH, SEALED_CONTENT_TYPE, SEALED_PATH, SealedTransportError, StaleTransportKeyError, createAttestedSealedFetch, createSealedFetch, fetchAttestedTransportKey, transportKeyBinding, } from './sealed.js';
|
|
2
|
+
export { createQuoteVerifier } from './verify.js';
|
package/dist/sealed/noise.js
CHANGED
|
@@ -94,9 +94,17 @@ export async function initiate(remoteStatic, prologue, fixedEphemeral) {
|
|
|
94
94
|
}
|
|
95
95
|
export async function x25519KeyPair(secret) {
|
|
96
96
|
if (!secret) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
let pair;
|
|
98
|
+
try {
|
|
99
|
+
pair = (await crypto.subtle.generateKey({ name: 'X25519' }, true, [
|
|
100
|
+
'deriveBits',
|
|
101
|
+
]));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new Error('Sealed transport needs WebCrypto with X25519, which this runtime lacks ' +
|
|
105
|
+
`(${error?.name ?? 'no crypto.subtle'}). Current browsers and Node have ` +
|
|
106
|
+
'it; in React Native, install react-native-quick-crypto as the global crypto.');
|
|
107
|
+
}
|
|
100
108
|
const publicKey = new Uint8Array(await crypto.subtle.exportKey('raw', pair.publicKey));
|
|
101
109
|
return { privateKey: pair.privateKey, publicKey };
|
|
102
110
|
}
|
package/dist/sealed/sealed.d.ts
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import type { TeeAttestRequest, TeeAttestResponseData } from '../admin-api/admin-types.js';
|
|
39
39
|
import { initiate } from './noise.js';
|
|
40
|
+
import type { DcapCollateral } from './verify.js';
|
|
40
41
|
/** Where a session is opened, relative to the node's base URL. */
|
|
41
42
|
export declare const HANDSHAKE_PATH = "/sealed/v2/handshake";
|
|
42
43
|
/** Where sealed requests are posted, relative to the node's base URL. */
|
|
@@ -68,18 +69,25 @@ export declare class SealedTransportError extends Error {
|
|
|
68
69
|
export declare function transportKeyBinding(inner: Uint8Array, transportKey: Uint8Array): Promise<Uint8Array>;
|
|
69
70
|
export interface VerifyTransportQuote {
|
|
70
71
|
/**
|
|
71
|
-
* Verify `quoteB64`
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
72
|
+
* Verify `quoteB64` — never by asking the node being attested — and check
|
|
73
|
+
* that its report data is `nonce || reportDataSuffix` (both hex). Resolve
|
|
74
|
+
* true only when the quote is genuine, its measurements are ones you accept,
|
|
75
|
+
* and the report data matches. {@link createQuoteVerifier} does all of this
|
|
76
|
+
* in the page. `collateral` is what the node sent with the quote, when the
|
|
77
|
+
* verifier asked for it.
|
|
77
78
|
*/
|
|
78
79
|
(args: {
|
|
79
80
|
quoteB64: string;
|
|
80
81
|
nonce: string;
|
|
81
82
|
reportDataSuffix: string;
|
|
83
|
+
collateral?: DcapCollateral;
|
|
82
84
|
}): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Ask the node for the collateral its quote verifies against, and pass it
|
|
87
|
+
* in as `collateral`. Off unless set, because a node that predates the field
|
|
88
|
+
* refuses the request.
|
|
89
|
+
*/
|
|
90
|
+
readonly includeCollateral?: boolean;
|
|
83
91
|
}
|
|
84
92
|
/**
|
|
85
93
|
* Ask a node for its transport key and accept it only if a quote you verified
|
|
@@ -95,6 +103,38 @@ export declare function fetchAttestedTransportKey(admin: {
|
|
|
95
103
|
applicationId?: string;
|
|
96
104
|
applicationHash?: string;
|
|
97
105
|
}): Promise<Uint8Array>;
|
|
106
|
+
export interface AttestedSealedFetchOptions {
|
|
107
|
+
/** The node's base URL. */
|
|
108
|
+
baseUrl: string;
|
|
109
|
+
/**
|
|
110
|
+
* Decides whether the node's quote is one you trust — typically
|
|
111
|
+
* {@link createQuoteVerifier}. Asked again whenever the node restarts.
|
|
112
|
+
*/
|
|
113
|
+
verify: VerifyTransportQuote;
|
|
114
|
+
/** Also require the node to run this application with this bytecode hash. */
|
|
115
|
+
applicationId?: string;
|
|
116
|
+
applicationHash?: string;
|
|
117
|
+
/** Fetch that carries the attestation and the envelopes. Defaults to global `fetch`. */
|
|
118
|
+
fetch?: typeof fetch;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* {@link createSealedFetch} for a node you know only by URL: it attests the
|
|
122
|
+
* node's transport key with `verify`, over `POST /admin-api/tee/attest` (the
|
|
123
|
+
* one call that needs no credential and is never sealed), and seals everything
|
|
124
|
+
* else to it. A restarted node is attested again, through the same verifier.
|
|
125
|
+
*
|
|
126
|
+
* This is what to hand a {@link RelayClient} for a TEE relay, so the warrant
|
|
127
|
+
* and the method's arguments reach only the attested TD:
|
|
128
|
+
*
|
|
129
|
+
* ```ts
|
|
130
|
+
* const relay = new RelayClient({
|
|
131
|
+
* relayUrl,
|
|
132
|
+
* fetch: createAttestedSealedFetch({ baseUrl: relayUrl, verify: createQuoteVerifier(...) }),
|
|
133
|
+
* ...
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare function createAttestedSealedFetch(options: AttestedSealedFetchOptions): typeof fetch;
|
|
98
138
|
export interface SealedFetchOptions {
|
|
99
139
|
/** The node's base URL, as given to `MeroJsConfig.baseUrl`. */
|
|
100
140
|
baseUrl: string;
|
package/dist/sealed/sealed.js
CHANGED
|
@@ -109,6 +109,7 @@ export async function fetchAttestedTransportKey(admin, verify, options = {}) {
|
|
|
109
109
|
nonce,
|
|
110
110
|
applicationId: options.applicationId,
|
|
111
111
|
bindTransportKey: true,
|
|
112
|
+
...(verify.includeCollateral ? { includeCollateral: true } : {}),
|
|
112
113
|
});
|
|
113
114
|
if (!attested.transportPublicKey) {
|
|
114
115
|
throw new Error('The node did not report a transport key; it predates sealed transport');
|
|
@@ -118,11 +119,62 @@ export async function fetchAttestedTransportKey(admin, verify, options = {}) {
|
|
|
118
119
|
? fromHex(options.applicationHash, 'applicationHash', 32)
|
|
119
120
|
: new Uint8Array(32);
|
|
120
121
|
const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));
|
|
121
|
-
|
|
122
|
+
const verified = await verify({
|
|
123
|
+
quoteB64: attested.quoteB64,
|
|
124
|
+
nonce,
|
|
125
|
+
reportDataSuffix,
|
|
126
|
+
collateral: attested.collateral,
|
|
127
|
+
});
|
|
128
|
+
if (!verified) {
|
|
122
129
|
throw new Error('The attestation did not verify, so its transport key is not trusted');
|
|
123
130
|
}
|
|
124
131
|
return transportKey;
|
|
125
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* {@link createSealedFetch} for a node you know only by URL: it attests the
|
|
135
|
+
* node's transport key with `verify`, over `POST /admin-api/tee/attest` (the
|
|
136
|
+
* one call that needs no credential and is never sealed), and seals everything
|
|
137
|
+
* else to it. A restarted node is attested again, through the same verifier.
|
|
138
|
+
*
|
|
139
|
+
* This is what to hand a {@link RelayClient} for a TEE relay, so the warrant
|
|
140
|
+
* and the method's arguments reach only the attested TD:
|
|
141
|
+
*
|
|
142
|
+
* ```ts
|
|
143
|
+
* const relay = new RelayClient({
|
|
144
|
+
* relayUrl,
|
|
145
|
+
* fetch: createAttestedSealedFetch({ baseUrl: relayUrl, verify: createQuoteVerifier(...) }),
|
|
146
|
+
* ...
|
|
147
|
+
* });
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function createAttestedSealedFetch(options) {
|
|
151
|
+
const baseFetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
152
|
+
const attestUrl = `${options.baseUrl.replace(/\/+$/, '')}/admin-api/tee/attest`;
|
|
153
|
+
const admin = {
|
|
154
|
+
async teeAttest(request) {
|
|
155
|
+
const response = await baseFetch(attestUrl, {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: { 'content-type': 'application/json' },
|
|
158
|
+
body: JSON.stringify(request),
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw new Error(`The node refused to attest: HTTP ${response.status}`);
|
|
162
|
+
}
|
|
163
|
+
const body = (await response.json());
|
|
164
|
+
if (!body.data)
|
|
165
|
+
throw new Error('The attestation response is malformed');
|
|
166
|
+
return body.data;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
return createSealedFetch({
|
|
170
|
+
baseUrl: options.baseUrl,
|
|
171
|
+
transportPublicKey: () => fetchAttestedTransportKey(admin, options.verify, {
|
|
172
|
+
applicationId: options.applicationId,
|
|
173
|
+
applicationHash: options.applicationHash,
|
|
174
|
+
}),
|
|
175
|
+
fetch: baseFetch,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
126
178
|
/**
|
|
127
179
|
* A `fetch` that seals every request to the node's attested transport key.
|
|
128
180
|
* Pass it as `MeroJsConfig.fetch`.
|
|
@@ -139,6 +191,10 @@ export function createSealedFetch(options) {
|
|
|
139
191
|
let transportKey = attest
|
|
140
192
|
? Promise.resolve().then(attest)
|
|
141
193
|
: Promise.resolve(options.transportPublicKey);
|
|
194
|
+
// Attesting starts now, before any request awaits it. A node that fails to
|
|
195
|
+
// attest must surface on the request that needed the key, not earlier as an
|
|
196
|
+
// unhandled rejection; the failure is still thrown to every awaiter.
|
|
197
|
+
transportKey.catch(() => undefined);
|
|
142
198
|
let session;
|
|
143
199
|
const openSession = async () => {
|
|
144
200
|
try {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifying a TEE node's quote in the page, before trusting the key it binds.
|
|
3
|
+
*
|
|
4
|
+
* A quote proves what runs in a TD only once it has been checked: its
|
|
5
|
+
* signatures against Intel's root, the TCB status of the platform, the
|
|
6
|
+
* measurements of the image, and that its report data is the nonce and the
|
|
7
|
+
* binding this client asked for. {@link createQuoteVerifier} does all of that
|
|
8
|
+
* here, with nothing but the node: the node hands over the Intel-signed
|
|
9
|
+
* collateral with its quote (`includeCollateral`), and a node cannot forge
|
|
10
|
+
* collateral, only choose which valid collateral to serve.
|
|
11
|
+
*
|
|
12
|
+
* The DCAP verification itself — parsing the quote, checking its signature
|
|
13
|
+
* chain and appraising its TCB level against the collateral — comes from a
|
|
14
|
+
* library you pass in, normally `verify` from `@phala/dcap-qvl`, a pure
|
|
15
|
+
* JavaScript implementation that runs in browsers, Node and React Native. It
|
|
16
|
+
* is passed in rather than bundled so this SDK keeps no runtime dependencies,
|
|
17
|
+
* and applications that never talk to a TEE node pay nothing for it.
|
|
18
|
+
*/
|
|
19
|
+
import type { VerifyTransportQuote } from './sealed.js';
|
|
20
|
+
/**
|
|
21
|
+
* The collateral a quote is verified against — TCB info, QE identity, CRLs and
|
|
22
|
+
* their chains — in dcap-qvl's form: byte fields are hex strings or byte
|
|
23
|
+
* arrays. What `/admin-api/tee/attest` returns with `includeCollateral`, and
|
|
24
|
+
* what `@phala/dcap-qvl`'s `getCollateral` fetches.
|
|
25
|
+
*/
|
|
26
|
+
export interface DcapCollateral {
|
|
27
|
+
pck_crl_issuer_chain: string;
|
|
28
|
+
root_ca_crl: number[] | string;
|
|
29
|
+
pck_crl: number[] | string;
|
|
30
|
+
tcb_info_issuer_chain: string;
|
|
31
|
+
tcb_info: string;
|
|
32
|
+
tcb_info_signature: number[] | string;
|
|
33
|
+
qe_identity_issuer_chain: string;
|
|
34
|
+
qe_identity: string;
|
|
35
|
+
qe_identity_signature: number[] | string;
|
|
36
|
+
}
|
|
37
|
+
/** What a DCAP verifier returns for a quote whose signature chain verified. */
|
|
38
|
+
export interface DcapVerifiedReport {
|
|
39
|
+
status: string;
|
|
40
|
+
advisory_ids?: string[];
|
|
41
|
+
report: {
|
|
42
|
+
type: string;
|
|
43
|
+
data: unknown;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Verify a quote's signature chain against `collateral` as of `nowSecs`, and
|
|
48
|
+
* appraise its TCB level. Throws when it does not verify. `verify` from
|
|
49
|
+
* `@phala/dcap-qvl` has exactly this shape.
|
|
50
|
+
*/
|
|
51
|
+
export type DcapVerify = (quote: Uint8Array, collateral: DcapCollateral, nowSecs: number) => DcapVerifiedReport;
|
|
52
|
+
export interface QuoteVerifierOptions {
|
|
53
|
+
/** DCAP verification: `verify` from `@phala/dcap-qvl`. */
|
|
54
|
+
dcapVerify: DcapVerify;
|
|
55
|
+
/**
|
|
56
|
+
* MRTDs (hex) of the images trusted. Required: the MRTD names the image a TD
|
|
57
|
+
* booted, and a quote that proves only "some genuine TD" proves nothing
|
|
58
|
+
* about which code reads the traffic.
|
|
59
|
+
*/
|
|
60
|
+
allowedMrtd: string[];
|
|
61
|
+
/** RTMR allowlists (hex); a register is not checked when its list is left out. */
|
|
62
|
+
allowedRtmr0?: string[];
|
|
63
|
+
allowedRtmr1?: string[];
|
|
64
|
+
allowedRtmr2?: string[];
|
|
65
|
+
allowedRtmr3?: string[];
|
|
66
|
+
/**
|
|
67
|
+
* TCB statuses accepted. Defaults to `['UpToDate']`. `Revoked` is refused
|
|
68
|
+
* even when listed.
|
|
69
|
+
*/
|
|
70
|
+
allowedTcbStatuses?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Where to get collateral when the node sends none (it predates
|
|
73
|
+
* `includeCollateral`): for example
|
|
74
|
+
* `(quote) => getCollateral(PHALA_PCCS_URL, quote)` from `@phala/dcap-qvl`.
|
|
75
|
+
* Without it, such a node's quote is refused.
|
|
76
|
+
*/
|
|
77
|
+
fetchCollateral?: (quote: Uint8Array) => Promise<DcapCollateral>;
|
|
78
|
+
/** The current time in milliseconds; defaults to `Date.now`. For tests. */
|
|
79
|
+
now?: () => number;
|
|
80
|
+
}
|
|
81
|
+
/** A verifier for {@link fetchAttestedTransportKey}, which asks the node for collateral. */
|
|
82
|
+
export type QuoteVerifier = VerifyTransportQuote & {
|
|
83
|
+
readonly includeCollateral: true;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* A verifier that checks a TEE node's quote here, in the page, and accepts it
|
|
87
|
+
* only when every check passes:
|
|
88
|
+
*
|
|
89
|
+
* - its signatures verify against Intel's root, with collateral that is valid
|
|
90
|
+
* now;
|
|
91
|
+
* - the platform's TCB status is one allowed (`UpToDate` by default), and
|
|
92
|
+
* never `Revoked`;
|
|
93
|
+
* - the MRTD, and any RTMRs given, are ones allowed;
|
|
94
|
+
* - its report data is the nonce followed by the binding the client computed.
|
|
95
|
+
*
|
|
96
|
+
* Any failure rejects with the reason; nothing is trusted from a quote that
|
|
97
|
+
* does not pass. Mock quotes never pass: they carry no signature.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { verify as dcapVerify } from '@phala/dcap-qvl';
|
|
101
|
+
*
|
|
102
|
+
* const verifier = createQuoteVerifier({ dcapVerify, allowedMrtd: [trustedMrtd] });
|
|
103
|
+
* const fetch = createSealedFetch({
|
|
104
|
+
* baseUrl,
|
|
105
|
+
* transportPublicKey: () => fetchAttestedTransportKey(admin, verifier),
|
|
106
|
+
* });
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function createQuoteVerifier(options: QuoteVerifierOptions): QuoteVerifier;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifying a TEE node's quote in the page, before trusting the key it binds.
|
|
3
|
+
*
|
|
4
|
+
* A quote proves what runs in a TD only once it has been checked: its
|
|
5
|
+
* signatures against Intel's root, the TCB status of the platform, the
|
|
6
|
+
* measurements of the image, and that its report data is the nonce and the
|
|
7
|
+
* binding this client asked for. {@link createQuoteVerifier} does all of that
|
|
8
|
+
* here, with nothing but the node: the node hands over the Intel-signed
|
|
9
|
+
* collateral with its quote (`includeCollateral`), and a node cannot forge
|
|
10
|
+
* collateral, only choose which valid collateral to serve.
|
|
11
|
+
*
|
|
12
|
+
* The DCAP verification itself — parsing the quote, checking its signature
|
|
13
|
+
* chain and appraising its TCB level against the collateral — comes from a
|
|
14
|
+
* library you pass in, normally `verify` from `@phala/dcap-qvl`, a pure
|
|
15
|
+
* JavaScript implementation that runs in browsers, Node and React Native. It
|
|
16
|
+
* is passed in rather than bundled so this SDK keeps no runtime dependencies,
|
|
17
|
+
* and applications that never talk to a TEE node pay nothing for it.
|
|
18
|
+
*/
|
|
19
|
+
import { hex } from '../crypto/internal.js';
|
|
20
|
+
const MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
|
|
21
|
+
/**
|
|
22
|
+
* A verifier that checks a TEE node's quote here, in the page, and accepts it
|
|
23
|
+
* only when every check passes:
|
|
24
|
+
*
|
|
25
|
+
* - its signatures verify against Intel's root, with collateral that is valid
|
|
26
|
+
* now;
|
|
27
|
+
* - the platform's TCB status is one allowed (`UpToDate` by default), and
|
|
28
|
+
* never `Revoked`;
|
|
29
|
+
* - the MRTD, and any RTMRs given, are ones allowed;
|
|
30
|
+
* - its report data is the nonce followed by the binding the client computed.
|
|
31
|
+
*
|
|
32
|
+
* Any failure rejects with the reason; nothing is trusted from a quote that
|
|
33
|
+
* does not pass. Mock quotes never pass: they carry no signature.
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { verify as dcapVerify } from '@phala/dcap-qvl';
|
|
37
|
+
*
|
|
38
|
+
* const verifier = createQuoteVerifier({ dcapVerify, allowedMrtd: [trustedMrtd] });
|
|
39
|
+
* const fetch = createSealedFetch({
|
|
40
|
+
* baseUrl,
|
|
41
|
+
* transportPublicKey: () => fetchAttestedTransportKey(admin, verifier),
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function createQuoteVerifier(options) {
|
|
46
|
+
const allowedMrtd = measurements(options.allowedMrtd, 'allowedMrtd');
|
|
47
|
+
if (allowedMrtd.length === 0) {
|
|
48
|
+
throw new Error('allowedMrtd is empty, so no quote would ever be accepted');
|
|
49
|
+
}
|
|
50
|
+
const allowedRtmr = [
|
|
51
|
+
measurements(options.allowedRtmr0 ?? [], 'allowedRtmr0'),
|
|
52
|
+
measurements(options.allowedRtmr1 ?? [], 'allowedRtmr1'),
|
|
53
|
+
measurements(options.allowedRtmr2 ?? [], 'allowedRtmr2'),
|
|
54
|
+
measurements(options.allowedRtmr3 ?? [], 'allowedRtmr3'),
|
|
55
|
+
];
|
|
56
|
+
const allowedStatuses = options.allowedTcbStatuses ?? ['UpToDate'];
|
|
57
|
+
const now = options.now ?? Date.now;
|
|
58
|
+
const verifier = async ({ quoteB64, nonce, reportDataSuffix, collateral, }) => {
|
|
59
|
+
const quote = fromBase64(quoteB64);
|
|
60
|
+
const against = collateral ?? (await options.fetchCollateral?.(quote));
|
|
61
|
+
if (!against) {
|
|
62
|
+
throw new Error('The node sent no collateral for its quote, and no fetchCollateral was given to get it');
|
|
63
|
+
}
|
|
64
|
+
let verified;
|
|
65
|
+
try {
|
|
66
|
+
verified = options.dcapVerify(quote, against, Math.floor(now() / 1000));
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new Error(`The quote did not verify: ${error?.message ?? String(error)}`);
|
|
70
|
+
}
|
|
71
|
+
if (verified.status === 'Revoked' || !allowedStatuses.includes(verified.status)) {
|
|
72
|
+
throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
|
|
73
|
+
}
|
|
74
|
+
const td = tdReport(verified.report);
|
|
75
|
+
if (!allowedMrtd.includes(hex(td.mrTd))) {
|
|
76
|
+
throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);
|
|
77
|
+
}
|
|
78
|
+
const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];
|
|
79
|
+
registers.forEach((register, index) => {
|
|
80
|
+
const allowed = allowedRtmr[index];
|
|
81
|
+
if (allowed.length > 0 && !allowed.includes(hex(register))) {
|
|
82
|
+
throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (hex(td.reportData) !== `${nonce}${reportDataSuffix}`.toLowerCase()) {
|
|
86
|
+
throw new Error('The quote does not commit to this nonce and binding');
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
return Object.assign(verifier, { includeCollateral: true });
|
|
91
|
+
}
|
|
92
|
+
function measurements(values, label) {
|
|
93
|
+
return values.map((value) => {
|
|
94
|
+
const normalized = value.trim().toLowerCase();
|
|
95
|
+
if (!MEASUREMENT_HEX.test(normalized)) {
|
|
96
|
+
throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** The TD report of a TDX quote (1.0 or 1.5); anything else is refused. */
|
|
102
|
+
function tdReport(report) {
|
|
103
|
+
const data = report.data;
|
|
104
|
+
if (report.type === 'td10' && data)
|
|
105
|
+
return data;
|
|
106
|
+
if (report.type === 'td15' && data?.base)
|
|
107
|
+
return data.base;
|
|
108
|
+
throw new Error(`The quote is a ${report.type} quote, not a TDX one`);
|
|
109
|
+
}
|
|
110
|
+
function fromBase64(value) {
|
|
111
|
+
const binary = atob(value);
|
|
112
|
+
const bytes = new Uint8Array(binary.length);
|
|
113
|
+
for (let i = 0; i < binary.length; i += 1)
|
|
114
|
+
bytes[i] = binary.charCodeAt(i);
|
|
115
|
+
return bytes;
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calimero-network/mero-js",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.27.0",
|
|
4
4
|
"description": "Pure JavaScript SDK for Calimero",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"sideEffects": false,
|
|
19
19
|
"devDependencies": {
|
|
20
|
+
"@phala/dcap-qvl": "0.5.3",
|
|
20
21
|
"@semantic-release/changelog": "^6.0.3",
|
|
21
22
|
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
22
23
|
"@semantic-release/exec": "^7.0.0",
|