Skip to main content

foctet_core/
auth.rs

1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
2use rand_core::OsRng;
3use zeroize::Zeroizing;
4
5use crate::CoreError;
6
7/// Authentication mode discriminator for native handshake messages.
8pub const HANDSHAKE_AUTH_NONE: u8 = 0;
9/// Ed25519-based transcript authentication for native handshake messages.
10pub const HANDSHAKE_AUTH_ED25519: u8 = 1;
11
12/// Local long-term identity key pair used to sign handshake transcripts.
13#[derive(Clone, Eq, PartialEq)]
14pub struct IdentityKeyPair {
15    secret_key: Zeroizing<[u8; 32]>,
16    public_key: [u8; 32],
17}
18
19impl core::fmt::Debug for IdentityKeyPair {
20    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21        f.debug_struct("IdentityKeyPair")
22            .field("public_key", &self.public_key)
23            .finish()
24    }
25}
26
27impl IdentityKeyPair {
28    /// Generates a fresh Ed25519 identity key pair.
29    pub fn generate() -> Self {
30        let signing_key = SigningKey::generate(&mut OsRng);
31        Self::from_secret_key_bytes(signing_key.to_bytes())
32    }
33
34    /// Builds an identity key pair from Ed25519 secret-key bytes.
35    pub fn from_secret_key_bytes(secret_key: [u8; 32]) -> Self {
36        let signing_key = SigningKey::from_bytes(&secret_key);
37        let public_key = signing_key.verifying_key().to_bytes();
38        Self {
39            secret_key: Zeroizing::new(secret_key),
40            public_key,
41        }
42    }
43
44    /// Returns the Ed25519 public key bytes.
45    pub fn public_key(&self) -> [u8; 32] {
46        self.public_key
47    }
48
49    /// Returns the Ed25519 secret key bytes.
50    pub fn secret_key_bytes(&self) -> [u8; 32] {
51        *self.secret_key
52    }
53
54    /// Signs handshake transcript bytes.
55    pub fn sign(&self, message: &[u8]) -> [u8; 64] {
56        let signing_key = SigningKey::from_bytes(&self.secret_key);
57        signing_key.sign(message).to_bytes()
58    }
59}
60
61/// Peer identity pin used to verify remote handshake authentication.
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct PeerIdentity {
64    /// Expected Ed25519 public key bytes for the remote peer.
65    pub public_key: [u8; 32],
66}
67
68impl PeerIdentity {
69    /// Creates a pinned peer identity from public-key bytes.
70    pub fn new(public_key: [u8; 32]) -> Self {
71        Self { public_key }
72    }
73}
74
75/// Authentication payload attached to a handshake control message.
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct HandshakeAuth {
78    /// Claimed Ed25519 identity public key.
79    pub identity_public_key: [u8; 32],
80    /// Ed25519 signature over the transcript message.
81    pub signature: [u8; 64],
82}
83
84impl HandshakeAuth {
85    /// Creates an authentication payload from a local identity and transcript message.
86    pub fn sign(identity: &IdentityKeyPair, message: &[u8]) -> Self {
87        Self {
88            identity_public_key: identity.public_key(),
89            signature: identity.sign(message),
90        }
91    }
92
93    /// Verifies the authentication payload against the transcript message.
94    pub fn verify(&self, message: &[u8]) -> Result<(), CoreError> {
95        let verifying_key = VerifyingKey::from_bytes(&self.identity_public_key)
96            .map_err(|_| CoreError::InvalidPeerAuthentication)?;
97        let signature = Signature::from_bytes(&self.signature);
98        verifying_key
99            .verify(message, &signature)
100            .map_err(|_| CoreError::InvalidPeerAuthentication)
101    }
102
103    /// Returns encoded byte length without the auth-mode discriminator.
104    pub const fn encoded_len() -> usize {
105        32 + 64
106    }
107}
108
109/// Session-level handshake authentication configuration.
110#[derive(Clone, Debug, Default, Eq, PartialEq)]
111pub struct SessionAuthConfig {
112    local_identity: Option<IdentityKeyPair>,
113    peer_identity: Option<PeerIdentity>,
114    require_peer_authentication: bool,
115}
116
117impl SessionAuthConfig {
118    /// Creates an empty authentication configuration.
119    pub fn new() -> Self {
120        Self::default()
121    }
122
123    /// Attaches a local identity used to sign native handshake messages.
124    pub fn with_local_identity(mut self, identity: IdentityKeyPair) -> Self {
125        self.local_identity = Some(identity);
126        self
127    }
128
129    /// Pins the expected remote identity public key.
130    pub fn with_peer_identity(mut self, identity: PeerIdentity) -> Self {
131        self.peer_identity = Some(identity);
132        self
133    }
134
135    /// Requires the remote side to present a valid authenticated handshake.
136    pub fn require_peer_authentication(mut self, require: bool) -> Self {
137        self.require_peer_authentication = require;
138        self
139    }
140
141    /// Returns the local identity, if configured.
142    pub fn local_identity(&self) -> Option<&IdentityKeyPair> {
143        self.local_identity.as_ref()
144    }
145
146    /// Returns the pinned peer identity, if configured.
147    pub fn peer_identity(&self) -> Option<PeerIdentity> {
148        self.peer_identity
149    }
150
151    /// Returns whether remote handshake authentication is mandatory.
152    pub fn requires_peer_authentication(&self) -> bool {
153        self.require_peer_authentication
154    }
155}