simple_srp/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod errors;
4
5pub use srp::groups;
6
7use std::marker::PhantomData;
8use rand::RngCore;
9use serde::{Deserialize, Serialize};
10use sha2::Digest;
11use srp::{ClientVerifier, Group};
12
13use errors::SimpleSrpError;
14
15// To simplify work with hex strings
16
17pub struct CryptoString(Vec<u8>);
18
19impl CryptoString {
20    #[inline]
21    pub const fn as_bytes(&self) -> &[u8] {
22        self.0.as_slice()
23    }
24
25    #[inline]
26    pub fn hex(self) -> String {
27        hex::encode(self.0)
28    }
29}
30
31impl From<Vec<u8>> for CryptoString {
32    #[inline]
33    fn from(value: Vec<u8>) -> Self {
34        CryptoString(value)
35    }
36}
37
38impl TryFrom<String> for CryptoString {
39    type Error = hex::FromHexError;
40
41    #[inline]
42    fn try_from(value: String) -> Result<Self, Self::Error> {
43        hex::decode(value).map(CryptoString::from)
44    }
45}
46
47// For keys
48
49pub struct KeyPair {
50    pub private: CryptoString,
51    pub public: CryptoString,
52}
53
54impl KeyPair {
55    #[inline]
56    pub fn from_parts(private: String, public: String) -> Result<Self, hex::FromHexError> {
57        Ok(KeyPair {
58            private: CryptoString::try_from(private)?,
59            public: CryptoString::try_from(public)?,
60        })
61    }
62}
63
64// Structs to communicate client-server SRP data
65
66#[derive(Debug, Serialize, Deserialize)]
67pub struct SignupCredentials {
68    pub username: String,
69    pub salt: String,
70    pub verifier: String,
71}
72
73#[derive(Debug, Serialize, Deserialize)]
74pub struct ClientHello {
75    pub username: String,
76    // Client public key (A)
77    pub client: String,
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81pub struct ServerHello {
82    pub salt: String,
83    // Server public key (B)
84    pub server: String,
85}
86
87#[derive(Debug, Serialize, Deserialize)]
88pub struct LoginEvidence {
89    // Client evidence (M1)
90    pub evidence: String,
91}
92
93#[derive(Debug, Serialize, Deserialize)]
94pub struct AuthResult {
95    pub result: bool,
96    // Server evidence (M2)
97    pub evidence: String,
98}
99
100pub struct Client<G: Group, D: Digest, const SALT_LEN: usize = 64, const PRIVATE_KEY_LEN: usize = 64> {
101    d: PhantomData<(G, D)>,
102}
103
104impl<G: Group, D: Digest, const SALT_LEN: usize, const PRIVATE_KEY_LEN: usize> Client<G, D, SALT_LEN, PRIVATE_KEY_LEN> {
105    pub const fn new() -> Self {
106        Client {
107            d: PhantomData,
108        }
109    }
110
111    pub fn sign_up(&self, username: String, password: String) -> SignupCredentials {
112        let mut salt = [0u8; SALT_LEN];
113        rand::rng().fill_bytes(&mut salt);
114        let verifier = srp::Client::<G, D>::new()
115            .compute_verifier(
116                username.as_bytes(),
117                password.as_bytes(),
118                &salt
119            );
120
121        SignupCredentials {
122            username,
123            salt: hex::encode(salt),
124            verifier: hex::encode(verifier),
125        }
126    }
127
128
129    pub fn login_hello(&self, username: String) -> (ClientHello, KeyPair) {
130        let mut private = [0u8; PRIVATE_KEY_LEN];
131        rand::rng().fill_bytes(&mut private);
132        let public = srp::Client::<G, D>::new()
133            .compute_public_ephemeral(&private);
134
135        (
136            ClientHello {
137                username,
138                client: hex::encode(&public),
139            },
140            KeyPair {
141                private: Vec::from(private).into(),
142                public: public.into(),
143            },
144        )
145    }
146
147    pub fn create_evidence(
148        &self,
149        username: String, password: String,
150        salt: String, server: String, pair: KeyPair
151    ) -> Result<(LoginEvidence, ClientVerifier<D>), SimpleSrpError> {
152        let client = srp::Client::<G, D>::new();
153        let session = client.process_reply(
154            pair.private.as_bytes(),
155            username.as_bytes(),
156            password.as_bytes(),
157            &hex::decode(salt)?,
158            &hex::decode(server)?,
159        )?;
160
161        Ok((
162            LoginEvidence {
163                evidence: hex::encode(session.proof()),
164            },
165            session,
166        ))
167    }
168
169    pub fn verify_server<'a>(&self, expected: &'a ClientVerifier<D>, server_evidence: String) -> Result<&'a [u8], SimpleSrpError> {
170        expected.verify_server(&hex::decode(server_evidence)?)
171            .map_err(SimpleSrpError::from)
172    }
173}
174
175pub struct Server<G: Group, D: Digest, const PRIVATE_KEY_LEN: usize = 64> {
176    d: PhantomData<(G, D)>,
177}
178
179impl<G: Group, D: Digest, const PRIVATE_KEY_LEN: usize> Server<G, D, PRIVATE_KEY_LEN> {
180    pub const fn new() -> Self {
181        Server {
182            d: PhantomData,
183        }
184    }
185
186    pub fn hello_reply(&self, salt: String, verifier: String) -> Result<(ServerHello, KeyPair), SimpleSrpError> {
187        let mut private = [0u8; PRIVATE_KEY_LEN];
188        rand::rng().fill_bytes(&mut private);
189        let public = srp::Server::<G, D>::new()
190            .compute_public_ephemeral(&private, &hex::decode(verifier)?);
191
192        Ok((
193            ServerHello {
194                salt,
195                server: hex::encode(&public),
196            },
197            KeyPair {
198                private: Vec::from(private).into(),
199                public: public.into(),
200            },
201        ))
202    }
203
204    pub fn authenticate(
205        &self,
206        username: String, salt: String, verifier: String,
207        pair: KeyPair, client: String, evidence: String
208    ) -> Result<AuthResult, SimpleSrpError> {
209        let server = srp::Server::<G, D>::new();
210        let session = server.process_reply(
211            username.as_bytes(),
212            &hex::decode(salt)?,
213            pair.private.as_bytes(),
214            &hex::decode(verifier)?,
215            &hex::decode(client)?,
216        )?;
217        session.verify_client(&hex::decode(evidence)?)?;
218
219        Ok(AuthResult {
220            result: true,
221            evidence: hex::encode(session.proof()),
222        })
223    }
224}