1extern crate base64;
16extern crate chrono;
17extern crate futures;
18extern crate quick_xml;
19extern crate ring;
20extern crate tokio;
21extern crate url;
22
23use std::cell::UnsafeCell;
24use std::sync::atomic::{AtomicU32, Ordering};
25use std::sync::Arc;
26use std::time::SystemTime;
27
28use base64::{engine::general_purpose, Engine as _};
29use chrono::{DateTime, FixedOffset};
30
31use data_encoding::HEXUPPER;
32use futures::io::{AsyncBufRead, AsyncReadExt};
33
34use quick_xml::events::Event;
35use quick_xml::reader::Reader;
36use ring::hmac;
37
38use tokio::sync::Semaphore;
39use tokio::time::{self, Duration, Instant};
40
41use url::{Host, Url};
42
43use crate::ffi::log::platform_log;
44use crate::http::request::{Request, GET};
45use crate::http::response::Response;
46use crate::http::{HttpClient, HttpConnectionHandle};
47
48use crate::internet::header::{self, Header};
49
50use crate::internet::syntax;
51use crate::security::aka::{self, AkaChallenge, AkaResponse, AsAkaAlgorithm};
52use crate::security::authentication::{AuthenticationMethod, AuthenticationMethods};
53use crate::security::SecurityContext;
54
55use crate::util::rand;
56use crate::util::raw_string::FromRawStr;
57
58use super::authentication::digest::{DigestAnswerParams, DigestCredentials};
59
60const LOG_TAG: &str = "gba";
61
62pub struct GbaContext {
63 impi: String,
64 bsf_url: String,
65 bsf_realm: String,
66
67 subscription_id: i32,
68
69 bootstrapped_context: UnsafeCell<Option<Arc<BootstrappedContext>>>,
70 bootstrap_sem: Semaphore,
71}
72
73impl GbaContext {
74 pub fn new(
75 impi: String,
76 bsf_url: String,
77 bsf_realm: String,
78 subscription_id: i32,
79 ) -> GbaContext {
80 platform_log(
81 LOG_TAG,
82 format!(
83 "init GbaContext with url: {}, realm: {}",
84 &bsf_url, &bsf_realm
85 ),
86 );
87
88 GbaContext {
89 impi,
90 bsf_url,
91 bsf_realm,
92
93 subscription_id,
94
95 bootstrapped_context: None.into(),
96 bootstrap_sem: Semaphore::new(1),
97 }
98 }
99
100 pub fn try_get_bootstrapped_context(&self) -> Option<Arc<BootstrappedContext>> {
101 match self.bootstrap_sem.try_acquire() {
102 Ok(_permit) => {
103 let cell;
104 unsafe {
105 cell = &*self.bootstrapped_context.get();
106 }
107 if let Some(bootstrapped_context) = cell {
108 if bootstrapped_context.expires > Instant::now() {
109 return Some(Arc::clone(&bootstrapped_context));
110 }
111 }
112 None
113 }
114 Err(_) => None,
115 }
116 }
117
118 pub async fn get_bootstrapped_context(
119 &self,
120 http_client: &HttpClient,
121 ) -> Result<Arc<BootstrappedContext>, ErrorKind> {
122 platform_log(LOG_TAG, "get_bootstrapped_context");
123 match time::timeout_at(
124 Instant::now() + Duration::from_secs(60),
125 self.bootstrap(http_client),
126 )
127 .await
128 {
129 Ok(context) => context,
130
131 Err(_) => Err(ErrorKind::TimeOut),
132 }
133 }
134
135 async fn bootstrap(
136 &self,
137 http_client: &HttpClient,
138 ) -> Result<Arc<BootstrappedContext>, ErrorKind> {
139 platform_log(LOG_TAG, "bootstrap");
140 match self.bootstrap_sem.acquire().await {
141 Ok(_permit) => {
142 platform_log(LOG_TAG, "permit acquired");
143
144 let cell;
145 unsafe {
146 cell = &*self.bootstrapped_context.get();
147 }
148
149 if let Some(bootstrapped_context) = cell {
150 if bootstrapped_context.expires > Instant::now() {
151 return Ok(Arc::clone(&bootstrapped_context));
152 }
153 }
154
155 let bsf_url = format!("http://{}/", &self.bsf_url);
156
157 if let Ok(url) = Url::parse(&bsf_url) {
158 match http_client.connect(&url, false).await {
159 Ok(conn) => {
160 let mut digest_answer = DigestAnswerParams {
161 realm: self.bsf_realm.as_bytes().to_vec(),
162 algorithm: None,
163
164 username: self.impi.as_bytes().to_vec(),
165 uri: b"".to_vec(),
166
167 challenge: None,
168
169 credentials: None,
170 };
171
172 if let Ok(authorization) =
173 digest_answer.make_authorization_header(None, true, true)
174 {
175 let (resp, resp_stream) =
176 send_ub_message(&conn, &url, authorization).await?;
177
178 platform_log(LOG_TAG, format!("ub status {}", resp.status_code));
179
180 match decode_ub_resp(
181 &mut digest_answer,
182 resp,
183 resp_stream,
184 self.impi.as_bytes(),
185 b"\"/\"",
186 self.subscription_id,
187 )
188 .await
189 {
190 UbResult::Res(authorization, rand, mut ck, mut ik) => {
191 let (resp, resp_stream) =
192 send_ub_message(&conn, &url, authorization).await?;
193
194 platform_log(
195 LOG_TAG,
196 format!("ub status {}", resp.status_code),
197 );
198
199 match decode_ub_resp(
200 &mut digest_answer,
201 resp,
202 resp_stream,
203 self.impi.as_bytes(),
204 b"\"/\"",
205 self.subscription_id,
206 )
207 .await
208 {
209 UbResult::Bootstrapped(b_tid, expiry) => {
210 ck.append(&mut ik);
211
212 let bootstrapped_context = BootstrappedContext {
213 impi: self.impi.clone(),
214 rand,
215 ks: ck,
216 b_tid,
217 expires: expiry,
218 used_count: AtomicU32::new(0),
219 };
220
221 let bootstrapped_context =
222 Arc::new(bootstrapped_context);
223
224 let cell;
225 unsafe {
226 cell = &mut *self.bootstrapped_context.get();
227 }
228
229 cell.replace(Arc::clone(&bootstrapped_context));
230
231 return Ok(bootstrapped_context);
232 }
233
234 _ => {}
235 }
236 }
237
238 UbResult::Auts(authorization) => {
239 platform_log(LOG_TAG, "on AUTS response");
240
241 let (resp, resp_stream) =
242 send_ub_message(&conn, &url, authorization).await?;
243
244 platform_log(
245 LOG_TAG,
246 format!("ub status {}", resp.status_code),
247 );
248
249 match decode_ub_resp(
250 &mut digest_answer,
251 resp,
252 resp_stream,
253 self.impi.as_bytes(),
254 b"\"/\"",
255 self.subscription_id,
256 )
257 .await
258 {
259 UbResult::Res(authorization, rand, mut ck, mut ik) => {
260 let (resp, resp_stream) =
261 send_ub_message(&conn, &url, authorization)
262 .await?;
263
264 platform_log(
265 LOG_TAG,
266 format!("ub status {}", resp.status_code),
267 );
268
269 match decode_ub_resp(
270 &mut digest_answer,
271 resp,
272 resp_stream,
273 self.impi.as_bytes(),
274 b"\"/\"",
275 self.subscription_id,
276 )
277 .await
278 {
279 UbResult::Bootstrapped(b_tid, expiry) => {
280 ck.append(&mut ik);
281
282 let bootstrapped_context =
283 BootstrappedContext {
284 impi: self.impi.clone(),
285 rand,
286 ks: ck,
287 b_tid,
288 expires: expiry,
289 used_count: AtomicU32::new(0),
290 };
291
292 let bootstrapped_context =
293 Arc::new(bootstrapped_context);
294
295 let cell;
296 unsafe {
297 cell = &mut *self
298 .bootstrapped_context
299 .get();
300 }
301
302 cell.replace(Arc::clone(
303 &bootstrapped_context,
304 ));
305
306 return Ok(bootstrapped_context);
307 }
308
309 _ => {}
310 }
311 }
312
313 _ => {}
314 }
315 }
316
317 _ => {}
318 }
319 }
320
321 Err(ErrorKind::Authentication)
322 }
323
324 Err(e) => {
325 platform_log(LOG_TAG, format!("http error {:?}", e));
326
327 Err(ErrorKind::ConnectionClosed)
328 }
329 }
330 } else {
331 Err(ErrorKind::Http)
332 }
333 }
334
335 Err(_) => Err(ErrorKind::Closed),
336 }
337 }
338}
339
340unsafe impl Send for GbaContext {}
341unsafe impl Sync for GbaContext {}
342
343async fn send_ub_message(
344 connection: &HttpConnectionHandle,
345 url: &Url,
346 authorization: Vec<u8>,
347) -> Result<(Response, Option<Box<dyn AsyncBufRead + Send + Unpin>>), ErrorKind> {
348 let host = url.host_str().unwrap();
349
350 let host = match url.host() {
351 Some(Host::Domain(domain)) => String::from(domain),
352
353 Some(Host::Ipv4(ip)) => ip.to_string(),
354
355 Some(Host::Ipv6(ip)) => ip.to_string(),
356
357 None => String::from(host),
358 };
359
360 let mut req = Request::new_with_default_headers(GET, &host, "/", None);
361
362 req.headers
363 .push(Header::new(b"Authorization", authorization));
364
365 match connection.send(req, |_| {}).await {
366 Ok((resp, resp_stream)) => Ok((resp, resp_stream)),
367
368 Err(_) => Err(ErrorKind::Http),
369 }
370}
371
372enum UbResult {
373 Bootstrapped(String, Instant),
374 Res(Vec<u8>, [u8; 16], Vec<u8>, Vec<u8>),
375 Auts(Vec<u8>),
376 Error(ErrorKind),
377}
378
379async fn decode_ub_resp<'a>(
380 digest_answer: &'a mut DigestAnswerParams,
381 resp: Response,
382 resp_stream: Option<Box<dyn AsyncBufRead + Send + Unpin>>,
383 impi: &'a [u8],
384 uri: &'a [u8],
385 subscription_id: i32,
386) -> UbResult {
387 platform_log(LOG_TAG, "decode_ub_resp");
388 let status_code = resp.status_code;
389 if status_code == 200 {
390 if let Some(mut resp_stream) = resp_stream {
391 let mut resp_data = Vec::new();
392 if let Ok(_) = resp_stream.read_to_end(&mut resp_data).await {
393 if let Ok(resp_str) = std::str::from_utf8(&resp_data) {
394 platform_log(LOG_TAG, format!("ub resp.string = {:?}", resp_str,));
395
396 let mut xml_reader = Reader::from_str(resp_str);
397 let mut buf = Vec::new();
398 loop {
399 match xml_reader.read_event_into(&mut buf) {
400 Ok(Event::Start(ref e)) => {
401 if e.name().as_ref() == b"BootstrappingInfo" {
402 let mut b_tid: Option<String> = None;
403 let mut expiry: Option<DateTime<FixedOffset>> = None;
404 let mut level = 1;
405 loop {
406 match xml_reader.read_event_into(&mut buf) {
407 Ok(Event::Start(ref e)) => {
408 if e.name().as_ref() == b"btid" {
409 let mut level = 1;
410 loop {
411 match xml_reader.read_event_into(&mut buf) {
412 Ok(Event::Text(e)) => {
413 if level == 1 {
414 if let Ok(t) = e.unescape() {
415 b_tid.replace(
416 t.into_owned(),
417 );
418 }
419 }
420 }
421 Ok(Event::Start(_)) => level += 1,
422 Ok(Event::End(_)) => {
423 level -= 1;
424 if level == 0 {
425 break;
426 }
427 }
428 _ => {}
429 }
430 }
431 buf.clear();
432 } else if e.name().as_ref() == b"lifetime" {
433 let mut level = 1;
434 loop {
435 match xml_reader.read_event_into(&mut buf) {
436 Ok(Event::Text(e)) => {
437 if level == 1 {
438 if let Ok(t) = e.unescape() {
439 if let Ok(lifetime) =
440 DateTime::parse_from_rfc3339(
441 &t,
442 )
443 {
444 expiry.replace(lifetime);
445 }
446 }
447 }
448 }
449 Ok(Event::Start(_)) => level += 1,
450 Ok(Event::End(_)) => {
451 level -= 1;
452 if level == 0 {
453 break;
454 }
455 }
456 _ => {}
457 }
458 }
459 buf.clear();
460 }
461 }
462 Ok(Event::End(ref e)) => {
463 level -= 1;
464 if level == 0 {
465 break;
466 }
467 }
468 Ok(Event::Eof) => break,
469 _ => {}
470 }
471 }
472
473 buf.clear();
474
475 if let (Some(b_tid), Some(expiry)) = (b_tid, expiry) {
476 let expiry = SystemTime::UNIX_EPOCH
477 + std::time::Duration::from_secs(
478 expiry.timestamp() as u64
479 );
480 if let Ok(timeout) =
481 expiry.duration_since(SystemTime::now())
482 {
483 let expiry = std::time::Instant::now() + timeout;
484 let expiry = Instant::from_std(expiry);
485 return UbResult::Bootstrapped(b_tid, expiry);
486 }
487 }
488 }
489 }
490 Ok(Event::Eof) => break,
491 _ => {}
492 }
493 }
494 }
495 }
496 }
497 } else if status_code == 401 {
498 if let Some(www_authenticate_header) =
499 header::search(&resp.headers, b"WWW-Authenticate", false)
500 {
501 let www_authenticate_header_value = www_authenticate_header.get_value();
502 for method in AuthenticationMethods::new(www_authenticate_header_value) {
503 if let AuthenticationMethod::Digest(challenge_params) = method {
504 if let Ok(algorithm) = challenge_params.algorithm.as_aka_algorithm() {
505 if let Ok(aka_challenge) =
506 AkaChallenge::from_raw_str(challenge_params.nonce)
507 {
508 digest_answer.challenge = Some(challenge_params.to_challenge());
509
510 if digest_answer.uri.len() == 0 {
512 digest_answer.uri = b"/".to_vec();
513 }
514
515 if let Ok(response) =
516 aka::aka_do_challenge(&aka_challenge, subscription_id)
517 {
518 match response {
519 AkaResponse::Successful(res, Some((ck, ik))) => {
520 let cnonce = rand::create_raw_alpha_numeric_string(16);
521 digest_answer.credentials = Some(DigestCredentials {
522 password: res,
523 client_data: Some(b"GET".to_vec()),
524 client_nonce: Some((cnonce, 1)),
525 entity_digest: None,
526 extra_params: Vec::new(),
527 });
528 if let Ok(authorization) = digest_answer
529 .make_authorization_header(
530 Some(algorithm.algorithm),
531 false,
532 false,
533 )
534 {
535 return UbResult::Res(
536 authorization,
537 aka_challenge.rand,
538 ck,
539 ik,
540 );
541 }
542 }
543
544 AkaResponse::SyncFailure(auts) => {
545 let cnonce = rand::create_raw_alpha_numeric_string(16);
546 digest_answer.credentials = Some(DigestCredentials {
547 password: b"".to_vec(),
548 client_data: Some(b"GET".to_vec()),
549 client_nonce: Some((cnonce, 1)),
550 entity_digest: None,
551 extra_params: Vec::new(),
552 });
553 if let Ok(mut authorization) = digest_answer
554 .make_authorization_header(
555 Some(algorithm.algorithm),
556 false,
557 false,
558 )
559 {
560 authorization.extend_from_slice(b",auts=\"");
561 let encoded = general_purpose::STANDARD.encode(&auts);
562 let mut encoded = Vec::from(encoded);
563 authorization.append(&mut encoded);
564 authorization.extend_from_slice(b"\"");
565 return UbResult::Auts(authorization);
566 }
567 }
568
569 _ => {}
570 }
571 }
572 }
573 }
574 }
575 }
576
577 return UbResult::Error(ErrorKind::Authentication);
578 }
579 }
580
581 UbResult::Error(ErrorKind::Http)
582}
583
584pub struct BootstrappedContext {
585 pub impi: String,
586 pub rand: [u8; 16],
587 pub ks: Vec<u8>,
588 pub b_tid: String,
589 pub expires: Instant,
590 pub used_count: AtomicU32, }
592
593impl BootstrappedContext {
594 pub fn get_credential(
595 &self,
596 fqdn: &[u8],
597 cipher_id: Option<(u8, u8)>,
598 ) -> Result<GbaCredential, ErrorKind> {
599 let naf_id = gba_get_naf_id(fqdn, cipher_id);
600
601 let ks_naf = gba_me_ks_naf(&self.ks, &self.rand, self.impi.as_bytes(), &naf_id)?;
602 let ks_naf_hex = HEXUPPER.encode(&ks_naf);
603 platform_log(LOG_TAG, format!("ks_naf_hex: {}", &ks_naf_hex));
604
605 let encoded = general_purpose::STANDARD.encode(&ks_naf);
606 let encoded = Vec::from(encoded);
607
608 Ok(GbaCredential {
609 username: self.b_tid.as_bytes().to_vec(),
610 password: encoded,
611 })
612 }
613
614 pub fn increase_and_get_use_count(&self) -> u32 {
615 self.used_count.fetch_add(1, Ordering::SeqCst)
616 }
617}
618
619fn gba_kdf(ks: &[u8], p0: &[u8], p1: &[u8], p2: &[u8], p3: &[u8]) -> Result<Vec<u8>, ErrorKind> {
620 if p0.len() < 256 && p1.len() < 256 && p2.len() < 256 && p3.len() < 65536 {
621 let mut message = Vec::with_capacity(p0.len() + p1.len() + p2.len() + p3.len() + 9);
622
623 message.push(1);
624
625 message.extend_from_slice(p0);
626 message.push(0);
627 message.push(p0.len() as u8);
628
629 message.extend_from_slice(p1);
630 message.push(0);
631 message.push(p1.len() as u8);
632
633 let p2_len = (p2.len() as u16).to_be_bytes();
634
635 message.extend_from_slice(p2);
636 message.push(p2_len[0]);
637 message.push(p2_len[1]);
638
639 message.extend_from_slice(p3);
640
641 let p3_len = (p3.len() as u16).to_be_bytes();
642
643 message.push(p3_len[0]);
644 message.push(p3_len[1]);
645
646 let key = hmac::Key::new(hmac::HMAC_SHA256, ks);
647
648 let signature = hmac::sign(&key, &message);
649
650 return Ok(signature.as_ref().to_vec());
651 }
652
653 Err(ErrorKind::BadInput)
654}
655
656fn gba_me_ks_naf(ks: &[u8], rand: &[u8], impi: &[u8], naf_id: &[u8]) -> Result<Vec<u8>, ErrorKind> {
657 gba_kdf(ks, b"gba-me", rand, impi, naf_id)
658}
659
660fn gba_get_naf_id(fqdn: &[u8], cipher_id: Option<(u8, u8)>) -> Vec<u8> {
661 let mut naf_id = Vec::with_capacity(fqdn.len() + 5);
662
663 naf_id.extend_from_slice(fqdn);
664
665 if let Some((yy, zz)) = cipher_id {
666 naf_id.push(0x01);
667 naf_id.push(0x00);
668 naf_id.push(0x01);
669 naf_id.push(yy);
670 naf_id.push(zz);
671 } else {
672 naf_id.push(0x01);
673 naf_id.push(0x00);
674 naf_id.push(0x00);
675 naf_id.push(0x00);
676 naf_id.push(0x02);
677 }
678
679 naf_id
680}
681
682pub struct GbaCredential {
683 pub username: Vec<u8>,
684 pub password: Vec<u8>,
685}
686
687const UA_BOOTSTRAPPING_REQUIRED_INDICATION_AKA_ME: &[u8] = b"3GPP-bootstrapping";
688
689const UA_BOOTSTRAPPING_REQUIRED_INDICATION_AKA_UICC: &[u8] = b"3GPP-bootstrapping-uicc";
690
691const UA_BOOTSTRAPPING_REQUIRED_INDICATION_GBA_DIGEST: &[u8] = b"3GPP-bootstrapping-digest";
692
693pub enum GbaRealm<'a> {
694 Me(&'a [u8]),
695 Uicc(&'a [u8]),
696 Digest(&'a [u8]),
697}
698
699pub fn get_gba_realm<'a>(realm: &'a [u8]) -> Option<GbaRealm<'a>> {
700 if let Some(idx) = realm.iter().position(|c| *c == b'@') {
701 match &realm[..idx] {
702 UA_BOOTSTRAPPING_REQUIRED_INDICATION_AKA_ME => {
703 return Some(GbaRealm::Me(&realm[idx + 1..]));
704 }
705
706 UA_BOOTSTRAPPING_REQUIRED_INDICATION_AKA_UICC => {
707 return Some(GbaRealm::Uicc(&realm[idx + 1..]));
708 }
709
710 UA_BOOTSTRAPPING_REQUIRED_INDICATION_GBA_DIGEST => {
711 return Some(GbaRealm::Digest(&realm[idx + 1..]));
712 }
713
714 _ => {}
715 }
716 }
717
718 None
719}
720
721pub async fn try_process_401_response(
722 context: &GbaContext,
723 fqdn: &[u8],
724 cipher_id: Option<(u8, u8)>,
725 method: &[u8],
726 resource_uri: &[u8],
727 body_digest: Option<&[u8]>,
728 www_authenticate_header: &Header,
729 http_client: &HttpClient,
730 security_context: &SecurityContext,
731) -> Option<Result<DigestAnswerParams, ErrorKind>> {
732 platform_log(LOG_TAG, "on 401");
733
734 let www_authenticate_header_value = www_authenticate_header.get_value();
735 for auth_method in AuthenticationMethods::new(www_authenticate_header_value) {
736 if let AuthenticationMethod::Digest(challenge_params) = auth_method {
737 if let Some(gba_realm) = get_gba_realm(challenge_params.realm) {
738 platform_log(LOG_TAG, "is GBA bootstrap response");
739
740 match gba_realm {
741 GbaRealm::Me(gba_fqdn) => {
742 if gba_fqdn == fqdn {
743 match context.get_bootstrapped_context(http_client).await {
744 Ok(bootstrapped_context) => {
745 if let Ok(credential) =
746 bootstrapped_context.get_credential(fqdn, cipher_id)
747 {
748 let challenge = challenge_params.to_challenge();
749
750 let cnonce = rand::create_raw_alpha_numeric_string(16);
751 let nc = bootstrapped_context.increase_and_get_use_count();
752
753 let digest_answer = DigestAnswerParams {
754 realm: syntax::unquote(resource_uri).to_vec(),
755 algorithm: Some(challenge_params.algorithm.to_vec()),
756
757 username: credential.username,
758 uri: syntax::unquote(resource_uri).to_vec(),
759
760 challenge: Some(challenge),
761
762 credentials: Some(DigestCredentials {
763 password: credential.password,
764 client_data: Some(method.to_vec()),
765 client_nonce: Some((cnonce, nc)),
766 entity_digest: match body_digest {
767 Some(body_digest) => Some(body_digest.to_vec()),
768 None => None,
769 },
770 extra_params: Vec::new(),
771 }),
772 };
773
774 return Some(Ok(digest_answer));
775 }
776 }
777
778 Err(e) => {
779 return Some(Err(e));
780 }
781 }
782 } else {
783 platform_log(LOG_TAG, format!("bootstrap indicated fqdn {:?} which is different from {:?} we requested", std::str::from_utf8(gba_fqdn), std::str::from_utf8(fqdn)));
784 }
785
786 return Some(Err(ErrorKind::Authentication));
787 }
788
789 _ => {
790 return Some(Err(ErrorKind::Authentication));
791 }
792 }
793 }
794 }
795 }
796
797 None
798}
799
800pub enum ErrorKind {
801 TimeOut,
802 Closed,
803 ConnectionClosed,
804 Http,
805 Authentication,
806 BadInput,
807 Hmac,
808}