Files
fips/src/identity/auth.rs
T
Johnathan Corgan 58664c7c77 Update dependencies: rand 0.10, rtnetlink 0.20, tun 0.8, and others
Bump rand (0.8→0.10), rtnetlink (0.14→0.20), tun (0.7→0.8),
simple-dns (0.9→0.11), socket2 (0.5→0.6), and criterion (0.5→0.8).

Migrate all rand call sites: thread_rng()→rng(), gen()→random(),
gen_range()→random_range(), RngCore→Rng trait. Work around secp256k1
0.30 requiring rand 0.8 by generating random bytes directly and
constructing SecretKey from slice.

Migrate rtnetlink to builder-based API: LinkSetRequest replaced with
LinkUnspec builder + change(), RouteAddRequest replaced with
RouteMessageBuilder.

Remove bloom benchmark (criterion 0.8 incompatible with old harness
config).
2026-02-24 18:02:06 +00:00

68 lines
2.0 KiB
Rust

//! Authentication challenge-response protocol.
use rand::Rng;
use secp256k1::{Secp256k1, XOnlyPublicKey};
use sha2::{Digest, Sha256};
use super::{IdentityError, NodeAddr};
/// Domain separation string for authentication challenges.
const AUTH_DOMAIN: &[u8] = b"fips-auth-v1";
/// A 32-byte random authentication challenge.
#[derive(Clone, Copy, Debug)]
pub struct AuthChallenge([u8; 32]);
impl AuthChallenge {
/// Generate a new random challenge.
pub fn generate() -> Self {
let mut bytes = [0u8; 32];
rand::rng().fill_bytes(&mut bytes);
Self(bytes)
}
/// Create a challenge from bytes.
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
/// Return the challenge bytes.
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
/// Verify a response to this challenge.
pub fn verify(&self, response: &AuthResponse) -> Result<NodeAddr, IdentityError> {
let digest = auth_challenge_digest(&self.0, response.timestamp);
let secp = Secp256k1::new();
secp.verify_schnorr(&response.signature, &digest, &response.pubkey)
.map_err(|_| IdentityError::SignatureVerificationFailed)?;
Ok(NodeAddr::from_pubkey(&response.pubkey))
}
}
/// Response to an authentication challenge.
#[derive(Clone, Debug)]
pub struct AuthResponse {
/// The responder's public key.
pub pubkey: XOnlyPublicKey,
/// Timestamp included in the signed message.
pub timestamp: u64,
/// Schnorr signature over the challenge digest.
pub signature: secp256k1::schnorr::Signature,
}
/// Compute the digest for an authentication challenge.
pub(super) fn auth_challenge_digest(challenge: &[u8; 32], timestamp: u64) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(AUTH_DOMAIN);
hasher.update(challenge);
hasher.update(timestamp.to_be_bytes());
let result = hasher.finalize();
let mut digest = [0u8; 32];
digest.copy_from_slice(&result);
digest
}