@praecise/tere
Version:
Trusted Execution Runtime Environment SDK
411 lines (410 loc) • 16.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TereClient = void 0;
// packages/sdk/src/client.ts
const axios_1 = __importDefault(require("axios"));
const types_1 = require("./types");
/**
* Main client for interacting with the TERE service
*/
class TereClient {
/**
* Create a new TERE client
* @param options Client configuration options
*/
constructor(options = {}) {
this.endpoint = options.endpoint || 'https://api.tere.praecise.com';
// Initialize HTTP client
this.httpClient = axios_1.default.create({
baseURL: this.endpoint,
headers: {
'Content-Type': 'application/json',
...(options.apiKey ? { 'Authorization': `ApiKey ${options.apiKey}` } : {})
},
timeout: options.timeout || 30000,
});
}
/**
* Deploy a TERE script to a Trusted Execution Environment
* @param options Deployment options
* @returns Deployment result
*/
async deploy(options) {
try {
// Prepare binary data
let binaryData;
if (Buffer.isBuffer(options.tereBinary)) {
binaryData = options.tereBinary.toString('base64');
}
else if (typeof options.tereBinary === 'string') {
// Check if it's already base64
if (/^[A-Za-z0-9+/=]+$/.test(options.tereBinary)) {
binaryData = options.tereBinary;
}
else {
// Convert to base64
binaryData = Buffer.from(options.tereBinary).toString('base64');
}
}
else {
throw new types_1.TereError('Invalid TERE binary format', 'invalid_binary', 400);
}
// Convert config to API format
const apiConfig = options.config ? {
tee_type: options.config.teeType,
provider: options.config.provider,
location: options.config.location,
resource_limits: options.config.resourceLimits ? {
cpu_cores: options.config.resourceLimits.cpuCores,
memory_mb: options.config.resourceLimits.memoryMb,
storage_gb: options.config.resourceLimits.storageGb
} : undefined,
security_settings: options.config.securitySettings ? {
secure_boot: options.config.securitySettings.secureBoot,
integrity_monitoring: options.config.securitySettings.integrityMonitoring,
vtpm: options.config.securitySettings.vtpm,
confidential_compute_type: options.config.securitySettings.confidentialComputeType,
min_firmware_version: options.config.securitySettings.minFirmwareVersion,
enable_hsm: options.config.securitySettings.enableHsm,
hsm_key_ring: options.config.securitySettings.hsmKeyRing
} : undefined,
network_config: options.config.networkConfig ? {
network: options.config.networkConfig.network,
subnet: options.config.networkConfig.subnet,
use_public_ip: options.config.networkConfig.usePublicIp,
network_tags: options.config.networkConfig.networkTags
} : undefined
} : undefined;
// Prepare payload
const payload = {
name: options.name,
tere_binary: binaryData,
description: options.description,
config: apiConfig
};
// Make API request
const response = await this.httpClient.post('/api/deploy', payload);
// Parse and return response
return {
scriptId: response.data.script_id,
attestation: response.data.attestation,
instanceInfo: {
teeType: response.data.instance_info.tee_type,
location: response.data.instance_info.location,
attestationVerificationUrl: response.data.instance_info.attestation_verification_url,
details: response.data.instance_info.details || {}
}
};
}
catch (error) {
throw this.handleError(error, 'Deployment failed');
}
}
/**
* Execute a function in a deployed script
* @param options Execute options
* @returns Execution result
*/
async execute(options) {
try {
// Serialize arguments if they're an array
let serializedArgs;
if (Array.isArray(options.arguments)) {
serializedArgs = Buffer.from(JSON.stringify(options.arguments)).toString('base64');
}
else if (typeof options.arguments === 'string') {
// Check if it's already base64
if (/^[A-Za-z0-9+/=]+$/.test(options.arguments)) {
serializedArgs = options.arguments;
}
else {
// Assume it's JSON and convert to base64
serializedArgs = Buffer.from(options.arguments).toString('base64');
}
}
else {
// Convert object or any other type to JSON and then to base64
serializedArgs = Buffer.from(JSON.stringify(options.arguments)).toString('base64');
}
// Create payload
const payload = {
script_id: options.scriptId,
function: options.function,
arguments: serializedArgs,
wait_for_result: options.waitForResult !== undefined ? options.waitForResult : true,
caller_id: options.callerId,
nonce: options.nonce,
hsm_key_id: options.hsmKeyId // Add HSM key ID if provided
};
// Make API request
const response = await this.httpClient.post('/api/execute', payload);
// Parse resource usage if available
let resourceUsage = undefined;
if (response.data.resource_usage) {
resourceUsage = {
gasUsed: response.data.resource_usage.gas_used,
executionTimeMs: response.data.resource_usage.execution_time_ms,
memoryBytes: response.data.resource_usage.memory_bytes
};
}
// Parse and return response
return {
result: response.data.result,
attestation: response.data.attestation,
resourceUsage,
jobId: response.data.job_id,
hsmAttestation: response.data.hsm_attestation // Include HSM attestation if available
};
}
catch (error) {
throw this.handleError(error, 'Execution failed');
}
}
/**
* Get the status of an asynchronous job
* @param jobId ID of the job to check
* @returns Current job status and result if completed
*/
async getJobStatus(jobId) {
try {
// Make API request
const response = await this.httpClient.get(`/api/jobs/${jobId}`);
// Parse resource usage if available
let resourceUsage = undefined;
if (response.data.resource_usage) {
resourceUsage = {
gasUsed: response.data.resource_usage.gas_used,
executionTimeMs: response.data.resource_usage.execution_time_ms,
memoryBytes: response.data.resource_usage.memory_bytes
};
}
// Parse and return response
return {
result: response.data.result,
attestation: response.data.attestation,
resourceUsage,
jobId: response.data.job_id,
hsmAttestation: response.data.hsm_attestation // Include HSM attestation if available
};
}
catch (error) {
throw this.handleError(error, 'Failed to get job status');
}
}
/**
* List all deployed scripts
* @returns Array of script information
*/
async listScripts() {
try {
// Make API request
const response = await this.httpClient.get('/api/scripts');
// Parse and return response
return response.data.map((script) => ({
id: script.id,
name: script.name,
description: script.description,
provider: script.provider,
teeType: script.tee_type,
location: script.location,
creationTime: new Date(script.creation_time),
status: script.status,
hsmEnabled: script.hsm_enabled // Include HSM status
}));
}
catch (error) {
throw this.handleError(error, 'Failed to list scripts');
}
}
/**
* Get information about a specific script
* @param scriptId ID of the script
* @returns Script information
*/
async getScript(scriptId) {
try {
// Make API request
const response = await this.httpClient.get(`/api/scripts/${scriptId}`);
// Parse and return response
return {
id: response.data.id,
name: response.data.name,
description: response.data.description,
provider: response.data.provider,
teeType: response.data.tee_type,
location: response.data.location,
creationTime: new Date(response.data.creation_time),
status: response.data.status,
hsmEnabled: response.data.hsm_enabled // Include HSM status
};
}
catch (error) {
throw this.handleError(error, 'Failed to get script');
}
}
/**
* Start a stopped instance
* @param scriptId ID of the script
*/
async startInstance(scriptId) {
try {
await this.httpClient.post(`/api/instances/${scriptId}/start`);
}
catch (error) {
throw this.handleError(error, 'Failed to start instance');
}
}
/**
* Stop a running instance
* @param scriptId ID of the script
*/
async stopInstance(scriptId) {
try {
await this.httpClient.post(`/api/instances/${scriptId}/stop`);
}
catch (error) {
throw this.handleError(error, 'Failed to stop instance');
}
}
/**
* Delete a deployed script
* @param scriptId ID of the script
* @param options Options for deletion
*/
async deleteScript(scriptId, options) {
try {
const queryParams = (options === null || options === void 0 ? void 0 : options.preserveState) ? '?preserveState=true' : '';
await this.httpClient.delete(`/api/scripts/${scriptId}${queryParams}`);
}
catch (error) {
throw this.handleError(error, 'Failed to delete script');
}
}
/**
* Verify an attestation
* @param options Verification options
* @returns Verification result
*/
async verifyAttestation(options) {
try {
// Create payload
const payload = {
attestation: options.attestation,
expected_nonce: options.expectedNonce
};
// Make API request
const response = await this.httpClient.post('/api/verify-attestation', payload);
// Map API response to SDK type
let details = undefined;
if (response.data.details) {
details = {
teeType: response.data.details.tee_type,
provider: response.data.details.provider,
instanceId: response.data.details.instance_id,
timestamp: new Date(response.data.details.timestamp),
secureBoot: response.data.details.secure_boot,
integrityVerified: response.data.details.integrity_verified,
firmwareVersion: response.data.details.firmware_version,
// Add HSM info if available
hsmInfo: response.data.details.hsm_info ? {
enabled: response.data.details.hsm_info.enabled,
protectionLevel: response.data.details.hsm_info.protection_level,
keyDetails: response.data.details.hsm_info.key_details
} : undefined
};
}
return {
valid: response.data.valid,
details
};
}
catch (error) {
throw this.handleError(error, 'Attestation verification failed');
}
}
/**
* List HSM keys available for a script
* @param scriptId ID of the script
* @returns List of HSM keys and their details
*/
async listHsmKeys(scriptId) {
try {
// Make API request
const response = await this.httpClient.get(`/api/scripts/${scriptId}/hsm-keys`);
// Parse and return response
return response.data.map((key) => ({
id: key.id,
name: key.name,
purpose: key.purpose,
algorithm: key.algorithm,
protectionLevel: key.protection_level,
createTime: new Date(key.create_time),
status: key.status
}));
}
catch (error) {
throw this.handleError(error, 'Failed to list HSM keys');
}
}
/**
* Create a new HSM key for a script
* @param scriptId ID of the script
* @param keyId ID for the new key
* @param purpose Purpose of the key ('encrypt', 'sign', 'decrypt')
* @param algorithm Optional algorithm specification
* @returns Details of the created key
*/
async createHsmKey(scriptId, keyId, purpose, algorithm) {
try {
// Create payload
const payload = {
key_id: keyId,
purpose,
algorithm
};
// Make API request
const response = await this.httpClient.post(`/api/scripts/${scriptId}/hsm-keys`, payload);
return {
id: response.data.id,
name: response.data.name,
purpose: response.data.purpose,
algorithm: response.data.algorithm,
protectionLevel: response.data.protection_level,
createTime: new Date(response.data.create_time),
status: response.data.status
};
}
catch (error) {
throw this.handleError(error, 'Failed to create HSM key');
}
}
/**
* Handle errors from API calls
* @param error Original error
* @param defaultMessage Default error message
* @returns Properly formatted TereError
*/
handleError(error, defaultMessage) {
if (axios_1.default.isAxiosError(error)) {
const axiosError = error;
if (axiosError.response) {
const data = axiosError.response.data;
const errorCode = data.error || 'unknown_error';
const message = data.message || defaultMessage;
const requestId = data.request_id;
return new types_1.TereError(message, errorCode, axiosError.response.status, requestId);
}
else if (axiosError.request) {
return new types_1.TereError('No response received from server', 'network_error', 0);
}
}
if (error instanceof Error) {
return new types_1.TereError(error.message, 'unknown_error', 0);
}
return new types_1.TereError(String(error) || defaultMessage, 'unknown_error', 0);
}
}
exports.TereClient = TereClient;