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/>.

//! This module defines types for creating HTTP Signatures.

use std::{collections::BTreeMap, sync::Arc};

use base64::encode;
use ring::{
    hmac::{self, SigningKey},
    rand,
    signature::{self, RSAKeyPair},
};

use crate::{error::CreationError, ShaSize, SignatureAlgorithm};

pub enum CreateKey {
    RSA(RSAKeyPair, ShaSize),
    HMAC(SigningKey, ShaSize),
}

impl CreateKey {
    #[cfg(feature = "openssl")]
    pub fn from_openssl_rsa(
        rsa: &openssl::rsa::Rsa<openssl::pkey::Private>,
        size: ShaSize,
    ) -> Result<Self, failure::Error> {
        let vec = rsa.private_key_to_der()?;

        let input = untrusted::Input::from(&vec);
        Ok(CreateKey::RSA(RSAKeyPair::from_der(input)?, size))
    }

    pub fn rsa(key: RSAKeyPair, size: ShaSize) -> Self {
        CreateKey::RSA(key, size)
    }

    pub fn hmac(key: SigningKey, size: ShaSize) -> Self {
        CreateKey::HMAC(key, size)
    }
}

/// The `HttpSignature` struct, this is the entry point for creating Authorization or Signature
/// headers. It contains all the values required for generation.
pub struct HttpSignature {
    /// The keyId field in the header
    key_id: String,
    /// The key used to sign the request
    key: CreateKey,
    /// The headers that will be included in the signature
    headers: BTreeMap<String, Vec<String>>,
}

impl HttpSignature {
    /// Create a new HttpSignature from its components.
    ///
    /// This method will Error if `headers` is empty.
    ///
    /// ### Example
    /// ```rust
    /// # extern crate http_signatures;
    /// # extern crate ring as ring;
    /// #
    /// # use std::{collections::BTreeMap, fs::File, io::Read};
    /// use http_signatures::{
    ///     CreateKey,
    /// #   Error,
    ///     HttpSignature,
    ///     Input,
    ///     ShaSize,
    ///     REQUEST_TARGET,
    /// };
    /// use ring::signature::RSAKeyPair;
    ///
    /// # fn run() -> Result<(), Error> {
    /// let mut priv_key_file = File::open("tests/assets/private.der")?;
    /// let mut priv_key_vec = Vec::new();
    /// priv_key_file.read_to_end(&mut priv_key_vec)?;
    /// let priv_key_input = Input::from(&priv_key_vec);
    /// let priv_key = RSAKeyPair::from_der(priv_key_input).unwrap();
    ///
    /// let priv_creation_key = CreateKey::rsa(priv_key, ShaSize::SHA512);
    /// let key_id = "tests/assets/public.der".into();
    ///
    /// let mut headers = BTreeMap::new();
    /// headers.insert(REQUEST_TARGET.into(), vec!["get /".into()]);
    ///
    /// let http_sig = HttpSignature::new(key_id, priv_creation_key, headers)?;
    /// # Ok(())
    /// # }
    /// # fn main() { run().unwrap(); }
    /// ```
    pub fn new(
        key_id: String,
        key: CreateKey,
        headers: BTreeMap<String, Vec<String>>,
    ) -> Result<Self, CreationError> {
        let key = key.into();

        if headers.is_empty() {
            return Err(CreationError::NoHeaders);
        }

        Ok(HttpSignature {
            key_id,
            key,
            headers,
        })
    }

    pub fn key_id(&self) -> &str {
        &self.key_id
    }

    pub fn headers(&self) -> &BTreeMap<String, Vec<String>> {
        &self.headers
    }

    /// Generate the Authorization Header from the `HttpSignature`
    ///
    /// This method errors if signing the signing-string fails.
    ///
    /// ### Example
    /// ```rust
    /// # extern crate http_signatures;
    /// # extern crate ring as ring;
    /// #
    /// # use std::{collections::BTreeMap, fs::File, io::Read};
    /// # use http_signatures::{CreateKey, Error, Input, ShaSize, REQUEST_TARGET};
    /// # use ring::signature::RSAKeyPair;
    /// use http_signatures::HttpSignature;
    /// # fn run() -> Result<(), Error> {
    /// # let mut priv_key_file = File::open("tests/assets/private.der")?;
    /// # let mut priv_key_vec = Vec::new();
    /// # priv_key_file.read_to_end(&mut priv_key_vec)?;
    /// # let priv_key_input = Input::from(&priv_key_vec);
    /// # let priv_key = RSAKeyPair::from_der(priv_key_input).unwrap();
    /// #
    /// # let priv_creation_key = CreateKey::rsa(priv_key, ShaSize::SHA512);
    /// # let key_id = "tests/assets/public.der".into();
    /// # let mut headers = BTreeMap::new();
    /// # headers.insert(REQUEST_TARGET.into(), vec!["get /".into()]);
    /// # let http_signature = HttpSignature::new(key_id, priv_creation_key, headers)?;
    ///
    /// let auth_header = http_signature.authorization_header()?;
    /// println!("Authorization: {}", auth_header);
    /// # Ok(())
    /// # }
    /// ```
    pub fn authorization_header(self) -> Result<String, CreationError> {
        Ok(self.signature()?.authorization())
    }

