From f0520c1a6d0bbc33c4eed5e5aa7a9b5374975abb Mon Sep 17 00:00:00 2001 From: Monadic Cat Date: Tue, 9 May 2023 10:39:05 -0500 Subject: [PATCH] feat(signature-validation): add ed25519 signature validation to twilight-util --- twilight-util/Cargo.toml | 7 ++- twilight-util/src/lib.rs | 3 ++ twilight-util/src/signature_validation.rs | 64 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 twilight-util/src/signature_validation.rs diff --git a/twilight-util/Cargo.toml b/twilight-util/Cargo.toml index 5386bf7b767..30cf139522f 100644 --- a/twilight-util/Cargo.toml +++ b/twilight-util/Cargo.toml @@ -16,6 +16,10 @@ version = "0.15.4" twilight-model = { default-features = false, optional = true, path = "../twilight-model", version = "0.15.4" } twilight-validate = { default-features = false, optional = true, path = "../twilight-validate", version = "0.15.3" } +# Signature validation +ed25519-dalek = { version = "2.0.0-rc.2", optional = true, default-features = false} +hex = { version = "0.4.3", optional = true, default-features = false } + [dev-dependencies] chrono = { default-features = false, features = ["std"], version = "0.4" } static_assertions = { default-features = false, version = "1" } @@ -26,7 +30,8 @@ builder = ["dep:twilight-model", "dep:twilight-validate"] link = ["dep:twilight-model"] permission-calculator = ["dep:twilight-model"] snowflake = ["dep:twilight-model"] -full = ["builder", "link", "permission-calculator", "snowflake"] +signature-validation = ["dep:ed25519-dalek", "dep:hex"] +full = ["builder", "link", "permission-calculator", "snowflake", "signature-validation"] [package.metadata.docs.rs] all-features = true diff --git a/twilight-util/src/lib.rs b/twilight-util/src/lib.rs index 073acfc62f7..7fa07522624 100644 --- a/twilight-util/src/lib.rs +++ b/twilight-util/src/lib.rs @@ -23,3 +23,6 @@ pub mod permission_calculator; #[cfg(feature = "snowflake")] pub mod snowflake; + +#[cfg(feature = "signature-validation")] +pub mod signature_validation; diff --git a/twilight-util/src/signature_validation.rs b/twilight-util/src/signature_validation.rs new file mode 100644 index 00000000000..f1d10707f6a --- /dev/null +++ b/twilight-util/src/signature_validation.rs @@ -0,0 +1,64 @@ +//! Provides signature validation as is required for bots which work by giving Discord +//! an HTTPS endpoint to send Interactions to. + +use ed25519_dalek::{Signature, SignatureError, VerifyingKey}; + +#[derive(Debug)] +pub struct FromHexError(hex::FromHexError); +#[derive(Debug)] +pub struct SigError(SignatureError); + +#[derive(Debug)] +pub enum SignatureValidationFailure { + Hex(FromHexError), + InvalidSignature(SigError), +} +impl std::fmt::Display for SignatureValidationFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} +impl std::error::Error for SignatureValidationFailure {} + +#[derive(Debug)] +pub enum KeyError { + Hex(FromHexError), + Sig(SignatureError), +} +impl std::fmt::Display for KeyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} +impl std::error::Error for KeyError {} + + +pub struct Key(VerifyingKey); +impl Key { + fn from_hex(pub_key: &[u8]) -> Result { + let mut key = [0; 32]; + hex::decode_to_slice(pub_key, &mut key).map_err(|e| KeyError::Hex(FromHexError(e)))?; + VerifyingKey::from_bytes(&key) + .map(|key| Self(key)) + .map_err(|e| KeyError::Sig(e)) + } +} + +pub const SIGNATURE_HEADER: &str = "x-signature-ed25519"; +pub const TIMESTAMP_HEADER: &str = "x-signature-timestamp"; + +/// Validates that a signature is valid for a given message body, timestamp, and signing key. +pub fn check_signature(sig: &[u8], timestamp: &[u8], body: &[u8], key: &Key) -> Result<(), SignatureValidationFailure> { + let mut sig_buf = [0; 64]; + hex::decode_to_slice(sig, &mut sig_buf).map_err(|e| SignatureValidationFailure::Hex(FromHexError(e)))?; + let sig = Signature::from_bytes(&sig_buf); + + + let mut buf = Vec::with_capacity(timestamp.len() + body.len()); + buf.extend_from_slice(timestamp); + buf.extend_from_slice(body); + match key.0.verify_strict(&buf, &sig) { + Ok(()) => Ok(()), + Err(e) => Err(SignatureValidationFailure::InvalidSignature(SigError(e))), + } +}