Skip to content

Commit

Permalink
wip: signature base design
Browse files Browse the repository at this point in the history
  • Loading branch information
junkurihara committed Jan 24, 2024
1 parent 2ae426b commit 1b23161
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 32 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ sha2 = { version = "0.10.8", default-features = false }

# encoding
base64 = { version = "0.21.6" }

# # for request and response headers
# http = "1.0.0"
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod util;

use crate::{
crypto::{PublicKey, SecretKey, SigningKey, VerifyingKey},
signature_params::{HttpSignatureParams, HttpSignatureParamsBuildConfig},
signature_params::{HttpSignatureParams, HttpSignatureParamsBuilder},
};

#[cfg(test)]
Expand Down
41 changes: 34 additions & 7 deletions src/message_component.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use rustc_hash::FxHashSet as HashSet;

/* ---------------------------------------------------------------- */
#[derive(Debug, Clone)]
/// Http message component
pub(crate) struct HttpMessageComponent {
/// Http message component identifier
id: HttpMessageComponentIdentifier,
pub(crate) id: HttpMessageComponentIdentifier,
/// Http message component value
value: HttpMessageComponentValue,
pub(crate) value: HttpMessageComponentValue,
}

impl TryFrom<&str> for HttpMessageComponent {
Expand All @@ -15,13 +16,38 @@ impl TryFrom<&str> for HttpMessageComponent {
let Some((id, value)) = val.split_once(':') else {
return Err(anyhow::anyhow!("Invalid http message component: {}", val));
};
let id = id.trim();
if ensure_component_id(id).is_err() {
return Err(anyhow::anyhow!("Invalid http message component id: {}", id));
}
Ok(Self {
id: HttpMessageComponentIdentifier::from(id.trim()),
id: HttpMessageComponentIdentifier::from(id),
value: HttpMessageComponentValue::from(value.trim()),
})
}
}

impl TryFrom<(&str, &str)> for HttpMessageComponent {
type Error = anyhow::Error;
fn try_from((k, v): (&str, &str)) -> std::result::Result<Self, anyhow::Error> {
let id = k.trim();
if ensure_component_id(id).is_err() {
return Err(anyhow::anyhow!("Invalid http message component id: {}", id));
}
Ok(Self {
id: HttpMessageComponentIdentifier::from(id),
value: HttpMessageComponentValue::from(v.trim()),
})
}
}

fn ensure_component_id(id: &str) -> anyhow::Result<()> {
if !id.starts_with('"') || !(id.ends_with('"') || id[1..].contains("\";")) {
return Err(anyhow::anyhow!("Invalid http message component id: {}", id));
}
Ok(())
}

impl std::fmt::Display for HttpMessageComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// This always can append single trailing space (SP) for empty value
Expand All @@ -31,6 +57,7 @@ impl std::fmt::Display for HttpMessageComponent {
}

