http-signatures 0.8.0

An implementation of the HTTP Signatures RFC
// This file is part of HTTP Signatures

// HTTP Signatures is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// HTTP Signatures is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with HTTP Signatures  If not, see <http://www.gnu.org/licenses/>.

use std::{collections::HashMap, str::FromStr};

use base64::decode;
use failure::Fail;
use ring::{error::Unspecified, hmac, signature};
use untrusted::Input;

use crate::{
    error::{DecodeError, VerificationError},
    ShaSize, SignatureAlgorithm, REQUEST_TARGET,
};

const KEY_ID: &str = "keyId";
const HEADERS: &str = "headers";
const ALGORITHM: &str = "algorithm";
const DATE: &str = "date";
const SIGNATURE: &str = "signature";

/// The verification key
///
/// If you have openssl in your project, you can enable the `openssl` feature on this crate and use
/// `.as_verify_key()?` on an
/// [`openssl::rsa::Rsa`](https://docs.rs/openssl/0.10.16/openssl/rsa/struct.Rsa.html) type.
///
/// This should contain a valid HMAC key, or a valid PKCS#1 DER encoded RSA key.
#[derive(Clone)]
pub struct VerifyKey(Vec<u8>);

impl VerifyKey {
    /// Create a VerifyKey from an unchecked slice
    ///
    /// This is potentially dangerous, since it doesn't enforce that the slice contains a PKCS#1
    /// DER encoded RSA Public Key, or a valid HMAC key.
    ///
    /// For RSA verification, this key must be constructed from a PKCS#1 DER encoded byte string.
    pub fn unchecked_from_slice(s: &[u8]) -> Self {
        VerifyKey(s.into())
    }

    /// Create a VerifyKey from an unchecked vec
    ///
    /// This is potentially dangerous, since it doesn't enforce that the slice contains a PKCS#1
    /// DER encoded RSA Public Key, or a valid HMAC key.
    ///
    /// For RSA verification, this key must be constructed from a PKCS#1 DER encoded byte string.
    pub fn unchecked_from_vec(v: Vec<u8>) -> Self {
        VerifyKey(v)
    }
}

/// This trait is designed to reduce the amount of times you need to use one of VerifyKey's
/// `unchecked_from_*` methods. By default, it is implemented for `openssl`'s `Rsa` type, through
/// the `openssl` feature.
pub trait AsVerifyKey {
    type Error: Fail;

    /// Create a VerifyKey from a given type
    fn as_verify_key(&self) -> Result<VerifyKey, Self::Error>;
}

#[cfg(feature = "openssl")]
impl AsVerifyKey for openssl::rsa::Rsa<openssl::pkey::Public> {
    type Error = openssl::error::ErrorStack;

    fn as_verify_key(&self) -> Result<VerifyKey, Self::Error> {
        Ok(VerifyKey(self.public_key_to_der_pkcs1()?))
    }
}

#[cfg(feature = "openssl")]
impl AsVerifyKey for openssl::rsa::Rsa<openssl::pkey::Private> {
    type Error = openssl::error::ErrorStack;

    fn as_verify_key(&self) -> Result<VerifyKey, Self::Error> {
        Ok(VerifyKey(self.public_key_to_der_pkcs1()?))
    }
}

/// The `SignedHeader` struct is the direct reasult of reading in the Authorization or Signature
/// header from a given request.
///
/// It contains the keys to the request's headers in the correct order for recreating the signing
/// string, the algorithm used to create the signature, and the signature itself.
///
/// It also contains the `key_id`.
#[derive(Debug)]
pub struct SignedHeader {
    pub key_id: String,
    header_keys: Vec<String>,
    algorithm: SignatureAlgorithm,
    signature: Vec<u8>,
}

impl SignedHeader {
    /// Try to create an `SignedHeader` from a given String.
    pub fn new(s: &str) -> Result<Self, DecodeError> {
        s.parse()
    }

    /// Try to verify the current `SignedHeader`.
    pub fn verify(
        self,
        headers: &[(&str, &str)],
        method: &str,
        path: &str,
        query: Option<&str>,
        key: VerifyKey,
    ) -> Result<(), VerificationError> {
        let vah = CheckSignedHeader {
            auth_header: self,
            headers: headers,
            method: method,
            path: path,
            query: query,
        };

        let key = key.0;
        vah.verify(Input::from(&key))
    }
}