    /// Generate the Signature Header from the `HttpSignature`
    ///
    /// This method errors if signing the signing-string fails.
    ///
    /// ### Example
    /// ```rust
    /// # extern crate http_signatures;
    /// # extern crate ring as ring;
    /// #
    /// # use std::{collections::BTreeMap, fs::File, io::Read};
    /// # use http_signatures::{CreateKey, Error, Input, SignatureAlgorithm, ShaSize, REQUEST_TARGET};
    /// # use ring::signature::RSAKeyPair;
    /// use http_signatures::HttpSignature;
    /// # fn run() -> Result<(), Error> {
    /// # let mut priv_key_file = File::open("tests/assets/private.der")?;
    /// # let mut priv_key_vec = Vec::new();
    /// # priv_key_file.read_to_end(&mut priv_key_vec)?;
    /// # let priv_key_input = Input::from(&priv_key_vec);
    /// # let priv_key = RSAKeyPair::from_der(priv_key_input).unwrap();
    /// #
    /// # let priv_creation_key = CreateKey::rsa(priv_key, ShaSize::SHA512);
    /// # let key_id = "tests/assets/public.der".into();
    /// # let mut headers = BTreeMap::new();
    /// # headers.insert(REQUEST_TARGET.into(), vec!["get /".into()]);
    /// # let http_signature = HttpSignature::new(key_id, priv_creation_key, headers)?;
    ///
    /// let sig_header = http_signature.signature_header()?;
    /// println!("Signature: {}", sig_header);
    /// # Ok(())
    /// # }
    /// ```
    pub fn signature_header(self) -> Result<String, CreationError> {
        Ok(self.signature()?.signature())
    }

    pub fn signature(self) -> Result<Signature, CreationError> {
        let signing_string: SigningString = self.into();
        Signature::from_signing_string(signing_string)
    }
}

/// The `SigningString` struct uses what was given in the `HttpSignature` struct, but also has a
/// plaintext field called `signing_string` which holds the string used to sign the request.
///
/// Since `From<HttpSignature<T>>` was implemented for `SigningString`, the transition is as
/// simple as calling `http_signature.into()`.
///
/// This struct does not have public fields, and does not have a constructor since it should only
/// be used as an intermediate point from `HttpSignature<T>` to the signed string.
pub struct SigningString {
    key_id: String,
    key: CreateKey,
    headers: Vec<String>,
    // The plaintext string used to sign the request
    pub signing_string: String,
}

impl From<HttpSignature> for SigningString {
    fn from(http_signature: HttpSignature) -> Self {
        let (header_keys, signing_vec): (Vec<_>, Vec<_>) = http_signature
            .headers
            .iter()
            .map(|(header, values)| {
                (
                    header.to_lowercase(),
                    format!("{}: {}", header.to_lowercase(), values.join(", ")),
                )
            })
            .unzip();

        SigningString {
            key_id: http_signature.key_id,
            key: http_signature.key,
            headers: header_keys,
            signing_string: signing_vec.join("\n"),
        }
    }
}

/// `Signature` is the result of using the `key` of `SigningString` to sign the
/// `signing_string`.
///
/// To get the Authorization or Signature Header String from the Signature, the `authorization`
/// and `signature` methods are provided.
#[derive(Clone, Debug)]
pub struct Signature {
    sig: String,
    key_id: String,
    headers: Vec<String>,
    algorithm: SignatureAlgorithm,
}

impl Signature {
    /// Get the Authorization Header String.
    pub fn authorization(self) -> String {
        format!("Signature {}", self.header())
    }

    /// Get the Signature Header String.
    pub fn signature(self) -> String {
        self.header()
    }

    fn header(self) -> String {
        let alg: &str = self.algorithm.into();

        format!(
            "Signature keyId=\"{}\",algorithm=\"{}\",headers=\"{}\",signature=\"{}\"",
            self.key_id,
            alg,
            self.headers.join(" "),
            self.sig,
        )
    }

    fn rsa(key: RSAKeyPair, size: ShaSize, signing_string: &[u8]) -> Result<String, CreationError> {
        let key_pair = Arc::new(key);

        let mut signing_state =
            signature::RSASigningState::new(key_pair).map_err(|_| CreationError::SigningError)?;

        let rng = rand::SystemRandom::new();
        let mut signature = vec![0; signing_state.key_pair().public_modulus_len()];
        signing_state
            .sign(
                size.rsa_algorithm(),
                &rng,
                signing_string,
                signature.as_mut_slice(),
            )
            .map_err(|_| CreationError::SigningError)?;

        Ok(encode(signature.as_slice()))
    }

    fn hmac(hmac_key: SigningKey, signing_string: &[u8]) -> Result<String, CreationError> {
        let signature = hmac::sign(&hmac_key, signing_string);

        Ok(encode(signature.as_ref()))
    }

    fn from_signing_string(signing_string: SigningString) -> Result<Self, CreationError> {
        Ok(match signing_string.key {
            CreateKey::RSA(rsa_key_pair, sha_size) => Signature {
                sig: Signature::rsa(
                    rsa_key_pair,
                    sha_size,
                    signing_string.signing_string.as_ref(),
                )?,
                key_id: signing_string.key_id,
                headers: signing_string.headers,
                algorithm: SignatureAlgorithm::RSA(sha_size),
            },
            CreateKey::HMAC(signing_key, sha_size) => Signature {
                sig: Signature::hmac(signing_key, signing_string.signing_string.as_ref())?,
                key_id: signing_string.key_id,
                headers: signing_string.headers,
                algorithm: SignatureAlgorithm::HMAC(sha_size),
            },
        })
    }
}