Files
nym/nym-node/src/node/key_rotation/manager.rs
T
Jędrzej Stuczyński 827c13b69e moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-08 16:17:43 +01:00

192 lines
7.2 KiB
Rust

// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::{KeyIOFailure, NymNodeError};
use crate::node::helpers::{load_key, store_key};
use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
use crate::node::key_rotation::key::{SphinxPrivateKey, SphinxPublicKey};
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use std::fs;
use std::path::{Path, PathBuf};
use tracing::{trace, warn};
pub(crate) struct SphinxKeyManager {
pub(crate) keys: ActiveSphinxKeys,
primary_key_path: PathBuf,
secondary_key_path: PathBuf,
}
impl SphinxKeyManager {
// only called by newly initialised nym-nodes
pub(crate) fn initialise_new<R, P>(
rng: &mut R,
current_rotation_id: u32,
primary_key_path: P,
secondary_key_path: P,
) -> Result<Self, NymNodeError>
where
R: RngCore + CryptoRng,
P: AsRef<Path>,
{
let primary = SphinxPrivateKey::new(rng, current_rotation_id);
trace!("attempting to store primary x25519 sphinx key");
let primary_key_path = primary_key_path.as_ref();
store_key(&primary, primary_key_path, "x25519 sphinx")?;
Ok(SphinxKeyManager {
keys: ActiveSphinxKeys::new_fresh(primary),
primary_key_path: primary_key_path.to_path_buf(),
secondary_key_path: secondary_key_path.as_ref().to_path_buf(),
})
}
// moves the primary key to the secondary file
// and vice verse, i.e. secondary to the primary
fn swap_key_files<P: AsRef<Path>>(
primary_path: P,
secondary_path: P,
) -> Result<(), NymNodeError> {
let tmp_path = primary_path.as_ref().with_extension("tmp");
// 1. COPY: primary -> temp
fs::copy(primary_path.as_ref(), &tmp_path).map_err(|err| KeyIOFailure::KeyCopyFailure {
key: "old x25519 sphinx primary".to_string(),
source: primary_path.as_ref().to_path_buf(),
destination: tmp_path.clone(),
err,
})?;
// 2. MOVE: secondary -> primary
fs::rename(secondary_path.as_ref(), primary_path.as_ref()).map_err(|err| {
KeyIOFailure::KeyMoveFailure {
key: "x25519 sphinx secondary".to_string(),
source: secondary_path.as_ref().to_path_buf(),
destination: primary_path.as_ref().to_path_buf(),
err,
}
})?;
// 3. MOVE temp -> secondary
fs::rename(&tmp_path, secondary_path.as_ref()).map_err(|err| {
KeyIOFailure::KeyMoveFailure {
key: "old x25519 sphinx primary".to_string(),
source: tmp_path.clone(),
destination: primary_path.as_ref().to_path_buf(),
err,
}
})?;
Ok(())
}
pub(crate) fn generate_key_for_new_rotation(
&self,
expected_rotation: u32,
) -> Result<SphinxPublicKey, NymNodeError> {
let mut rng = OsRng;
let new = SphinxPrivateKey::new(&mut rng, expected_rotation);
let pub_key = (&new).into();
store_key(
&new,
&self.secondary_key_path,
"x22519 (pre-announced) sphinx",
)?;
self.keys.set_secondary(new);
Ok(pub_key)
}
pub(crate) fn rotate_keys(&self, expected_new_rotation: u32) -> Result<(), NymNodeError> {
if !self.keys.rotate(expected_new_rotation) {
self.generate_key_for_new_rotation(expected_new_rotation)?;
self.keys.rotate(expected_new_rotation);
}
Self::swap_key_files(&self.primary_key_path, &self.secondary_key_path)
}
pub(crate) fn remove_overlap_key(&self) -> Result<(), NymNodeError> {
self.keys.deactivate_secondary();
fs::remove_file(&self.secondary_key_path).map_err(|err| {
KeyIOFailure::KeyRemovalFailure {
key: "old x25519 sphinx secondary".to_string(),
path: self.secondary_key_path.clone(),
err,
}
})?;
Ok(())
}
pub(crate) fn try_load_or_regenerate<P: AsRef<Path>>(
current_rotation_id: u32,
primary_key_path: P,
secondary_key_path: P,
) -> Result<Self, NymNodeError> {
// if the temporary key exists, it means we crashed in the middle of rotating the key.
// rather than trying to figure out which exact step failed, just delete it and it will be redone
// (we still have the two keys, they just might be in the wrong order)
let tmp_location = primary_key_path.as_ref().with_extension("tmp");
if tmp_location.exists() {
warn!("we seem to have crashed in the middle of rotating the sphinx key");
fs::remove_file(&tmp_location).map_err(|err| KeyIOFailure::KeyRemovalFailure {
key: "old x25519 sphinx (temp location)".to_string(),
path: tmp_location,
err,
})?;
}
// primary key should always be present
let mut primary: SphinxPrivateKey =
load_key(primary_key_path.as_ref(), "x25519 sphinx primary")?;
let mut secondary: Option<SphinxPrivateKey> = if secondary_key_path.as_ref().exists() {
Some(load_key(
secondary_key_path.as_ref(),
"x25519 sphinx secondary",
)?)
} else {
None
};
let primary_id = primary.rotation_id();
let secondary_id = secondary.as_ref().map(|k| k.rotation_id());
// 1. check for failed (or missed) rotation, i.e. secondary > primary AND current_rotation > primary
if let Some(secondary_id) = secondary_id
&& secondary_id > primary_id
&& current_rotation_id > primary_id
{
Self::swap_key_files(primary_key_path.as_ref(), secondary_key_path.as_ref())?;
// SAFETY: we just checked secondary exists
#[allow(clippy::unwrap_used)]
let tmp = secondary.take().unwrap();
secondary = Some(primary);
primary = tmp;
}
// if upon loading it turns out that the node has been inactive for a long time,
// immediately rotate keys (but leave 1h grace period for current primary, i.e. set it as secondary)
if primary.rotation_id() != current_rotation_id {
warn!(
"this node has been inactive for more than a key rotation duration. the current primary key was generated for rotation {} while the current rotation is {current_rotation_id}. new key will be generated now.",
primary.rotation_id()
);
let this = SphinxKeyManager {
keys: ActiveSphinxKeys::new_loaded(primary, None),
primary_key_path: primary_key_path.as_ref().to_path_buf(),
secondary_key_path: secondary_key_path.as_ref().to_path_buf(),
};
this.generate_key_for_new_rotation(current_rotation_id)?;
return Ok(this);
}
Ok(SphinxKeyManager {
keys: ActiveSphinxKeys::new_loaded(primary, secondary),
primary_key_path: primary_key_path.as_ref().to_path_buf(),
secondary_key_path: secondary_key_path.as_ref().to_path_buf(),
})
}
}