@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 CHANGED
@@ -1,2 +1,2 @@
1
- version=0.5.0
2
- commit=b08239de7716543a49b3a0311635dfa859429f3b
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 body = JSON.stringify({ challenge: challenge.toString("base64") });
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 body = JSON.stringify({
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
  }
@@ -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 body = JSON.stringify({ challenge: challenge.toString("base64") });
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 body = JSON.stringify({
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
  }
@@ -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 ? "prod"?.toLowerCase() : void 0;
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") {