~vpzom/hancock

2bae9749cbb08e8ffcec4b0bc821ee45a7b79cc4 — Colin Reeder 10 months ago a17a024
More progress
2 files changed, 130 insertions(+), 1 deletions(-)

M src/httpbis.rs
M src/lib.rs
M src/httpbis.rs => src/httpbis.rs +107 -1
@@ 465,8 465,27 @@ impl<'a> HttpbisSignature<'a> {
                            .collect::<Result<_, _>>()?;

                        let mut params = SignatureParams::default();
                        let mut params_src = String::new();

                        for (key, value) in list.params {
                            params_src.push(';');
                            <String as AsRef<str>>::as_ref(&key)
                                .serialize_as_bare_item(&mut params_src)?;
                            match value {
                                sfv::BareItem::Integer(value) => {
                                    value.serialize_as_bare_item(&mut params_src)?
                                }
                                sfv::BareItem::String(ref value) => {
                                    <String as AsRef<str>>::as_ref(&value)
                                        .serialize_as_bare_item(&mut params_src)?
                                }
                                _ => {
                                    // all supported parameters use those types so others are
                                    // invalid
                                    return Err(crate::ParseError::InvalidStructure);
                                }
                            }

                            if key == "created" {
                                params.created = match value {
                                    sfv::BareItem::Integer(value) => Some(


@@ 510,7 529,7 @@ impl<'a> HttpbisSignature<'a> {
                            }
                        }

                        inputs.insert(name, (covered_components, params));
                        inputs.insert(name, (covered_components, params, params_src));
                    }
                }
            }


@@ 518,9 537,48 @@ impl<'a> HttpbisSignature<'a> {

        let mut result = Vec::new();

        for value in headers.get_all(crate::SIGNATURE_HEADER) {
            let dict = sfv::Parser::parse_dictionary(value.as_bytes())
                .map_err(crate::ParseError::InvalidSyntax)?;

            for (name, value) in dict {
                let input = match inputs.remove(&name) {
                    Some(input) => input,
                    None => continue, // maybe should throw an error?
                };

                match value {
                    sfv::ListEntry::Item(value) => {
                        if !value.params.is_empty() {
                            return Err(crate::ParseError::UnknownParam);
                        }

                        if let sfv::BareItem::ByteSeq(content) = value.bare_item {
                            result.push(Self {
                                name: name.into(),
                                params: input.1,
                                params_src: input.2.into(),
                                covered_components: input.0.into(),
                                signature: content,
                            });
                        } else {
                            return Err(crate::ParseError::InvalidStructure);
                        }
                    }
                    sfv::ListEntry::InnerList(_) => {
                        return Err(crate::ParseError::InvalidStructure)
                    }
                }
            }
        }

        Ok(result)
    }

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

    fn verify_inner<E: std::fmt::Debug, B>(
        &self,
        src: RequestOrResponse<B>,


@@ 555,6 613,18 @@ impl<'a> HttpbisSignature<'a> {
    ) -> Result<bool, crate::VerifyError<E>> {
        self.verify_inner(RequestOrResponse::Request(request), None, verify)
    }

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

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

    pub fn covered_components(&self) -> &[ComponentId<'_>] {
        &self.covered_components
    }
}

enum RequestOrResponse<B> {


@@ 649,6 719,14 @@ impl<'a> AsBareItem for &Cow<'a, str> {
    }
}

impl<'a> AsBareItem for i64 {
    fn serialize_as_bare_item(&self, result: &mut String) -> Result<(), crate::CommonError> {
        use std::fmt::Write;
        write!(result, "{}", self).unwrap();
        Ok(())
    }
}

fn create_signature_base<B>(
    params_src: &str,
    covered_components: &[ComponentId<'_>],


@@ 750,4 828,32 @@ mod test {
"@signature-params": ("@status" "content-type" "content-digest" "content-length");created=1618884473;keyid="test-key-ecc-p256""#,
        );
    }

    #[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 result = HttpbisSignature::parse_from_request(req).unwrap();
        assert_eq!(result.len(), 1);

        let result = result.into_iter().next().unwrap();
        assert_eq!(result.name(), "sig-b21");

        let params = result.params();
        assert_eq!(params.created, Some(1618884473));

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

M src/lib.rs => src/lib.rs +23 -0
@@ 44,6 44,18 @@ pub enum ParseError {

    #[error("A parameter value was outside of supported range")]
    ValueOutOfRange,

    #[error("An invalid character was found")]
    InvalidCharacter,

    #[error("Specified parameters cannot be used together")]
    ConflictingParams,

    #[error("A requested component was not found")]
    MissingComponent,

    #[error("Attempted to use an unimplemented feature")]
    Unsupported,
}

/// Errors that may be produced when creating a signature


@@ 100,6 112,17 @@ impl<T: std::fmt::Debug> From<CommonError> for VerifyError<T> {
    }
}

impl From<CommonError> for ParseError {
    fn from(src: CommonError) -> Self {
        match src {
            CommonError::InvalidCharacter => Self::InvalidCharacter,
            CommonError::ConflictingParams => Self::ConflictingParams,
            CommonError::MissingComponent => Self::MissingComponent,
            CommonError::Unsupported => Self::Unsupported,
        }
    }
}

/// Errors that may be produced when verifying a signature
#[derive(Debug, thiserror::Error)]
pub enum VerifyError<T: std::fmt::Debug> {