diff --git a/httpsig/Cargo.toml b/httpsig/Cargo.toml index d60a37a..dff84af 100644 --- a/httpsig/Cargo.toml +++ b/httpsig/Cargo.toml @@ -14,6 +14,7 @@ rust-version.workspace = true [dependencies] anyhow = { version = "1.0.79" } +thiserror = { version = "1.0.57" } tracing = { version = "0.1.40" } rustc-hash = { version = "1.1.0" } indexmap = { version = "2.2.3" } diff --git a/httpsig/src/crypto/asymmetric.rs b/httpsig/src/crypto/asymmetric.rs index a086648..60764fb 100644 --- a/httpsig/src/crypto/asymmetric.rs +++ b/httpsig/src/crypto/asymmetric.rs @@ -1,5 +1,8 @@ use super::AlgorithmName; -use anyhow::{anyhow, bail, ensure, Result}; +use crate::{ + error::{HttpSigError, HttpSigResult}, + trace::*, +}; use ecdsa::{ elliptic_curve::{sec1::ToEncodedPoint, PublicKey as EcPublicKey, SecretKey as EcSecretKey}, signature::{DigestSigner, DigestVerifier}, @@ -10,7 +13,6 @@ use p384::NistP384; use pkcs8::{der::Decode, Document, PrivateKeyInfo}; use sha2::{Digest, Sha256, Sha384}; use spki::SubjectPublicKeyInfoRef; -use tracing::debug; #[allow(non_upper_case_globals, dead_code)] /// Algorithm OIDs @@ -44,8 +46,8 @@ pub enum SecretKey { impl SecretKey { /// parse der /// Derive secret key from der bytes - pub fn from_der(der: &[u8]) -> Result { - let pki = PrivateKeyInfo::from_der(der).map_err(|e| anyhow!("Error decoding private key: {}", e))?; + pub fn from_der(der: &[u8]) -> HttpSigResult { + let pki = PrivateKeyInfo::from_der(der).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; match pki.algorithm.oid.to_string().as_ref() { // ec @@ -54,20 +56,22 @@ impl SecretKey { let param = pki .algorithm .parameters_oid() - .map_err(|e| anyhow!("Error decoding private key: {}", e))?; + .map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; let sk_bytes = sec1::EcPrivateKey::try_from(pki.private_key) - .map_err(|e| anyhow!("Error decoding EcPrivateKey: {e}"))? + .map_err(|e| HttpSigError::ParsePrivateKeyError(format!("Error decoding EcPrivateKey: {e}")))? .private_key; match param.to_string().as_ref() { params_oids::Secp256r1 => { - let sk = p256::SecretKey::from_bytes(sk_bytes.into()).map_err(|e| anyhow!("Error decoding private key: {}", e))?; + let sk = + p256::SecretKey::from_bytes(sk_bytes.into()).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; Ok(Self::EcdsaP256Sha256(sk)) } params_oids::Secp384r1 => { - let sk = p384::SecretKey::from_bytes(sk_bytes.into()).map_err(|e| anyhow!("Error decoding private key: {}", e))?; + let sk = + p384::SecretKey::from_bytes(sk_bytes.into()).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; Ok(Self::EcdsaP384Sha384(sk)) } - _ => bail!("Unsupported curve"), + _ => Err(HttpSigError::ParsePrivateKeyError("Unsupported curve".to_string())), } } // ed25519 @@ -78,14 +82,18 @@ impl SecretKey { let sk = ed25519_compact::KeyPair::from_seed(ed25519_compact::Seed::new(seed)).sk; Ok(Self::Ed25519(sk)) } - _ => bail!("Unsupported algorithm that supports PEM format keys"), + _ => Err(HttpSigError::ParsePrivateKeyError( + "Unsupported algorithm that supports PEM format keys".to_string(), + )), } } /// Derive secret key from pem string - pub fn from_pem(pem: &str) -> Result { - let (tag, doc) = Document::from_pem(pem).map_err(|e| anyhow!("Error decoding private key: {}", e))?; - ensure!(tag == "PRIVATE KEY", "Invalid tag"); + pub fn from_pem(pem: &str) -> HttpSigResult { + let (tag, doc) = Document::from_pem(pem).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; + if tag != "PRIVATE KEY" { + return Err(HttpSigError::ParsePrivateKeyError("Invalid tag".to_string())); + }; Self::from_der(doc.as_bytes()) } @@ -101,7 +109,7 @@ impl SecretKey { impl super::SigningKey for SecretKey { /// Sign data - fn sign(&self, data: &[u8]) -> Result> { + fn sign(&self, data: &[u8]) -> HttpSigResult> { match &self { Self::EcdsaP256Sha256(sk) => { let sk = ecdsa::SigningKey::from(sk); @@ -136,7 +144,7 @@ impl super::SigningKey for SecretKey { } impl super::VerifyingKey for SecretKey { - fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> { + fn verify(&self, data: &[u8], signature: &[u8]) -> HttpSigResult<()> { self.public_key().verify(data, signature) } @@ -164,68 +172,83 @@ pub enum PublicKey { impl PublicKey { #[allow(dead_code)] /// Convert from pem string - pub fn from_pem(pem: &str) -> Result { - let (tag, doc) = Document::from_pem(pem).map_err(|e| anyhow!("Error decoding public key: {}", e))?; - ensure!(tag == "PUBLIC KEY", "Invalid tag"); - let spki_ref = SubjectPublicKeyInfoRef::from_der(doc.as_bytes()).map_err(|e| anyhow!("Error decoding public key: {}", e))?; + pub fn from_pem(pem: &str) -> HttpSigResult { + let (tag, doc) = Document::from_pem(pem).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; + if tag != "PUBLIC KEY" { + return Err(HttpSigError::ParsePublicKeyError("Invalid tag".to_string())); + }; + + let spki_ref = SubjectPublicKeyInfoRef::from_der(doc.as_bytes()) + .map_err(|e| HttpSigError::ParsePublicKeyError(format!("Error decoding SubjectPublicKeyInfo: {e}").to_string()))?; match spki_ref.algorithm.oid.to_string().as_ref() { // ec algorithm_oids::EC => { let param = spki_ref .algorithm .parameters_oid() - .map_err(|e| anyhow!("Error decoding public key: {}", e))?; - let public_key = spki_ref.subject_public_key.as_bytes().ok_or(anyhow!("Invalid public key"))?; + .map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; + let public_key = spki_ref + .subject_public_key + .as_bytes() + .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))?; match param.to_string().as_ref() { params_oids::Secp256r1 => { - let pk = - EcPublicKey::::from_sec1_bytes(public_key).map_err(|e| anyhow!("Error decoding public key: {}", e))?; + let pk = EcPublicKey::::from_sec1_bytes(public_key) + .map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; Ok(Self::EcdsaP256Sha256(pk)) } params_oids::Secp384r1 => { - let pk = - EcPublicKey::::from_sec1_bytes(public_key).map_err(|e| anyhow!("Error decoding public key: {}", e))?; + let pk = EcPublicKey::::from_sec1_bytes(public_key) + .map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; Ok(Self::EcdsaP384Sha384(pk)) } - _ => bail!("Unsupported curve"), + _ => Err(HttpSigError::ParsePublicKeyError("Unsupported curve".to_string())), } } // ed25519 algorithm_oids::Ed25519 => { - let public_key = spki_ref.subject_public_key.as_bytes().ok_or(anyhow!("Invalid public key"))?; - let pk = ed25519_compact::PublicKey::from_slice(public_key).map_err(|e| anyhow!("Error decoding public key: {}", e))?; + let public_key = spki_ref + .subject_public_key + .as_bytes() + .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))?; + let pk = + ed25519_compact::PublicKey::from_slice(public_key).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; Ok(Self::Ed25519(pk)) } - _ => bail!("Unsupported algorithm that supports PEM format keys"), + _ => Err(HttpSigError::ParsePublicKeyError( + "Unsupported algorithm that supports PEM format keys".to_string(), + )), } } } impl super::VerifyingKey for PublicKey { /// Verify signature - fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> { + fn verify(&self, data: &[u8], signature: &[u8]) -> HttpSigResult<()> { match self { Self::EcdsaP256Sha256(pk) => { - let signature = - ecdsa::Signature::::from_bytes(signature.into()).map_err(|e| anyhow!("Error decoding signature: {}", e))?; + let signature = ecdsa::Signature::::from_bytes(signature.into()) + .map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?; let vk = ecdsa::VerifyingKey::from(pk); let mut digest = ::new(); digest.update(data); vk.verify_digest(digest, &signature) - .map_err(|e| anyhow!("Error verifying signature: {}", e)) + .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) } Self::EcdsaP384Sha384(pk) => { - let signature = - ecdsa::Signature::::from_bytes(signature.into()).map_err(|e| anyhow!("Error decoding signature: {}", e))?; + let signature = ecdsa::Signature::::from_bytes(signature.into()) + .map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?; let vk = ecdsa::VerifyingKey::from(pk); let mut digest = ::new(); digest.update(data); vk.verify_digest(digest, &signature) - .map_err(|e| anyhow!("Error verifying signature: {}", e)) + .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) } Self::Ed25519(pk) => { - let sig = ed25519_compact::Signature::from_slice(signature).map_err(|e| anyhow!("Error decoding signature: {}", e))?; - pk.verify(data, &sig).map_err(|e| anyhow!("Error verifying signature: {}", e)) + let sig = + ed25519_compact::Signature::from_slice(signature).map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?; + pk.verify(data, &sig) + .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) } } } @@ -338,7 +361,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= } #[test] - fn test_kid() -> Result<()> { + fn test_kid() -> HttpSigResult<()> { use super::super::VerifyingKey; let sk = SecretKey::from_pem(P256_SECERT_KEY)?; let pk = PublicKey::from_pem(P256_PUBLIC_KEY)?; diff --git a/httpsig/src/crypto/mod.rs b/httpsig/src/crypto/mod.rs index 806dcd6..89b129a 100644 --- a/httpsig/src/crypto/mod.rs +++ b/httpsig/src/crypto/mod.rs @@ -1,6 +1,8 @@ mod asymmetric; mod symmetric; +use crate::error::HttpSigResult; + pub use asymmetric::{PublicKey, SecretKey}; pub use symmetric::SharedKey; @@ -31,14 +33,14 @@ impl std::fmt::Display for AlgorithmName { /// SigningKey trait pub trait SigningKey { - fn sign(&self, data: &[u8]) -> anyhow::Result>; + fn sign(&self, data: &[u8]) -> HttpSigResult>; fn key_id(&self) -> String; fn alg(&self) -> AlgorithmName; } /// VerifyingKey trait pub trait VerifyingKey { - fn verify(&self, data: &[u8], signature: &[u8]) -> anyhow::Result<()>; + fn verify(&self, data: &[u8], signature: &[u8]) -> HttpSigResult<()>; fn key_id(&self) -> String; fn alg(&self) -> AlgorithmName; } diff --git a/httpsig/src/crypto/symmetric.rs b/httpsig/src/crypto/symmetric.rs index cfe6690..5fe279e 100644 --- a/httpsig/src/crypto/symmetric.rs +++ b/httpsig/src/crypto/symmetric.rs @@ -1,5 +1,5 @@ use super::AlgorithmName; -use anyhow::Result; +use crate::error::{HttpSigError, HttpSigResult}; use base64::{engine::general_purpose, Engine as _}; use hmac::{Hmac, Mac}; use sha2::{Digest, Sha256}; @@ -16,7 +16,7 @@ pub enum SharedKey { impl SharedKey { /// Create a new shared key from base64 encoded string - pub fn from_base64(key: &str) -> Result { + pub fn from_base64(key: &str) -> HttpSigResult { let key = general_purpose::STANDARD.decode(key)?; Ok(SharedKey::HmacSha256(key)) } @@ -24,7 +24,7 @@ impl SharedKey { impl super::SigningKey for SharedKey { /// Sign the data - fn sign(&self, data: &[u8]) -> Result> { + fn sign(&self, data: &[u8]) -> HttpSigResult> { match self { SharedKey::HmacSha256(key) => { let mut mac = HmacSha256::new_from_slice(key).unwrap(); @@ -46,13 +46,13 @@ impl super::SigningKey for SharedKey { } impl super::VerifyingKey for SharedKey { /// Verify the mac - fn verify(&self, data: &[u8], expected_mac: &[u8]) -> Result<()> { + fn verify(&self, data: &[u8], expected_mac: &[u8]) -> HttpSigResult<()> { use super::SigningKey; let calcurated_mac = self.sign(data)?; if calcurated_mac == expected_mac { Ok(()) } else { - Err(anyhow::anyhow!("Invalid mac")) + Err(HttpSigError::InvalidSignature("Invalid MAC".to_string())) } } @@ -86,6 +86,7 @@ mod tests { let key = SharedKey::HmacSha256(inner.to_vec()); let data = b"hello"; let signature = key.sign(data).unwrap(); - key.verify(data, &signature).unwrap(); + let res = key.verify(data, &signature); + assert!(res.is_ok()); } } diff --git a/httpsig/src/error.rs b/httpsig/src/error.rs index abad703..8cf0ac5 100644 --- a/httpsig/src/error.rs +++ b/httpsig/src/error.rs @@ -1 +1,29 @@ -pub use anyhow::{anyhow, bail, Result}; +use thiserror::Error; + +/// Result type for http signature +pub type HttpSigResult = std::result::Result; + +/// Error type for http signature +#[derive(Error, Debug)] +pub enum HttpSigError { + #[error("Base64 decode error: {0}")] + Base64DecodeError(#[from] base64::DecodeError), + + /* ----- Crypto errors ----- */ + /// Invalid private key for asymmetric algorithm + #[error("Failed to parse private key: {0}")] + ParsePrivateKeyError(String), + /// Invalid public key for asymmetric algorithm + #[error("Failed to parse public key: {0}")] + ParsePublicKeyError(String), + + /// Signature parse error + #[error("Failed to parse signature: {0}")] + ParseSignatureError(String), + + // #[error("Failed to verify digest: {0}")] + // VerifyDigestError(#[from] ), + /// Invalid Signature + #[error("Invalid Signature: {0}")] + InvalidSignature(String), +} diff --git a/httpsig/src/lib.rs b/httpsig/src/lib.rs index 578a8f1..fb596f7 100644 --- a/httpsig/src/lib.rs +++ b/httpsig/src/lib.rs @@ -1,4 +1,5 @@ mod crypto; +mod error; mod message_component; mod signature_base; mod signature_params; @@ -14,6 +15,7 @@ pub mod prelude { pub use crate::{ crypto::{PublicKey, SecretKey, SharedKey, SigningKey, VerifyingKey}, + error::{HttpSigError, HttpSigResult}, signature_base::{HttpSignature, HttpSignatureBase, HttpSignatureHeaders}, signature_params::HttpSignatureParams, }; diff --git a/httpsig/src/signature_base.rs b/httpsig/src/signature_base.rs index c59c665..7b7a3ad 100644 --- a/httpsig/src/signature_base.rs +++ b/httpsig/src/signature_base.rs @@ -158,7 +158,7 @@ impl HttpSignatureBase { /// Build signature from given signing key pub fn build_raw_signature(&self, signing_key: &impl SigningKey) -> anyhow::Result> { let bytes = self.as_bytes(); - signing_key.sign(&bytes) + signing_key.sign(&bytes).map_err(|e| anyhow!(e)) } /// Build the signature and signature-input headers structs @@ -182,7 +182,9 @@ impl HttpSignatureBase { signature_headers: &HttpSignatureHeaders, ) -> anyhow::Result<()> { let signature_bytes = signature_headers.signature.0.as_slice(); - verifying_key.verify(&self.as_bytes(), signature_bytes) + verifying_key + .verify(&self.as_bytes(), signature_bytes) + .map_err(|e| anyhow!(e)) } }