feat(wasm): secure device API (private keys never cross to JS)
- register_device() generates signing + deploy keypairs via core device module, stores them in DEVICE_STATE (once_cell Lazy<Mutex>), and returns only public keys to JS - sign_for_git() signs data using the internal signing key - get_device_info() returns name and public keys; returns null if not registered - clear_device() zeroes and drops device state (logout / re-registration) - Removed generate_device_keypair() which exposed raw private key bytes Fixes audit I5: private key material no longer crosses the WASM boundary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
//! looked up per call via a u32 handle. JS cannot read key bytes.
|
||||
|
||||
mod session;
|
||||
mod device;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@@ -206,26 +207,53 @@ pub fn rate_passphrase(p: &str) -> Result<JsValue, JsError> {
|
||||
}))
|
||||
}
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
use base64::Engine;
|
||||
|
||||
/// Generate an ed25519 keypair for device registration.
|
||||
/// Returns JSON: { "public_key_hex": "...", "private_key_base64": "..." }
|
||||
/// Register a new device, generating ed25519 keypairs for signing and deploy.
|
||||
/// Returns JSON: { "signing_public_key": "ssh-ed25519 ...", "deploy_public_key": "ssh-ed25519 ..." }
|
||||
/// Private keys are kept internal to WASM and never cross to JS.
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_device_keypair() -> Result<JsValue, JsError> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let signing_key = SigningKey::generate(&mut rng);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
|
||||
let public_hex = hex::encode(verifying_key.as_bytes());
|
||||
let private_b64 = base64::engine::general_purpose::STANDARD.encode(signing_key.as_bytes());
|
||||
pub fn register_device(name: &str) -> Result<JsValue, JsError> {
|
||||
let (signing_pub, deploy_pub) =
|
||||
device::register_device(name).map_err(|e| JsError::new(&e))?;
|
||||
|
||||
js_value_for(&serde_json::json!({
|
||||
"public_key_hex": public_hex,
|
||||
"private_key_base64": private_b64,
|
||||
"signing_public_key": signing_pub,
|
||||
"deploy_public_key": deploy_pub,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Sign `data` using the registered device's signing key.
|
||||
/// Returns JSON: { "signature": "<base64>" }
|
||||
/// Errors if no device has been registered via register_device().
|
||||
#[wasm_bindgen]
|
||||
pub fn sign_for_git(data: &[u8]) -> Result<JsValue, JsError> {
|
||||
let signature = device::sign_for_git(data).map_err(|e| JsError::new(&e))?;
|
||||
|
||||
js_value_for(&serde_json::json!({
|
||||
"signature": signature,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the current device's name and public keys.
|
||||
/// Returns JSON: { "name": "...", "signing_public_key": "...", "deploy_public_key": "..." }
|
||||
/// Returns null if no device is registered in this session.
|
||||
#[wasm_bindgen]
|
||||
pub fn get_device_info() -> Result<JsValue, JsError> {
|
||||
match device::get_device_info() {
|
||||
Some((name, signing_pub, deploy_pub)) => js_value_for(&serde_json::json!({
|
||||
"name": name,
|
||||
"signing_public_key": signing_pub,
|
||||
"deploy_public_key": deploy_pub,
|
||||
})),
|
||||
None => Ok(JsValue::NULL),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the in-memory device state (call on logout or before re-registration).
|
||||
#[wasm_bindgen]
|
||||
pub fn clear_device() {
|
||||
device::clear_device();
|
||||
}
|
||||
|
||||
/// Extract field history from a decrypted item JSON.
|
||||
/// Returns JSON array of { field_id, field_name, current_value, entries: [{ value, changed_at }] }
|
||||
#[wasm_bindgen]
|
||||
@@ -307,6 +335,8 @@ pub fn totp_compute(
|
||||
|
||||
// ── Backup container bridge ─────────────────────────────────────────────────
|
||||
|
||||
use base64::Engine;
|
||||
|
||||
use relicario_core::backup::{
|
||||
pack_backup as core_pack_backup,
|
||||
unpack_backup as core_unpack_backup,
|
||||
|
||||
Reference in New Issue
Block a user