1use std::convert::TryInto;
2use std::sync::Arc;
3use std::time::SystemTime;
4
5use chrono::Utc;
6use http::header::{HeaderName, HeaderValue, AUTHORIZATION, DATE, HOST};
7use itertools::Itertools;
8use thiserror::Error;
9
10use sha2::Digest;
11
12use crate::algorithm::{HttpDigest, HttpSignatureSign};
13use crate::canonicalize::{CanonicalizeConfig, CanonicalizeError, CanonicalizeExt, RequestLike};
14use crate::header::{Header, PseudoHeader};
15use crate::{DefaultDigestAlgorithm, DefaultSignatureAlgorithm, DATE_FORMAT};
16
17pub trait ClientRequestLike: RequestLike {
21 fn host(&self) -> Option<String> {
27 None
28 }
29 fn set_header(&mut self, header: HeaderName, value: HeaderValue);
33 fn compute_digest(&mut self, digest: &dyn HttpDigest) -> Option<String>;
36}
37
38#[derive(Debug, Error)]
40#[non_exhaustive]
41pub enum SigningError {
42 #[error("Failed to canonicalize request")]
46 Canonicalize(#[source] CanonicalizeError),
47
48 #[error("Signature creation date was in the future")]
50 InvalidSignatureCreationDate,
51
52 #[error("Signature expires date was in the past")]
54 InvalidSignatureExpiresDate,
55}
56
57impl From<CanonicalizeError> for SigningError {
58 fn from(other: CanonicalizeError) -> Self {
59 Self::Canonicalize(other)
60 }
61}
62
63#[derive(Debug, Copy, Clone, PartialEq)]
64enum SignatureCreated {
65 Omit,
66 Automatic,
67 Absolute(i64),
68}
69
70impl SignatureCreated {
71 fn get(self, ts: i64) -> Option<i64> {
72 match self {
73 Self::Omit => None,
74 Self::Automatic => Some(ts),
75 Self::Absolute(ts) => Some(ts),
76 }
77 }
78}
79
80#[derive(Debug, Copy, Clone, PartialEq)]
81enum SignatureExpires {
82 Omit,
83 Relative(i64),
84 Absolute(i64),
85}
86
87impl SignatureExpires {
88 fn get(self, ts: i64) -> Option<i64> {
89 match self {
90 Self::Omit => None,
91 Self::Relative(offset) => Some(ts.saturating_add(offset)),
92 Self::Absolute(ts) => Some(ts),
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct SigningConfig {
100 signature: Arc<dyn HttpSignatureSign>,
101 digest: Arc<dyn HttpDigest>,
102 key_id: String,
103 headers: Vec<Header>,
104 compute_digest: bool,
105 add_date: bool,
106 add_host: bool,
107 skip_missing: bool,
108 signature_created: SignatureCreated,
109 signature_expires: SignatureExpires,
110}
111
112impl SigningConfig {
113 pub fn new_default(key_id: &str, key: &[u8]) -> Self {
116 Self::new(key_id, DefaultSignatureAlgorithm::new(key))
117 }
118
119 pub fn new<SigAlg: HttpSignatureSign>(key_id: &str, signature: SigAlg) -> Self {
122 SigningConfig {
123 signature: Arc::new(signature),
124 digest: Arc::new(DefaultDigestAlgorithm::new()),
125 key_id: key_id.into(),
126 headers: [
127 Header::Pseudo(PseudoHeader::RequestTarget),
128 Header::Normal(HOST),
129 Header::Normal(DATE),
130 Header::Normal(HeaderName::from_static("digest")),
131 ]
132 .to_vec(),
133 compute_digest: true,
134 add_date: true,
135 add_host: true,
136 skip_missing: true,
137 signature_created: SignatureCreated::Omit,
138 signature_expires: SignatureExpires::Omit,
139 }
140 }
141
142 pub fn key_id(&self) -> &str {
144 self.key_id.as_ref()
145 }
146 pub fn digest(&self) -> &dyn HttpDigest {
148 &*self.digest
149 }
150 fn set_digest<DigestAlg: HttpDigest>(&mut self, digest: DigestAlg) -> &mut Self {
152 self.digest = Arc::new(digest);
153 self
154 }
155 pub fn with_digest<DigestAlg: HttpDigest>(mut self, digest: DigestAlg) -> Self {
157 self.set_digest(digest);
158 self
159 }
160 pub fn compute_digest(&self) -> bool {
165 self.compute_digest
166 }
167 pub fn set_compute_digest(&mut self, compute_digest: bool) -> &mut Self {
172 self.compute_digest = compute_digest;
173 self
174 }
175 pub fn with_compute_digest(mut self, compute_digest: bool) -> Self {
180 self.set_compute_digest(compute_digest);
181 self
182 }
183 pub fn add_date(&self) -> bool {
188 self.add_date
189 }
190 pub fn set_add_date(&mut self, add_date: bool) -> &mut Self {
195 self.add_date = add_date;
196 self
197 }
198 pub fn with_add_date(mut self, add_date: bool) -> Self {
203 self.set_add_date(add_date);
204 self
205 }
206 pub fn add_host(&self) -> bool {
211 self.add_host
212 }
213 pub fn set_add_host(&mut self, add_host: bool) -> &mut Self {
218 self.add_host = add_host;
219 self
220 }
221 pub fn with_add_host(mut self, add_host: bool) -> Self {
226 self.set_add_host(add_host);
227 self
228 }
229 pub fn headers(&self) -> impl IntoIterator<Item = &Header> {
234 &self.headers
235 }
236 pub fn set_headers(&mut self, headers: &[Header]) -> &mut Self {
241 self.headers = headers.to_vec();
242 self
243 }
244 pub fn with_headers(mut self, headers: &[Header]) -> Self {
249 self.set_headers(headers);
250 self
251 }
252 pub fn skip_missing(&self) -> bool {
257 self.skip_missing
258 }
259 pub fn set_skip_missing(&mut self, skip_missing: bool) -> &mut Self {
264 self.skip_missing = skip_missing;
265 self
266 }
267 pub fn with_skip_missing(mut self, skip_missing: bool) -> Self {
272 self.set_skip_missing(skip_missing);
273 self
274 }
275 pub fn set_signature_created_auto(&mut self) -> &mut Self {
280 self.signature_created = SignatureCreated::Automatic;
281 self
282 }
283 pub fn with_signature_created_auto(mut self) -> Self {
288 self.signature_created = SignatureCreated::Automatic;
289 self
290 }
291 pub fn signature_created_auto(&self) -> bool {
296 self.signature_created == SignatureCreated::Automatic
297 }
298 pub fn set_signature_created_at(&mut self, ts: i64) -> &mut Self {
303 self.signature_created = SignatureCreated::Absolute(ts);
304 self
305 }
306 pub fn with_signature_created_at(mut self, ts: i64) -> Self {
311 self.signature_created = SignatureCreated::Absolute(ts);
312 self
313 }
314 pub fn signature_created_at(&self) -> Option<i64> {
319 if let SignatureCreated::Absolute(ts) = self.signature_created {
320 Some(ts)
321 } else {
322 None
323 }
324 }
325 pub fn set_signature_expires_relative(&mut self, offset: i64) -> &mut Self {
330 self.signature_expires = SignatureExpires::Relative(offset);
331 self
332 }
333 pub fn with_signature_expires_auto(mut self, offset: i64) -> Self {
338 self.signature_expires = SignatureExpires::Relative(offset);
339 self
340 }
341 pub fn signature_expires_relative(&self) -> Option<i64> {
346 if let SignatureExpires::Relative(offset) = self.signature_expires {
347 Some(offset)
348 } else {
349 None
350 }
351 }
352 pub fn set_signature_expires_at(&mut self, ts: i64) -> &mut Self {
357 self.signature_expires = SignatureExpires::Absolute(ts);
358 self
359 }
360 pub fn with_signature_expires_at(mut self, ts: i64) -> Self {
365 self.signature_expires = SignatureExpires::Absolute(ts);
366 self
367 }
368 pub fn signature_expires_at(&self) -> Option<i64> {
373 if let SignatureExpires::Absolute(ts) = self.signature_expires {
374 Some(ts)
375 } else {
376 None
377 }
378 }
379}
380
381pub trait SigningExt: Sized {
384 fn signed(mut self, config: &SigningConfig) -> Result<Self, SigningError> {
386 self.sign(config)?;
387 Ok(self)
388 }
389
390 fn sign(&mut self, config: &SigningConfig) -> Result<(), SigningError>;
392}
393
394fn add_auto_headers<R: ClientRequestLike>(request: &mut R, config: &SigningConfig) -> Vec<Header> {
395 let digest_header = HeaderName::from_static("digest");
396
397 if config.add_date && !request.has_header(&DATE.into()) {
399 let date = Utc::now().format(DATE_FORMAT).to_string();
400 request.set_header(
401 DATE,
402 date.try_into()
403 .expect("Dates should always be valid header values"),
404 );
405 }
406 if config.add_host && !request.has_header(&HOST.into()) {
408 if let Some(host) = request.host() {
409 request.set_header(
410 HOST,
411 host.try_into()
412 .expect("Host should be valid in a HTTP header"),
413 );
414 }
415 }
416 if config.compute_digest && !request.has_header(&digest_header.clone().into()) {
418 if let Some(digest_str) = request.compute_digest(&*config.digest) {
419 let digest = format!("{}={}", config.digest.name(), digest_str);
420 request.set_header(
421 digest_header,
422 digest
423 .try_into()
424 .expect("Digest should be valid in a HTTP header"),
425 );
426 }
427 }
428
429 if config.skip_missing {
431 config
432 .headers
433 .iter()
434 .filter(|header| request.has_header(header))
435 .cloned()
436 .collect()
437 } else {
438 config.headers.clone()
439 }
440}
441
442fn unix_timestamp() -> i64 {
443 SystemTime::now()
444 .duration_since(SystemTime::UNIX_EPOCH)
445 .expect("Unix time to be positive")
446 .as_secs() as i64
447}
448
449impl<R: ClientRequestLike> SigningExt for R {
450 fn sign(&mut self, config: &SigningConfig) -> Result<(), SigningError> {
451 let headers = add_auto_headers(self, config);
453
454 let joined_headers = headers.iter().map(|header| header.as_str()).join(" ");
455
456 let ts = unix_timestamp();
458 let mut canonicalize_config = CanonicalizeConfig::new().with_headers(headers);
459 if let Some(created) = config.signature_created.get(ts) {
460 if created > ts {
461 return Err(SigningError::InvalidSignatureCreationDate);
462 }
463 canonicalize_config.set_signature_created(created.into());
464 }
465 if let Some(expires) = config.signature_expires.get(ts) {
466 if expires < ts {
467 return Err(SigningError::InvalidSignatureExpiresDate);
468 }
469 canonicalize_config.set_signature_expires(expires.into());
470 }
471
472 let content = self.canonicalize(&canonicalize_config)?;
474
475 let signature = config.signature.http_sign(content.as_bytes());
477
478 let auth_header = format!(
480 r#"Signature keyId="{}",algorithm="{}",signature="{}",headers="{}""#,
481 config.key_id, "hs2019", signature, joined_headers
482 );
483
484 self.set_header(
486 AUTHORIZATION,
487 auth_header
488 .try_into()
489 .expect("Signature scheme should generate a valid header"),
490 );
491
492 Ok(())
493 }
494}