import { X509Certificate } from "npm:@peculiar/x509"; import { AsnParser } from "npm:@peculiar/asn1-schema"; import { SubjectAlternativeName } from "npm:@peculiar/asn1-x509"; // Deno binding for the spire_ffi Rust crate if (Deno.build.arch !== "x86_64" && Deno.build.arch !== "aarch64") { throw new Error("Unsupported architecture"); } const libPath = (() => { if (Deno.build.os === "windows") return "./spire_ffi.dll"; if (Deno.build.os === "darwin") return "./libspire_ffi.dylib"; return "./libspire_ffi.so"; })(); let dylib: Deno.DynamicLibrary | null = null; try { dylib = Deno.dlopen(libPath, { fetch_svid: { parameters: ["pointer"], result: "pointer", nonblocking: true, }, free_svid: { parameters: ["pointer"], result: "void", }, argon2id_derive: { parameters: [ "pointer", "usize", "pointer", "usize", "u32", "u32", "pointer", "usize", ], result: "i32", nonblocking: true, }, }); } catch (_e) { console.warn( `Failed to load ${libPath}. Workload API fetching will be mocked/disabled if used.`, ); } export interface SvidResponse { spiffe_id: string; x509_svid: Uint8Array; x509_svid_key: Uint8Array; bundle: Uint8Array; } export async function fetchSpiffeIdentity( socketPath: string = "/var/run/spire/agent.sock", ): Promise { if (!dylib) { console.warn( `[SPIRE FFI] Dynamic library (${libPath}) is not loaded. Mocking SVID response for local development.`, ); return { spiffe_id: "spiffe://local.dev/mock", x509_svid: new Uint8Array(), x509_svid_key: new Uint8Array(), bundle: new Uint8Array(), }; } const encoder = new TextEncoder(); const encodedPath = encoder.encode(socketPath + "\0"); const pathPtr = Deno.UnsafePointer.of(encodedPath); const fetch_svid = dylib.symbols .fetch_svid as unknown as (( ptr: Deno.PointerValue, ) => Promise); const free_svid = dylib.symbols .free_svid as unknown as ((ptr: Deno.PointerValue) => void); const resPtr = await fetch_svid(pathPtr); if (resPtr === null) { throw new Error("fetch_svid returned a null pointer"); } const resView = new Deno.UnsafePointerView(resPtr); let errorMsg: string | null = null; let spiffe_id: string | null = null; let offset = 0; const ptrSize = 8; // 64-bit pointers const spiffe_id_ptr = resView.getPointer(offset); offset += ptrSize; const x509_svid_ptr = resView.getPointer(offset); offset += ptrSize; const x509_svid_len = Number(resView.getBigUint64(offset)); offset += ptrSize; const x509_svid_key_ptr = resView.getPointer(offset); offset += ptrSize; const x509_svid_key_len = Number(resView.getBigUint64(offset)); offset += ptrSize; const bundle_ptr = resView.getPointer(offset); offset += ptrSize; const bundle_len = Number(resView.getBigUint64(offset)); offset += ptrSize; const error_ptr = resView.getPointer(offset); if (error_ptr !== null) { errorMsg = new Deno.UnsafePointerView(error_ptr).getCString(); } if (errorMsg !== null) { free_svid(resPtr); throw new Error(errorMsg); } if (spiffe_id_ptr !== null) { spiffe_id = new Deno.UnsafePointerView(spiffe_id_ptr).getCString(); } if (!spiffe_id) { free_svid(resPtr); throw new Error("spiffe_id is null"); } const x509_svid = x509_svid_ptr !== null && x509_svid_len > 0 ? new Uint8Array( new Deno.UnsafePointerView(x509_svid_ptr).getArrayBuffer(x509_svid_len), ) : new Uint8Array(); const x509_svid_key = x509_svid_key_ptr !== null && x509_svid_key_len > 0 ? new Uint8Array( new Deno.UnsafePointerView(x509_svid_key_ptr).getArrayBuffer( x509_svid_key_len, ), ) : new Uint8Array(); const bundle = bundle_ptr !== null && bundle_len > 0 ? new Uint8Array( new Deno.UnsafePointerView(bundle_ptr).getArrayBuffer(bundle_len), ) : new Uint8Array(); // Create copies of the typed arrays before freeing the memory const svidData = { spiffe_id, x509_svid: new Uint8Array(x509_svid), x509_svid_key: new Uint8Array(x509_svid_key), bundle: new Uint8Array(bundle), }; // Free the memory on the Rust side free_svid(resPtr); return svidData; } /** * Extracts the SPIFFE ID from an incoming client TLS connection. */ export let extractSpiffeIdFromCert = function extractSpiffeIdFromCert( certBundle: string, ): string | null { if (!certBundle || typeof certBundle !== "string") { return null; } try { const cert = new X509Certificate(certBundle); const sanExtension = cert.extensions.find((ext) => ext.type === "2.5.29.17" ); // Subject Alternative Name if (!sanExtension) { return null; } const san = AsnParser.parse(sanExtension.value, SubjectAlternativeName); for (const name of san) { if ( name.uniformResourceIdentifier && name.uniformResourceIdentifier.startsWith("spiffe://") ) { return name.uniformResourceIdentifier; } } } catch (_e) { return null; } return null; }; export async function deriveArgon2idKey( password: Uint8Array, salt: Uint8Array, ): Promise { const outBuf = new Uint8Array(32); if (!dylib) { console.warn( `[SPIRE FFI] Dynamic library (${libPath}) is not loaded. Mocking Argon2id derivation for local development.`, ); outBuf.fill(0xaa); return outBuf; } const iterations = 12; const memoryKb = 65536; const passwordPtr = Deno.UnsafePointer.of(password); const saltPtr = Deno.UnsafePointer.of(salt); const outPtr = Deno.UnsafePointer.of(outBuf); const argon2id_derive = dylib.symbols .argon2id_derive as unknown as (( passwordPtr: Deno.PointerValue, passwordLen: number | bigint, saltPtr: Deno.PointerValue, saltLen: number | bigint, iterations: number, memoryKb: number, outPtr: Deno.PointerValue, outLen: number | bigint, ) => Promise); const res = await argon2id_derive( passwordPtr, password.length, saltPtr, salt.length, iterations, memoryKb, outPtr, outBuf.length, ); if (res !== 0) { throw new Error(`Argon2id derivation failed: ${res}`); } return outBuf; } export const spireWrapper = { get extractSpiffeIdFromCert() { return extractSpiffeIdFromCert; }, set extractSpiffeIdFromCert(val: any) { extractSpiffeIdFromCert = val; }, };