Spaces:
Running
Running
| import { execFile } from "node:child_process"; | |
| import { X509Certificate } from "node:crypto"; | |
| import fs from "node:fs/promises"; | |
| import path from "node:path"; | |
| import tls from "node:tls"; | |
| import { promisify } from "node:util"; | |
| import type { GatewayTlsConfig } from "../../config/types.gateway.js"; | |
| import { CONFIG_DIR, ensureDir, resolveUserPath, shortenHomeInString } from "../../utils.js"; | |
| import { normalizeFingerprint } from "./fingerprint.js"; | |
| const execFileAsync = promisify(execFile); | |
| export type GatewayTlsRuntime = { | |
| enabled: boolean; | |
| required: boolean; | |
| certPath?: string; | |
| keyPath?: string; | |
| caPath?: string; | |
| fingerprintSha256?: string; | |
| tlsOptions?: tls.TlsOptions; | |
| error?: string; | |
| }; | |
| async function fileExists(filePath: string): Promise<boolean> { | |
| try { | |
| await fs.access(filePath); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| async function generateSelfSignedCert(params: { | |
| certPath: string; | |
| keyPath: string; | |
| log?: { info?: (msg: string) => void }; | |
| }): Promise<void> { | |
| const certDir = path.dirname(params.certPath); | |
| const keyDir = path.dirname(params.keyPath); | |
| await ensureDir(certDir); | |
| if (keyDir !== certDir) { | |
| await ensureDir(keyDir); | |
| } | |
| await execFileAsync("openssl", [ | |
| "req", | |
| "-x509", | |
| "-newkey", | |
| "rsa:2048", | |
| "-sha256", | |
| "-days", | |
| "3650", | |
| "-nodes", | |
| "-keyout", | |
| params.keyPath, | |
| "-out", | |
| params.certPath, | |
| "-subj", | |
| "/CN=openclaw-gateway", | |
| ]); | |
| await fs.chmod(params.keyPath, 0o600).catch(() => {}); | |
| await fs.chmod(params.certPath, 0o600).catch(() => {}); | |
| params.log?.info?.( | |
| `gateway tls: generated self-signed cert at ${shortenHomeInString(params.certPath)}`, | |
| ); | |
| } | |
| export async function loadGatewayTlsRuntime( | |
| cfg: GatewayTlsConfig | undefined, | |
| log?: { info?: (msg: string) => void; warn?: (msg: string) => void }, | |
| ): Promise<GatewayTlsRuntime> { | |
| if (!cfg || cfg.enabled !== true) { | |
| return { enabled: false, required: false }; | |
| } | |
| const autoGenerate = cfg.autoGenerate !== false; | |
| const baseDir = path.join(CONFIG_DIR, "gateway", "tls"); | |
| const certPath = resolveUserPath(cfg.certPath ?? path.join(baseDir, "gateway-cert.pem")); | |
| const keyPath = resolveUserPath(cfg.keyPath ?? path.join(baseDir, "gateway-key.pem")); | |
| const caPath = cfg.caPath ? resolveUserPath(cfg.caPath) : undefined; | |
| const hasCert = await fileExists(certPath); | |
| const hasKey = await fileExists(keyPath); | |
| if (!hasCert && !hasKey && autoGenerate) { | |
| try { | |
| await generateSelfSignedCert({ certPath, keyPath, log }); | |
| } catch (err) { | |
| return { | |
| enabled: false, | |
| required: true, | |
| certPath, | |
| keyPath, | |
| error: `gateway tls: failed to generate cert (${String(err)})`, | |
| }; | |
| } | |
| } | |
| if (!(await fileExists(certPath)) || !(await fileExists(keyPath))) { | |
| return { | |
| enabled: false, | |
| required: true, | |
| certPath, | |
| keyPath, | |
| error: "gateway tls: cert/key missing", | |
| }; | |
| } | |
| try { | |
| const cert = await fs.readFile(certPath, "utf8"); | |
| const key = await fs.readFile(keyPath, "utf8"); | |
| const ca = caPath ? await fs.readFile(caPath, "utf8") : undefined; | |
| const x509 = new X509Certificate(cert); | |
| const fingerprintSha256 = normalizeFingerprint(x509.fingerprint256 ?? ""); | |
| if (!fingerprintSha256) { | |
| return { | |
| enabled: false, | |
| required: true, | |
| certPath, | |
| keyPath, | |
| caPath, | |
| error: "gateway tls: unable to compute certificate fingerprint", | |
| }; | |
| } | |
| return { | |
| enabled: true, | |
| required: true, | |
| certPath, | |
| keyPath, | |
| caPath, | |
| fingerprintSha256, | |
| tlsOptions: { | |
| cert, | |
| key, | |
| ca, | |
| minVersion: "TLSv1.3", | |
| }, | |
| }; | |
| } catch (err) { | |
| return { | |
| enabled: false, | |
| required: true, | |
| certPath, | |
| keyPath, | |
| caPath, | |
| error: `gateway tls: failed to load cert (${String(err)})`, | |
| }; | |
| } | |
| } | |