Skip to content

Commit

Permalink
Split out canonicalization into a separate module so it can be shared…
Browse files Browse the repository at this point in the history
… between

signing and verification
  • Loading branch information
Diggsey committed Apr 20, 2020
1 parent 1815ab5 commit bba8986
Show file tree
Hide file tree
Showing 11 changed files with 582 additions and 210 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ hmac = "0.7.1"
openssl = { version = "0.10.29", optional = true }
log = "0.4.8"
anyhow = "1.0.28"
itertools = "0.9.0"
thiserror = "1.0.15"
91 changes: 70 additions & 21 deletions http-sig-validator/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::error::Error;
use std::fs;
use std::io;
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Context;
use anyhow::{anyhow, Context};
use http_sig::mock_request::MockRequest;
use http_sig::{
Header, RsaSha256Sign, RsaSha256Verify, SigningConfig, SigningExt, SimpleKeyProvider,
VerifyingConfig, VerifyingExt,
CanonicalizeConfig, CanonicalizeExt, Header, RsaSha256Sign, RsaSha256Verify, SigningConfig,
SigningExt, SimpleKeyProvider, VerifyingConfig, VerifyingExt,
};
use structopt::StructOpt;

Expand All @@ -27,8 +27,9 @@ struct Opt {
mode: Mode,

/// A list of header names, optionally quoted
#[structopt(short = "d", long, global = true)]
headers: Option<String>,
#[allow(clippy::option_option)]
#[structopt(short = "d", long, global = true, min_values = 0)]
headers: Option<Option<String>>,

/// A Key Id string.
#[structopt(short, long = "keyId", global = true)]
Expand Down Expand Up @@ -60,6 +61,35 @@ struct Opt {
}

impl Opt {
fn parse_headers(&self) -> Result<Option<Vec<Header>>, Box<dyn Error>> {
Ok(if let Some(headers) = &self.headers {
Some(if let Some(headers) = headers {
let headers: Vec<Header> = headers
.split_ascii_whitespace()
.map(|s| s.parse::<Header>().with_context(|| format!("{:?}", s)))
.collect::<Result<_, _>>()?;
headers
} else {
Vec::new()
})
} else {
None
})
}
fn canonicalize_config(&self) -> Result<CanonicalizeConfig, Box<dyn Error>> {
let mut config = CanonicalizeConfig::default();
if let Some(created) = self.created {
config.set_signature_created(created.into());
}
if let Some(expires) = self.expires {
config.set_signature_expires(expires.into());
}
if let Some(headers) = self.parse_headers()? {
config.set_headers(headers);
}

Ok(config)
}
fn signing_config(&self) -> Result<SigningConfig, Box<dyn Error>> {
let key_id = self.key_id.clone().unwrap_or_default();
let key_data = if let Some(key) = self.private_key.as_ref() {
Expand All @@ -68,26 +98,37 @@ impl Opt {
None
};

#[allow(clippy::single_match)]
let mut config = match (self.algorithm.as_deref(), key_data) {
(Some("rsa-sha256"), Some(pkey)) => {
match self.algorithm.as_deref() {
Some("rsa-sha256") | Some("hs2019") | None => {}
Some(other) => return Err(anyhow!("Unknown algorithm: {}", other).into()),
}

let mut config = match (self.key_type.as_deref(), key_data) {
(Some("rsa"), Some(pkey)) | (Some("RSA"), Some(pkey)) => {
SigningConfig::new(&key_id, RsaSha256Sign::new_pem(&pkey)?)
}
_ => SigningConfig::new_default(&key_id, b""),
(Some(_), None) => return Err(anyhow!("No key provided").into()),
(Some(other), Some(_)) => return Err(anyhow!("Unknown key type: {}", other).into()),
(None, _) => SigningConfig::new_default(&key_id, b""),
};

if let Some(headers) = &self.headers {
let headers: Vec<Header> = headers
.split_ascii_whitespace()
.map(|s| s.parse::<Header>().with_context(|| format!("{:?}", s)))
.collect::<Result<_, _>>()?;
if let Some(headers) = self.parse_headers()? {
config.set_headers(&headers);
}

if let Some(created) = self.created {
config.set_signature_created_at(created);
}

if let Some(expires) = self.expires {
config.set_signature_expires_at(expires);
}

// Disable various convenience options that would mess up the test suite
config.set_add_date(false);
config.set_compute_digest(false);
config.set_add_host(false);
config.set_skip_missing(false);

Ok(config)
}
Expand All @@ -101,12 +142,18 @@ impl Opt {

let mut key_provider = SimpleKeyProvider::default();

#[allow(clippy::single_match)]
match (self.algorithm.as_deref(), key_data) {
(Some("rsa-sha256"), Some(pkey)) => {
match self.algorithm.as_deref() {
Some("hs2019") | None => {}
Some(other) => return Err(anyhow!("Unknown algorithm: {}", other).into()),
}

match (self.key_type.as_deref(), key_data) {
(Some("rsa"), Some(pkey)) | (Some("RSA"), Some(pkey)) => {
key_provider.add(&key_id, Arc::new(RsaSha256Verify::new_pem(&pkey)?));
}
_ => {}
(Some(_), None) => return Err(anyhow!("No key provided").into()),
(Some(other), Some(_)) => return Err(anyhow!("Unknown key type: {}", other).into()),
(None, _) => {}
}

let mut config = VerifyingConfig::new(key_provider);
Expand All @@ -127,10 +174,12 @@ fn main() -> Result<(), Box<dyn Error>> {

let mut req = MockRequest::from_reader(&mut io::stdin().lock())?;

log::info!("{:?}", req);

match opt.mode {
Mode::Canonicalize => {
let res = req.canonicalize(&opt.signing_config()?)?;
print!("{}", res);
let res = req.canonicalize(&opt.canonicalize_config()?)?;
io::stdout().lock().write_all(res.as_bytes())?;
}
Mode::Sign => {
req.sign(&opt.signing_config()?)?;
Expand Down
21 changes: 2 additions & 19 deletions src/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@ use std::fmt::Debug;
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256, Sha512};

/// Implementations of this trait correspond to signature algorithms
/// listed here:
/// https://tools.ietf.org/id/draft-cavage-http-signatures-12.html#hsa-registry
///
/// If the HTTP signatures draft is accepted, these will be moved to a registry
/// managed by the IANA.
pub trait HttpSignature: Debug + Send + Sync + 'static {
/// Must return the name exactly as specified in the above list of HTTP
/// signature algorithms.
fn name(&self) -> &str;
}

/// Implements the signing half of an HTTP signature algorithm. For symmetric
/// algorithms the same type implements both signing and verification.
pub trait HttpSignatureSign: HttpSignature {
pub trait HttpSignatureSign: Debug + Send + Sync + 'static {
/// Returns the encoded signature, ready for inclusion in the HTTP Authorization
/// header. For all currently supported signature schemes, the encoding is
/// specified to be base64.
Expand All @@ -26,7 +14,7 @@ pub trait HttpSignatureSign: HttpSignature {

/// Implements the verification half of an HTTP signature algorithm. For symmetric
/// algorithms the same type implements both signing and verification.
pub trait HttpSignatureVerify: HttpSignature {
pub trait HttpSignatureVerify: Debug + Send + Sync + 'static {
/// Returns true if the signature is valid for the provided content. The
/// implementation should be sure to perform any comparisons in constant
/// time.
Expand Down Expand Up @@ -63,11 +51,6 @@ macro_rules! hmac_signature {
}
}

impl HttpSignature for $typename {
fn name(&self) -> &str {
$name
}
}
impl HttpSignatureSign for $typename {
fn http_sign(&self, bytes_to_sign: &[u8]) -> String {
let mut hmac = self.0.clone();
Expand Down
13 changes: 1 addition & 12 deletions src/algorithm/openssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use openssl::rsa::Padding;
use openssl::rsa::Rsa;
use openssl::sign::{Signer, Verifier};

use crate::{HttpSignature, HttpSignatureSign, HttpSignatureVerify};
use crate::{HttpSignatureSign, HttpSignatureVerify};

macro_rules! rsa_signature {
({$sign_name:ident, $verify_name:ident}($hash_alg:ident) = $name:literal) => {
Expand Down Expand Up @@ -63,16 +63,6 @@ macro_rules! rsa_signature {
}
}

impl HttpSignature for $sign_name {
fn name(&self) -> &str {
$name
}
}
impl HttpSignature for $verify_name {
fn name(&self) -> &str {
$name
}
}
impl HttpSignatureSign for $sign_name {
fn http_sign(&self, bytes_to_sign: &[u8]) -> String {
let mut signer = Signer::new(MessageDigest::$hash_alg(), &self.0).unwrap();
Expand All @@ -89,7 +79,6 @@ macro_rules! rsa_signature {
};
let mut verifier = Verifier::new(MessageDigest::$hash_alg(), &self.0).unwrap();
verifier.set_rsa_padding(Padding::PKCS1).unwrap();
dbg!(std::str::from_utf8(bytes_to_verify).unwrap(), signature);
match verifier.verify_oneshot(&tag, bytes_to_verify) {
Ok(true) => true,
Ok(false) => false,
Expand Down
Loading

0 comments on commit bba8986

Please sign in to comment.