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
15pub 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
47pub 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#[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 pub client: String,
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81pub struct ServerHello {
82 pub salt: String,
83 pub server: String,
85}
86
87#[derive(Debug, Serialize, Deserialize)]
88pub struct LoginEvidence {
89 pub evidence: String,
91}
92
93#[derive(Debug, Serialize, Deserialize)]
94pub struct AuthResult {
95 pub result: bool,
96 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}