@layr-labs/ecloud-sdk 0.5.0 → 1.0.0-dev.1

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.1
2
+ commit=2f1f9686f28371adcd7521bfd335c15cd928f61d
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
@@ -317,6 +317,20 @@ var BillingApiClient = class {
317
317
  const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
318
318
  await this.makeAuthenticatedRequest(endpoint, "DELETE", productId);
319
319
  }
320
+ async getPaymentMethods() {
321
+ const endpoint = `${this.config.billingApiServerURL}/v1/payment-methods`;
322
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
323
+ return resp.json();
324
+ }
325
+ async purchaseCredits(amountCents, paymentMethodId) {
326
+ const endpoint = `${this.config.billingApiServerURL}/v1/credits/purchase`;
327
+ const body = { amountCents };
328
+ if (paymentMethodId) {
329
+ body.paymentMethodId = paymentMethodId;
330
+ }
331
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", body);
332
+ return resp.json();
333
+ }
320
334
  // ==========================================================================
321
335
  // Internal Methods
322
336
  // ==========================================================================
@@ -326,10 +340,19 @@ var BillingApiClient = class {
326
340
  * Uses session auth if useSession is true, otherwise uses EIP-712 signature auth.
327
341
  */
328
342
  async makeAuthenticatedRequest(url, method, productId, body) {
329
- if (this.useSession) {
330
- return this.makeSessionAuthenticatedRequest(url, method, body);
343
+ if (this.options.verbose) {
344
+ console.debug(`[BillingAPI] ${method} ${url}`);
345
+ }
346
+ const resp = this.useSession ? await this.makeSessionAuthenticatedRequest(url, method, body) : await this.makeSignatureAuthenticatedRequest(url, method, productId, body);
347
+ if (this.options.verbose) {
348
+ const data = await resp.json();
349
+ console.debug(`[BillingAPI] Response:`, JSON.stringify(data, null, 2));
350
+ return {
351
+ json: async () => data,
352
+ text: async () => JSON.stringify(data)
353
+ };
331
354
  }
332
- return this.makeSignatureAuthenticatedRequest(url, method, productId, body);
355
+ return resp;
333
356
  }
334
357
  /**
335
358
  * Make a request using session-based authentication (cookies)
@@ -515,7 +538,13 @@ function getEnvironmentConfig(environment, chainID) {
515
538
  const resolvedChainID = chainID || (environment === "sepolia" || environment === "sepolia-dev" ? SEPOLIA_CHAIN_ID : MAINNET_CHAIN_ID);
516
539
  return {
517
540
  ...env,
518
- chainID: BigInt(resolvedChainID)
541
+ chainID: BigInt(resolvedChainID),
542
+ ...process.env.ECLOUD_USER_API_URL && {
543
+ userApiServerURL: process.env.ECLOUD_USER_API_URL
544
+ },
545
+ ...process.env.ECLOUD_RPC_URL && {
546
+ defaultRPCURL: process.env.ECLOUD_RPC_URL
547
+ }
519
548
  };
520
549
  }
521
550
  function getBillingEnvironmentConfig(build) {
@@ -523,10 +552,15 @@ function getBillingEnvironmentConfig(build) {
523
552
  if (!config) {
524
553
  throw new Error(`Unknown billing environment: ${build}`);
525
554
  }
526
- return config;
555
+ return {
556
+ ...config,
557
+ ...process.env.ECLOUD_BILLING_API_URL && {
558
+ billingApiServerURL: process.env.ECLOUD_BILLING_API_URL
559
+ }
560
+ };
527
561
  }
528
562
  function getBuildType() {
529
- const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
563
+ const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
530
564
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
531
565
  const buildType = buildTimeType || runtimeType;
532
566
  if (buildType === "dev") {
@@ -2066,7 +2100,7 @@ function createBillingModule(config) {
2066
2100
  const address = walletClient.account.address;
2067
2101
  const logger = getLogger(verbose);
2068
2102
  const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());
2069
- const billingApi = new BillingApiClient(billingEnvConfig, walletClient);
2103
+ const billingApi = new BillingApiClient(billingEnvConfig, walletClient, { verbose });
2070
2104
  const environmentConfig = getEnvironmentConfig(environment);
2071
2105
  const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;
2072
2106
  if (!usdcCreditsAddress) {
@@ -2230,6 +2264,12 @@ function createBillingModule(config) {
2230
2264
  };
2231
2265
  }
2232
2266
  );
2267
+ },
2268
+ async getPaymentMethods() {
2269
+ return billingApi.getPaymentMethods();
2270
+ },
2271
+ async purchaseCredits(amountCents, paymentMethodId) {
2272
+ return billingApi.purchaseCredits(amountCents, paymentMethodId);
2233
2273
  }
2234
2274
  };
2235
2275
  return module2;