huskies: merge 1191 bug /identity node_id field reports the CRDT id, not the node_identity.key the signature uses
This commit is contained in:
@@ -161,16 +161,22 @@ pub struct NodeIdentity {
|
||||
/// Global node identity, initialised once at server startup.
|
||||
static IDENTITY: OnceLock<NodeIdentity> = OnceLock::new();
|
||||
|
||||
/// Load or create the node's Ed25519 keypair, storing it in a `0600` file.
|
||||
/// Signing keypair backing [`IDENTITY`].
|
||||
///
|
||||
/// Kept in a separate `OnceLock` (rather than as a field on [`NodeIdentity`])
|
||||
/// so that [`get_identity`] can never hand callers access to key material —
|
||||
/// only [`sign_challenge_with_identity`] can use it.
|
||||
static SIGNING_KEYPAIR: OnceLock<Ed25519KeyPair> = OnceLock::new();
|
||||
|
||||
/// Load or create the node's Ed25519 signing key, storing it in a `0600` file.
|
||||
///
|
||||
/// - **First boot**: generates a new keypair with `ed25519-dalek`, writes the
|
||||
/// 32-byte signing-key seed to `path` with Unix mode `0600`, then returns
|
||||
/// the derived `NodeIdentity`.
|
||||
/// - **Subsequent boots**: reads the 32-byte seed from `path`, reconstructs
|
||||
/// the keypair deterministically, and returns the same `NodeIdentity`.
|
||||
/// 32-byte signing-key seed to `path` with Unix mode `0600`, then returns it.
|
||||
/// - **Subsequent boots**: reads the 32-byte seed from `path` and reconstructs
|
||||
/// the keypair deterministically.
|
||||
///
|
||||
/// The file stores the raw 32-byte seed only. No headers, no PEM, no base64.
|
||||
pub fn load_or_create_keypair_file(path: &std::path::Path) -> std::io::Result<NodeIdentity> {
|
||||
fn load_or_create_signing_key(path: &std::path::Path) -> std::io::Result<Ed25519KeyPair> {
|
||||
let signing_key = if path.exists() {
|
||||
let bytes = std::fs::read(path)?;
|
||||
let seed: [u8; 32] = bytes.try_into().map_err(|_| {
|
||||
@@ -216,8 +222,16 @@ pub fn load_or_create_keypair_file(path: &std::path::Path) -> std::io::Result<No
|
||||
sk
|
||||
};
|
||||
|
||||
let pubkey_bytes = signing_key.verifying_key().to_bytes();
|
||||
let pubkey_hex = hex_encode(&pubkey_bytes);
|
||||
Ok(signing_key)
|
||||
}
|
||||
|
||||
/// Load or create the node's Ed25519 keypair, storing it in a `0600` file,
|
||||
/// and return its public [`NodeIdentity`].
|
||||
///
|
||||
/// See [`load_or_create_signing_key`] for the persistence behaviour.
|
||||
pub fn load_or_create_keypair_file(path: &std::path::Path) -> std::io::Result<NodeIdentity> {
|
||||
let signing_key = load_or_create_signing_key(path)?;
|
||||
let pubkey_hex = hex_encode(&signing_key.verifying_key().to_bytes());
|
||||
Ok(NodeIdentity {
|
||||
node_id: pubkey_hex.clone(),
|
||||
pubkey_hex,
|
||||
@@ -229,8 +243,14 @@ pub fn load_or_create_keypair_file(path: &std::path::Path) -> std::io::Result<No
|
||||
/// Should be called once at server startup. Subsequent calls are no-ops.
|
||||
pub fn init_identity(path: &std::path::Path) -> std::io::Result<()> {
|
||||
if IDENTITY.get().is_none() {
|
||||
let identity = load_or_create_keypair_file(path)?;
|
||||
let signing_key = load_or_create_signing_key(path)?;
|
||||
let pubkey_hex = hex_encode(&signing_key.verifying_key().to_bytes());
|
||||
let identity = NodeIdentity {
|
||||
node_id: pubkey_hex.clone(),
|
||||
pubkey_hex,
|
||||
};
|
||||
let _ = IDENTITY.set(identity);
|
||||
let _ = SIGNING_KEYPAIR.set(signing_key);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -241,6 +261,18 @@ pub fn get_identity() -> Option<&'static NodeIdentity> {
|
||||
IDENTITY.get()
|
||||
}
|
||||
|
||||
/// Sign `nonce` with the file-based node identity's private key.
|
||||
///
|
||||
/// Returns the signature (hex) if [`init_identity`] has been called;
|
||||
/// `None` otherwise. The returned signature always verifies against
|
||||
/// [`get_identity`]'s `pubkey_hex`, so callers of `GET /identity` can trust
|
||||
/// that the reported `node_id` and the challenge-response signature refer
|
||||
/// to the same key.
|
||||
pub fn sign_challenge_with_identity(nonce: &str) -> Option<SignatureHex> {
|
||||
let keypair = SIGNING_KEYPAIR.get()?;
|
||||
Some(sign_challenge(keypair, nonce))
|
||||
}
|
||||
|
||||
// ── Internal helpers ──────────────────────────────────────────────────
|
||||
|
||||
fn hex_encode(bytes: &[u8]) -> String {
|
||||
|
||||
Reference in New Issue
Block a user