/* ---------------------------------------------------------------- */
#[derive(Debug, Clone)]
/// Http message component value
pub(crate) struct HttpMessageComponentValue {
/// inner value originally from http message header or derived from http message
Expand All @@ -50,7 +77,7 @@ impl std::fmt::Display for HttpMessageComponentValue {
}

/* ---------------------------------------------------------------- */
#[derive(PartialEq, Eq, Hash, Debug)]
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
/// Http message component identifier
pub(crate) enum HttpMessageComponentIdentifier {
/// Http field component
Expand Down Expand Up @@ -125,7 +152,7 @@ impl From<&str> for HttpMessageComponentParam {
}
}

#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub(crate) struct HttpMessageComponentParams(pub(crate) HashSet<HttpMessageComponentParam>);
impl std::hash::Hash for HttpMessageComponentParams {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Expand Down Expand Up @@ -187,7 +214,7 @@ impl From<&str> for DerivedComponentName {
}
}

#[derive(PartialEq, Eq, Hash, Debug)]
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
/// Http derived component setting with optional parameters
pub(crate) struct DerivedComponentId {
/// derived component
Expand Down Expand Up @@ -232,7 +259,7 @@ impl std::fmt::Display for DerivedComponentId {
}

/* ---------------------------------------------------------------- */
#[derive(PartialEq, Eq, Hash, Debug)]
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
/// Http field component setting with optional parameters
/// https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-http-fields
pub(crate) struct HttpFieldComponentId {
Expand Down
76 changes: 74 additions & 2 deletions src/signature_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,78 @@ struct SignatureBase {
signature_params: HttpSignatureParams,
}

// creating signature base from http header lines and signature params builder config
impl SignatureBase {
pub(crate) fn try_new(
component_lines: &Vec<HttpMessageComponent>,
signature_params: &HttpSignatureParams,
) -> anyhow::Result<Self> {
// check if the order of component lines is the same as the order of covered message component ids
if component_lines.len() != signature_params.covered_components.len() {
anyhow::bail!("The number of component lines is not the same as the number of covered message component ids");
}

// creating signature base from http header lines including signature params
let assertion = component_lines
.iter()
.zip(signature_params.covered_components.iter())
.all(|(component_line, covered_component_id)| {
let component_line_id_string = &component_line.id.to_string();
component_line_id_string == covered_component_id
});
if !assertion {
anyhow::bail!("The order of component lines is not the same as the order of covered message component ids");
}

Ok(Self {
component_lines: component_lines.clone(),
signature_params: signature_params.clone(),
})
}
}

impl std::fmt::Display for SignatureBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut signature_base = String::new();
for component_line in &self.component_lines {
signature_base.push_str(&component_line.to_string());
signature_base.push('\n');
}
signature_base.push_str(&format!("\"@signature-params\": {}", self.signature_params.to_string()));
write!(f, "{}", signature_base)
}
}

// TODO: creating signature base from http header lines and signature params builder config

// TODO: creating signature base from http header lines including signature params

#[cfg(test)]
mod test {
use super::*;
use crate::signature_params::HttpSignatureParamsBuilder;

#[test]
fn test_signature_base() {
let mut signature_params = HttpSignatureParamsBuilder::default();
signature_params.created(1706091731);
signature_params.key_id("key_id");
signature_params.algorithm("rsa-sha256");
signature_params.covered_message_component_ids(&["\"@method\";req", "\"date\"", "\"content-digest\""]);
let signature_params = signature_params.build();

let component_lines = vec![
HttpMessageComponent::try_from(("\"@method\";req", "GET")).unwrap(),
HttpMessageComponent::try_from(("\"date\"", "Tue, 07 Jun 2014 20:51:35 GMT")).unwrap(),
HttpMessageComponent::try_from((
"\"content-digest\"",
"SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
))
.unwrap(),
];
let signature_base = SignatureBase::try_new(&component_lines, &signature_params).unwrap();
let test_string = r##""@method";req: GET
"date": Tue, 07 Jun 2014 20:51:35 GMT
"content-digest": SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
"@signature-params": ("@method";req "date" "content-digest");created=1706091731;alg="rsa-sha256";keyid="key_id""##;
assert_eq!(signature_base.to_string(), test_string);
}
}
61 changes: 39 additions & 22 deletions src/signature_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ use std::time::{SystemTime, UNIX_EPOCH};

const DEFAULT_EXPIRES_IN: u64 = 300;

#[derive(Debug, Clone)]
/// Struct defining Http message signature parameters
/// https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-signature-parameters
pub struct HttpSignatureParams {
/// created unix timestamp.
created: Option<u64>,
pub(crate) created: Option<u64>,
/// signature expires unix timestamp.
expires: Option<u64>,
pub(crate) expires: Option<u64>,
/// nonce
nonce: Option<String>,
pub(crate) nonce: Option<String>,
/// algorithm name
alg: Option<String>,
pub(crate) alg: Option<String>,
/// key id.
keyid: Option<String>,
pub(crate) keyid: Option<String>,
/// tag
tag: Option<String>,
pub(crate) tag: Option<String>,
/// covered component vector string: ordered message components, i.e., string of http_fields and derived_components
covered_components: Vec<String>,
pub(crate) covered_components: Vec<String>,
}

impl std::fmt::Display for HttpSignatureParams {
Expand Down Expand Up @@ -99,9 +100,9 @@ impl TryFrom<&str> for HttpSignatureParams {
}
}

/// Configuration for Http message signature parameters
/// Builder of Http message signature parameters
/// https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-19.html#name-signature-parameters
pub struct HttpSignatureParamsBuildConfig {
pub struct HttpSignatureParamsBuilder {
/// created unix timestamp. if none and set_created = false, use current timestamp
created: Option<u64>,
/// if true, `created` is set.
Expand All @@ -126,7 +127,7 @@ pub struct HttpSignatureParamsBuildConfig {
covered_components: Vec<HttpMessageComponentIdentifier>,
}

impl Default for HttpSignatureParamsBuildConfig {
impl Default for HttpSignatureParamsBuilder {
fn default() -> Self {
Self {
created: None,
Expand All @@ -143,14 +144,30 @@ impl Default for HttpSignatureParamsBuildConfig {
}
}

impl HttpSignatureParamsBuildConfig {
impl HttpSignatureParamsBuilder {
/// Set key id
pub fn key_id(&mut self, keyid: &str) -> &mut Self {
self.keyid = Some(keyid.to_string());
self
}
/// set algorithm
pub fn algorithm(&mut self, alg: &str) -> &mut Self {
self.alg = Some(alg.to_string());
self
}
/// Set keyid and alg from the key
pub fn set_key_info(&mut self, key: &impl VerifyingKey) {
pub fn key_info(&mut self, key: &impl VerifyingKey) {
self.keyid = Some(key.key_id().to_string());
self.alg = Some(key.alg().to_string());
}
/// Set artificial created
pub fn created(&mut self, created: u64) -> &mut Self {
self.created = Some(created);
self.set_created = true;
self
}
/// Set covered components
pub fn set_covered_message_component_ids(&mut self, components: &[&str]) {
pub fn covered_message_component_ids(&mut self, components: &[&str]) {
self.covered_components = components
.iter()
.map(|&c| HttpMessageComponentIdentifier::from(c))
Expand All @@ -167,8 +184,8 @@ impl HttpSignatureParamsBuildConfig {
}

/// Derive HttpSignatureParams object
pub fn derive_http_signature_params(&self) -> HttpSignatureParams {
let mut covered_components = self
pub fn build(&self) -> HttpSignatureParams {
let covered_components = self
.covered_components
.iter()
.map(|c| c.to_string())
Expand Down Expand Up @@ -232,22 +249,22 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=

#[test]
fn test_set_key_info() {
let mut params = HttpSignatureParamsBuildConfig::default();
params.set_covered_message_component_ids(&["\"@method\"", "\"@path\";bs", "\"@authority\""]);
let mut params = HttpSignatureParamsBuilder::default();
params.covered_message_component_ids(&["\"@method\"", "\"@path\";bs", "\"@authority\""]);
let x = vec!["\"@method\"", "\"@path\";bs", "\"@authority\""]
.into_iter()
.map(HttpMessageComponentIdentifier::from)
.collect::<Vec<_>>();
assert_eq!(params.covered_components, x);
params.set_key_info(&PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap());
params.key_info(&PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap());
assert_eq!(params.keyid, Some(EDDSA_KEY_ID.to_string()));
assert_eq!(params.alg, Some("ed25519".to_string()));
}

#[test]
fn test_http_signature_params() {
let mut params = HttpSignatureParamsBuildConfig::default();
params.set_covered_message_component_ids(&[
let mut params = HttpSignatureParamsBuilder::default();
params.covered_message_component_ids(&[
"\"@method\"",
"\"@path\";bs",
"\"@authority\"",
Expand All @@ -256,9 +273,9 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
"\"content-type\";bs",
"\"content-length\"",
]);
params.set_key_info(&PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap());
params.key_info(&PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap());

let params = params.derive_http_signature_params();
let params = params.build();
assert!(params.created.is_some());
assert!(params.expires.is_none());
assert!(params.nonce.is_none());
Expand Down

0 comments on commit 1b23161

Please sign in to comment.