Skip to content

Commit

Permalink
wip: custom error for crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
junkurihara committed Feb 16, 2024
1 parent ffdc588 commit b8a9a9f
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 50 deletions.
1 change: 1 addition & 0 deletions httpsig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
101 changes: 62 additions & 39 deletions httpsig/src/crypto/asymmetric.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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
Expand Down Expand Up @@ -44,8 +46,8 @@ pub enum SecretKey {
impl SecretKey {
/// parse der
/// Derive secret key from der bytes
pub fn from_der(der: &[u8]) -> Result<Self> {
let pki = PrivateKeyInfo::from_der(der).map_err(|e| anyhow!("Error decoding private key: {}", e))?;
pub fn from_der(der: &[u8]) -> HttpSigResult<Self> {
let pki = PrivateKeyInfo::from_der(der).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?;

match pki.algorithm.oid.to_string().as_ref() {
// ec
Expand All @@ -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
Expand All @@ -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<Self> {
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<Self> {
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())
}

Expand All @@ -101,7 +109,7 @@ impl SecretKey {

impl super::SigningKey for SecretKey {
/// Sign data
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
fn sign(&self, data: &[u8]) -> HttpSigResult<Vec<u8>> {
match &self {
Self::EcdsaP256Sha256(sk) => {
let sk = ecdsa::SigningKey::from(sk);
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -164,68 +172,83 @@ pub enum PublicKey {
impl PublicKey {
#[allow(dead_code)]
/// Convert from pem string
pub fn from_pem(pem: &str) -> Result<Self> {
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<Self> {
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::<NistP256>::from_sec1_bytes(public_key).map_err(|e| anyhow!("Error decoding public key: {}", e))?;
let pk = EcPublicKey::<NistP256>::from_sec1_bytes(public_key)
.map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?;
Ok(Self::EcdsaP256Sha256(pk))
}
params_oids::Secp384r1 => {
let pk =
EcPublicKey::<NistP384>::from_sec1_bytes(public_key).map_err(|e| anyhow!("Error decoding public key: {}", e))?;
let pk = EcPublicKey::<NistP384>::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::<NistP256>::from_bytes(signature.into()).map_err(|e| anyhow!("Error decoding signature: {}", e))?;
let signature = ecdsa::Signature::<NistP256>::from_bytes(signature.into())
.map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?;
let vk = ecdsa::VerifyingKey::from(pk);
let mut digest = <Sha256 as 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::<NistP384>::from_bytes(signature.into()).map_err(|e| anyhow!("Error decoding signature: {}", e))?;
let signature = ecdsa::Signature::<NistP384>::from_bytes(signature.into())
.map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?;
let vk = ecdsa::VerifyingKey::from(pk);
let mut digest = <Sha384 as 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()))
}
}
}
Expand Down Expand Up @@ -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)?;
Expand Down
6 changes: 4 additions & 2 deletions httpsig/src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod asymmetric;
mod symmetric;

use crate::error::HttpSigResult;

pub use asymmetric::{PublicKey, SecretKey};
pub use symmetric::SharedKey;

Expand Down Expand Up @@ -31,14 +33,14 @@ impl std::fmt::Display for AlgorithmName {

/// SigningKey trait
pub trait SigningKey {
fn sign(&self, data: &[u8]) -> anyhow::Result<Vec<u8>>;
fn sign(&self, data: &[u8]) -> HttpSigResult<Vec<u8>>;
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;
}
13 changes: 7 additions & 6 deletions httpsig/src/crypto/symmetric.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -16,15 +16,15 @@ pub enum SharedKey {

impl SharedKey {
/// Create a new shared key from base64 encoded string
pub fn from_base64(key: &str) -> Result<Self> {
pub fn from_base64(key: &str) -> HttpSigResult<Self> {
let key = general_purpose::STANDARD.decode(key)?;
Ok(SharedKey::HmacSha256(key))
}
}

impl super::SigningKey for SharedKey {
/// Sign the data
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
fn sign(&self, data: &[u8]) -> HttpSigResult<Vec<u8>> {
match self {
SharedKey::HmacSha256(key) => {
let mut mac = HmacSha256::new_from_slice(key).unwrap();
Expand All @@ -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()))
}
}

Expand Down Expand Up @@ -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());
}
}
30 changes: 29 additions & 1 deletion httpsig/src/error.rs
Original file line number Diff line number Diff line change
@@ -1 +1,29 @@
pub use anyhow::{anyhow, bail, Result};
use thiserror::Error;

/// Result type for http signature
pub type HttpSigResult<T> = std::result::Result<T, HttpSigError>;

/// 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),
}
2 changes: 2 additions & 0 deletions httpsig/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod crypto;
mod error;
mod message_component;
mod signature_base;
mod signature_params;
Expand All @@ -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,
};
Expand Down
6 changes: 4 additions & 2 deletions httpsig/src/signature_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl HttpSignatureBase {
/// Build signature from given signing key
pub fn build_raw_signature(&self, signing_key: &impl SigningKey) -> anyhow::Result<Vec<u8>> {
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
Expand All @@ -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))
}
}

Expand Down

0 comments on commit b8a9a9f

Please sign in to comment.