pub use filament_cap::ephemeral::*;
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use zeroize::Zeroize;
enum PendingEnroll {
Compact {
enroll_seed: [u8; 32],
device_pub: [u8; 32],
inv: InvitationV2,
},
Legacy {
enroll_seed: [u8; 32],
device_pub: [u8; 32],
auth_key: AuthKey,
},
}
impl PendingEnroll {
fn enroll_seed(&self) -> [u8; 32] {
match self {
PendingEnroll::Compact { enroll_seed, .. } | PendingEnroll::Legacy { enroll_seed, .. } => *enroll_seed,
}
}
fn device_pub(&self) -> [u8; 32] {
match self {
PendingEnroll::Compact { device_pub, .. } | PendingEnroll::Legacy { device_pub, .. } => *device_pub,
}
}
fn zeroize_seed(&mut self) {
match self {
PendingEnroll::Compact { enroll_seed, .. } => enroll_seed.zeroize(),
PendingEnroll::Legacy { enroll_seed, .. } => enroll_seed.zeroize(),
}
}
}
impl Drop for PendingEnroll {
fn drop(&mut self) {
self.zeroize_seed();
}
}
static PENDING_ENROLLS: OnceLock<Mutex<HashMap<String, PendingEnroll>>> = OnceLock::new();
pub fn register_enrollment(
peer_id: String,
enroll_seed: [u8; 32],
device_pub: [u8; 32],
inv: InvitationV2,
) {
let mut store = PENDING_ENROLLS.get_or_init(|| Mutex::new(HashMap::new())).lock().unwrap();
store.insert(peer_id, PendingEnroll::Compact { enroll_seed, device_pub, inv });
}
pub fn register_enrollment_legacy(
peer_id: String,
enroll_seed: [u8; 32],
device_pub: [u8; 32],
auth_key: AuthKey,
) {
let mut store = PENDING_ENROLLS.get_or_init(|| Mutex::new(HashMap::new())).lock().unwrap();
store.insert(peer_id, PendingEnroll::Legacy { enroll_seed, device_pub, auth_key });
}
pub fn build_enrollment_response(
peer_id: &str,
nonce: [u8; 32],
verifier_pub: [u8; 32],
device_cert: &serde_json::Value,
) -> Option<serde_json::Value> {
let store_ref = PENDING_ENROLLS.get_or_init(|| Mutex::new(HashMap::new()));
let mut store = store_ref.lock().unwrap();
let pe = match store.get(peer_id) {
Some(p) => p,
None => return None,
};
let cert = crate::identity::DeviceCert::from_json(device_cert)?;
if cert.verify(now_secs()).is_err() || cert.device_pub != verifier_pub {
return None;
}
let owner_ok = match pe {
PendingEnroll::Compact { inv, .. } => {
filament_cap::ephemeral::issuer_fingerprint(&cert.user_pub) == inv.issuer_fp
}
PendingEnroll::Legacy { auth_key, .. } => cert.user_pub == auth_key.issuer,
};
if !owner_ok {
return None;
}
let pe = store.remove(peer_id)?;
let enroll_kp = ring::signature::Ed25519KeyPair::from_seed_unchecked(&pe.enroll_seed()).ok()?;
let message = enrollment_possession_msg(&nonce, &pe.device_pub(), &verifier_pub);
let enroll_signature = enroll_kp.sign(&message);
let mut enroll_possession_sig = [0u8; 64];
enroll_possession_sig.copy_from_slice(enroll_signature.as_ref());
let device_possession_sig = crate::overlay::overlay_sign_possession(&message).ok()?;
let principal = match &pe {
PendingEnroll::Compact { inv, .. } => {
crate::ephemeral::EnrollmentPrincipal::Compact(inv.clone())
}
PendingEnroll::Legacy { auth_key, .. } => {
crate::ephemeral::EnrollmentPrincipal::Legacy(auth_key.clone())
}
};
let payload = crate::ephemeral::EnrollmentPayload {
principal,
device_pub: pe.device_pub(),
enroll_possession_sig,
device_possession_sig,
};
Some(payload.to_json())
}