Files
bare-operating-system/packages/bare-os-booter/lib/identity/bare-os-secret-handle-registry.js
T
2026-08-18 18:11:28 -04:00

121 lines
3.2 KiB
JavaScript

/**
* Opaque key-handle registry: TTL, optional max uses, revocation (no key material).
*/
/** @typedef {{ id: string, purpose: string, algorithm: string, atMs: number, expiresAtMs: number | null, maxUses: number, uses: number, scopes: string[], revoked: boolean }} BareOsSecretHandleRow */
/** @type {Map<string, BareOsSecretHandleRow>} */
const handles = new Map()
let seq = 0
const MAX_HANDLES = 4096
/**
* @param {{ purpose?: string, algorithm?: string, ttlMs?: number | null, maxUses?: number, scopes?: string[] }} opts
*/
export function bareOsSecretHandleAcquire(opts = {}) {
if (handles.size >= MAX_HANDLES) {
for (const [id, h] of handles) {
if (h.revoked || bareOsSecretHandleIsExpired(h)) {
handles.delete(id)
break
}
}
}
const purpose = String(opts.purpose || 'default').trim().slice(0, 64) || 'default'
const algorithm = String(opts.algorithm || 'opaque').trim().slice(0, 32) || 'opaque'
const ttlRaw =
opts.ttlMs != null
? Number.parseInt(String(opts.ttlMs), 10)
: Number.NaN
const ttlMs =
Number.isFinite(ttlRaw) && ttlRaw > 0
? Math.min(ttlRaw, 86400000)
: null
const maxUses =
opts.maxUses != null && Number.isFinite(Number(opts.maxUses)) && Number(opts.maxUses) > 0
? Math.min(1e6, Math.floor(Number(opts.maxUses)))
: 1e9
const scopes = Array.isArray(opts.scopes)
? opts.scopes
.map((s) => String(s).trim().slice(0, 48))
.filter(Boolean)
.slice(0, 8)
: []
const atMs = Date.now()
const id = `kh_${++seq}_${atMs.toString(36)}`
const row = {
id,
purpose,
algorithm,
atMs,
expiresAtMs: ttlMs != null ? atMs + ttlMs : null,
maxUses,
uses: 0,
scopes: scopes.length ? scopes : ['default'],
revoked: false
}
handles.set(id, row)
return {
handle: id,
algorithm,
atMs,
ttlMs,
scopes: row.scopes,
zeroizeAfterMs: ttlMs
}
}
/** @param {BareOsSecretHandleRow} h */
function bareOsSecretHandleIsExpired(h) {
if (h.revoked) return true
if (h.expiresAtMs != null && Date.now() > h.expiresAtMs) return true
if (h.uses >= h.maxUses) return true
return false
}
/**
* @param {string} id
* @returns {boolean}
*/
export function bareOsSecretHandleTouch(id) {
const h = handles.get(String(id || ''))
if (!h || bareOsSecretHandleIsExpired(h)) return false
h.uses++
if (h.uses >= h.maxUses) h.revoked = true
return !h.revoked
}
/**
* @param {string} id
*/
export function bareOsSecretHandleRelease(id) {
const h = handles.get(String(id || ''))
if (h) h.revoked = true
handles.delete(String(id || ''))
}
export function bareOsSecretHandlePruneExpired() {
const now = Date.now()
for (const [id, h] of handles) {
if (h.revoked || (h.expiresAtMs != null && now > h.expiresAtMs) || h.uses >= h.maxUses)
handles.delete(id)
}
}
/** Non-secret snapshot for `/proc` / posture. */
export function bareOsSecretHandleSnapshot() {
bareOsSecretHandlePruneExpired()
let active = 0
for (const h of handles.values()) {
if (!bareOsSecretHandleIsExpired(h)) active++
}
return {
schema: 1,
totalRegistered: handles.size,
activeHandles: active,
cap: MAX_HANDLES,
atMs: Date.now()
}
}