simple_srp/
lib.rs

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