]> git.proxmox.com Git - proxmox.git/blob - proxmox-tfa/src/u2f.rs
95aa41a113d7cc1faf70ef7c9d9dda46b7b080af
[proxmox.git] / proxmox-tfa / src / u2f.rs
1 //! U2F implementation.
2
3 use std::mem::MaybeUninit;
4 use std::io;
5
6 use anyhow::{bail, format_err, Error};
7 use openssl::ec::{EcGroup, EcKey, EcPoint};
8 use openssl::ecdsa::EcdsaSig;
9 use openssl::pkey::Public;
10 use openssl::sha;
11 use openssl::x509::X509;
12 use serde::{Deserialize, Serialize};
13
14 const CHALLENGE_LEN: usize = 32;
15 const U2F_VERSION: &str = "U2F_V2";
16
17 /// The "key" part of a registration, passed to `u2f.sign` in the registered keys list.
18 ///
19 /// Part of the U2F API, therefore `camelCase` and base64url without padding.
20 #[derive(Clone, Debug, Deserialize, Serialize)]
21 #[serde(rename_all = "camelCase")]
22 pub struct RegisteredKey {
23 /// Identifies the key handle on the client side. Used to create authentication challenges, so
24 /// the client knows which key to use. Must be remembered.
25 #[serde(with = "bytes_as_base64url_nopad")]
26 pub key_handle: Vec<u8>,
27
28 pub version: String,
29 }
30
31 /// Data we get when a u2f token responds to a registration challenge.
32 #[derive(Clone, Debug, Deserialize, Serialize)]
33 #[serde(rename_all = "kebab-case")]
34 pub struct Registration {
35 /// The key consisting of key handle and version, which can be passed to the registered-keys
36 /// list in `u2f.sign` in the browser.
37 pub key: RegisteredKey,
38
39 /// Public part of the client key identified via the `key_handle`. Required to verify future
40 /// authentication responses. Must be remembered.
41 #[serde(with = "bytes_as_base64")]
42 pub public_key: Vec<u8>,
43
44 /// Attestation certificate (in DER format) from which we originally copied the `key_handle`.
45 /// Not necessary for authentication, unless the hardware tokens should be restricted to
46 /// specific provider identities. Optional.
47 #[serde(
48 with = "bytes_as_base64",
49 default,
50 skip_serializing_if = "Vec::is_empty"
51 )]
52 pub certificate: Vec<u8>,
53 }
54
55 /// Result from a successful authentication. The client's hardware token will inform us about the
56 /// user-presence (it may have been configured to respond automatically instead of requiring user
57 /// interaction), and the number of authentications the key has performed.
58 /// We probably won't make much use of this.
59 #[derive(Clone, Debug)]
60 pub struct Authentication {
61 /// `true` if the user had to be present.
62 pub user_present: bool,
63
64 /// authentication count
65 pub counter: usize,
66 }
67
68 /// The hardware replies with a client data json object containing some information - this is the
69 /// subset we actually make use of.
70 #[derive(Deserialize)]
71 #[serde(rename_all = "camelCase")]
72 struct ClientData {
73 /// The challenge the device responded to. This should be compared against the server side
74 /// cached challenge!
75 challenge: String,
76
77 /// The origin the the browser told the device the challenge was coming from.
78 origin: String,
79 }
80
81 /// A registration challenge to be sent to the `u2f.register` function in the browser.
82 ///
83 /// Part of the U2F API, therefore `camelCase`.
84 #[derive(Clone, Debug, Deserialize, Serialize)]
85 #[serde(rename_all = "camelCase")]
86 pub struct RegistrationChallenge {
87 pub challenge: String,
88 pub version: String,
89 pub app_id: String,
90 }
91
92 /// The response we get from a successful call to the `u2f.register` function in the browser.
93 ///
94 /// Part of the U2F API, therefore `camelCase`.
95 #[derive(Clone, Debug, Deserialize)]
96 #[serde(rename_all = "camelCase")]
97 pub struct RegistrationResponse {
98 registration_data: String,
99 client_data: String,
100 version: String,
101 }
102
103 /// Authentication challenge data to be sent to the `u2f.sign` function in the browser. Does not
104 /// include the registered keys.
105 ///
106 /// Part of the U2F API, therefore `camelCase`.
107 #[derive(Clone, Debug, Deserialize, Serialize)]
108 #[serde(rename_all = "camelCase")]
109 pub struct AuthChallenge {
110 pub challenge: String,
111 pub app_id: String,
112 }
113
114 /// The response we get from a successful call to the `u2f.sign` function in the browser.
115 ///
116 /// Part of the U2F API, therefore `camelCase` and base64url without padding.
117 #[derive(Deserialize)]
118 #[serde(rename_all = "camelCase")]
119 pub struct AuthResponse {
120 #[serde(with = "bytes_as_base64url_nopad")]
121 pub key_handle: Vec<u8>,
122 pub client_data: String,
123 pub signature_data: String,
124 }
125
126 impl AuthResponse {
127 pub fn key_handle(&self) -> &[u8] {
128 &self.key_handle
129 }
130 }
131
132 /// A u2f context to create or verify challenges with.
133 #[derive(Deserialize, Serialize)]
134 pub struct U2f {
135 app_id: String,
136 origin: String,
137 }
138
139 impl U2f {
140 /// Create a new U2F context consisting of an appid and origin.
141 pub fn new(app_id: String, origin: String) -> Self {
142 Self { app_id, origin }
143 }
144
145 /// Get a challenge object which can be directly passed to `u2f.register` on the browser side.
146 pub fn registration_challenge(&self) -> Result<RegistrationChallenge, Error> {
147 Ok(RegistrationChallenge {
148 challenge: challenge()?,
149 version: U2F_VERSION.to_owned(),
150 app_id: self.app_id.clone(),
151 })
152 }
153
154 /// Convenience method to verify the json formatted response object string.
155 pub fn registration_verify(
156 &self,
157 challenge: &str,
158 response: &str,
159 ) -> Result<Option<Registration>, Error> {
160 let response: RegistrationResponse = serde_json::from_str(response)
161 .map_err(|err| format_err!("error parsing response: {}", err))?;
162 self.registration_verify_obj(challenge, response)
163 }
164
165 /// Verifies the registration response object.
166 pub fn registration_verify_obj(
167 &self,
168 challenge: &str,
169 response: RegistrationResponse,
170 ) -> Result<Option<Registration>, Error> {
171 let client_data_decoded = decode(&response.client_data)
172 .map_err(|err| format_err!("error decoding client data in response: {}", err))?;
173
174 let client_data: ClientData = serde_json::from_reader(&mut &client_data_decoded[..])
175 .map_err(|err| format_err!("error parsing client data: {}", err))?;
176
177 if client_data.challenge != challenge {
178 bail!("registration challenge did not match");
179 }
180
181 if client_data.origin != self.origin {
182 bail!(
183 "origin in client registration did not match: {:?} != {:?}",
184 client_data.origin,
185 self.origin,
186 );
187 }
188
189 let registration_data = decode(&response.registration_data)
190 .map_err(|err| format_err!("error decoding registration data in response: {}", err))?;
191
192 let registration_data = RegistrationResponseData::from_raw(&registration_data)?;
193
194 let mut digest = sha::Sha256::new();
195 digest.update(&[0u8]);
196 digest.update(&sha::sha256(self.app_id.as_bytes()));
197 digest.update(&sha::sha256(&client_data_decoded));
198 digest.update(registration_data.key_handle);
199 digest.update(registration_data.public_key);
200 let digest = digest.finish();
201
202 let signature = EcdsaSig::from_der(registration_data.signature)
203 .map_err(|err| format_err!("error decoding signature in response: {}", err))?;
204
205 // can we decode the public key?
206 drop(decode_public_key(registration_data.public_key)?);
207
208 match signature.verify(&digest, &registration_data.cert_key) {
209 Ok(true) => Ok(Some(Registration {
210 key: RegisteredKey {
211 key_handle: registration_data.key_handle.to_vec(),
212 version: response.version,
213 },
214 public_key: registration_data.public_key.to_vec(),
215 certificate: registration_data.certificate.to_vec(),
216 })),
217 Ok(false) => Ok(None),
218 Err(err) => bail!("openssl error while verifying signature: {}", err),
219 }
220 }
221
222 /// Get a challenge object which can be directly passwd to `u2f.sign` on the browser side.
223 pub fn auth_challenge(&self) -> Result<AuthChallenge, Error> {
224 Ok(AuthChallenge {
225 challenge: challenge()?,
226 app_id: self.app_id.clone(),
227 })
228 }
229
230 /// Convenience method to verify the json formatted response object string.
231 pub fn auth_verify(
232 &self,
233 public_key: &[u8],
234 challenge: &str,
235 response: &str,
236 ) -> Result<Option<Authentication>, Error> {
237 let response: AuthResponse = serde_json::from_str(response)
238 .map_err(|err| format_err!("error parsing response: {}", err))?;
239 self.auth_verify_obj(public_key, challenge, response)
240 }
241
242 /// Verifies the authentication response object.
243 pub fn auth_verify_obj(
244 &self,
245 public_key: &[u8],
246 challenge: &str,
247 response: AuthResponse,
248 ) -> Result<Option<Authentication>, Error> {
249 let client_data_decoded = decode(&response.client_data)
250 .map_err(|err| format_err!("error decoding client data in response: {}", err))?;
251
252 let client_data: ClientData = serde_json::from_reader(&mut &client_data_decoded[..])
253 .map_err(|err| format_err!("error parsing client data: {}", err))?;
254
255 if client_data.challenge != challenge {
256 bail!("authentication challenge did not match");
257 }
258
259 if client_data.origin != self.origin {
260 bail!(
261 "origin in client authentication did not match: {:?} != {:?}",
262 client_data.origin,
263 self.origin,
264 );
265 }
266
267 let signature_data = decode(&response.signature_data)
268 .map_err(|err| format_err!("error decoding signature data in response: {}", err))?;
269
270 // an ecdsa signature is much longer than 16 bytes but we only need to parse the first 5
271 // anyway...
272 if signature_data.len() < 1 + 4 + 16 {
273 bail!("invalid signature data");
274 }
275
276 let presence_and_counter_bytes = &signature_data[0..5];
277 let user_present = presence_and_counter_bytes[0] != 0;
278 let counter_bytes = &presence_and_counter_bytes[1..];
279 let counter: u32 =
280 u32::from_be(unsafe { std::ptr::read_unaligned(counter_bytes.as_ptr() as *const u32) });
281 let signature = EcdsaSig::from_der(&signature_data[5..])
282 .map_err(|err| format_err!("error decoding signature in response: {}", err))?;
283
284 let public_key = decode_public_key(public_key)?;
285
286 let mut digest = sha::Sha256::new();
287 digest.update(&sha::sha256(self.app_id.as_bytes()));
288 digest.update(presence_and_counter_bytes);
289 digest.update(&sha::sha256(&client_data_decoded));
290 let digest = digest.finish();
291
292 match signature.verify(&digest, &public_key) {
293 Ok(true) => Ok(Some(Authentication {
294 user_present,
295 counter: counter as usize,
296 })),
297 Ok(false) => Ok(None),
298 Err(err) => bail!("openssl error while verifying signature: {}", err),
299 }
300 }
301 }
302
303 /// base64url encoding
304 fn encode(data: &[u8]) -> String {
305 let mut out = base64::encode_config(data, base64::URL_SAFE_NO_PAD);
306 while out.ends_with('=') {
307 out.pop();
308 }
309 out
310 }
311
312 /// base64url decoding
313 fn decode(data: &str) -> Result<Vec<u8>, Error> {
314 Ok(base64::decode_config(data, base64::URL_SAFE_NO_PAD)?)
315 }
316
317 /// produce a challenge, which is just a bunch of random data
318 fn challenge() -> Result<String, Error> {
319 let mut data = MaybeUninit::<[u8; CHALLENGE_LEN]>::uninit();
320 let data = unsafe {
321 let buf: &mut [u8; CHALLENGE_LEN] = &mut *data.as_mut_ptr();
322 let rc = libc::getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0);
323 if rc == -1 {
324 return Err(io::Error::last_os_error().into());
325 }
326 if rc as usize != buf.len() {
327 // `CHALLENGE_LEN` is small, so short reads cannot happen (see `getrandom(2)`)
328 bail!("short getrandom call");
329 }
330 data.assume_init()
331 };
332 Ok(encode(&data))
333 }
334
335 /// Used while parsing the binary registration response. The slices point directly into the
336 /// original response byte data, and the public key is extracted from the contained X509
337 /// certificate.
338 #[derive(Debug)]
339 pub struct RegistrationResponseData<'a> {
340 public_key: &'a [u8],
341 key_handle: &'a [u8],
342 certificate: &'a [u8],
343 signature: &'a [u8],
344
345 /// The client's public key in decoded and parsed form.
346 cert_key: EcKey<Public>,
347 }
348
349 impl<'a> RegistrationResponseData<'a> {
350 /// Parse the binary registration data into its parts and extract the certificate's public key.
351 ///
352 /// See https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html
353 pub fn from_raw(data: &'a [u8]) -> Result<Self, Error> {
354 // [ 0x05 | 65b pubkey | 1b keyhandle len | keyhandle | certificate (1 DER obj) | signature ]
355
356 if data.len() <= (1 + 65 + 1 + 71) {
357 bail!("registration data too short");
358 }
359
360 if data[0] != 0x05 {
361 bail!(
362 "invalid registration data, reserved byte is 0x{:02x}, expected 0x05",
363 data[0]
364 );
365 }
366
367 let public_key = &data[1..66];
368
369 let key_handle_len = usize::from(data[66]);
370 let data = &data[67..];
371
372 if data.len() <= key_handle_len + 71 {
373 bail!("registration data invalid too short");
374 }
375
376 let key_handle = &data[..key_handle_len];
377 let data = &data[key_handle_len..];
378 if data[0] != 0x30 {
379 bail!("error decoding X509 certificate: not a SEQUENCE tag");
380 }
381 let cert_len = der_length(&data[1..])? + 1; // plus the tag!
382 let certificate = &data[..cert_len];
383 let x509 = X509::from_der(certificate)
384 .map_err(|err| format_err!("error decoding X509 certificate: {}", err))?;
385 let signature = &data[cert_len..];
386
387 Ok(Self {
388 public_key,
389 key_handle,
390 certificate,
391 signature,
392 cert_key: x509.public_key()?.ec_key()?,
393 })
394 }
395 }
396
397 /// Decode the raw 65 byte ec public key into an `openssl::EcKey<Public>`.
398 fn decode_public_key(data: &[u8]) -> Result<EcKey<Public>, Error> {
399 if data.len() != 65 {
400 bail!("invalid public key length {}, expected 65", data.len());
401 }
402
403 let group = EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1)
404 .map_err(|err| format_err!("openssl error, failed to instantiate ec curve: {}", err))?;
405
406 let mut bn = openssl::bn::BigNumContext::new().map_err(|err| {
407 format_err!(
408 "openssl error, failed to instantiate bignum context: {}",
409 err
410 )
411 })?;
412
413 let point = EcPoint::from_bytes(&group, data, &mut bn)
414 .map_err(|err| format_err!("failed to decode public key point: {}", err))?;
415
416 let key = EcKey::from_public_key(&group, &point)
417 .map_err(|err| format_err!("failed to instantiate public key: {}", err))?;
418
419 key.check_key()
420 .map_err(|err| format_err!("public key failed self check: {}", err))?;
421
422 Ok(key)
423 }
424
425 /// The only DER thing we need: lengths.
426 ///
427 /// Returns the length *including* the size of the length itself.
428 fn der_length(data: &[u8]) -> Result<usize, Error> {
429 if data[0] == 0 {
430 bail!("error decoding X509 certificate: bad length (0)");
431 }
432
433 if data[0] < 0x80 {
434 return Ok(usize::from(data[0]) + 1);
435 }
436
437 let count = usize::from(data[0] & 0x7F);
438 if count == 0x7F {
439 // X.609; 8.1.3.5, the value `1111111` shall not be used
440 bail!("error decoding X509 certificate: illegal length value");
441 }
442
443 if count == 0 {
444 // "indefinite" form not allowed in DER
445 bail!("error decoding X509 certificate: illegal length form");
446 }
447
448 if count > std::mem::size_of::<usize>() {
449 bail!("error decoding X509 certificate: unsupported length");
450 }
451
452 if count > (data.len() - 1) {
453 bail!("error decoding X509 certificate: truncated length data");
454 }
455
456 let mut len = 0;
457 for i in 0..count {
458 len = (len << 8) | usize::from(data[1 + i]);
459 }
460
461 Ok(len + count + 1)
462 }
463
464 #[cfg(test)]
465 mod test {
466 // The test data in here is generated with a yubi key...
467
468 use serde::Deserialize;
469
470 const TEST_APPID: &str = "https://u2ftest.enonet.errno.eu";
471
472 const TEST_REGISTRATION_JSON: &str =
473 "{\"challenge\":\"mZoWLngnAh8p98nPkFOIBXecd0CbmgEx5tEd5jNswgY\",\"response\":{\"client\
474 Data\":\"eyJjaGFsbGVuZ2UiOiJtWm9XTG5nbkFoOHA5OG5Qa0ZPSUJYZWNkMENibWdFeDV0RWQ1ak5zd2dZI\
475 iwib3JpZ2luIjoiaHR0cHM6Ly91MmZ0ZXN0LmVub25ldC5lcnJuby5ldSIsInR5cCI6Im5hdmlnYXRvci5pZC5\
476 maW5pc2hFbnJvbGxtZW50In0\",\"registrationData\":\"BQR_9TmMowVeoAHp3ABljCa90eNG87t76D4W\
477 c9nsmK9ihNhhYNxYIq9tnRUPTBZ2X4kZKSB0LXMm32lOKQlNB56QQHlt81cRBfID7BvHk_XIJZc5ks5D3R1ZV1\
478 1fJudp3F-ii_KSdZaFb4cGaq0rEaVDfNR2ZR0T0ApMMCeTIaDAJRQwggJEMIIBLqADAgECAgRVYr6gMAsGCSqG\
479 SIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MD\
480 EwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTQzMjUz\
481 NDY4ODBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEszH3c9gUS5mVy-RYVRfhdYOqR2I2lcvoWsSCyAGfLJuU\
482 Z64EWw5m8TGy6jJDyR_aYC4xjz_F2NKnq65yvRQwmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0\
483 ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMAsGCSqGSIb3DQEBCwOCAQEArBbZs262s6m3bXWUs09Z9Pc-28\
484 n96yk162tFHKv0HSXT5xYU10cmBMpypXjjI-23YARoXwXn0bm-BdtulED6xc_JMqbK-uhSmXcu2wJ4ICA81BQd\
485 PutvaizpnjlXgDJjq6uNbsSAp98IStLLp7fW13yUw-vAsWb5YFfK9f46Yx6iakM3YqNvvs9M9EUJYl_VrxBJqn\
486 yLx2iaZlnpr13o8NcsKIJRdMUOBqt_ageQg3ttsyq_3LyoNcu7CQ7x8NmeCGm_6eVnZMQjDmwFdymwEN4OxfnM\
487 5MkcKCYhjqgIGruWkVHsFnJa8qjZXneVvKoiepuUQyDEJ2GcqvhU2YKY1zBFAiEA2mcfAS2XRcWy1lLJikFHGJ\
488 SbtOrrwswjOKEzwp6EonkCIFBxbLAmwUnblAWOVELASi610ZfPK-7qx2VwkWfHqnll\",\"version\":\"U2F\
489 _V2\"}}";
490
491 const TEST_AUTH_JSON: &str =
492 "{\"challenge\":\"8LE_-7Rd1vB3Otn3vJ7GyiwRQtYPMv-BWliCejH0d4Y\",\"response\":{\"clientD\
493 ata\":\"eyJjaGFsbGVuZ2UiOiI4TEVfLTdSZDF2QjNPdG4zdko3R3lpd1JRdFlQTXYtQldsaUNlakgwZDRZIiw\
494 ib3JpZ2luIjoiaHR0cHM6Ly91MmZ0ZXN0LmVub25ldC5lcnJuby5ldSIsInR5cCI6Im5hdmlnYXRvci5pZC5nZX\
495 RBc3NlcnRpb24ifQ\",\"keyHandle\":\"eW3zVxEF8gPsG8eT9cgllzmSzkPdHVlXXV8m52ncX6KL8pJ1loVv\
496 hwZqrSsRpUN81HZlHRPQCkwwJ5MhoMAlFA\",\"signatureData\":\"AQAAAQEwRAIgKdM9cmCLZDxntY-dT_\
497 OXbcVA1D5ewQunXVC-CYZ65pUCIAIOUBsu-dOmTym0ITZt6x75BFUSGlqYRuH5JKBcyO3M\"},\"user\":{\"c\
498 ertificate\":\"MIICRDCCAS6gAwIBAgIEVWK+oDALBgkqhkiG9w0BAQswLjEsMCoGA1UEAxMjWXViaWNvIFUy\
499 RiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMCoxKDA\
500 mBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE0MzI1MzQ2ODgwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAA\
501 RLMx93PYFEuZlcvkWFUX4XWDqkdiNpXL6FrEgsgBnyyblGeuBFsOZvExsuoyQ8kf2mAuMY8/xdjSp6uucr0UMJo\
502 zswOTAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuNTATBgsrBgEEAYLlHAIBAQQEAwIFIDALBgkq\
503 hkiG9w0BAQsDggEBAKwW2bNutrOpt211lLNPWfT3PtvJ/espNetrRRyr9B0l0+cWFNdHJgTKcqV44yPtt2AEaF8\
504 F59G5vgXbbpRA+sXPyTKmyvroUpl3LtsCeCAgPNQUHT7rb2os6Z45V4AyY6urjW7EgKffCErSy6e31td8lMPrwL\
505 Fm+WBXyvX+OmMeompDN2Kjb77PTPRFCWJf1a8QSap8i8dommZZ6a9d6PDXLCiCUXTFDgarf2oHkIN7bbMqv9y8q\
506 DXLuwkO8fDZnghpv+nlZ2TEIw5sBXcpsBDeDsX5zOTJHCgmIY6oCBq7lpFR7BZyWvKo2V53lbyqInqblEMgxCdh\
507 nKr4VNmCmNc=\",\"key\":{\"keyHandle\":\"eW3zVxEF8gPsG8eT9cgllzmSzkPdHVlXXV8m52ncX6KL8pJ\
508 1loVvhwZqrSsRpUN81HZlHRPQCkwwJ5MhoMAlFA\",\"version\":\"U2F_V2\"},\"public-key\":\"BH/1\
509 OYyjBV6gAencAGWMJr3R40bzu3voPhZz2eyYr2KE2GFg3Fgir22dFQ9MFnZfiRkpIHQtcybfaU4pCU0HnpA=\"}\
510 }";
511
512 #[test]
513 fn test_registration() {
514 let data = TEST_REGISTRATION_JSON;
515
516 #[derive(Deserialize)]
517 struct TestChallenge {
518 challenge: String,
519 response: super::RegistrationResponse,
520 }
521
522 let ts: TestChallenge =
523 serde_json::from_str(&data).expect("failed to parse json test data");
524
525 let context = super::U2f::new(TEST_APPID.to_string(), TEST_APPID.to_string());
526 let res = context
527 .registration_verify_obj(&ts.challenge, ts.response)
528 .expect("error trying to verify registration");
529 assert!(
530 res.is_some(),
531 "test registration signature fails verification"
532 );
533 }
534
535 #[test]
536 fn test_authentication() {
537 let data = TEST_AUTH_JSON;
538
539 #[derive(Deserialize)]
540 struct TestChallenge {
541 challenge: String,
542 user: super::Registration,
543 response: super::AuthResponse,
544 }
545
546 let ts: TestChallenge =
547 serde_json::from_str(&data).expect("failed to parse json test data");
548
549 let context = super::U2f::new(TEST_APPID.to_string(), TEST_APPID.to_string());
550 let res = context
551 .auth_verify_obj(&ts.user.public_key, &ts.challenge, ts.response)
552 .expect("error trying to verify authentication");
553 assert!(
554 res.is_some(),
555 "test authentication signature fails verification"
556 );
557 }
558 }
559
560 mod bytes_as_base64 {
561 use serde::{Deserialize, Deserializer, Serializer};
562
563 pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
564 serializer.serialize_str(&base64::encode(&data))
565 }
566
567 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
568 use serde::de::Error;
569 String::deserialize(deserializer).and_then(|string| {
570 base64::decode(&string).map_err(|err| Error::custom(err.to_string()))
571 })
572 }
573 }
574
575 mod bytes_as_base64url_nopad {
576 use serde::{Deserialize, Deserializer, Serializer};
577
578 pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
579 serializer.serialize_str(&base64::encode_config(
580 data.as_ref(),
581 base64::URL_SAFE_NO_PAD,
582 ))
583 }
584
585 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
586 use serde::de::Error;
587 String::deserialize(deserializer).and_then(|string| {
588 base64::decode_config(&string, base64::URL_SAFE_NO_PAD)
589 .map_err(|err| Error::custom(err.to_string()))
590 })
591 }
592 }