Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion httpsig-hyper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ indexmap = { version = "2.11.1" }

# content digest with rfc8941 structured field values
sha2 = { version = "0.10.9", default-features = false }
sfv = { version = "0.10.4" }
sfv = { version = "0.14.0" }

# encoding
base64 = { version = "0.22.1" }
Expand Down
11 changes: 6 additions & 5 deletions httpsig-hyper/src/hyper_content_digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use bytes::Bytes;
use http::{Request, Response};
use http_body::Body;
use http_body_util::{combinators::BoxBody, BodyExt, Full};
use sfv::FromStr;
use sha2::Digest;
use std::future::Future;
use std::str::FromStr;

// hyper's http specific extension to generate and verify http signature

Expand Down Expand Up @@ -209,20 +209,21 @@ async fn extract_content_digest(header_map: &http::HeaderMap) -> HyperDigestResu
.get(CONTENT_DIGEST_HEADER)
.ok_or(HyperDigestError::NoDigestHeader("No content-digest header".to_string()))?
.to_str()?;
let indexmap = sfv::Parser::parse_dictionary(content_digest_header.as_bytes())
let indexmap = sfv::Parser::new(content_digest_header)
.parse::<sfv::Dictionary>()
.map_err(|e| HyperDigestError::InvalidHeaderValue(e.to_string()))?;
if indexmap.len() != 1 {
return Err(HyperDigestError::InvalidHeaderValue(
"Content-Digest header should have only one value".to_string(),
));
};
let (cd_type, cd) = indexmap.iter().next().unwrap();
let cd_type = ContentDigestType::from_str(cd_type)
let cd_type = ContentDigestType::from_str(cd_type.as_str())
.map_err(|e| HyperDigestError::InvalidHeaderValue(format!("Invalid Content-Digest type: {e}")))?;
if !matches!(
cd,
sfv::ListEntry::Item(sfv::Item {
bare_item: sfv::BareItem::ByteSeq(_),
bare_item: sfv::BareItem::ByteSequence(_),
..
})
) {
Expand All @@ -233,7 +234,7 @@ async fn extract_content_digest(header_map: &http::HeaderMap) -> HyperDigestResu

let cd = match cd {
sfv::ListEntry::Item(sfv::Item {
bare_item: sfv::BareItem::ByteSeq(cd),
bare_item: sfv::BareItem::ByteSequence(cd),
..
}) => cd,
_ => unreachable!(),
Expand Down
2 changes: 1 addition & 1 deletion httpsig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ bytes = { version = "1.10.1" }
base64 = { version = "0.22.1" }

# for rfc8941 structured field values
sfv = { version = "0.10.4" }
sfv = { version = "0.14.0" }

[dev-dependencies]
rand-085 = { package = "rand", version = "0.8.5" } # testing only
12 changes: 9 additions & 3 deletions httpsig/src/message_component/component_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ impl TryFrom<&str> for HttpMessageComponentId {
/// But accept string in the form of `<name>` (without double quotations) when no param is given
fn try_from(val: &str) -> HttpSigResult<Self> {
let val = val.trim();
let item = if !val.starts_with('"') && !val.ends_with('"') && !val.is_empty() && !val.contains('"') {
let item: sfv::Item = if !val.starts_with('"') && !val.ends_with('"') && !val.is_empty() && !val.contains('"') {
// maybe insufficient, but it's enough for now
Parser::parse_item(format!("\"{val}\"").as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
Parser::new(format!("\"{val}\"").as_str())
.parse()
.map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
// Parser::parse_item(format!("\"{val}\"").as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
} else {
Parser::parse_item(val.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
Parser::new(val)
.parse()
.map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
// Parser::parse_item(val.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
};

let res = Self {
Expand Down
2 changes: 1 addition & 1 deletion httpsig/src/message_component/component_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl TryFrom<&BareItem> for HttpMessageComponentName {
fn try_from(value: &BareItem) -> HttpSigResult<Self> {
match value {
BareItem::String(name) => {
if name.starts_with('@') {
if name.as_str().starts_with('@') {
Ok(Self::Derived(DerivedComponentName::from(name.as_str())))
} else {
Ok(Self::HttpField(name.to_string()))
Expand Down
36 changes: 19 additions & 17 deletions httpsig/src/message_component/component_param.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::{HttpSigError, HttpSigResult};
use sfv::{Parser, SerializeValue};
use sfv::{FieldType, Parser};

type IndexSet<K> = indexmap::IndexSet<K, rustc_hash::FxBuildHasher>;

Expand Down Expand Up @@ -46,13 +46,13 @@ impl TryFrom<(&str, &sfv::BareItem)> for HttpMessageComponentParam {
"tr" => Ok(Self::Tr),
"req" => Ok(Self::Req),
"name" => {
let name = val.as_str().ok_or(HttpSigError::InvalidComponentParam(
let name = val.as_string().ok_or(HttpSigError::InvalidComponentParam(
"Invalid http field param: name".to_string(),
))?;
Ok(Self::Name(name.to_string()))
}
"key" => {
let key = val.as_str().ok_or(HttpSigError::InvalidComponentParam(
let key = val.as_string().ok_or(HttpSigError::InvalidComponentParam(
"Invalid http field param: key".to_string(),
))?;
Ok(Self::Key(key.to_string()))
Expand Down Expand Up @@ -106,10 +106,10 @@ pub(super) fn handle_params_sf(field_values: &mut [String]) -> HttpSigResult<()>
let parsed_list = field_values
.iter()
.map(|v| {
if let Ok(list) = Parser::parse_list(v.as_bytes()) {
list.serialize_value()
} else if let Ok(dict) = Parser::parse_dictionary(v.as_bytes()) {
dict.serialize_value()
if let Ok(list) = Parser::new(v).parse::<sfv::List>() {
list.serialize().ok_or("Failed to serialize structured field value for sf")
} else if let Ok(dict) = Parser::new(v).parse::<sfv::Dictionary>() {
dict.serialize().ok_or("Failed to serialize structured field value for sf")
} else {
Err("invalid structured field value for sf")
}
Expand All @@ -129,7 +129,8 @@ pub(super) fn handle_params_sf(field_values: &mut [String]) -> HttpSigResult<()>
pub(super) fn handle_params_key_into(field_values: &[String], key: &str) -> HttpSigResult<Vec<String>> {
let dicts = field_values
.iter()
.map(|v| Parser::parse_dictionary(v.as_bytes()))
.map(|v| Parser::new(v.as_str()).parse() as Result<sfv::Dictionary, _>)
// Parser::parse_dictionary(v.as_bytes()))
.collect::<Result<Vec<_>, _>>()
.map_err(|e| HttpSigError::InvalidComponentParam(format!("Failed to parse structured field value: {e}")))?;

Expand All @@ -138,11 +139,12 @@ pub(super) fn handle_params_key_into(field_values: &[String], key: &str) -> Http
.filter_map(|dict| {
dict.get(key).map(|v| {
let sfvalue: sfv::List = vec![v.clone()];
sfvalue.serialize_value()
// sfvalue.serialize_value()
sfvalue.serialize()
})
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| HttpSigError::InvalidComponentParam(format!("Failed to serialize structured field value: {e}")))?;
.collect::<Option<Vec<_>>>()
.ok_or_else(|| HttpSigError::InvalidComponentParam(format!("Failed to serialize structured field value")))?;

Ok(found_entries)
}
Expand All @@ -157,19 +159,19 @@ mod tests {
fn parser_test() {
// Parsing structured field value of Item type.
let item_header_input = "12.445;foo=bar";
let item = Parser::parse_item(item_header_input.as_bytes()).unwrap();
assert_eq!(item.serialize_value().unwrap(), item_header_input);
let item = Parser::new(item_header_input).parse::<sfv::Item>().unwrap();
assert_eq!(item.serialize(), item_header_input);

// Parsing structured field value of List type.
let list_header_input = " 1; a=tok, (\"foo\" \"bar\" );baz, ( )";
let list = Parser::parse_list(list_header_input.as_bytes()).unwrap();
assert_eq!(list.serialize_value().unwrap(), "1;a=tok, (\"foo\" \"bar\");baz, ()");
let list = Parser::new(list_header_input).parse::<sfv::List>().unwrap();
assert_eq!(list.serialize().unwrap(), "1;a=tok, (\"foo\" \"bar\");baz, ()");

// Parsing structured field value of Dictionary type.
let dict_header_input = "a=?0, b, c; foo=bar, rating=1.5, fruits=(apple pear), d";
let dict = Parser::parse_dictionary(dict_header_input.as_bytes()).unwrap();
let dict = Parser::new(dict_header_input).parse::<sfv::Dictionary>().unwrap();
assert_eq!(
dict.serialize_value().unwrap(),
dict.serialize().unwrap(),
"a=?0, b, c;foo=bar, rating=1.5, fruits=(apple pear), d"
);
}
Expand Down
20 changes: 13 additions & 7 deletions httpsig/src/signature_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::{
signature_params::HttpSignatureParams,
};
use base64::{engine::general_purpose, Engine as _};
use rustc_hash::FxBuildHasher;
use indexmap::IndexMap;
use rustc_hash::FxBuildHasher;
use sfv::{BareItem, Item, ListEntry, Parser};

/// IndexMap of signature name and HttpSignatureHeaders
Expand All @@ -30,10 +30,16 @@ pub struct HttpSignatureHeaders {
impl HttpSignatureHeaders {
/// Generates (possibly multiple) HttpSignatureHeaders from signature and signature-input header values
pub fn try_parse(signature_header: &str, signature_input_header: &str) -> HttpSigResult<HttpSignatureHeadersMap> {
let signature_input =
Parser::parse_dictionary(signature_input_header.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
let signature =
Parser::parse_dictionary(signature_header.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
let signature_input: sfv::Dictionary = Parser::new(signature_input_header)
.parse()
.map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
let signature: sfv::Dictionary = Parser::new(signature_header)
.parse()
.map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
// let signature_input =
// Parser::parse_dictionary(signature_input_header.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
// let signature =
// Parser::parse_dictionary(signature_header.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;

if signature.len() != signature_input.len() {
return Err(HttpSigError::BuildSignatureHeaderError(
Expand All @@ -50,7 +56,7 @@ impl HttpSignatureHeaders {
matches!(
v,
ListEntry::Item(Item {
bare_item: BareItem::ByteSeq(_),
bare_item: BareItem::ByteSequence(_),
..
})
)
Expand All @@ -73,7 +79,7 @@ impl HttpSignatureHeaders {

let signature_bytes = match signature.get(k) {
Some(ListEntry::Item(Item {
bare_item: BareItem::ByteSeq(v),
bare_item: BareItem::ByteSequence(v),
..
})) => v,
_ => unreachable!(),
Expand Down
26 changes: 15 additions & 11 deletions httpsig/src/signature_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
};
use base64::{engine::general_purpose, Engine as _};
use rand::Rng;
use sfv::{ListEntry, Parser, SerializeValue};
use sfv::{FieldType, ListEntry, Parser};
use std::time::{SystemTime, UNIX_EPOCH};

const DEFAULT_DURATION: u64 = 300;
Expand Down Expand Up @@ -167,9 +167,10 @@ impl TryFrom<&ListEntry> for HttpSignatureParams {
.items
.iter()
.map(|v| {
v.serialize_value()
.map_err(|e| HttpSigError::ParseSFVError(e.to_string()))
.and_then(|v| HttpMessageComponentId::try_from(v.as_str()))
HttpMessageComponentId::try_from(v.serialize().as_str())
// v.serialize_value()
// .map_err(|e| HttpSigError::ParseSFVError(e.to_string()))
// .and_then(|v| HttpMessageComponentId::try_from(v.as_str()))
})
.collect::<Result<Vec<_>, _>>()?;

Expand All @@ -193,12 +194,12 @@ impl TryFrom<&ListEntry> for HttpSignatureParams {
.params
.iter()
.for_each(|(key, bare_item)| match key.as_str() {
"created" => params.created = bare_item.as_int().map(|v| v as u64),
"expires" => params.expires = bare_item.as_int().map(|v| v as u64),
"nonce" => params.nonce = bare_item.as_str().map(|v| v.to_string()),
"alg" => params.alg = bare_item.as_str().map(|v| v.to_string()),
"keyid" => params.keyid = bare_item.as_str().map(|v| v.to_string()),
"tag" => params.tag = bare_item.as_str().map(|v| v.to_string()),
"created" => params.created = bare_item.as_integer().map(|v| v.try_into().unwrap()),
"expires" => params.expires = bare_item.as_integer().map(|v| v.try_into().unwrap()),
"nonce" => params.nonce = bare_item.as_string().map(|v| v.to_string()),
"alg" => params.alg = bare_item.as_string().map(|v| v.to_string()),
"keyid" => params.keyid = bare_item.as_string().map(|v| v.to_string()),
"tag" => params.tag = bare_item.as_string().map(|v| v.to_string()),
_ => {
error!("Ignore invalid signature parameter: {}", key)
}
Expand All @@ -211,7 +212,10 @@ impl TryFrom<&str> for HttpSignatureParams {
type Error = HttpSigError;
/// Convert from string to HttpSignatureParams
fn try_from(value: &str) -> HttpSigResult<Self> {
let sfv_parsed = Parser::parse_list(value.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
let sfv_parsed: sfv::List = Parser::new(value)
.parse()
.map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
// let sfv_parsed = Parser::parse_list(value.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?;
if sfv_parsed.len() != 1 || !matches!(sfv_parsed[0], ListEntry::InnerList(_)) {
return Err(HttpSigError::InvalidSignatureParams("Invalid signature params".to_string()));
}
Expand Down