@layr-labs/ecloud-sdk 0.4.0-dev.0 → 0.4.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 -0
- package/dist/attest.cjs +185 -0
- package/dist/attest.cjs.map +1 -0
- package/dist/attest.d.cts +28 -0
- package/dist/attest.d.ts +28 -0
- package/dist/attest.js +147 -0
- package/dist/attest.js.map +1 -0
- package/dist/billing.cjs +1380 -4
- package/dist/billing.cjs.map +1 -1
- package/dist/billing.d.cts +25 -3
- package/dist/billing.d.ts +25 -3
- package/dist/billing.js +1380 -4
- package/dist/billing.js.map +1 -1
- package/dist/browser.cjs +3 -2
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +4 -4
- package/dist/browser.d.ts +4 -4
- package/dist/browser.js +3 -2
- package/dist/browser.js.map +1 -1
- package/dist/{compute-DccJLbtV.d.cts → compute-BRDk7QM4.d.cts} +1 -1
- package/dist/{compute-DlilmZYC.d.ts → compute-CC55YQ_a.d.ts} +1 -1
- package/dist/compute.cjs +8 -3
- package/dist/compute.cjs.map +1 -1
- package/dist/compute.d.cts +2 -2
- package/dist/compute.d.ts +2 -2
- package/dist/compute.js +8 -3
- package/dist/compute.js.map +1 -1
- package/dist/{helpers-D_AbDeP4.d.ts → helpers-BcoV07Me.d.ts} +1 -1
- package/dist/{helpers-BNeMZYcY.d.cts → helpers-DdtPaQr9.d.cts} +1 -1
- package/dist/{index-DD7ZLbqD.d.cts → index-BEbhrwWl.d.cts} +1 -0
- package/dist/{index-DD7ZLbqD.d.ts → index-BEbhrwWl.d.ts} +1 -0
- package/dist/index.cjs +330 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +328 -95
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
- package/tools/kms-client-linux-amd64 +0 -0
- package/tools/tls-keygen-linux-amd64 +0 -0
package/VERSION
ADDED
package/dist/attest.cjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/attest.ts
|
|
31
|
+
var attest_exports = {};
|
|
32
|
+
__export(attest_exports, {
|
|
33
|
+
AttestClient: () => AttestClient,
|
|
34
|
+
JwtProvider: () => JwtProvider
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(attest_exports);
|
|
37
|
+
|
|
38
|
+
// src/client/modules/attest/attest-client.ts
|
|
39
|
+
var import_node_crypto = require("crypto");
|
|
40
|
+
var import_node_http = __toESM(require("http"), 1);
|
|
41
|
+
var import_jose = require("jose");
|
|
42
|
+
var DEFAULT_SOCKET_PATH = "/run/container_launcher/teeserver.sock";
|
|
43
|
+
var CHALLENGE_PREFIX = "COMPUTE_APP_JWT_REQUEST_RSA_KEY_V1";
|
|
44
|
+
var SIGNATURE_PREFIX = "COMPUTE_APP_KMS_SIGNATURE_V1";
|
|
45
|
+
var NULL_BYTE = Buffer.from([0]);
|
|
46
|
+
var AttestClient = class {
|
|
47
|
+
constructor(config) {
|
|
48
|
+
this.config = config;
|
|
49
|
+
}
|
|
50
|
+
async attest() {
|
|
51
|
+
const { publicKey, privateKey } = (0, import_node_crypto.generateKeyPairSync)("rsa", {
|
|
52
|
+
modulusLength: 4096,
|
|
53
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
54
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
55
|
+
});
|
|
56
|
+
const challengeHash = (0, import_node_crypto.createHash)("sha256").update(CHALLENGE_PREFIX).update(NULL_BYTE).update(publicKey).digest();
|
|
57
|
+
const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;
|
|
58
|
+
const attestationBytes = await this.getAttestation(socketPath, challengeHash);
|
|
59
|
+
const attestResponse = await this.postAttest(attestationBytes, publicKey);
|
|
60
|
+
this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);
|
|
61
|
+
const rsaPrivateKey = await crypto.subtle.importKey(
|
|
62
|
+
"pkcs8",
|
|
63
|
+
pemToBuffer(privateKey),
|
|
64
|
+
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
65
|
+
false,
|
|
66
|
+
["decrypt"]
|
|
67
|
+
);
|
|
68
|
+
const { plaintext } = await (0, import_jose.compactDecrypt)(
|
|
69
|
+
attestResponse.data.encryptedToken,
|
|
70
|
+
rsaPrivateKey
|
|
71
|
+
);
|
|
72
|
+
const decrypted = JSON.parse(new TextDecoder().decode(plaintext));
|
|
73
|
+
return decrypted.token;
|
|
74
|
+
}
|
|
75
|
+
verifySignature(dataJson, signature) {
|
|
76
|
+
const message = Buffer.concat([
|
|
77
|
+
Buffer.from(SIGNATURE_PREFIX),
|
|
78
|
+
NULL_BYTE,
|
|
79
|
+
Buffer.from(dataJson)
|
|
80
|
+
]);
|
|
81
|
+
const valid = (0, import_node_crypto.verify)(
|
|
82
|
+
"sha256",
|
|
83
|
+
message,
|
|
84
|
+
this.config.kmsPublicKey,
|
|
85
|
+
Buffer.from(signature, "base64")
|
|
86
|
+
);
|
|
87
|
+
if (!valid) {
|
|
88
|
+
throw new Error("KMS response signature verification failed");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
getAttestation(socketPath, challenge) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const body = JSON.stringify({ challenge: challenge.toString("base64") });
|
|
94
|
+
const req = import_node_http.default.request(
|
|
95
|
+
{
|
|
96
|
+
socketPath,
|
|
97
|
+
path: "/v1/bound_evidence",
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
"Content-Length": Buffer.byteLength(body)
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
(res) => {
|
|
105
|
+
const chunks = [];
|
|
106
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
107
|
+
res.on("end", () => {
|
|
108
|
+
if (res.statusCode !== 200) {
|
|
109
|
+
reject(new Error(`TEE attestation failed (${res.statusCode}): ${Buffer.concat(chunks).toString()}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
resolve(Buffer.concat(chunks));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
req.on("error", (err) => reject(new Error(`TEE attestation request failed: ${err.message}`)));
|
|
117
|
+
req.write(body);
|
|
118
|
+
req.end();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async postAttest(attestationBytes, rsaPublicKey) {
|
|
122
|
+
const url = `${this.config.kmsServerURL}/auth/attest`;
|
|
123
|
+
const body = JSON.stringify({
|
|
124
|
+
version: 3,
|
|
125
|
+
attestation: attestationBytes.toString("base64"),
|
|
126
|
+
rsaKey: rsaPublicKey,
|
|
127
|
+
audience: this.config.audience
|
|
128
|
+
});
|
|
129
|
+
const response = await fetch(url, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "Content-Type": "application/json" },
|
|
132
|
+
body
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const text = await response.text();
|
|
136
|
+
throw new Error(`KMS attest failed (${response.status}): ${text}`);
|
|
137
|
+
}
|
|
138
|
+
return response.json();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function pemToBuffer(pem) {
|
|
142
|
+
const b64 = pem.replace(/-----[A-Z ]+-----/g, "").replace(/\s/g, "");
|
|
143
|
+
const buf = Buffer.from(b64, "base64");
|
|
144
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/client/modules/attest/jwt-provider.ts
|
|
148
|
+
var JwtProvider = class {
|
|
149
|
+
constructor(attestClient, bufferSeconds = 30) {
|
|
150
|
+
this.attestClient = attestClient;
|
|
151
|
+
this.bufferSeconds = bufferSeconds;
|
|
152
|
+
}
|
|
153
|
+
async getToken() {
|
|
154
|
+
if (this.cachedToken && !this.isExpiringSoon()) {
|
|
155
|
+
return this.cachedToken;
|
|
156
|
+
}
|
|
157
|
+
if (this.pending) {
|
|
158
|
+
return this.pending;
|
|
159
|
+
}
|
|
160
|
+
this.pending = this.attestClient.attest().then((token) => {
|
|
161
|
+
this.cachedToken = token;
|
|
162
|
+
this.expiresAt = this.decodeJwtExp(token);
|
|
163
|
+
return token;
|
|
164
|
+
}).finally(() => {
|
|
165
|
+
this.pending = void 0;
|
|
166
|
+
});
|
|
167
|
+
return this.pending;
|
|
168
|
+
}
|
|
169
|
+
isExpiringSoon() {
|
|
170
|
+
if (!this.expiresAt) return true;
|
|
171
|
+
return Date.now() / 1e3 >= this.expiresAt - this.bufferSeconds;
|
|
172
|
+
}
|
|
173
|
+
decodeJwtExp(jwt) {
|
|
174
|
+
const payload = jwt.split(".")[1];
|
|
175
|
+
if (!payload) return void 0;
|
|
176
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
177
|
+
return decoded.exp;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
181
|
+
0 && (module.exports = {
|
|
182
|
+
AttestClient,
|
|
183
|
+
JwtProvider
|
|
184
|
+
});
|
|
185
|
+
//# sourceMappingURL=attest.cjs.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface AttestClientConfig {
|
|
2
|
+
kmsServerURL: string;
|
|
3
|
+
kmsPublicKey: string;
|
|
4
|
+
audience: string;
|
|
5
|
+
socketPath?: string;
|
|
6
|
+
}
|
|
7
|
+
declare class AttestClient {
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config: AttestClientConfig);
|
|
10
|
+
attest(): Promise<string>;
|
|
11
|
+
private verifySignature;
|
|
12
|
+
private getAttestation;
|
|
13
|
+
private postAttest;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class JwtProvider {
|
|
17
|
+
private attestClient;
|
|
18
|
+
private bufferSeconds;
|
|
19
|
+
private cachedToken?;
|
|
20
|
+
private expiresAt?;
|
|
21
|
+
private pending?;
|
|
22
|
+
constructor(attestClient: AttestClient, bufferSeconds?: number);
|
|
23
|
+
getToken(): Promise<string>;
|
|
24
|
+
private isExpiringSoon;
|
|
25
|
+
private decodeJwtExp;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { AttestClient, type AttestClientConfig, JwtProvider };
|
package/dist/attest.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface AttestClientConfig {
|
|
2
|
+
kmsServerURL: string;
|
|
3
|
+
kmsPublicKey: string;
|
|
4
|
+
audience: string;
|
|
5
|
+
socketPath?: string;
|
|
6
|
+
}
|
|
7
|
+
declare class AttestClient {
|
|
8
|
+
private config;
|
|
9
|
+
constructor(config: AttestClientConfig);
|
|
10
|
+
attest(): Promise<string>;
|
|
11
|
+
private verifySignature;
|
|
12
|
+
private getAttestation;
|
|
13
|
+
private postAttest;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare class JwtProvider {
|
|
17
|
+
private attestClient;
|
|
18
|
+
private bufferSeconds;
|
|
19
|
+
private cachedToken?;
|
|
20
|
+
private expiresAt?;
|
|
21
|
+
private pending?;
|
|
22
|
+
constructor(attestClient: AttestClient, bufferSeconds?: number);
|
|
23
|
+
getToken(): Promise<string>;
|
|
24
|
+
private isExpiringSoon;
|
|
25
|
+
private decodeJwtExp;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { AttestClient, type AttestClientConfig, JwtProvider };
|
package/dist/attest.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/client/modules/attest/attest-client.ts
|
|
2
|
+
import { generateKeyPairSync, createHash, verify } from "crypto";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import { compactDecrypt } from "jose";
|
|
5
|
+
var DEFAULT_SOCKET_PATH = "/run/container_launcher/teeserver.sock";
|
|
6
|
+
var CHALLENGE_PREFIX = "COMPUTE_APP_JWT_REQUEST_RSA_KEY_V1";
|
|
7
|
+
var SIGNATURE_PREFIX = "COMPUTE_APP_KMS_SIGNATURE_V1";
|
|
8
|
+
var NULL_BYTE = Buffer.from([0]);
|
|
9
|
+
var AttestClient = class {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
async attest() {
|
|
14
|
+
const { publicKey, privateKey } = generateKeyPairSync("rsa", {
|
|
15
|
+
modulusLength: 4096,
|
|
16
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
17
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
18
|
+
});
|
|
19
|
+
const challengeHash = createHash("sha256").update(CHALLENGE_PREFIX).update(NULL_BYTE).update(publicKey).digest();
|
|
20
|
+
const socketPath = this.config.socketPath ?? DEFAULT_SOCKET_PATH;
|
|
21
|
+
const attestationBytes = await this.getAttestation(socketPath, challengeHash);
|
|
22
|
+
const attestResponse = await this.postAttest(attestationBytes, publicKey);
|
|
23
|
+
this.verifySignature(JSON.stringify(attestResponse.data), attestResponse.signature);
|
|
24
|
+
const rsaPrivateKey = await crypto.subtle.importKey(
|
|
25
|
+
"pkcs8",
|
|
26
|
+
pemToBuffer(privateKey),
|
|
27
|
+
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
28
|
+
false,
|
|
29
|
+
["decrypt"]
|
|
30
|
+
);
|
|
31
|
+
const { plaintext } = await compactDecrypt(
|
|
32
|
+
attestResponse.data.encryptedToken,
|
|
33
|
+
rsaPrivateKey
|
|
34
|
+
);
|
|
35
|
+
const decrypted = JSON.parse(new TextDecoder().decode(plaintext));
|
|
36
|
+
return decrypted.token;
|
|
37
|
+
}
|
|
38
|
+
verifySignature(dataJson, signature) {
|
|
39
|
+
const message = Buffer.concat([
|
|
40
|
+
Buffer.from(SIGNATURE_PREFIX),
|
|
41
|
+
NULL_BYTE,
|
|
42
|
+
Buffer.from(dataJson)
|
|
43
|
+
]);
|
|
44
|
+
const valid = verify(
|
|
45
|
+
"sha256",
|
|
46
|
+
message,
|
|
47
|
+
this.config.kmsPublicKey,
|
|
48
|
+
Buffer.from(signature, "base64")
|
|
49
|
+
);
|
|
50
|
+
if (!valid) {
|
|
51
|
+
throw new Error("KMS response signature verification failed");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
getAttestation(socketPath, challenge) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const body = JSON.stringify({ challenge: challenge.toString("base64") });
|
|
57
|
+
const req = http.request(
|
|
58
|
+
{
|
|
59
|
+
socketPath,
|
|
60
|
+
path: "/v1/bound_evidence",
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
"Content-Length": Buffer.byteLength(body)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
(res) => {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
70
|
+
res.on("end", () => {
|
|
71
|
+
if (res.statusCode !== 200) {
|
|
72
|
+
reject(new Error(`TEE attestation failed (${res.statusCode}): ${Buffer.concat(chunks).toString()}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
resolve(Buffer.concat(chunks));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
req.on("error", (err) => reject(new Error(`TEE attestation request failed: ${err.message}`)));
|
|
80
|
+
req.write(body);
|
|
81
|
+
req.end();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async postAttest(attestationBytes, rsaPublicKey) {
|
|
85
|
+
const url = `${this.config.kmsServerURL}/auth/attest`;
|
|
86
|
+
const body = JSON.stringify({
|
|
87
|
+
version: 3,
|
|
88
|
+
attestation: attestationBytes.toString("base64"),
|
|
89
|
+
rsaKey: rsaPublicKey,
|
|
90
|
+
audience: this.config.audience
|
|
91
|
+
});
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "Content-Type": "application/json" },
|
|
95
|
+
body
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const text = await response.text();
|
|
99
|
+
throw new Error(`KMS attest failed (${response.status}): ${text}`);
|
|
100
|
+
}
|
|
101
|
+
return response.json();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function pemToBuffer(pem) {
|
|
105
|
+
const b64 = pem.replace(/-----[A-Z ]+-----/g, "").replace(/\s/g, "");
|
|
106
|
+
const buf = Buffer.from(b64, "base64");
|
|
107
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/client/modules/attest/jwt-provider.ts
|
|
111
|
+
var JwtProvider = class {
|
|
112
|
+
constructor(attestClient, bufferSeconds = 30) {
|
|
113
|
+
this.attestClient = attestClient;
|
|
114
|
+
this.bufferSeconds = bufferSeconds;
|
|
115
|
+
}
|
|
116
|
+
async getToken() {
|
|
117
|
+
if (this.cachedToken && !this.isExpiringSoon()) {
|
|
118
|
+
return this.cachedToken;
|
|
119
|
+
}
|
|
120
|
+
if (this.pending) {
|
|
121
|
+
return this.pending;
|
|
122
|
+
}
|
|
123
|
+
this.pending = this.attestClient.attest().then((token) => {
|
|
124
|
+
this.cachedToken = token;
|
|
125
|
+
this.expiresAt = this.decodeJwtExp(token);
|
|
126
|
+
return token;
|
|
127
|
+
}).finally(() => {
|
|
128
|
+
this.pending = void 0;
|
|
129
|
+
});
|
|
130
|
+
return this.pending;
|
|
131
|
+
}
|
|
132
|
+
isExpiringSoon() {
|
|
133
|
+
if (!this.expiresAt) return true;
|
|
134
|
+
return Date.now() / 1e3 >= this.expiresAt - this.bufferSeconds;
|
|
135
|
+
}
|
|
136
|
+
decodeJwtExp(jwt) {
|
|
137
|
+
const payload = jwt.split(".")[1];
|
|
138
|
+
if (!payload) return void 0;
|
|
139
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString());
|
|
140
|
+
return decoded.exp;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
export {
|
|
144
|
+
AttestClient,
|
|
145
|
+
JwtProvider
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=attest.js.map
|
|
@@ -0,0 +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":[]}
|