impl FromStr for SignedHeader {
    type Err = DecodeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.trim_start_matches("Signature ");
        let key_value = s
            .split(',')
            .filter_map(|item| {
                let eq_index = item.find('=')?;
                let tup = item.split_at(eq_index);
                let val = tup.1.get(1..)?;
                Some((tup.0, val))
            })
            .collect::<HashMap<&str, &str>>();

        let key_id = key_value
            .get(KEY_ID)
            .ok_or(DecodeError::MissingKey(KEY_ID))?
            .trim_start_matches('"')
            .trim_end_matches('"')
            .to_owned();

        let header_keys = key_value
            .get(HEADERS)
            .unwrap_or(&DATE)
            .trim_start_matches('"')
            .trim_end_matches('"')
            .split(' ')
            .map(String::from)
            .collect();

        let algorithm = (*key_value
            .get(ALGORITHM)
            .ok_or(DecodeError::MissingKey(ALGORITHM))?
            .trim_start_matches('"')
            .trim_end_matches('"'))
        .parse()?;

        let sig_string: String = key_value
            .get(SIGNATURE)
            .ok_or(DecodeError::MissingKey(SIGNATURE))?
            .trim_start_matches('"')
            .trim_end_matches('"')
            .into();

        let signature = decode(&sig_string).map_err(|_| DecodeError::NotBase64)?;

        Ok(SignedHeader {
            key_id,
            header_keys,
            algorithm,
            signature,
        })
    }
}

#[derive(Debug)]
struct CheckSignedHeader<'a> {
    auth_header: SignedHeader,
    headers: &'a [(&'a str, &'a str)],
    method: &'a str,
    path: &'a str,
    query: Option<&'a str>,
}

impl<'a> CheckSignedHeader<'a> {
    pub fn verify(&self, public_key_der: Input) -> Result<(), VerificationError> {
        let headers: HashMap<String, Vec<&str>> =
            self.headers
                .iter()
                .fold(HashMap::new(), |mut acc, &(key, value)| {
                    acc.entry(key.to_lowercase())
                        .or_insert_with(Vec::new)
                        .push(value);

                    acc
                });

        let mut headers: HashMap<&str, String> = headers
            .iter()
            .map(|(key, value)| (key.as_ref(), value.join(", ")))
            .collect();

        headers.insert(
            REQUEST_TARGET,
            if let Some(query) = self.query {
                format!("{} {}?{}", self.method.to_lowercase(), self.path, query,)
            } else {
                format!("{} {}", self.method.to_lowercase(), self.path,)
            },
        );

        let signing_vec = self.auth_header.header_keys.iter().fold(
            (Vec::new(), Vec::new()),
            |mut acc, header_key| {
                if let Some(header) = headers.get(header_key.to_owned().as_str()) {
                    acc.0.push(format!("{}: {}", header_key, header));
                } else {
                    acc.1.push(header_key.to_owned());
                }

                acc
            },
        );

        if !signing_vec.1.is_empty() {
            return Err(VerificationError::MissingHeaders(signing_vec.1.join(", ")));
        }

        let signing_string = signing_vec.0.join("\n");

        match self.auth_header.algorithm {
            SignatureAlgorithm::RSA(sha_size) => verify_rsa(
                public_key_der,
                sha_size,
                signing_string.as_bytes(),
                &self.auth_header.signature,
            ),
            SignatureAlgorithm::HMAC(sha_size) => verify_hmac(
                public_key_der,
                sha_size,
                signing_string.as_bytes(),
                &self.auth_header.signature,
            ),
        }
    }
}

fn verify_rsa<'a>(
    public_key_der: Input,
    sha_size: ShaSize,
    message: &'a [u8],
    signature: &'a [u8],
) -> Result<(), VerificationError> {
    // Verify the signature.
    signature::verify(
        sha_size.verification_algorithm(),
        public_key_der,
        Input::from(message),
        Input::from(signature),
    )
    .map_err(|Unspecified| VerificationError::BadSignature)?;

    Ok(())
}

fn verify_hmac<'a>(
    hmac_key: Input,
    sha_size: ShaSize,
    signing_string: &'a [u8],
    sig: &'a [u8],
) -> Result<(), VerificationError> {
    let hmac_key = hmac::SigningKey::new(sha_size.hmac_algorithm(), hmac_key.as_slice_less_safe());

    hmac::verify_with_own_key(&hmac_key, signing_string, sig)
        .map_err(|_| VerificationError::BadSignature)?;

    Ok(())
}