@layr-labs/ecloud-sdk 0.5.0 → 1.0.0-dev.2
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/VERSION +2 -2
- package/dist/attest.cjs +31 -9
- package/dist/attest.cjs.map +1 -1
- package/dist/attest.d.cts +3 -2
- package/dist/attest.d.ts +3 -2
- package/dist/attest.js +31 -9
- package/dist/attest.js.map +1 -1
- package/dist/billing.cjs +14 -2
- package/dist/billing.cjs.map +1 -1
- package/dist/billing.js +14 -2
- package/dist/billing.js.map +1 -1
- package/dist/browser.cjs +15 -3
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +15 -3
- package/dist/browser.js.map +1 -1
- package/dist/compute.cjs +11 -3
- package/dist/compute.cjs.map +1 -1
- package/dist/compute.js +11 -3
- package/dist/compute.js.map +1 -1
- package/dist/index.cjs +46 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
version=0.
|
|
2
|
-
commit=
|
|
1
|
+
version=1.0.0-dev.2
|
|
2
|
+
commit=44b22091b826ce951383ff8a7fb2136c010abb56
|
package/dist/attest.cjs
CHANGED
|
@@ -47,7 +47,10 @@ var AttestClient = class {
|
|
|
47
47
|
constructor(config) {
|
|
48
48
|
this.config = config;
|
|
49
49
|
}
|
|
50
|
-
async attest() {
|
|
50
|
+
async attest(extraData) {
|
|
51
|
+
if (extraData && extraData.length > 1048576) {
|
|
52
|
+
throw new Error(`extraData exceeds 1MB limit (${extraData.length} bytes)`);
|
|
53
|
+
}
|
|
51
54
|
const { publicKey, privateKey } = (0, import_node_crypto.generateKeyPairSync)("rsa", {
|
|
52
55
|
modulusLength: 4096,
|
|
53
56
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
@@ -55,8 +58,8 @@ var AttestClient = class {
|
|
|
55
58
|
});
|
|
56
59
|
const challengeHash = (0, import_node_crypto.createHash)("sha256").update(CHALLENGE_PREFIX).update(NULL_BYTE).update(publicKey).digest();
|
|
57
60
|
const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;
|
|
58
|
-
const attestationBytes = await this.getAttestation(socketPath, challengeHash);
|
|
59
|
-
const attestResponse = await this.postAttest(attestationBytes, publicKey);
|
|
61
|
+
const attestationBytes = await this.getAttestation(socketPath, challengeHash, extraData);
|
|
62
|
+
const attestResponse = await this.postAttest(attestationBytes, publicKey, extraData);
|
|
60
63
|
this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);
|
|
61
64
|
const rsaPrivateKey = await crypto.subtle.importKey(
|
|
62
65
|
"pkcs8",
|
|
@@ -88,9 +91,13 @@ var AttestClient = class {
|
|
|
88
91
|
throw new Error("KMS response signature verification failed");
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
|
-
getAttestation(socketPath, challenge) {
|
|
94
|
+
getAttestation(socketPath, challenge, extraData) {
|
|
92
95
|
return new Promise((resolve, reject) => {
|
|
93
|
-
const
|
|
96
|
+
const requestBody = { challenge: challenge.toString("base64") };
|
|
97
|
+
if (extraData && extraData.length > 0) {
|
|
98
|
+
requestBody.extra_data = extraData.toString("base64");
|
|
99
|
+
}
|
|
100
|
+
const body = JSON.stringify(requestBody);
|
|
94
101
|
const req = import_node_http.default.request(
|
|
95
102
|
{
|
|
96
103
|
socketPath,
|
|
@@ -118,14 +125,18 @@ var AttestClient = class {
|
|
|
118
125
|
req.end();
|
|
119
126
|
});
|
|
120
127
|
}
|
|
121
|
-
async postAttest(attestationBytes, rsaPublicKey) {
|
|
128
|
+
async postAttest(attestationBytes, rsaPublicKey, extraData) {
|
|
122
129
|
const url = `${this.config.kmsServerURL}/auth/attest`;
|
|
123
|
-
const
|
|
130
|
+
const requestBody = {
|
|
124
131
|
version: 3,
|
|
125
132
|
attestation: attestationBytes.toString("base64"),
|
|
126
133
|
rsaKey: rsaPublicKey,
|
|
127
134
|
audience: this.config.audience
|
|
128
|
-
}
|
|
135
|
+
};
|
|
136
|
+
if (extraData && extraData.length > 0) {
|
|
137
|
+
requestBody.extra_data = extraData.toString("base64");
|
|
138
|
+
}
|
|
139
|
+
const body = JSON.stringify(requestBody);
|
|
129
140
|
const response = await fetch(url, {
|
|
130
141
|
method: "POST",
|
|
131
142
|
headers: { "Content-Type": "application/json" },
|
|
@@ -147,10 +158,21 @@ function pemToBuffer(pem) {
|
|
|
147
158
|
// src/client/modules/attest/jwt-provider.ts
|
|
148
159
|
var JwtProvider = class {
|
|
149
160
|
constructor(attestClient, bufferSeconds = 30) {
|
|
161
|
+
this.pendingExtraData = /* @__PURE__ */ new Map();
|
|
150
162
|
this.attestClient = attestClient;
|
|
151
163
|
this.bufferSeconds = bufferSeconds;
|
|
152
164
|
}
|
|
153
|
-
async getToken() {
|
|
165
|
+
async getToken(extraData) {
|
|
166
|
+
if (extraData && extraData.length > 0) {
|
|
167
|
+
const key = extraData.toString("hex");
|
|
168
|
+
const existing = this.pendingExtraData.get(key);
|
|
169
|
+
if (existing) return existing;
|
|
170
|
+
const promise = this.attestClient.attest(extraData).finally(() => {
|
|
171
|
+
this.pendingExtraData.delete(key);
|
|
172
|
+
});
|
|
173
|
+
this.pendingExtraData.set(key, promise);
|
|
174
|
+
return promise;
|
|
175
|
+
}
|
|
154
176
|
if (this.cachedToken && !this.isExpiringSoon()) {
|
|
155
177
|
return this.cachedToken;
|
|
156
178
|
}
|
package/dist/attest.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/attest.ts","../src/client/modules/attest/attest-client.ts","../src/client/modules/attest/jwt-provider.ts"],"sourcesContent":["/**\n * Attestation module entry point\n *\n * Import from \"@layr-labs/ecloud-sdk/attest\" for direct access to attest module\n */\n\nexport * from \"./client/modules/attest\";\n","import { generateKeyPairSync, createHash, verify } from 'node:crypto';\nimport http from 'node:http';\nimport { compactDecrypt } from 'jose';\n\nexport interface AttestClientConfig {\n kmsServerURL: string;\n kmsPublicKey: string;\n audience: string;\n socketPath?: string;\n}\n\nconst DEFAULT_SOCKET_PATH = '/run/container_launcher/teeserver.sock';\nconst CHALLENGE_PREFIX = 'COMPUTE_APP_JWT_REQUEST_RSA_KEY_V1';\nconst SIGNATURE_PREFIX = 'COMPUTE_APP_KMS_SIGNATURE_V1';\nconst NULL_BYTE = Buffer.from([0x00]);\n\nexport class AttestClient {\n private config: AttestClientConfig;\n\n constructor(config: AttestClientConfig) {\n this.config = config;\n }\n\n async attest(): Promise<string> {\n const { publicKey, privateKey } = generateKeyPairSync('rsa', {\n modulusLength: 4096,\n publicKeyEncoding: { type: 'spki', format: 'pem' } as const,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' } as const,\n });\n\n const challengeHash = createHash('sha256')\n .update(CHALLENGE_PREFIX)\n .update(NULL_BYTE)\n .update(publicKey)\n .digest();\n\n const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;\n const attestationBytes = await this.getAttestation(socketPath, challengeHash);\n const attestResponse = await this.postAttest(attestationBytes, publicKey);\n\n this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);\n\n const rsaPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pemToBuffer(privateKey),\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false,\n ['decrypt'],\n );\n\n const { plaintext } = await compactDecrypt(\n attestResponse.data.encryptedToken,\n rsaPrivateKey,\n );\n\n const decrypted = JSON.parse(new TextDecoder().decode(plaintext));\n return decrypted.token;\n }\n\n private verifySignature(\n dataJson: string,\n signature: string,\n ): void {\n const message = Buffer.concat([\n Buffer.from(SIGNATURE_PREFIX),\n NULL_BYTE,\n Buffer.from(dataJson),\n ]);\n\n const valid = verify(\n 'sha256',\n message,\n this.config.kmsPublicKey,\n Buffer.from(signature, 'base64'),\n );\n\n if (!valid) {\n throw new Error('KMS response signature verification failed');\n }\n }\n\n private getAttestation(socketPath: string, challenge: Buffer): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const body = JSON.stringify({ challenge: challenge.toString('base64') });\n\n const req = http.request(\n {\n socketPath,\n path: '/v1/bound_evidence',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n if (res.statusCode !== 200) {\n reject(new Error(`TEE attestation failed (${res.statusCode}): ${Buffer.concat(chunks).toString()}`));\n return;\n }\n resolve(Buffer.concat(chunks));\n });\n },\n );\n\n req.on('error', (err) => reject(new Error(`TEE attestation request failed: ${err.message}`)));\n req.write(body);\n req.end();\n });\n }\n\n private async postAttest(\n attestationBytes: Buffer,\n rsaPublicKey: string,\n ): Promise<{ data: { encryptedToken: string }; signature: string }> {\n const url = `${this.config.kmsServerURL}/auth/attest`;\n const body = JSON.stringify({\n version: 3,\n attestation: attestationBytes.toString('base64'),\n rsaKey: rsaPublicKey,\n audience: this.config.audience,\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`KMS attest failed (${response.status}): ${text}`);\n }\n\n return response.json() as Promise<{ data: { encryptedToken: string }; signature: string }>;\n }\n}\n\nfunction pemToBuffer(pem: string): ArrayBuffer {\n const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\\s/g, '');\n const buf = Buffer.from(b64, 'base64');\n\n // Copy to a fresh ArrayBuffer to avoid offset issues with Buffer's shared pool\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);\n}\n","import type { AttestClient } from './attest-client';\n\nexport class JwtProvider {\n private attestClient: AttestClient;\n private bufferSeconds: number;\n private cachedToken?: string;\n private expiresAt?: number;\n private pending?: Promise<string>;\n\n constructor(attestClient: AttestClient, bufferSeconds: number = 30) {\n this.attestClient = attestClient;\n this.bufferSeconds = bufferSeconds;\n }\n\n async getToken(): Promise<string> {\n if (this.cachedToken && !this.isExpiringSoon()) {\n return this.cachedToken;\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.attestClient\n .attest()\n .then((token) => {\n this.cachedToken = token;\n this.expiresAt = this.decodeJwtExp(token);\n return token;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n private isExpiringSoon(): boolean {\n if (!this.expiresAt) return true;\n return Date.now() / 1000 >= this.expiresAt - this.bufferSeconds;\n }\n\n private decodeJwtExp(jwt: string): number | undefined {\n const payload = jwt.split('.')[1];\n if (!payload) return undefined;\n\n const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString());\n return decoded.exp;\n }\n}\n"],"mappings":"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","names":["http"]}
|
|
1
|
+
{"version":3,"sources":["../src/attest.ts","../src/client/modules/attest/attest-client.ts","../src/client/modules/attest/jwt-provider.ts"],"sourcesContent":["/**\n * Attestation module entry point\n *\n * Import from \"@layr-labs/ecloud-sdk/attest\" for direct access to attest module\n */\n\nexport * from \"./client/modules/attest\";\n","import { generateKeyPairSync, createHash, verify } from 'node:crypto';\nimport http from 'node:http';\nimport { compactDecrypt } from 'jose';\n\nexport interface AttestClientConfig {\n kmsServerURL: string;\n kmsPublicKey: string;\n audience: string;\n socketPath?: string;\n}\n\nconst DEFAULT_SOCKET_PATH = '/run/container_launcher/teeserver.sock';\nconst CHALLENGE_PREFIX = 'COMPUTE_APP_JWT_REQUEST_RSA_KEY_V1';\nconst SIGNATURE_PREFIX = 'COMPUTE_APP_KMS_SIGNATURE_V1';\nconst NULL_BYTE = Buffer.from([0x00]);\n\nexport class AttestClient {\n private config: AttestClientConfig;\n\n constructor(config: AttestClientConfig) {\n this.config = config;\n }\n\n async attest(extraData?: Buffer): Promise<string> {\n // go-tpm-tools hashes extraData (SHA-256/SHA-512) before binding it into the\n // hardware nonce, so callers can pass arbitrary data up to 1MB.\n if (extraData && extraData.length > 1_048_576) {\n throw new Error(`extraData exceeds 1MB limit (${extraData.length} bytes)`);\n }\n\n const { publicKey, privateKey } = generateKeyPairSync('rsa', {\n modulusLength: 4096,\n publicKeyEncoding: { type: 'spki', format: 'pem' } as const,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' } as const,\n });\n\n const challengeHash = createHash('sha256')\n .update(CHALLENGE_PREFIX)\n .update(NULL_BYTE)\n .update(publicKey)\n .digest();\n\n const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;\n const attestationBytes = await this.getAttestation(socketPath, challengeHash, extraData);\n const attestResponse = await this.postAttest(attestationBytes, publicKey, extraData);\n\n this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);\n\n const rsaPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pemToBuffer(privateKey),\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false,\n ['decrypt'],\n );\n\n const { plaintext } = await compactDecrypt(\n attestResponse.data.encryptedToken,\n rsaPrivateKey,\n );\n\n const decrypted = JSON.parse(new TextDecoder().decode(plaintext));\n return decrypted.token;\n }\n\n private verifySignature(\n dataJson: string,\n signature: string,\n ): void {\n const message = Buffer.concat([\n Buffer.from(SIGNATURE_PREFIX),\n NULL_BYTE,\n Buffer.from(dataJson),\n ]);\n\n const valid = verify(\n 'sha256',\n message,\n this.config.kmsPublicKey,\n Buffer.from(signature, 'base64'),\n );\n\n if (!valid) {\n throw new Error('KMS response signature verification failed');\n }\n }\n\n private getAttestation(socketPath: string, challenge: Buffer, extraData?: Buffer): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const requestBody: Record<string, string> = { challenge: challenge.toString('base64') };\n if (extraData && extraData.length > 0) {\n requestBody.extra_data = extraData.toString('base64');\n }\n const body = JSON.stringify(requestBody);\n\n const req = http.request(\n {\n socketPath,\n path: '/v1/bound_evidence',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n if (res.statusCode !== 200) {\n reject(new Error(`TEE attestation failed (${res.statusCode}): ${Buffer.concat(chunks).toString()}`));\n return;\n }\n resolve(Buffer.concat(chunks));\n });\n },\n );\n\n req.on('error', (err) => reject(new Error(`TEE attestation request failed: ${err.message}`)));\n req.write(body);\n req.end();\n });\n }\n\n private async postAttest(\n attestationBytes: Buffer,\n rsaPublicKey: string,\n extraData?: Buffer,\n ): Promise<{ data: { encryptedToken: string }; signature: string }> {\n const url = `${this.config.kmsServerURL}/auth/attest`;\n const requestBody: Record<string, unknown> = {\n version: 3,\n attestation: attestationBytes.toString('base64'),\n rsaKey: rsaPublicKey,\n audience: this.config.audience,\n };\n if (extraData && extraData.length > 0) {\n requestBody.extra_data = extraData.toString('base64');\n }\n const body = JSON.stringify(requestBody);\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`KMS attest failed (${response.status}): ${text}`);\n }\n\n return response.json() as Promise<{ data: { encryptedToken: string }; signature: string }>;\n }\n}\n\nfunction pemToBuffer(pem: string): ArrayBuffer {\n const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\\s/g, '');\n const buf = Buffer.from(b64, 'base64');\n\n // Copy to a fresh ArrayBuffer to avoid offset issues with Buffer's shared pool\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);\n}\n","import type { AttestClient } from './attest-client';\n\nexport class JwtProvider {\n private attestClient: AttestClient;\n private bufferSeconds: number;\n private cachedToken?: string;\n private expiresAt?: number;\n private pending?: Promise<string>;\n private pendingExtraData = new Map<string, Promise<string>>();\n\n constructor(attestClient: AttestClient, bufferSeconds: number = 30) {\n this.attestClient = attestClient;\n this.bufferSeconds = bufferSeconds;\n }\n\n async getToken(extraData?: Buffer): Promise<string> {\n // When extraData is provided, bypass long-lived cache but deduplicate\n // concurrent requests for the same extraData to avoid thundering herd\n // on TEE hardware calls.\n if (extraData && extraData.length > 0) {\n const key = extraData.toString('hex');\n const existing = this.pendingExtraData.get(key);\n if (existing) return existing;\n const promise = this.attestClient.attest(extraData).finally(() => {\n this.pendingExtraData.delete(key);\n });\n this.pendingExtraData.set(key, promise);\n return promise;\n }\n\n if (this.cachedToken && !this.isExpiringSoon()) {\n return this.cachedToken;\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.attestClient\n .attest()\n .then((token) => {\n this.cachedToken = token;\n this.expiresAt = this.decodeJwtExp(token);\n return token;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n private isExpiringSoon(): boolean {\n if (!this.expiresAt) return true;\n return Date.now() / 1000 >= this.expiresAt - this.bufferSeconds;\n }\n\n private decodeJwtExp(jwt: string): number | undefined {\n const payload = jwt.split('.')[1];\n if (!payload) return undefined;\n\n const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString());\n return decoded.exp;\n }\n}\n"],"mappings":"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","names":["http"]}
|
package/dist/attest.d.cts
CHANGED
|
@@ -7,7 +7,7 @@ interface AttestClientConfig {
|
|
|
7
7
|
declare class AttestClient {
|
|
8
8
|
private config;
|
|
9
9
|
constructor(config: AttestClientConfig);
|
|
10
|
-
attest(): Promise<string>;
|
|
10
|
+
attest(extraData?: Buffer): Promise<string>;
|
|
11
11
|
private verifySignature;
|
|
12
12
|
private getAttestation;
|
|
13
13
|
private postAttest;
|
|
@@ -19,8 +19,9 @@ declare class JwtProvider {
|
|
|
19
19
|
private cachedToken?;
|
|
20
20
|
private expiresAt?;
|
|
21
21
|
private pending?;
|
|
22
|
+
private pendingExtraData;
|
|
22
23
|
constructor(attestClient: AttestClient, bufferSeconds?: number);
|
|
23
|
-
getToken(): Promise<string>;
|
|
24
|
+
getToken(extraData?: Buffer): Promise<string>;
|
|
24
25
|
private isExpiringSoon;
|
|
25
26
|
private decodeJwtExp;
|
|
26
27
|
}
|
package/dist/attest.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface AttestClientConfig {
|
|
|
7
7
|
declare class AttestClient {
|
|
8
8
|
private config;
|
|
9
9
|
constructor(config: AttestClientConfig);
|
|
10
|
-
attest(): Promise<string>;
|
|
10
|
+
attest(extraData?: Buffer): Promise<string>;
|
|
11
11
|
private verifySignature;
|
|
12
12
|
private getAttestation;
|
|
13
13
|
private postAttest;
|
|
@@ -19,8 +19,9 @@ declare class JwtProvider {
|
|
|
19
19
|
private cachedToken?;
|
|
20
20
|
private expiresAt?;
|
|
21
21
|
private pending?;
|
|
22
|
+
private pendingExtraData;
|
|
22
23
|
constructor(attestClient: AttestClient, bufferSeconds?: number);
|
|
23
|
-
getToken(): Promise<string>;
|
|
24
|
+
getToken(extraData?: Buffer): Promise<string>;
|
|
24
25
|
private isExpiringSoon;
|
|
25
26
|
private decodeJwtExp;
|
|
26
27
|
}
|
package/dist/attest.js
CHANGED
|
@@ -10,7 +10,10 @@ var AttestClient = class {
|
|
|
10
10
|
constructor(config) {
|
|
11
11
|
this.config = config;
|
|
12
12
|
}
|
|
13
|
-
async attest() {
|
|
13
|
+
async attest(extraData) {
|
|
14
|
+
if (extraData && extraData.length > 1048576) {
|
|
15
|
+
throw new Error(`extraData exceeds 1MB limit (${extraData.length} bytes)`);
|
|
16
|
+
}
|
|
14
17
|
const { publicKey, privateKey } = generateKeyPairSync("rsa", {
|
|
15
18
|
modulusLength: 4096,
|
|
16
19
|
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
@@ -18,8 +21,8 @@ var AttestClient = class {
|
|
|
18
21
|
});
|
|
19
22
|
const challengeHash = createHash("sha256").update(CHALLENGE_PREFIX).update(NULL_BYTE).update(publicKey).digest();
|
|
20
23
|
const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;
|
|
21
|
-
const attestationBytes = await this.getAttestation(socketPath, challengeHash);
|
|
22
|
-
const attestResponse = await this.postAttest(attestationBytes, publicKey);
|
|
24
|
+
const attestationBytes = await this.getAttestation(socketPath, challengeHash, extraData);
|
|
25
|
+
const attestResponse = await this.postAttest(attestationBytes, publicKey, extraData);
|
|
23
26
|
this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);
|
|
24
27
|
const rsaPrivateKey = await crypto.subtle.importKey(
|
|
25
28
|
"pkcs8",
|
|
@@ -51,9 +54,13 @@ var AttestClient = class {
|
|
|
51
54
|
throw new Error("KMS response signature verification failed");
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
|
-
getAttestation(socketPath, challenge) {
|
|
57
|
+
getAttestation(socketPath, challenge, extraData) {
|
|
55
58
|
return new Promise((resolve, reject) => {
|
|
56
|
-
const
|
|
59
|
+
const requestBody = { challenge: challenge.toString("base64") };
|
|
60
|
+
if (extraData && extraData.length > 0) {
|
|
61
|
+
requestBody.extra_data = extraData.toString("base64");
|
|
62
|
+
}
|
|
63
|
+
const body = JSON.stringify(requestBody);
|
|
57
64
|
const req = http.request(
|
|
58
65
|
{
|
|
59
66
|
socketPath,
|
|
@@ -81,14 +88,18 @@ var AttestClient = class {
|
|
|
81
88
|
req.end();
|
|
82
89
|
});
|
|
83
90
|
}
|
|
84
|
-
async postAttest(attestationBytes, rsaPublicKey) {
|
|
91
|
+
async postAttest(attestationBytes, rsaPublicKey, extraData) {
|
|
85
92
|
const url = `${this.config.kmsServerURL}/auth/attest`;
|
|
86
|
-
const
|
|
93
|
+
const requestBody = {
|
|
87
94
|
version: 3,
|
|
88
95
|
attestation: attestationBytes.toString("base64"),
|
|
89
96
|
rsaKey: rsaPublicKey,
|
|
90
97
|
audience: this.config.audience
|
|
91
|
-
}
|
|
98
|
+
};
|
|
99
|
+
if (extraData && extraData.length > 0) {
|
|
100
|
+
requestBody.extra_data = extraData.toString("base64");
|
|
101
|
+
}
|
|
102
|
+
const body = JSON.stringify(requestBody);
|
|
92
103
|
const response = await fetch(url, {
|
|
93
104
|
method: "POST",
|
|
94
105
|
headers: { "Content-Type": "application/json" },
|
|
@@ -110,10 +121,21 @@ function pemToBuffer(pem) {
|
|
|
110
121
|
// src/client/modules/attest/jwt-provider.ts
|
|
111
122
|
var JwtProvider = class {
|
|
112
123
|
constructor(attestClient, bufferSeconds = 30) {
|
|
124
|
+
this.pendingExtraData = /* @__PURE__ */ new Map();
|
|
113
125
|
this.attestClient = attestClient;
|
|
114
126
|
this.bufferSeconds = bufferSeconds;
|
|
115
127
|
}
|
|
116
|
-
async getToken() {
|
|
128
|
+
async getToken(extraData) {
|
|
129
|
+
if (extraData && extraData.length > 0) {
|
|
130
|
+
const key = extraData.toString("hex");
|
|
131
|
+
const existing = this.pendingExtraData.get(key);
|
|
132
|
+
if (existing) return existing;
|
|
133
|
+
const promise = this.attestClient.attest(extraData).finally(() => {
|
|
134
|
+
this.pendingExtraData.delete(key);
|
|
135
|
+
});
|
|
136
|
+
this.pendingExtraData.set(key, promise);
|
|
137
|
+
return promise;
|
|
138
|
+
}
|
|
117
139
|
if (this.cachedToken && !this.isExpiringSoon()) {
|
|
118
140
|
return this.cachedToken;
|
|
119
141
|
}
|
package/dist/attest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/modules/attest/attest-client.ts","../src/client/modules/attest/jwt-provider.ts"],"sourcesContent":["import { generateKeyPairSync, createHash, verify } from 'node:crypto';\nimport http from 'node:http';\nimport { compactDecrypt } from 'jose';\n\nexport interface AttestClientConfig {\n kmsServerURL: string;\n kmsPublicKey: string;\n audience: string;\n socketPath?: string;\n}\n\nconst DEFAULT_SOCKET_PATH = '/run/container_launcher/teeserver.sock';\nconst CHALLENGE_PREFIX = 'COMPUTE_APP_JWT_REQUEST_RSA_KEY_V1';\nconst SIGNATURE_PREFIX = 'COMPUTE_APP_KMS_SIGNATURE_V1';\nconst NULL_BYTE = Buffer.from([0x00]);\n\nexport class AttestClient {\n private config: AttestClientConfig;\n\n constructor(config: AttestClientConfig) {\n this.config = config;\n }\n\n async attest(): Promise<string> {\n const { publicKey, privateKey } = generateKeyPairSync('rsa', {\n modulusLength: 4096,\n publicKeyEncoding: { type: 'spki', format: 'pem' } as const,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' } as const,\n });\n\n const challengeHash = createHash('sha256')\n .update(CHALLENGE_PREFIX)\n .update(NULL_BYTE)\n .update(publicKey)\n .digest();\n\n const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;\n const attestationBytes = await this.getAttestation(socketPath, challengeHash);\n const attestResponse = await this.postAttest(attestationBytes, publicKey);\n\n this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);\n\n const rsaPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pemToBuffer(privateKey),\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false,\n ['decrypt'],\n );\n\n const { plaintext } = await compactDecrypt(\n attestResponse.data.encryptedToken,\n rsaPrivateKey,\n );\n\n const decrypted = JSON.parse(new TextDecoder().decode(plaintext));\n return decrypted.token;\n }\n\n private verifySignature(\n dataJson: string,\n signature: string,\n ): void {\n const message = Buffer.concat([\n Buffer.from(SIGNATURE_PREFIX),\n NULL_BYTE,\n Buffer.from(dataJson),\n ]);\n\n const valid = verify(\n 'sha256',\n message,\n this.config.kmsPublicKey,\n Buffer.from(signature, 'base64'),\n );\n\n if (!valid) {\n throw new Error('KMS response signature verification failed');\n }\n }\n\n private getAttestation(socketPath: string, challenge: Buffer): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const body = JSON.stringify({ challenge: challenge.toString('base64') });\n\n const req = http.request(\n {\n socketPath,\n path: '/v1/bound_evidence',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n if (res.statusCode !== 200) {\n reject(new Error(`TEE attestation failed (${res.statusCode}): ${Buffer.concat(chunks).toString()}`));\n return;\n }\n resolve(Buffer.concat(chunks));\n });\n },\n );\n\n req.on('error', (err) => reject(new Error(`TEE attestation request failed: ${err.message}`)));\n req.write(body);\n req.end();\n });\n }\n\n private async postAttest(\n attestationBytes: Buffer,\n rsaPublicKey: string,\n ): Promise<{ data: { encryptedToken: string }; signature: string }> {\n const url = `${this.config.kmsServerURL}/auth/attest`;\n const body = JSON.stringify({\n version: 3,\n attestation: attestationBytes.toString('base64'),\n rsaKey: rsaPublicKey,\n audience: this.config.audience,\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`KMS attest failed (${response.status}): ${text}`);\n }\n\n return response.json() as Promise<{ data: { encryptedToken: string }; signature: string }>;\n }\n}\n\nfunction pemToBuffer(pem: string): ArrayBuffer {\n const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\\s/g, '');\n const buf = Buffer.from(b64, 'base64');\n\n // Copy to a fresh ArrayBuffer to avoid offset issues with Buffer's shared pool\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);\n}\n","import type { AttestClient } from './attest-client';\n\nexport class JwtProvider {\n private attestClient: AttestClient;\n private bufferSeconds: number;\n private cachedToken?: string;\n private expiresAt?: number;\n private pending?: Promise<string>;\n\n constructor(attestClient: AttestClient, bufferSeconds: number = 30) {\n this.attestClient = attestClient;\n this.bufferSeconds = bufferSeconds;\n }\n\n async getToken(): Promise<string> {\n if (this.cachedToken && !this.isExpiringSoon()) {\n return this.cachedToken;\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.attestClient\n .attest()\n .then((token) => {\n this.cachedToken = token;\n this.expiresAt = this.decodeJwtExp(token);\n return token;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n private isExpiringSoon(): boolean {\n if (!this.expiresAt) return true;\n return Date.now() / 1000 >= this.expiresAt - this.bufferSeconds;\n }\n\n private decodeJwtExp(jwt: string): number | undefined {\n const payload = jwt.split('.')[1];\n if (!payload) return undefined;\n\n const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString());\n return decoded.exp;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client/modules/attest/attest-client.ts","../src/client/modules/attest/jwt-provider.ts"],"sourcesContent":["import { generateKeyPairSync, createHash, verify } from 'node:crypto';\nimport http from 'node:http';\nimport { compactDecrypt } from 'jose';\n\nexport interface AttestClientConfig {\n kmsServerURL: string;\n kmsPublicKey: string;\n audience: string;\n socketPath?: string;\n}\n\nconst DEFAULT_SOCKET_PATH = '/run/container_launcher/teeserver.sock';\nconst CHALLENGE_PREFIX = 'COMPUTE_APP_JWT_REQUEST_RSA_KEY_V1';\nconst SIGNATURE_PREFIX = 'COMPUTE_APP_KMS_SIGNATURE_V1';\nconst NULL_BYTE = Buffer.from([0x00]);\n\nexport class AttestClient {\n private config: AttestClientConfig;\n\n constructor(config: AttestClientConfig) {\n this.config = config;\n }\n\n async attest(extraData?: Buffer): Promise<string> {\n // go-tpm-tools hashes extraData (SHA-256/SHA-512) before binding it into the\n // hardware nonce, so callers can pass arbitrary data up to 1MB.\n if (extraData && extraData.length > 1_048_576) {\n throw new Error(`extraData exceeds 1MB limit (${extraData.length} bytes)`);\n }\n\n const { publicKey, privateKey } = generateKeyPairSync('rsa', {\n modulusLength: 4096,\n publicKeyEncoding: { type: 'spki', format: 'pem' } as const,\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' } as const,\n });\n\n const challengeHash = createHash('sha256')\n .update(CHALLENGE_PREFIX)\n .update(NULL_BYTE)\n .update(publicKey)\n .digest();\n\n const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;\n const attestationBytes = await this.getAttestation(socketPath, challengeHash, extraData);\n const attestResponse = await this.postAttest(attestationBytes, publicKey, extraData);\n\n this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);\n\n const rsaPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pemToBuffer(privateKey),\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false,\n ['decrypt'],\n );\n\n const { plaintext } = await compactDecrypt(\n attestResponse.data.encryptedToken,\n rsaPrivateKey,\n );\n\n const decrypted = JSON.parse(new TextDecoder().decode(plaintext));\n return decrypted.token;\n }\n\n private verifySignature(\n dataJson: string,\n signature: string,\n ): void {\n const message = Buffer.concat([\n Buffer.from(SIGNATURE_PREFIX),\n NULL_BYTE,\n Buffer.from(dataJson),\n ]);\n\n const valid = verify(\n 'sha256',\n message,\n this.config.kmsPublicKey,\n Buffer.from(signature, 'base64'),\n );\n\n if (!valid) {\n throw new Error('KMS response signature verification failed');\n }\n }\n\n private getAttestation(socketPath: string, challenge: Buffer, extraData?: Buffer): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const requestBody: Record<string, string> = { challenge: challenge.toString('base64') };\n if (extraData && extraData.length > 0) {\n requestBody.extra_data = extraData.toString('base64');\n }\n const body = JSON.stringify(requestBody);\n\n const req = http.request(\n {\n socketPath,\n path: '/v1/bound_evidence',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n if (res.statusCode !== 200) {\n reject(new Error(`TEE attestation failed (${res.statusCode}): ${Buffer.concat(chunks).toString()}`));\n return;\n }\n resolve(Buffer.concat(chunks));\n });\n },\n );\n\n req.on('error', (err) => reject(new Error(`TEE attestation request failed: ${err.message}`)));\n req.write(body);\n req.end();\n });\n }\n\n private async postAttest(\n attestationBytes: Buffer,\n rsaPublicKey: string,\n extraData?: Buffer,\n ): Promise<{ data: { encryptedToken: string }; signature: string }> {\n const url = `${this.config.kmsServerURL}/auth/attest`;\n const requestBody: Record<string, unknown> = {\n version: 3,\n attestation: attestationBytes.toString('base64'),\n rsaKey: rsaPublicKey,\n audience: this.config.audience,\n };\n if (extraData && extraData.length > 0) {\n requestBody.extra_data = extraData.toString('base64');\n }\n const body = JSON.stringify(requestBody);\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`KMS attest failed (${response.status}): ${text}`);\n }\n\n return response.json() as Promise<{ data: { encryptedToken: string }; signature: string }>;\n }\n}\n\nfunction pemToBuffer(pem: string): ArrayBuffer {\n const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\\s/g, '');\n const buf = Buffer.from(b64, 'base64');\n\n // Copy to a fresh ArrayBuffer to avoid offset issues with Buffer's shared pool\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);\n}\n","import type { AttestClient } from './attest-client';\n\nexport class JwtProvider {\n private attestClient: AttestClient;\n private bufferSeconds: number;\n private cachedToken?: string;\n private expiresAt?: number;\n private pending?: Promise<string>;\n private pendingExtraData = new Map<string, Promise<string>>();\n\n constructor(attestClient: AttestClient, bufferSeconds: number = 30) {\n this.attestClient = attestClient;\n this.bufferSeconds = bufferSeconds;\n }\n\n async getToken(extraData?: Buffer): Promise<string> {\n // When extraData is provided, bypass long-lived cache but deduplicate\n // concurrent requests for the same extraData to avoid thundering herd\n // on TEE hardware calls.\n if (extraData && extraData.length > 0) {\n const key = extraData.toString('hex');\n const existing = this.pendingExtraData.get(key);\n if (existing) return existing;\n const promise = this.attestClient.attest(extraData).finally(() => {\n this.pendingExtraData.delete(key);\n });\n this.pendingExtraData.set(key, promise);\n return promise;\n }\n\n if (this.cachedToken && !this.isExpiringSoon()) {\n return this.cachedToken;\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.attestClient\n .attest()\n .then((token) => {\n this.cachedToken = token;\n this.expiresAt = this.decodeJwtExp(token);\n return token;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n private isExpiringSoon(): boolean {\n if (!this.expiresAt) return true;\n return Date.now() / 1000 >= this.expiresAt - this.bufferSeconds;\n }\n\n private decodeJwtExp(jwt: string): number | undefined {\n const payload = jwt.split('.')[1];\n if (!payload) return undefined;\n\n const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString());\n return decoded.exp;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/billing.cjs
CHANGED
|
@@ -496,6 +496,12 @@ var CHAIN_ID_TO_ENVIRONMENT = {
|
|
|
496
496
|
[SEPOLIA_CHAIN_ID.toString()]: "sepolia",
|
|
497
497
|
[MAINNET_CHAIN_ID.toString()]: "mainnet-alpha"
|
|
498
498
|
};
|
|
499
|
+
function getApiUrlOverride() {
|
|
500
|
+
const raw = process.env.ECLOUD_API_URL;
|
|
501
|
+
if (!raw) return void 0;
|
|
502
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
503
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
504
|
+
}
|
|
499
505
|
function getEnvironmentConfig(environment, chainID) {
|
|
500
506
|
const env = ENVIRONMENTS[environment];
|
|
501
507
|
if (!env) {
|
|
@@ -513,9 +519,11 @@ function getEnvironmentConfig(environment, chainID) {
|
|
|
513
519
|
}
|
|
514
520
|
}
|
|
515
521
|
const resolvedChainID = chainID || (environment === "sepolia" || environment === "sepolia-dev" ? SEPOLIA_CHAIN_ID : MAINNET_CHAIN_ID);
|
|
522
|
+
const apiUrlOverride = getApiUrlOverride();
|
|
516
523
|
return {
|
|
517
524
|
...env,
|
|
518
|
-
chainID: BigInt(resolvedChainID)
|
|
525
|
+
chainID: BigInt(resolvedChainID),
|
|
526
|
+
...apiUrlOverride ? { userApiServerURL: apiUrlOverride } : {}
|
|
519
527
|
};
|
|
520
528
|
}
|
|
521
529
|
function getBillingEnvironmentConfig(build) {
|
|
@@ -523,10 +531,14 @@ function getBillingEnvironmentConfig(build) {
|
|
|
523
531
|
if (!config) {
|
|
524
532
|
throw new Error(`Unknown billing environment: ${build}`);
|
|
525
533
|
}
|
|
534
|
+
const apiUrlOverride = getApiUrlOverride();
|
|
535
|
+
if (apiUrlOverride) {
|
|
536
|
+
return { billingApiServerURL: apiUrlOverride };
|
|
537
|
+
}
|
|
526
538
|
return config;
|
|
527
539
|
}
|
|
528
540
|
function getBuildType() {
|
|
529
|
-
const buildTimeType = true ? "
|
|
541
|
+
const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
|
|
530
542
|
const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
|
|
531
543
|
const buildType = buildTimeType || runtimeType;
|
|
532
544
|
if (buildType === "dev") {
|