util: implement Signature header parser
parent
4573bfe7d7
commit
f349ae99c7
@ -0,0 +1,142 @@
|
||||
use crate::util::transcode::base64_decode;
|
||||
use bytes::Bytes;
|
||||
use reqwest::header::HeaderValue;
|
||||
use std::ops::Not;
|
||||
|
||||
use super::{Dictionary, ParseHeader, ParseOptions};
|
||||
|
||||
pub struct SignatureHeader {
|
||||
key_id: String,
|
||||
signature: Bytes,
|
||||
algorithm: Option<String>,
|
||||
created: Option<i64>,
|
||||
expires: Option<i64>,
|
||||
headers: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl SignatureHeader {
|
||||
pub fn from_header(value: &HeaderValue) -> Option<Self> {
|
||||
let dict = Dictionary::parse_from_header(value, ParseOptions::signature_header()).ok()?;
|
||||
|
||||
let key_id = dict.get_nocase("keyid").and_then(|val| val.as_string())?;
|
||||
let signature = dict.get_nocase("signature").and_then(|val| {
|
||||
val.as_byte_sequence()
|
||||
.or_else(|| val.as_string().and_then(|s| base64_decode(&s).ok()))
|
||||
})?;
|
||||
let algorithm = dict.get_nocase("algorithm").and_then(|val| val.as_string());
|
||||
let created = dict.get_nocase("created").and_then(|val| val.as_integer());
|
||||
let expires = dict.get_nocase("expires").and_then(|val| val.as_integer());
|
||||
let headers = dict.get_nocase("headers").and_then(|val| {
|
||||
val.as_string()
|
||||
.map(|list| {
|
||||
list.split(' ')
|
||||
.filter_map(|s| s.is_empty().not().then(|| String::from(s)))
|
||||
.collect()
|
||||
})
|
||||
.or_else(|| {
|
||||
val.as_list()
|
||||
.map(|list| list.iter().filter_map(|e| e.as_string()).collect())
|
||||
})
|
||||
});
|
||||
|
||||
Some(SignatureHeader {
|
||||
key_id,
|
||||
signature,
|
||||
algorithm,
|
||||
created,
|
||||
expires,
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn key_id(&self) -> &str {
|
||||
self.key_id.as_str()
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &[u8] {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
|
||||
pub fn algorithm(&self) -> Option<&str> {
|
||||
self.algorithm.as_deref()
|
||||
}
|
||||
|
||||
pub fn created(&self) -> Option<i64> {
|
||||
self.created
|
||||
}
|
||||
|
||||
pub fn expires(&self) -> Option<i64> {
|
||||
self.expires
|
||||
}
|
||||
|
||||
pub fn headers(&self) -> &[String] {
|
||||
self.headers.as_deref().unwrap_or(&[])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn mksig(header: &'static str) -> Option<SignatureHeader> {
|
||||
SignatureHeader::from_header(&HeaderValue::from_str(header).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_signature1() {
|
||||
let header = mksig(r#"keyId="https://my-example.com/actor#main-key",headers="(request-target) host date",signature="dGhpcyBpcyBub3QgYW4gYWN0dWFsIHNpZ25hdHVyZSBsbWFvCg==""#).unwrap();
|
||||
assert_eq!(header.key_id(), "https://my-example.com/actor#main-key");
|
||||
assert_eq!(header.headers(), &["(request-target)", "host", "date"]);
|
||||
assert_eq!(
|
||||
header.signature(),
|
||||
base64_decode("dGhpcyBpcyBub3QgYW4gYWN0dWFsIHNpZ25hdHVyZSBsbWFvCg==")
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
);
|
||||
assert!(header.algorithm().is_none());
|
||||
assert!(header.created().is_none());
|
||||
assert!(header.expires().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_signature2() {
|
||||
let header = mksig(r#"keyid="rsa-key-1", algorithm="hs2019", created=1402170695, expires=1402170995, headers="(request-target) (created) (expires) host date digest content-length", signature="aGkgaW0gZ2F5IHV3dQo=""#).unwrap();
|
||||
assert_eq!(header.key_id(), "rsa-key-1");
|
||||
assert_eq!(header.algorithm(), Some("hs2019"));
|
||||
assert_eq!(header.created(), Some(1402170695));
|
||||
assert_eq!(header.expires(), Some(1402170995));
|
||||
assert_eq!(
|
||||
header.headers(),
|
||||
&[
|
||||
"(request-target)",
|
||||
"(created)",
|
||||
"(expires)",
|
||||
"host",
|
||||
"date",
|
||||
"digest",
|
||||
"content-length"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
header.signature(),
|
||||
base64_decode("aGkgaW0gZ2F5IHV3dQo=").unwrap().as_ref()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_signature3() {
|
||||
let header = mksig(
|
||||
r#"keyid="a", headers=("@request-target" "digest"), signature=:aGkgaW0gZ2F5IHV3dQo=:"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(header.key_id(), "a");
|
||||
assert_eq!(header.algorithm(), None);
|
||||
assert_eq!(header.created(), None);
|
||||
assert_eq!(header.expires(), None);
|
||||
assert_eq!(header.headers(), &["@request-target", "digest"]);
|
||||
assert_eq!(
|
||||
header.signature(),
|
||||
base64_decode("aGkgaW0gZ2F5IHV3dQo=").unwrap().as_ref()
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue