1
#![allow(clippy::module_name_repetitions)]
2

            
3
//! OPAQUE client side handling.
4

            
5
use opaque_ke::errors::ProtocolError;
6
use serde::{Deserialize, Serialize};
7

            
8
use crate::{
9
	cipher_suite, Config, Error, ExportKey, LoginFinalization, LoginRequest, LoginResponse,
10
	PublicKey, RegistrationFinalization, RegistrationRequest, RegistrationResponse, Result,
11
};
12

            
13
/// Client configuration.
14
864
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
15
pub struct ClientConfig {
16
	/// Common config.
17
	config: Config,
18
	/// Server key pair.
19
	public_key: Option<PublicKey>,
20
}
21

            
22
impl ClientConfig {
23
	/// Create a new [`ClientConfig`].
24
	///
25
	/// A [`PublicKey`] can be used to ensure the servers identity
26
	/// during registration or login. If no [`PublicKey`] could be obtained
27
	/// beforehand, it can be retrieved after successful registration or login.
28
	///
29
	/// # Errors
30
	/// [`Error::Config`] if [`PublicKey`] was not created with the same
31
	/// [`Config`].
32
	pub fn new(config: Config, public_key: Option<PublicKey>) -> Result<Self> {
33
228
		if let Some(public_key) = public_key {
34
83
			if public_key.config != config {
35
1
				return Err(Error::Config);
36
82
			}
37
145
		}
38

            
39
227
		Ok(Self { config, public_key })
40
228
	}
41

            
42
	/// Returns the [`Config`] associated with this [`ClientConfig`].
43
	#[must_use]
44
3
	pub const fn config(&self) -> Config {
45
3
		self.config
46
3
	}
47

            
48
	/// Returns the [`PublicKey`] associated with this [`ClientConfig`].
49
	#[must_use]
50
150
	pub const fn public_key(&self) -> Option<PublicKey> {
51
150
		self.public_key
52
150
	}
53
}
54

            
55
/// Holds the state of a registration process. See [`register`](Self::register).
56
#[must_use = "Use `finish()` to complete the registration process"]
57
288
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58
pub struct ClientRegistration {
59
	/// [`ClientConfig`] of this [`ClientRegistration`].
60
	config: ClientConfig,
61
	/// Client registration state.
62
	state: cipher_suite::ClientRegistration,
63
}
64

            
65
impl ClientRegistration {
66
	/// Returns the [`ClientConfig`] associated with this
67
	/// [`ClientRegistration`].
68
	#[must_use]
69
3
	pub const fn config(&self) -> ClientConfig {
70
3
		self.config
71
3
	}
72

            
73
	/// Starts the registration process. The returned [`RegistrationRequest`]
74
	/// has to be send to the server to drive the registration process. See
75
	/// [`ServerRegistration::register()`](crate::ServerRegistration::register).
76
	///
77
	/// # Errors
78
	/// [`Error::Opaque`] on internal OPAQUE error.
79
157
	pub fn register<P: AsRef<[u8]>>(
80
157
		config: ClientConfig,
81
157
		password: P,
82
157
	) -> Result<(Self, RegistrationRequest)> {
83
157
		let (state, message) = cipher_suite::ClientRegistration::register(
84
157
			config.config.cipher_suite,
85
157
			password.as_ref(),
86
157
		)?;
87

            
88
157
		Ok((Self { config, state }, RegistrationRequest {
89
157
			config: config.config,
90
157
			message,
91
157
		}))
92
157
	}
93

            
94
	/// Finishes the registration process. The returned
95
	/// [`RegistrationFinalization`] has to be send back to the server to finish
96
	/// the registration process. See
97
	/// [`ServerRegistration::finish()`](crate::ServerRegistration::finish).
98
	///
99
	/// [`ClientFile`] can be used to validate the server during login. See
100
	/// [`ClientLogin::login()`].
101
	///
102
	/// [`ExportKey`] can be used to encrypt data and store it safely on
103
	/// the server. See [`ExportKey`] for more details.
104
	///
105
	/// # Errors
106
	/// - [`Error::Config`] if [`ClientRegistration`] and
107
	///   [`RegistrationResponse`] were not created with the same [`Config`]
108
	/// - [`Error::InvalidServer`] if the public key given in
109
	///   [`register()`](Self::register) does not match the servers public key
110
	/// - [`Error::Opaque`] on internal OPAQUE error
111
232
	pub fn finish(
112
232
		self,
113
232
		response: RegistrationResponse,
114
232
	) -> Result<(ClientFile, RegistrationFinalization, ExportKey)> {
115
232
		if self.config.config != response.config {
116
2
			return Err(Error::Config);
117
230
		}
118

            
119
230
		let (message, new_public_key, export_key) = self
120
230
			.state
121
230
			.finish(response.message, &self.config.config.mhf().to_slow_hash())?;
122

            
123
230
		let public_key = if let Some(public_key) = self.config.public_key {
124
81
			if public_key.key != new_public_key {
125
1
				return Err(Error::InvalidServer);
126
80
			}
127
80

            
128
80
			public_key
129
		} else {
130
149
			PublicKey::new(self.config.config, new_public_key)
131
		};
132

            
133
229
		Ok((
134
229
			ClientFile(public_key),
135
229
			RegistrationFinalization {
136
229
				config: self.config.config,
137
229
				message,
138
229
			},
139
229
			ExportKey::new(export_key),
140
229
		))
141
232
	}
142
}
143

            
144
/// Use this to enable server validation during login.
145
576
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
146
pub struct ClientFile(PublicKey);
147

            
148
impl ClientFile {
149
	/// Returns the [`Config`] associated with this [`ClientFile`].
150
	#[must_use]
151
2
	pub const fn config(&self) -> Config {
152
2
		self.0.config
153
2
	}
154

            
155
	/// Returns the servers [`PublicKey`] associated with this [`ClientFile`].
156
	#[must_use]
157
146
	pub const fn public_key(&self) -> PublicKey {
158
146
		self.0
159
146
	}
160
}
161

            
162
/// Holds the state of a login process. See [`login`](Self::login).
163
#[must_use = "Does nothing if not `finish`ed"]
164
288
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
165
pub struct ClientLogin {
166
	/// [`ClientConfig`] of this [`ClientLogin`].
167
	config: ClientConfig,
168
	/// Client login state.
169
	state: cipher_suite::ClientLogin,
170
}
171

            
172
impl ClientLogin {
173
	/// Returns the [`ClientConfig`] associated with this [`ClientLogin`].
174
	#[must_use]
175
3
	pub const fn config(&self) -> ClientConfig {
176
3
		self.config
177
3
	}
178

            
179
	/// Starts the login process. The returned [`LoginRequest`] has to be send
180
	/// to the server to drive the login process. See
181
	/// [`ServerLogin::login()`](crate::ServerLogin::login).
182
	///
183
	/// If a [`ClientFile`] was stored during registration, it can validate the
184
	/// server when passed.
185
	///
186
	/// # Errors
187
	/// - [`Error::Config`] if [`ClientConfig`] and [`ClientFile`] were not
188
	///   created with the same [`Config`]
189
	/// - [`Error::ConfigPublicKey`] if [`PublicKey`] in [`ClientConfig`] and
190
	///   [`ClientFile`] don't match
191
	/// - [`Error::Opaque`] on internal OPAQUE error
192
	pub fn login<P: AsRef<[u8]>>(
193
		mut config: ClientConfig,
194
		file: Option<ClientFile>,
195
		password: P,
196
	) -> Result<(Self, LoginRequest)> {
197
159
		if let Some(file) = &file {
198
153
			if file.0.config != config.config {
199
2
				return Err(Error::Config);
200
151
			}
201

            
202
151
			if let Some(public_key) = config.public_key {
203
79
				if public_key != file.0 {
204
2
					return Err(Error::ConfigPublicKey);
205
77
				}
206
72
			} else {
207
72
				config.public_key = Some(file.0);
208
72
			}
209
6
		}
210

            
211
155
		let (state, message) =
212
155
			cipher_suite::ClientLogin::login(config.config.cipher_suite, password.as_ref())?;
213

            
214
155
		Ok((Self { config, state }, LoginRequest {
215
155
			config: config.config,
216
155
			message,
217
155
		}))
218
159
	}
219

            
220
	/// Finishes the login process. The returned [`LoginFinalization`] has to be
221
	/// send back to the server to finish the login process.
222
	///
223
	/// [`ClientFile`] can be used to validate the server during the next login.
224
	/// See [`login()`](Self::login).
225
	///
226
	/// [`ExportKey`] can be used to encrypt data and store it on safely on
227
	/// the server. See [`ExportKey`] for more details.
228
	///
229
	/// # Errors
230
	/// - [`Error::Config`] if [`ClientLogin`] and [`LoginResponse`] were not
231
	///   created with the same [`Config`]
232
	/// - [`Error::Credentials`] if credentials don't match
233
	/// - [`Error::InvalidServer`] if the public key given in
234
	///   [`login()`](Self::login) does not match the servers public key
235
	/// - [`Error::Opaque`] on internal OPAQUE error
236
229
	pub fn finish(
237
229
		self,
238
229
		response: LoginResponse,
239
229
	) -> Result<(ClientFile, LoginFinalization, ExportKey)> {
240
229
		if self.config.config != response.config {
241
2
			return Err(Error::Config);
242
227
		}
243

            
244
227
		let (message, new_public_key, export_key) = match self
245
227
			.state
246
227
			.finish(response.message, &self.config.config.mhf().to_slow_hash())
247
		{
248
225
			Ok(result) => result,
249
2
			Err(Error::Opaque(ProtocolError::InvalidLoginError)) => return Err(Error::Credentials),
250
			Err(error) => return Err(error),
251
		};
252

            
253
225
		let public_key = if let Some(public_key) = self.config.public_key {
254
223
			if public_key.key != new_public_key {
255
1
				return Err(Error::InvalidServer);
256
222
			}
257
222

            
258
222
			public_key
259
		} else {
260
2
			PublicKey::new(self.config.config, new_public_key)
261
		};
262

            
263
224
		Ok((
264
224
			ClientFile(public_key),
265
224
			LoginFinalization {
266
224
				config: self.config.config,
267
224
				message,
268
224
			},
269
224
			ExportKey::new(export_key),
270
224
		))
271
229
	}
272
}