ndn_cert/challenge/
pin.rs1use std::{future::Future, pin::Pin};
23
24use crate::{
25 challenge::{ChallengeHandler, ChallengeOutcome, ChallengeState},
26 error::CertError,
27 protocol::CertRequest,
28};
29
30pub struct PinChallenge {
35 pin_hash: [u8; 32],
37 max_tries: u8,
39}
40
41impl PinChallenge {
42 pub fn new(pin: &str) -> Self {
44 Self::new_with_max_tries(pin, 3)
45 }
46
47 pub fn new_with_max_tries(pin: &str, max_tries: u8) -> Self {
49 use ring::digest::{SHA256, digest};
50 let hash = digest(&SHA256, pin.as_bytes());
51 let mut pin_hash = [0u8; 32];
52 pin_hash.copy_from_slice(hash.as_ref());
53 Self {
54 pin_hash,
55 max_tries,
56 }
57 }
58}
59
60impl ChallengeHandler for PinChallenge {
61 fn challenge_type(&self) -> &'static str {
62 "pin"
63 }
64
65 fn begin<'a>(
66 &'a self,
67 _req: &'a CertRequest,
68 ) -> Pin<Box<dyn Future<Output = Result<ChallengeState, CertError>> + Send + 'a>> {
69 let max_tries = self.max_tries;
70 Box::pin(async move {
71 Ok(ChallengeState {
72 challenge_type: "pin".to_string(),
73 data: serde_json::json!({ "remaining_tries": max_tries }),
74 })
75 })
76 }
77
78 fn verify<'a>(
79 &'a self,
80 state: &'a ChallengeState,
81 parameters: &'a serde_json::Map<String, serde_json::Value>,
82 ) -> Pin<Box<dyn Future<Output = Result<ChallengeOutcome, CertError>> + Send + 'a>> {
83 use ring::digest::{SHA256, digest};
84
85 let code = parameters
86 .get("code")
87 .and_then(|v| v.as_str())
88 .map(str::to_string);
89 let remaining_tries = state
90 .data
91 .get("remaining_tries")
92 .and_then(|v| v.as_u64())
93 .unwrap_or(1) as u8;
94 let pin_hash = self.pin_hash;
95
96 Box::pin(async move {
97 let code = match code {
98 Some(c) => c,
99 None => {
100 return Ok(ChallengeOutcome::Denied(
101 "missing 'code' parameter".to_string(),
102 ));
103 }
104 };
105
106 let submitted_hash = digest(&SHA256, code.as_bytes());
107 let matches = submitted_hash.as_ref() == pin_hash;
108
109 if matches {
110 Ok(ChallengeOutcome::Approved)
111 } else if remaining_tries <= 1 {
112 Ok(ChallengeOutcome::Denied(
113 "PIN verification failed: no attempts remaining".to_string(),
114 ))
115 } else {
116 let new_tries = remaining_tries - 1;
117 Ok(ChallengeOutcome::Pending {
118 status_message: format!("Incorrect PIN — {new_tries} attempt(s) remaining"),
119 remaining_tries: new_tries,
120 remaining_time_secs: 300,
121 next_state: ChallengeState {
122 challenge_type: "pin".to_string(),
123 data: serde_json::json!({ "remaining_tries": new_tries }),
124 },
125 })
126 }
127 })
128 }
129}