2026-07-19 12:00:50 +01:00
import { createHash , createPrivateKey , createPublicKey , generateKeyPairSync , sign as ed25519Sign , verify as ed25519Verify } from "node:crypto" ;
import { chmod , mkdir , open , readFile , stat } from "node:fs/promises" ;
import { dirname } from "node:path" ;
const ID _DOMAIN = Buffer . from ( "epiphany.host-incarnation.identity.v0\0" , "utf8" ) ;
const SIGNATURE _DOMAIN = Buffer . from ( "epiphany.host-incarnation.signature.v0\0" , "utf8" ) ;
export async function enrollSignedIdentity ( path , label = "Bifrost identity" ) { await mkdir ( dirname ( path ) , { recursive : true } ) ; const { privateKey } = generateKeyPairSync ( "ed25519" ) , seed = Buffer . from ( privateKey . export ( { format : "jwk" } ) . d , "base64url" ) , handle = await open ( path , "wx" , 0o600 ) ; try { await handle . writeFile ( seed ) ; await handle . sync ( ) ; } finally { await handle . close ( ) ; } await chmod ( path , 0o600 ) ; return loadSignedIdentity ( path , label ) ; }
export async function loadSignedIdentity ( path , label = "Bifrost identity" ) { const info = await stat ( path ) ; if ( ! info . isFile ( ) ) throw new Error ( ` ${ label } must be a regular file. ` ) ; if ( process . platform !== "win32" && ( info . mode & 0o077 ) !== 0 ) throw new Error ( ` ${ label } private key must have mode 0600. ` ) ; if ( process . platform !== "win32" && typeof process . geteuid === "function" && info . uid !== process . geteuid ( ) ) throw new Error ( ` ${ label } private key must be owned by the service user. ` ) ; const seed = await readFile ( path ) ; if ( seed . length !== 32 ) throw new Error ( ` ${ label } seed must be exactly 32 bytes. ` ) ; const key = createPrivateKey ( { key : Buffer . concat ( [ Buffer . from ( "302e020100300506032b657004220420" , "hex" ) , seed ] ) , format : "der" , type : "pkcs8" } ) , publicKey = createPublicKey ( key ) . export ( { format : "der" , type : "spki" } ) . subarray ( - 32 ) , identityId = createHash ( "sha256" ) . update ( ID _DOMAIN ) . update ( publicKey ) . digest ( "hex" ) ; return { identityId , publicKey , key } ; }
2026-07-22 00:34:04 +01:00
export async function enrollDomainIdentity ( path , label , identityDomain ) { await enrollSignedIdentity ( path , label ) ; return loadDomainIdentity ( path , label , identityDomain ) ; }
export async function loadDomainIdentity ( path , label , identityDomain ) { const identity = await loadSignedIdentity ( path , label ) ; return { ... identity , identityId : createHash ( "sha256" ) . update ( Buffer . from ( identityDomain , "utf8" ) ) . update ( identity . publicKey ) . digest ( "hex" ) } ; }
2026-07-19 12:00:50 +01:00
export function signPurpose ( identity , purpose , payload ) { return ed25519Sign ( null , domainMessage ( purpose , payload ) , identity . key ) ; }
export function verifyPurpose ( publicKey , purpose , payload , signature ) { const key = createPublicKey ( { key : Buffer . concat ( [ Buffer . from ( "302a300506032b6570032100" , "hex" ) , Buffer . from ( publicKey ) ] ) , format : "der" , type : "spki" } ) ; return ed25519Verify ( null , domainMessage ( purpose , payload ) , key , signature ) ; }
2026-07-22 00:34:04 +01:00
export function signDomainPurpose ( identity , signatureDomain , purpose , payload ) { return ed25519Sign ( null , domainMessageWith ( signatureDomain , purpose , payload ) , identity . key ) ; }
export function verifyDomainPurpose ( publicKey , signatureDomain , purpose , payload , signature ) { const key = createPublicKey ( { key : Buffer . concat ( [ Buffer . from ( "302a300506032b6570032100" , "hex" ) , Buffer . from ( publicKey ) ] ) , format : "der" , type : "spki" } ) ; return ed25519Verify ( null , domainMessageWith ( signatureDomain , purpose , payload ) , key , signature ) ; }
2026-07-19 12:00:50 +01:00
export function trustAnchor ( identity , createdAt ) { return { schemaVersion : "epiphany.host_identity_trust_anchor.v0" , identityId : identity . identityId , publicKey : new Uint8Array ( identity . publicKey ) , assurance : "os_service_file_bound" , identityCreatedAt : createdAt , sourceIdentityRecordSha256 : ` sha256- ${ createHash ( "sha256" ) . update ( identity . publicKey ) . digest ( "hex" ) } ` } ; }
function domainMessage ( purpose , payload ) { const p = Buffer . from ( purpose , "utf8" ) ; return Buffer . concat ( [ SIGNATURE _DOMAIN , u64 ( p . length ) , p , u64 ( payload . length ) , payload ] ) ; }
2026-07-22 00:34:04 +01:00
function domainMessageWith ( signatureDomain , purpose , payload ) { const p = Buffer . from ( purpose , "utf8" ) ; return Buffer . concat ( [ Buffer . from ( signatureDomain , "utf8" ) , u64 ( p . length ) , p , u64 ( payload . length ) , payload ] ) ; }
2026-07-19 12:00:50 +01:00
function u64 ( value ) { const b = Buffer . alloc ( 8 ) ; b . writeBigUInt64BE ( BigInt ( value ) ) ; return b ; }