-
Notifications
You must be signed in to change notification settings - Fork 47
/
digital_locker.rs
343 lines (304 loc) · 12.1 KB
/
digital_locker.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! Demonstrates an implementation of a server-side secured digital locker using
//! the client's OPAQUE export key, over a command-line interface
//!
//! A client can password-protect a secret message to be stored in a digital
//! locker, controlled by the server. The locker's contents are only revealed to
//! the holder of the password when attempting to open the locker.
//!
//! The client-server interactions are executed in a three-step protocol within
//! the account_registration (for password registration) and account_login (for
//! password login) functions. These steps must be performed in the specific
//! sequence outlined in each of these functions.
//!
//! The CipherSuite trait allows the application to configure the primitives
//! used by OPAQUE, but must be kept consistent across the steps of the
//! protocol.
//!
//! In a more realistic client-server interaction, the client must send messages
//! over "the wire" to the server. These bytes are serialized and explicitly
//! annotated in the below functions.
use std::process::exit;
use chacha20poly1305::aead::{Aead, KeyInit};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use generic_array::GenericArray;
use opaque_ke::ciphersuite::CipherSuite;
use opaque_ke::rand::rngs::OsRng;
use opaque_ke::rand::RngCore;
use opaque_ke::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
};
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
use rustyline::Editor;
// The ciphersuite trait allows to specify the underlying primitives that will
// be used in the OPAQUE protocol
#[allow(dead_code)]
struct DefaultCipherSuite;
#[cfg(feature = "ristretto255")]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_ke::Ristretto255;
type KeGroup = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type Ksf = opaque_ke::ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = p256::NistP256;
type KeGroup = p256::NistP256;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type Ksf = opaque_ke::ksf::Identity;
}
struct Locker {
contents: Vec<u8>,
password_file: GenericArray<u8, ServerRegistrationLen<DefaultCipherSuite>>,
}
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
let mut rng = OsRng;
let mut nonce_bytes = [0u8; 12];
rng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
[nonce_bytes.to_vec(), ciphertext].concat()
}
// Decrypt using a key and a ciphertext (nonce included) to recover the original
// plaintext
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
cipher
.decrypt(
Nonce::from_slice(&ciphertext[..12]),
ciphertext[12..].as_ref(),
)
.unwrap()
}
// Password-based registration and encryption of client secret message between a
// client and server
fn register_locker(
server_setup: &ServerSetup<DefaultCipherSuite>,
locker_id: usize,
password: String,
secret_message: String,
) -> Locker {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes())
.unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(®istration_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
// Server sends registration_response_bytes to client
let client_finish_registration_result = client_registration_start_result
.state
.finish(
&mut client_rng,
password.as_bytes(),
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
// Client encrypts secret message using export key
let ciphertext = encrypt(
&client_finish_registration_result.export_key,
secret_message.as_bytes(),
);
// Client sends message_bytes to server
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
);
Locker {
contents: ciphertext,
password_file: password_file.serialize(),
}
}
// Open the contents of a locker with a password between a client and server
fn open_locker(
server_setup: &ServerSetup<DefaultCipherSuite>,
locker_id: usize,
password: String,
locker: &Locker,
) -> Result<String, String> {
let mut client_rng = OsRng;
let client_login_start_result =
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<DefaultCipherSuite>::deserialize(&locker.password_file).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
if result.is_err() {
// Client-detected login failure
return Err(String::from("Incorrect password, please try again."));
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize();
// Client sends credential_finalization_bytes to server
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.unwrap();
// Server sends locker contents, encrypted under the session key, to the client
let encrypted_locker_contents =
encrypt(&server_login_finish_result.session_key, &locker.contents);
// Client decrypts contents of locker, first under the session key, and then
let plaintext = decrypt(
&client_login_finish_result.export_key,
&decrypt(
&client_login_finish_result.session_key,
&encrypted_locker_contents,
),
);
String::from_utf8(plaintext).map_err(|_| String::from("UTF8 error"))
}
fn main() {
let mut rng = OsRng;
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
let mut rl = Editor::<(), _>::new().unwrap();
let mut registered_lockers: Vec<Locker> = vec![];
loop {
display_lockers(®istered_lockers);
println!("Enter an option (1 or 2):");
println!("1) Register a locker");
println!("2) Open a locker\n");
let readline = rl.readline("> ");
match readline {
Ok(line) => {
if line != "1" && line != "2" {
println!("Error: Invalid option (either specify 1 or 2)");
continue;
}
match line.as_ref() {
"1" => {
let (password, secret_message) = get_two_strings(
"Choose a password",
"Set a secret message",
&mut rl,
None,
);
let locker_id = registered_lockers.len();
registered_lockers.push(register_locker(
&server_setup,
locker_id,
password,
secret_message,
));
continue;
}
"2" => {
let (locker, password) = get_two_strings(
"Choose a locker number",
"Enter the password",
&mut rl,
None,
);
let locker_index: usize = match locker.parse() {
Ok(index) => index,
Err(_) => {
println!("Error: Could not find locker number");
continue;
}
};
if locker_index >= registered_lockers.len() {
println!("Error: Could not find locker number");
continue;
}
match open_locker(
&server_setup,
locker_index,
password,
®istered_lockers[locker_index],
) {
Ok(contents) => {
println!("\n\nSuccess! Contents: {contents}\n\n");
}
Err(err) => {
println!("\n\nError encountered, could not open locker: {err}\n\n");
}
}
}
_ => exit(0),
}
}
Err(err) => {
handle_error(err);
exit(0)
}
}
}
}
// Helper functions
fn display_lockers(lockers: &[Locker]) {
let mut locker_numbers = vec![];
for (i, _) in lockers.iter().enumerate() {
locker_numbers.push(i);
}
println!("\nCurrently registered locker numbers: {locker_numbers:?}\n");
}
// Handle readline errors
fn handle_error(err: ReadlineError) {
match err {
ReadlineError::Interrupted => {
println!("CTRL-C");
}
ReadlineError::Eof => {
println!("CTRL-D");
}
err => {
println!("Error: {err:?}");
}
}
}
// A function run on the client which extracts two strings from the CLI
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<(), DefaultHistory>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{query}: "));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
None => get_two_strings(s1, s2, rl, Some(line)),
},
Err(err) => {
handle_error(err);
exit(0)
}
}
}