~vpzom/hancock

c01a161e6b629266d1a9d0beaf4531958d961721 — Colin Reeder 10 months ago 2bae974
Some more progress
1 files changed, 84 insertions(+), 43 deletions(-)

M src/httpbis.rs
M src/httpbis.rs => src/httpbis.rs +84 -43
@@ 108,7 108,7 @@ impl<'a> ComponentId<'a> {
    fn serialize_value_into<B>(
        &self,
        result: &mut String,
        src: &RequestOrResponse<B>,
        src: &RequestOrResponseRef<B>,
    ) -> Result<(), crate::CommonError> {
        match self {
            ComponentId::HttpField(component) => {


@@ 143,20 143,20 @@ impl<'a> ComponentId<'a> {
                }
            }
            ComponentId::Method => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    result.push_str(req.method().as_str());
                }
                _ => return Err(crate::CommonError::MissingComponent),
            },
            ComponentId::TargetUri => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    use std::fmt::Write;
                    write!(result, "{}", req.uri()).unwrap();
                }
                _ => return Err(crate::CommonError::MissingComponent),
            },
            ComponentId::Authority => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    if let Some(authority) = req.uri().authority() {
                        result.push_str(authority.as_str());
                    } else {


@@ 166,7 166,7 @@ impl<'a> ComponentId<'a> {
                _ => return Err(crate::CommonError::MissingComponent),
            },
            ComponentId::Scheme => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    if let Some(scheme) = req.uri().scheme_str() {
                        result.push_str(scheme);
                    } else {


@@ 176,7 176,7 @@ impl<'a> ComponentId<'a> {
                _ => return Err(crate::CommonError::MissingComponent),
            },
            ComponentId::RequestTarget => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    if let Some(value) = req.uri().path_and_query() {
                        result.push_str(value.as_str());
                    } else {


@@ 186,13 186,13 @@ impl<'a> ComponentId<'a> {
                _ => return Err(crate::CommonError::MissingComponent),
            },
            ComponentId::Path => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    result.push_str(req.uri().path());
                }
                _ => return Err(crate::CommonError::MissingComponent),
            },
            ComponentId::Query => match src {
                RequestOrResponse::Request(req) => {
                RequestOrResponseRef::Request(req) => {
                    result.push('?');
                    result.push_str(req.uri().query().unwrap_or(""));
                }


@@ 202,7 202,7 @@ impl<'a> ComponentId<'a> {
                return Err(crate::CommonError::Unsupported);
            }
            ComponentId::Status => match src {
                RequestOrResponse::Response(res) => {
                RequestOrResponseRef::Response(res) => {
                    result.push_str(res.status().as_str());
                }
                _ => return Err(crate::CommonError::MissingComponent),


@@ 363,7 363,7 @@ impl<'a> HttpbisSignature<'a> {
        name: Cow<'a, str>,
        params: SignatureParams<'a>,
        covered_components: Cow<'a, [ComponentId<'a>]>,
        src: RequestOrResponse<B>,
        src: RequestOrResponseRef<B>,
        req: Option<&'a http::Request<B>>,
        sign: impl FnOnce(&[u8]) -> Result<Vec<u8>, E>,
    ) -> Result<Self, crate::SignError<E>> {


@@ 386,14 386,14 @@ impl<'a> HttpbisSignature<'a> {
        name: impl Into<Cow<'a, str>>,
        params: SignatureParams<'a>,
        covered_components: impl Into<Cow<'a, [ComponentId<'a>]>>,
        request: http::Request<B>,
        request: &http::Request<B>,
        sign: impl FnOnce(&[u8]) -> Result<Vec<u8>, E>,
    ) -> Result<Self, crate::SignError<E>> {
        Self::create_inner(
            name.into(),
            params,
            covered_components.into(),
            RequestOrResponse::Request(request),
            RequestOrResponseRef::Request(request),
            None,
            sign,
        )


@@ 445,7 445,7 @@ impl<'a> HttpbisSignature<'a> {
        Ok(())
    }

    fn parse_inner<B>(src: RequestOrResponse<B>) -> Result<Vec<Self>, crate::ParseError> {
    fn parse_inner<B>(src: RequestOrResponseRef<B>) -> Result<Vec<Self>, crate::ParseError> {
        let headers = src.headers();

        let mut inputs = HashMap::new();


@@ 469,8 469,8 @@ impl<'a> HttpbisSignature<'a> {

                        for (key, value) in list.params {
                            params_src.push(';');
                            <String as AsRef<str>>::as_ref(&key)
                                .serialize_as_bare_item(&mut params_src)?;
                            params_src.push_str(&key);
                            params_src.push('=');
                            match value {
                                sfv::BareItem::Integer(value) => {
                                    value.serialize_as_bare_item(&mut params_src)?


@@ 575,13 575,13 @@ impl<'a> HttpbisSignature<'a> {
        Ok(result)
    }

    pub fn parse_from_request<B>(req: http::Request<B>) -> Result<Vec<Self>, crate::ParseError> {
        Self::parse_inner(RequestOrResponse::Request(req))
    pub fn parse_from_request<B>(req: &http::Request<B>) -> Result<Vec<Self>, crate::ParseError> {
        Self::parse_inner(RequestOrResponseRef::Request(req))
    }

    fn verify_inner<E: std::fmt::Debug, B>(
        &self,
        src: RequestOrResponse<B>,
        src: RequestOrResponseRef<B>,
        req: Option<http::Request<B>>,
        verify: impl FnOnce(&[u8], &[u8]) -> Result<bool, E>,
    ) -> Result<bool, crate::VerifyError<E>> {


@@ 608,10 608,10 @@ impl<'a> HttpbisSignature<'a> {

    pub fn verify_request<E: std::fmt::Debug, B>(
        &self,
        request: http::Request<B>,
        request: &http::Request<B>,
        verify: impl FnOnce(&[u8], &[u8]) -> Result<bool, E>,
    ) -> Result<bool, crate::VerifyError<E>> {
        self.verify_inner(RequestOrResponse::Request(request), None, verify)
        self.verify_inner(RequestOrResponseRef::Request(request), None, verify)
    }

    pub fn params(&self) -> &SignatureParams {


@@ 627,16 627,16 @@ impl<'a> HttpbisSignature<'a> {
    }
}

enum RequestOrResponse<B> {
    Request(http::Request<B>),
    Response(http::Response<B>),
enum RequestOrResponseRef<'a, B> {
    Request(&'a http::Request<B>),
    Response(&'a http::Response<B>),
}

impl<B> RequestOrResponse<B> {
impl<'a, B> RequestOrResponseRef<'a, B> {
    pub fn headers(&self) -> &http::HeaderMap<http::HeaderValue> {
        match self {
            RequestOrResponse::Request(req) => req.headers(),
            RequestOrResponse::Response(res) => res.headers(),
            RequestOrResponseRef::Request(req) => req.headers(),
            RequestOrResponseRef::Response(res) => res.headers(),
        }
    }
}


@@ 730,7 730,7 @@ impl<'a> AsBareItem for i64 {
fn create_signature_base<B>(
    params_src: &str,
    covered_components: &[ComponentId<'_>],
    src: &RequestOrResponse<B>,
    src: &RequestOrResponseRef<B>,
    req: Option<&http::Request<B>>,
) -> Result<String, crate::CommonError> {
    let mut result = String::new();


@@ 765,6 765,23 @@ fn create_signature_base<B>(
mod test {
    use super::*;

    fn get_sample_request() -> http::Request<&'static str> {
        http::Request::builder()
            .method(http::Method::POST)
            .uri("/foo?param=Value&Pet=dog")
            .header(http::header::HOST, "example.com")
            .header(http::header::DATE, "Tue, 20 Apr 2021 02:07:55 GMT")
            .header(http::header::CONTENT_TYPE, "application/json")
            .header(
                "Content-Digest",
                "sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:",
            )
            .header(http::header::CONTENT_LENGTH, "18")
            .body(r#"{"hello": "world"}"#)
            .unwrap()
    }

    #[test]
    fn test_params_minimal() {
        let res = SignatureParams::default().serialize::<()>();


@@ 789,7 806,7 @@ mod test {
    fn test_base_minimal() {
        let req = http::Request::new(());
        let result =
            create_signature_base("", &[], &RequestOrResponse::Request(req), None).unwrap();
            create_signature_base("", &[], &RequestOrResponseRef::Request(&req), None).unwrap();

        assert_eq!(result, r#""@signature-params": ()"#);
    }


@@ 814,7 831,7 @@ mod test {
                )),
                ComponentId::HttpField(HttpFieldComponentId::new(http::header::CONTENT_LENGTH)),
            ],
            &RequestOrResponse::Response(res),
            &RequestOrResponseRef::Response(&res),
            None,
        )
        .unwrap();


@@ 831,21 848,19 @@ mod test {

    #[test]
    fn test_parse_minimal() {
        let req = http::Request::builder()
            .method(http::Method::POST)
            .uri("/foo?param=Value&Pet=dog")
            .header(http::header::HOST, "example.com")
            .header(http::header::DATE, "Tue, 20 Apr 2021 02:07:55 GMT")
            .header(http::header::CONTENT_TYPE, "application/json")
            .header("Content-Digest", "sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+T\
  aPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:")
            .header(http::header::CONTENT_LENGTH, "18")
            .header(SIGNATURE_INPUT_HEADER, r#"sig-b21=();created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd""#)
            .header(crate::SIGNATURE_HEADER, "sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:")
            .body(r#"{"hello": "world"}"#)
            .unwrap();
        let req = {
            let mut req = get_sample_request();

            req.headers_mut()
                .insert(SIGNATURE_INPUT_HEADER, r#"sig-b21=();created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd""#.try_into().unwrap());

            req.headers_mut()
                .insert(crate::SIGNATURE_HEADER, "sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:".try_into().unwrap());

            req
        };

        let result = HttpbisSignature::parse_from_request(req).unwrap();
        let result = HttpbisSignature::parse_from_request(&req).unwrap();
        assert_eq!(result.len(), 1);

        let result = result.into_iter().next().unwrap();


@@ 856,4 871,30 @@ mod test {

        assert!(result.covered_components().is_empty());
    }

    #[test]
    fn test_verify_minimal() {
        let req = {
            let mut req = get_sample_request();

            req.headers_mut()
                .insert(SIGNATURE_INPUT_HEADER, r#"sig-b21=();created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd""#.try_into().unwrap());

            req.headers_mut()
                .insert(crate::SIGNATURE_HEADER, "sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:".try_into().unwrap());

            req
        };

        let sigs = HttpbisSignature::parse_from_request(&req).unwrap();
        assert_eq!(sigs.len(), 1);

        let sig = sigs.first().unwrap();
        let result = sig.verify_request(&req, |content, sig| {
            assert_eq!(content, br#""@signature-params": ();created=1618884473;keyid="test-key-rsa-pss";nonce="b3k2pp5k7z-50gnwp.yemd""#);
            Result::<_, std::convert::Infallible>::Ok(true)
        }).unwrap();

        assert!(result);
    }
}