rust_rcs_core/security/authentication/
digest.rs1extern crate data_encoding;
16extern crate md5;
17extern crate ring;
18
19use data_encoding::HEXLOWER;
20
21use md5::{Digest, Md5};
22
23use ring::digest;
24use ring::digest::SHA256;
25
26use crate::ffi::log::platform_log;
27use crate::internet::syntax;
28
29use super::challenge::Challenge;
30
31const LOG_TAG: &str = "digest";
32
33pub struct DigestChallengeParams<'a> {
34 pub realm: &'a [u8],
35 pub nonce: &'a [u8],
36 pub algorithm: &'a [u8],
37
38 pub domain: Option<&'a [u8]>,
39 pub opaque: Option<&'a [u8]>,
40 pub qop: Option<&'a [u8]>,
41}
42
43impl<'a> DigestChallengeParams<'a> {
44 pub fn from_challenge(challenge: Challenge<'a>) -> Option<DigestChallengeParams<'a>> {
45 let mut realm: Option<&'a [u8]> = None;
46 let mut nonce: Option<&'a [u8]> = None;
47 let mut algorithm: Option<&'a [u8]> = None;
48
49 let mut domain: Option<&'a [u8]> = None;
50 let mut opaque: Option<&'a [u8]> = None;
51 let mut qop: Option<&'a [u8]> = None; for param in challenge.get_params() {
54 match param.name {
55 b"realm" => {
56 realm = match param.value {
57 Some(v) => Some(syntax::unquote(v)),
58 None => None,
59 };
60 }
61
62 b"nonce" => {
63 nonce = match param.value {
64 Some(v) => Some(syntax::unquote(v)),
65 None => None,
66 };
67 }
68
69 b"algorithm" => {
70 algorithm = param.value;
71 }
72
73 b"domain" => {
74 domain = match param.value {
75 Some(v) => Some(syntax::unquote(v)),
76 None => None,
77 };
78 }
79
80 b"opaque" => {
81 opaque = match param.value {
82 Some(v) => Some(syntax::unquote(v)),
83 None => None,
84 };
85 }
86
87 b"qop" => {
88 qop = match param.value {
89 Some(v) => Some(syntax::unquote(v)),
90 None => None,
91 };
92 }
93
94 _ => {}
95 }
96 }
97
98 if let (Some(realm), Some(nonce)) = (realm, nonce) {
99 if let Some(algorithm) = algorithm {
100 Some(DigestChallengeParams {
101 realm,
102 nonce,
103 algorithm,
104
105 domain,
106 opaque,
107 qop,
108 })
109 } else {
110 Some(DigestChallengeParams {
111 realm,
112 nonce,
113 algorithm: b"MD5",
114
115 domain,
116 opaque,
117 qop,
118 })
119 }
120 } else {
121 None
122 }
123 }
124
125 pub fn preferred_qop(&self, have_entity: bool) -> Option<&'a [u8]> {
126 if let Some(qop) = self.qop {
127 let mut auth: Option<&[u8]> = None;
128 let mut auth_int: Option<&[u8]> = None;
129 let mut iter = qop.split(|c| *c == b',');
130 while let Some(qop) = iter.next() {
131 if qop == b"auth" {
132 auth.replace(qop);
133 } else if qop == b"auth-int" {
134 auth_int.replace(qop);
135 }
136 }
137
138 if let Some(auth_int) = auth_int {
139 if have_entity {
140 return Some(auth_int);
141 }
142 }
143
144 if let Some(auth) = auth {
145 return Some(auth);
146 }
147
148 if let Some(auth_int) = auth_int {
149 return Some(auth_int);
150 }
151 }
152
153 None
154 }
155
156 fn create_response(
157 &self,
158 username: &[u8],
159 uri: &[u8],
160 credentials: &DigestCredentials,
161 hash_algorithm: &[u8],
162 ) -> Result<(String, Option<&[u8]>), ErrorKind> {
163 let mut hash_function = HashFunction::new(hash_algorithm)?;
164
165 hash_function.update(username);
166 hash_function.update(b":");
167 hash_function.update(self.realm);
168 hash_function.update(b":");
169 hash_function.update(&credentials.password);
170
171 let a1 = hash_function.finish();
172
173 platform_log(LOG_TAG, format!("on intermediate result a1: {}", &a1));
174
175 let mut hash_function = HashFunction::new(hash_algorithm)?;
176
177 if let Some(method) = &credentials.client_data {
178 hash_function.update(method);
179 }
180
181 hash_function.update(b":");
182 hash_function.update(uri);
183
184 let qop;
185
186 if let Some(entity_digest) = &credentials.entity_digest {
187 qop = self.preferred_qop(true);
188 if let Some(b"auth-int") = qop {
189 hash_function.update(b":");
190 hash_function.update(entity_digest);
191 }
192 } else {
193 qop = self.preferred_qop(false);
194 if let Some(b"auth-int") = qop {
195 hash_function.update(b":");
196 let empty = empty_digest(hash_algorithm)?;
197 hash_function.update(empty.as_bytes());
198 }
199 }
200
201 let a2 = hash_function.finish();
202
203 platform_log(LOG_TAG, format!("on intermediate result a2: {}", &a2));
204
205 let mut hash_function = HashFunction::new(hash_algorithm)?;
206
207 hash_function.update(a1.as_bytes());
208 hash_function.update(b":");
209 hash_function.update(self.nonce);
210 hash_function.update(b":");
211
212 match qop {
213 Some(qop) => {
214 if qop == b"auth" || qop == b"auth-int" {
215 if let Some((cnonce, nc)) = &credentials.client_nonce {
216 hash_function.update(format!("{:08x}", nc).as_bytes());
217 hash_function.update(b":");
218 hash_function.update(cnonce);
219 hash_function.update(b":");
220 hash_function.update(qop);
221 hash_function.update(b":");
222 } else {
223 return Err(ErrorKind::MissingInput);
224 }
225 }
226 }
227
228 _ => {}
229 }
230
231 hash_function.update(a2.as_bytes());
232
233 Ok((hash_function.finish(), qop))
234 }
235
236 pub fn to_challenge(&self) -> DigestChallenge {
237 DigestChallenge {
238 realm: self.realm.to_vec(),
239 nonce: self.nonce.to_vec(),
240 algorithm: self.algorithm.to_vec(),
241
242 domain: match self.domain {
243 Some(domain) => Some(domain.to_vec()),
244 None => None,
245 },
246
247 opaque: match self.opaque {
248 Some(opaque) => Some(opaque.to_vec()),
249 None => None,
250 },
251
252 qop: match self.qop {
253 Some(qop) => Some(qop.to_vec()),
254 None => None,
255 },
256 }
257 }
258}
259
260pub struct DigestChallenge {
261 pub realm: Vec<u8>,
262 pub nonce: Vec<u8>,
263 pub algorithm: Vec<u8>,
264
265 pub domain: Option<Vec<u8>>,
266 pub opaque: Option<Vec<u8>>,
267 pub qop: Option<Vec<u8>>,
268}
269
270impl DigestChallenge {
271 pub fn as_challenge_param(&self) -> DigestChallengeParams {
272 DigestChallengeParams {
273 realm: &self.realm,
274 nonce: &self.nonce,
275 algorithm: &self.algorithm,
276
277 domain: match &self.domain {
278 Some(domain) => Some(&domain),
279 None => None,
280 },
281
282 opaque: match &self.opaque {
283 Some(opaque) => Some(&opaque),
284 None => None,
285 },
286
287 qop: match &self.qop {
288 Some(qop) => Some(&qop),
289 None => None,
290 },
291 }
292 }
293}
294
295pub struct DigestCredentials {
296 pub password: Vec<u8>,
297
298 pub client_data: Option<Vec<u8>>,
299 pub client_nonce: Option<(Vec<u8>, u32)>,
300 pub entity_digest: Option<Vec<u8>>,
301
302 pub extra_params: Vec<(Vec<u8>, Vec<u8>)>,
303}
304
305pub struct DigestAnswerParams {
307 pub realm: Vec<u8>,
308 pub algorithm: Option<Vec<u8>>,
309
310 pub username: Vec<u8>,
311 pub uri: Vec<u8>,
312
313 pub challenge: Option<DigestChallenge>,
314
315 pub credentials: Option<DigestCredentials>,
316}
317
318impl DigestAnswerParams {
319 pub fn make_authorization_header(
320 &self,
321 hash_algorithm: Option<&[u8]>,
322 with_empty_nonce: bool,
323 with_empty_response: bool,
324 ) -> Result<Vec<u8>, ErrorKind> {
325 platform_log(LOG_TAG, format!("make authorization header with algorithm={} username={} password={} method={} realm={} uri={} qop={} nonce={} cnonce={} nc={}", if let Some(hash_algorithm) = &hash_algorithm{
326 String::from_utf8_lossy(hash_algorithm)
327 } else {
328 std::borrow::Cow::Borrowed("")
329 }, String::from_utf8_lossy(&self.username),
330 if let Some(credentials) = &self.credentials {
331 HEXLOWER.encode(&credentials.password)
332 } else {
333 String::from("")
334 },
335 if let Some(credentials) = &self.credentials {
336 if let Some(client_data) = &credentials.client_data {
337 String::from_utf8_lossy(client_data)
338 } else {
339 std::borrow::Cow::Borrowed("")
340 }
341 } else {
342 std::borrow::Cow::Borrowed("")
343 },
344 if let Some(challenge) = &self.challenge {
345 String::from_utf8_lossy(&challenge.realm)
346 } else {
347 String::from_utf8_lossy(&self.realm)
348 },
349 String::from_utf8_lossy(&self.uri),
350 if let Some(challenge) = &self.challenge {
351 let mut have_entity = false;
352 if let Some(credentials) = &self.credentials {
353 if let Some(_) = credentials.entity_digest {
354 have_entity = true;
355 }
356 }
357 if let Some(qop) = challenge.as_challenge_param().preferred_qop(have_entity) {
358 String::from_utf8_lossy(qop)
359 } else {
360 std::borrow::Cow::Borrowed("")
361 }
362 } else {
363 std::borrow::Cow::Borrowed("")
364 },
365 if let Some(challenge) = &self.challenge {
366 String::from_utf8_lossy(&challenge.nonce)
367 } else {
368 std::borrow::Cow::Borrowed("")
369 },
370 if let Some(credentials) = &self.credentials {
371 if let Some((client_nonce, _)) = &credentials.client_nonce {
372 String::from_utf8_lossy(client_nonce)
373 } else {
374 std::borrow::Cow::Borrowed("")
375 }
376 } else {
377 std::borrow::Cow::Borrowed("")
378 },
379 if let Some(credentials) = &self.credentials {
380 if let Some((_, nc)) = &credentials.client_nonce {
381 format!("{}", nc)
382 } else {
383 String::from("")
384 }
385 } else {
386 String::from("")
387 },
388 ));
389
390 let mut v = Vec::new();
391
392 v.extend_from_slice(b"Digest ");
393 v.extend_from_slice(b"username=\"");
394 v.extend_from_slice(&self.username);
395 v.extend_from_slice(b"\"");
396 if let Some(challenge) = &self.challenge {
397 v.extend_from_slice(b",realm=\"");
398 v.extend_from_slice(&challenge.realm);
399 v.extend_from_slice(b"\"");
400 } else {
401 v.extend_from_slice(b",realm=\"");
402 v.extend_from_slice(&self.realm);
403 v.extend_from_slice(b"\"");
404 }
405 v.extend_from_slice(b",uri=\"");
406 v.extend_from_slice(&self.uri);
407 v.extend_from_slice(b"\"");
408
409 if let Some(algorithm) = &self.algorithm {
410 v.extend_from_slice(b",algorithm=");
411 v.extend_from_slice(&algorithm);
412 }
413
414 if let Some(challenge) = &self.challenge {
415 if let Some(domain) = &challenge.domain {
416 v.extend_from_slice(b",domain=\"");
417 v.extend_from_slice(&domain);
418 v.extend_from_slice(b"\"");
419 }
420
421 let mut have_entity = false;
422 if let Some(credentials) = &self.credentials {
423 if let Some(_) = credentials.entity_digest {
424 have_entity = true;
425 }
426 }
427
428 if let Some(qop) = challenge.as_challenge_param().preferred_qop(have_entity) {
429 v.extend_from_slice(b",qop=");
430 v.extend_from_slice(qop);
431 }
432
433 v.extend_from_slice(b",nonce=\"");
434 v.extend_from_slice(&challenge.nonce);
435 v.extend_from_slice(b"\"");
436 } else {
437 if with_empty_nonce {
438 v.extend_from_slice(b",nonce=\"");
439 v.extend_from_slice(b"");
440 v.extend_from_slice(b"\"");
441 }
442 }
443
444 let mut response_appended = false;
445
446 if let Some(credentials) = &self.credentials {
447 if let Some((cnonce, nc)) = &credentials.client_nonce {
448 v.extend_from_slice(b",cnonce=\"");
449 v.extend_from_slice(&cnonce);
450 v.extend_from_slice(b"\",nc=");
451 v.extend_from_slice(format!("{:08x}", nc).as_bytes());
452 }
453
454 if let (Some(challenge), Some(algorithm)) = (&self.challenge, hash_algorithm) {
455 if let Ok((response, qop)) = challenge.as_challenge_param().create_response(
456 &self.username,
457 &self.uri,
458 &credentials,
459 algorithm,
460 ) {
461 v.extend_from_slice(b",response=\"");
462 v.extend_from_slice(response.as_bytes());
463 v.extend_from_slice(b"\"");
464 response_appended = true;
465 }
466 }
467 }
468
469 if !response_appended && with_empty_response {
470 v.extend_from_slice(b",response=\"");
471 v.extend_from_slice(b"");
472 v.extend_from_slice(b"\"");
473 }
474
475 if let Some(credentials) = &self.credentials {
476 for (extra_param_name, extra_param_value) in &credentials.extra_params {
477 v.extend_from_slice(b",");
478 v.extend_from_slice(extra_param_name);
479 v.extend_from_slice(b"=\"");
480 v.extend_from_slice(extra_param_value);
481 v.extend_from_slice(b"\"");
482 }
483 }
484
485 if let Some(challenge) = &self.challenge {
486 if let Some(opaque) = &challenge.opaque {
487 v.extend_from_slice(b",opaque=\"");
488 v.extend_from_slice(&opaque);
489 v.extend_from_slice(b"\"");
490 }
491 }
492
493 Ok(v)
494 }
495}
496
497pub enum ErrorKind {
498 MissingInput,
499 NoSuchAlgorithm,
500}
501
502enum HashFunction {
503 MD5(Md5),
504 SHA256(digest::Context),
505}
506
507impl HashFunction {
508 fn new(algorithm: &[u8]) -> Result<HashFunction, ErrorKind> {
509 if algorithm.eq_ignore_ascii_case(b"md5") {
510 Ok(HashFunction::MD5(Md5::new()))
511 } else if algorithm.eq_ignore_ascii_case(b"sha256")
512 || algorithm.eq_ignore_ascii_case(b"sha-256")
513 {
514 Ok(HashFunction::SHA256(digest::Context::new(&SHA256)))
515 } else {
516 Err(ErrorKind::NoSuchAlgorithm)
517 }
518 }
519
520 fn update(&mut self, data: &[u8]) {
521 match self {
522 HashFunction::MD5(md5) => md5.update(data),
523
524 HashFunction::SHA256(context) => context.update(data),
525 }
526 }
527
528 fn finish(self) -> String {
529 match self {
530 HashFunction::MD5(md5) => HEXLOWER.encode(&md5.finalize()),
531
532 HashFunction::SHA256(context) => HEXLOWER.encode(context.finish().as_ref()),
533 }
534 }
535}
536
537pub fn empty_digest(algorithm: &[u8]) -> Result<String, ErrorKind> {
538 match algorithm {
539 b"md5" => {
540 let md5 = Md5::new();
541 Ok(HEXLOWER.encode(&md5.finalize()))
542 }
543
544 b"sha256" | b"sha-256" => {
545 let context = digest::Context::new(&SHA256);
546 Ok(HEXLOWER.encode(context.finish().as_ref()))
547 }
548
549 _ => Err(ErrorKind::NoSuchAlgorithm),
550 }
551}