auth-yes/server/spire_ffi.ts
google-labs-jules[bot] 9da28a6416 feat(spire-ffi): implement native Argon2id derivation
- Added `argon2` v0.5 dependency to `spire_ffi/Cargo.toml`
- Implemented `argon2id_derive` FFI function in `spire_ffi/src/lib.rs` with C-ABI.
- Added Deno FFI binding `deriveArgon2idKey` in `server/spire_ffi.ts` with `nonblocking: true` to prevent stalling the event loop.
- Pre-allocates output buffer on the Deno side as the standard FFI pattern.
- Included fallback mock behavior when `libspire_ffi.so` is not loaded, returning a 32-byte 0xaa filled array.
- Updated unit tests in `server/spire_ffi.test.ts` to test mock usage and successful generation.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-24 04:19:48 +00:00

260 lines
6.4 KiB
TypeScript

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<any> | 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<SvidResponse> {
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<Deno.PointerValue>);
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<Uint8Array> {
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<number>);
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;
},
};