1use crate::{
2 constants::*,
3 error::{log_error, VerificationError},
4 normalize,
5};
6use failure::{bail, Error, ResultExt};
7use std::{collections::HashMap, path::Path, sync::Mutex};
8use time::Duration;
9use url::{Host, Url};
10use x509_parser::objects::Nid;
11
12pub struct RequestVerifier {
14 cert_cache: Mutex<HashMap<String, Vec<u8>>>,
15}
16
17impl Default for RequestVerifier {
27 fn default() -> Self {
28 RequestVerifier {
29 cert_cache: Mutex::new(HashMap::new()),
30 }
31 }
32}
33
34impl RequestVerifier {
35 pub fn new() -> Self {
37 RequestVerifier::default()
38 }
39
40 pub fn verify(
50 &self,
51 signature_cert_chain_url: &str,
52 signature: &str,
53 body: &[u8],
54 timestamp: &str,
55 timestamp_tolerance_millis: Option<u64>,
56 ) -> Result<(), Error> {
57 if let Err(e) = self.retrieve_and_validate_cert(signature_cert_chain_url, signature, body) {
58 log_error(e)?;
59 };
60
61 if let Err(e) = self.validate_timestamp(timestamp, timestamp_tolerance_millis) {
62 log_error(e)?;
63 };
64
65 Ok(())
66 }
67
68 fn retrieve_and_validate_cert(
69 &self,
70 signature_cert_chain_url: &str,
71 signature: &str,
72 body: &[u8],
73 ) -> Result<(), Error> {
74 self.validate_cert_url(&signature_cert_chain_url)?;
76
77 let mut not_exists = false;
79 if !self
80 .cert_cache
81 .lock()
82 .unwrap()
83 .contains_key(&signature_cert_chain_url.to_string())
84 {
85 not_exists = true;
86 self.retrieve_cert(&signature_cert_chain_url)
87 .context(VerificationError::RetrieveCert)?;
88 }
89
90 let cert_cache = self.cert_cache.lock().unwrap();
93 let pem_bytes = cert_cache
94 .get(&signature_cert_chain_url.to_string())
95 .ok_or(VerificationError::MissingCertCache)?;
96 let (_, pem) =
97 x509_parser::pem::pem_to_der(pem_bytes).map_err(|_| VerificationError::PemParse)?;
98 drop(cert_cache);
99 let certificate = pem.parse_x509().map_err(|_| VerificationError::CertParse)?;
100
101 let not_before = certificate.tbs_certificate.validity.not_before;
103 let not_after = certificate.tbs_certificate.validity.not_after;
104 let now_utc = time::now_utc();
105 if now_utc < not_before || now_utc > not_after {
106 bail!(VerificationError::ExpiredCert)
107 }
108
109 if not_exists {
112 let mut sans: Vec<&str> = Vec::new();
113 for ext in &certificate.tbs_certificate.extensions {
114 if ext.oid == x509_parser::objects::nid2obj(&Nid::SubjectAltName).unwrap() {
115 let (_, ber) = der_parser::parse_der(&ext.value)
116 .map_err(|_| VerificationError::CertExtParse)?;
117 for b in ber.into_iter() {
118 if let der_parser::ber::BerObjectContent::Unknown(_, i) = b.content {
119 sans.push(
120 std::str::from_utf8(i).context(VerificationError::SanExtension)?,
121 )
122 } else {
123 bail!(VerificationError::SanExtension)
124 }
125 }
126 }
127 }
128 if !sans.contains(&CERT_CHAIN_DOMAIN) {
129 bail!(VerificationError::DomainNotInSan)
130 }
131 }
132
133 let pkey = certificate
135 .tbs_certificate
136 .subject_pki
137 .subject_public_key
138 .data;
139
140 self.validate_request_body(signature, body, pkey)?;
142
143 Ok(())
144 }
145
146 fn retrieve_cert(&self, signature_cert_chain_url: &str) -> Result<(), Error> {
147 let mut resp = reqwest::blocking::get(signature_cert_chain_url)?;
149 let mut buf: Vec<u8> = vec![];
150 resp.copy_to(&mut buf)?;
151
152 let _ = self
154 .cert_cache
155 .lock()
156 .unwrap()
157 .insert(signature_cert_chain_url.to_string(), buf);
158
159 Ok(())
160 }
161
162 fn validate_cert_url(&self, signature_cert_chain_url: &str) -> Result<(), Error> {
163 let parsed_url = Url::parse(signature_cert_chain_url)?;
164
165 let scheme = parsed_url.scheme();
166 if scheme != CERT_CHAIN_URL_SCHEME {
167 bail!(VerificationError::UrlScheme {
168 scheme: scheme.to_string()
169 })
170 }
171
172 if let Some(hostname) = parsed_url.host() {
173 match hostname {
174 Host::Domain(hostname) => {
175 if hostname.to_lowercase() != CERT_CHAIN_URL_HOSTNAME {
176 bail!(VerificationError::UrlHostname {
177 hostname: hostname.to_string()
178 });
179 }
180 }
181 Host::Ipv4(ip) => bail!(VerificationError::UrlHostname {
182 hostname: format!("{}", ip)
183 }),
184 Host::Ipv6(ip) => bail!(VerificationError::UrlHostname {
185 hostname: format!("{}", ip)
186 }),
187 }
188 } else {
189 bail!(VerificationError::UrlHostname {
190 hostname: "".to_string()
191 })
192 }
193
194 let path = Path::new(parsed_url.path());
195 let normalized_path = normalize::normalize_path(&path);
196 if !normalized_path.starts_with(CERT_CHAIN_URL_STARTPATH) {
197 bail!(VerificationError::UrlPath {
198 path: format!("{}", normalized_path.display())
199 })
200 }
201
202 if let Some(port) = parsed_url.port() {
203 if port != CERT_CHAIN_URL_PORT {
204 bail!(VerificationError::UrlPort { port })
205 }
206 }
207
208 Ok(())
209 }
210
211 fn validate_request_body(
212 &self,
213 signature: &str,
214 body: &[u8],
215 pkey_bytes: &[u8],
216 ) -> Result<(), Error> {
217 let decoded_signature = base64::decode(&signature)?;
218
219 let pkey = ring::signature::UnparsedPublicKey::new(
220 &ring::signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
221 pkey_bytes,
222 );
223
224 pkey.verify(body, &decoded_signature)?;
225
226 Ok(())
227 }
228
229 fn validate_timestamp(
230 &self,
231 timestamp: &str,
232 timestamp_tolerance_millis: Option<u64>,
233 ) -> Result<(), Error> {
234 let tolerance_millis = {
236 if let Some(t) = timestamp_tolerance_millis {
237 Duration::milliseconds(t as i64)
238 } else {
239 Duration::milliseconds(DEFAULT_TIMESTAMP_TOLERANCE_IN_MILLIS)
240 }
241 };
242
243 if tolerance_millis > Duration::milliseconds(MAX_TIMESTAMP_TOLERANCE_IN_MILLIS) {
245 bail!(VerificationError::TimestampMax {
246 millis: tolerance_millis.num_milliseconds()
247 });
248 }
249
250 let timestamp =
252 time::strptime(timestamp, "%FT%TZ").context(VerificationError::TimestampParse {
253 timestamp: timestamp.to_owned(),
254 })?;
255 let utc_now = time::now_utc();
256
257 let duration_between = utc_now - timestamp;
259 if duration_between > tolerance_millis {
260 bail!(VerificationError::Timestamp);
261 };
262
263 Ok(())
264 }
265}