From cf84d9526027fd9a9fdd98b2d3b471d6655abe76 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 18 Oct 2021 11:54:43 +0400 Subject: [PATCH] remove dependcy on libsodium (deprecated) --- webhook/Cargo.toml | 3 +- webhook/cargo/BUILD.bazel | 13 ++++- webhook/src/handler/error.rs | 2 +- webhook/src/handler/handler.rs | 4 +- webhook/src/handler/make_service.rs | 3 ++ webhook/src/handler/signature.rs | 47 +++++++----------- .../src/handler/tests/handler_integration.rs | 14 +++--- webhook/src/handler/tests/signature.rs | 17 ++++--- webhook/src/handler/tests/utils.rs | 49 ++++--------------- webhook/src/main.rs | 4 ++ 10 files changed, 68 insertions(+), 88 deletions(-) diff --git a/webhook/Cargo.toml b/webhook/Cargo.toml index 7e73c43..4ef2674 100644 --- a/webhook/Cargo.toml +++ b/webhook/Cargo.toml @@ -8,11 +8,12 @@ hyper = { version = "0.14", features = ["full"] } tokio = { version = "1", features = ["full"] } common = { path = "../common/rust" } serde = { version = "1.0.8", features = ["derive"] } -libsodium-sys = "0.2.7" hex = "0.4.3" serde_json = { version = "1.0" } libc = "0.2.101" lazy_static = "1.4.0" +ed25519-dalek = "1" +rand = "0.7" [[bin]] name = "webhook" diff --git a/webhook/cargo/BUILD.bazel b/webhook/cargo/BUILD.bazel index 4569cc1..8d3e7f4 100644 --- a/webhook/cargo/BUILD.bazel +++ b/webhook/cargo/BUILD.bazel @@ -12,6 +12,15 @@ licenses([ ]) # Aliased targets +alias( + name = "ed25519_dalek", + actual = "@raze__ed25519_dalek__1_0_1//:ed25519_dalek", + tags = [ + "cargo-raze", + "manual", + ], +) + alias( name = "hex", actual = "@raze__hex__0_4_3//:hex", @@ -49,8 +58,8 @@ alias( ) alias( - name = "libsodium_sys", - actual = "@raze__libsodium_sys__0_2_7//:libsodium_sys", + name = "rand", + actual = "@raze__rand__0_7_3//:rand", tags = [ "cargo-raze", "manual", diff --git a/webhook/src/handler/error.rs b/webhook/src/handler/error.rs index ccec59f..f998b84 100644 --- a/webhook/src/handler/error.rs +++ b/webhook/src/handler/error.rs @@ -1,4 +1,4 @@ -use hyper::{Body, Error, Response, StatusCode}; +use hyper::{Body, Response, StatusCode}; pub struct WebhookError { pub code: StatusCode, diff --git a/webhook/src/handler/handler.rs b/webhook/src/handler/handler.rs index 4af2ba6..d482e01 100644 --- a/webhook/src/handler/handler.rs +++ b/webhook/src/handler/handler.rs @@ -17,12 +17,14 @@ use std::{ task::{Context, Poll}, time::Duration, }; +use ed25519_dalek::PublicKey; /// Hyper service used to handle the discord webhooks #[derive(Clone)] pub struct HandlerService { pub config: Arc, pub nats: Arc, + pub public_key: Arc } impl HandlerService { @@ -36,7 +38,7 @@ impl HandlerService { let contatenated_data = [timestamp.as_bytes().to_vec(), data.to_vec()].concat(); if let Ok(signature_str) = &signature.to_str() { if validate_signature( - &self.config.discord.public_key, + &self.public_key, &contatenated_data, signature_str, ) { diff --git a/webhook/src/handler/make_service.rs b/webhook/src/handler/make_service.rs index deeb2fe..9e90436 100644 --- a/webhook/src/handler/make_service.rs +++ b/webhook/src/handler/make_service.rs @@ -7,10 +7,12 @@ use std::{ sync::Arc, task::{Context, Poll}, }; +use ed25519_dalek::PublicKey; pub struct MakeSvc { pub settings: Arc, pub nats: Arc, + pub public_key: Arc } impl Service for MakeSvc { @@ -26,6 +28,7 @@ impl Service for MakeSvc { ready(Ok(HandlerService { config: self.settings.clone(), nats: self.nats.clone(), + public_key: self.public_key.clone() })) } } diff --git a/webhook/src/handler/signature.rs b/webhook/src/handler/signature.rs index c12f9e8..748fa6a 100644 --- a/webhook/src/handler/signature.rs +++ b/webhook/src/handler/signature.rs @@ -1,5 +1,8 @@ use common::prometheus::{Counter, HistogramVec, labels, opts, register_counter, register_histogram_vec}; -use libsodium_sys::crypto_sign_ed25519_verify_detached; +use ed25519_dalek::PublicKey; +use ed25519_dalek::Verifier; +use ed25519_dalek::Signature; +use std::convert::TryInto; lazy_static::lazy_static! { static ref SIGNATURE_TIME_HISTOGRAM: HistogramVec = register_histogram_vec!( @@ -8,43 +11,29 @@ lazy_static::lazy_static! { &["signature"] ).unwrap(); - static ref SIGNATURE_BODY_COUNTER: Counter = register_counter!(opts!( - "nova_webhook_", - "", + static ref SIGNATURE_COUNTER: Counter = register_counter!(opts!( + "nova_webhook_signatures_verify", + "number of signatures verification issued by the service", labels! {"handler" => "webhook_main"} )).unwrap(); } -/// Checks the signature of a given data using the hex signature and the public key. -pub fn validate_signature(hex_public_key: &str, data: &Vec, hex_signature: &str) -> bool { - SIGNATURE_BODY_COUNTER.inc(); +fn demo(v: Vec) -> [T; N] { + v.try_into() + .unwrap_or_else(|v: Vec| panic!("Expected a Vec of length {} but it was {}", N, v.len())) +} + +pub fn validate_signature(public_key: &PublicKey, data: &Vec, hex_signature: &str) -> bool { + SIGNATURE_COUNTER.inc(); let timer = SIGNATURE_TIME_HISTOGRAM.with_label_values(&["webhook_main"]).start_timer(); - // First, we need to check if the signature & private key is valid base64. let signature_result = hex::decode(hex_signature); - let public_key_result = hex::decode(hex_public_key); let mut result = false; - if signature_result.is_ok() && public_key_result.is_ok() { - // Since we now have the signatures in u8 vectors. We will initialize all the - // parameters for the ffi call to sodium. - let signature_pointer = signature_result.unwrap(); - let private_key_pointer = public_key_result.unwrap(); - - let data_pointer = data.as_ptr(); - let data_len = data.len() as u64; - - // A ffi call is considered unsafe by the Rust compiler - // we assume all the parameters are correct for the call - unsafe { - // If the signature is valid, sodium will return 0 - result = crypto_sign_ed25519_verify_detached( - signature_pointer.as_ptr(), - data_pointer, - data_len, - private_key_pointer.as_ptr(), - ) == 0; - } + if let Ok(signature) = signature_result { + let sig = Signature::from(demo(signature)); + + result = public_key.verify(data, &sig).is_ok(); } timer.observe_duration(); diff --git a/webhook/src/handler/tests/handler_integration.rs b/webhook/src/handler/tests/handler_integration.rs index fd0d67f..2475b94 100644 --- a/webhook/src/handler/tests/handler_integration.rs +++ b/webhook/src/handler/tests/handler_integration.rs @@ -47,7 +47,7 @@ lazy_static! { }; - static ref KEYPAIR: (String, [u8; 64]) = { + static ref TEST_KEYPAIR: ed25519_dalek::Keypair = { generate_keypair() }; @@ -60,7 +60,7 @@ lazy_static! { address: "0.0.0.0".to_string(), }, discord: crate::config::Discord { - public_key: KEYPAIR.0.clone(), + public_key: hex::encode(TEST_KEYPAIR.to_bytes()), client_id: 0, }, }, @@ -103,7 +103,7 @@ async fn respond_to_pings() { let ping = json!({ "type": 1 }).to_string(); let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, KEYPAIR.1); + let signature = sign_message(signature_data, &TEST_KEYPAIR); let req = Request::builder() .method(Method::POST) @@ -144,7 +144,7 @@ async fn response_500_when_no_nats_response() { let ping = json!({ "type": 0 }).to_string(); let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, KEYPAIR.1); + let signature = sign_message(signature_data, &TEST_KEYPAIR); // we must timeout let req = Request::builder() @@ -169,7 +169,7 @@ async fn respond_from_nats_response() { let ping = json!({ "type": 0 }).to_string(); let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, KEYPAIR.1); + let signature = sign_message(signature_data, &TEST_KEYPAIR); sub.with_handler(move |msg| { info!("Received {}", &msg); @@ -196,7 +196,7 @@ async fn response_400_when_invalid_json_body() { let ping = "{".to_string(); let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.as_bytes().to_vec()].concat(); - let signature = sign_message(signature_data, KEYPAIR.1); + let signature = sign_message(signature_data, &TEST_KEYPAIR); let req = Request::builder() .method(Method::POST) @@ -219,7 +219,7 @@ async fn response_400_when_invalid_utf8_body() { let timestamp = "my datetime :)"; let signature_data = [timestamp.as_bytes().to_vec(), ping.to_vec()].concat(); - let signature = sign_message(signature_data, KEYPAIR.1); + let signature = sign_message(signature_data, &TEST_KEYPAIR); let req = Request::builder() .method(Method::POST) diff --git a/webhook/src/handler/tests/signature.rs b/webhook/src/handler/tests/signature.rs index 475e446..490143b 100644 --- a/webhook/src/handler/tests/signature.rs +++ b/webhook/src/handler/tests/signature.rs @@ -1,30 +1,33 @@ use crate::handler::signature::validate_signature; - +use ed25519_dalek::PublicKey; #[test] fn validate_signature_test() { let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002"; - let public_key = "eefe0c24473737cb2035232e3b4eb91c206f0a14684168f3503f7d8316058d6f"; let content = "message de test incroyable".as_bytes().to_vec(); - assert!(validate_signature(public_key, &content, signature)) + let public_key = PublicKey::from_bytes(&hex::decode("eefe0c24473737cb2035232e3b4eb91c206f0a14684168f3503f7d8316058d6f").unwrap()).unwrap(); + + assert!(validate_signature(&public_key, &content, signature)) } #[test] fn validate_signature_reverse_test() { let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002"; - let public_key = "c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044"; + let public_key = PublicKey::from_bytes(&hex::decode("c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044").unwrap()).unwrap(); + let content = "ceci est un test qui ne fonctionnera pas!" .as_bytes() .to_vec(); - assert!(!validate_signature(public_key, &content, signature)) + assert!(!validate_signature(&public_key, &content, signature)) } #[test] fn invalid_hex() { let signature = "zzz"; - let public_key = "zzz"; + let public_key = PublicKey::from_bytes(&hex::decode("c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044").unwrap()).unwrap(); + let content = "ceci est un test qui ne fonctionnera pas!" .as_bytes() .to_vec(); - assert!(!validate_signature(public_key, &content, signature)) + assert!(!validate_signature(&public_key, &content, signature)) } \ No newline at end of file diff --git a/webhook/src/handler/tests/utils.rs b/webhook/src/handler/tests/utils.rs index f8cdac2..5e59f09 100644 --- a/webhook/src/handler/tests/utils.rs +++ b/webhook/src/handler/tests/utils.rs @@ -1,46 +1,15 @@ -pub fn generate_keypair() -> ( - String, - [u8; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize], -) { - use libsodium_sys::crypto_sign_ed25519_keypair; - let pk_s: String; +use rand::rngs::OsRng; +use ed25519_dalek::{Signer, Keypair, Signature}; - let mut pk = [0; libsodium_sys::crypto_sign_ed25519_PUBLICKEYBYTES as usize]; - let mut sk = [0; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize]; - - let pk_p = pk.as_mut_ptr(); - let sk_p = sk.as_mut_ptr(); - - // generate keypair - unsafe { - if crypto_sign_ed25519_keypair(pk_p, sk_p) < 0 { - panic!("keypair generation failed!"); - } - }; - - pk_s = hex::encode(pk); - return (pk_s, sk); +pub fn generate_keypair() -> Keypair { + let mut csprng = OsRng{}; + Keypair::generate(&mut csprng) } pub fn sign_message( - msg: Vec, - sk: [u8; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize], + message: Vec, + keypair: &Keypair, ) -> String { - use libc::c_ulonglong; - use libsodium_sys::crypto_sign_ed25519_detached; - - let len = msg.len(); - let mut signature_len: c_ulonglong = 0; - let mut str = [0; 64]; - unsafe { - crypto_sign_ed25519_detached( - str.as_mut_ptr(), - &mut signature_len, - msg.as_ptr(), - len as u64, - sk.as_ptr(), - ); - }; - - return hex::encode(str); + let signature: Signature = keypair.sign(&message); + return hex::encode(signature.to_bytes()); } \ No newline at end of file diff --git a/webhook/src/main.rs b/webhook/src/main.rs index 98e5f13..00d42ee 100644 --- a/webhook/src/main.rs +++ b/webhook/src/main.rs @@ -6,6 +6,7 @@ use crate::handler::make_service::MakeSvc; use crate::config::Config; use common::config::Settings; use common::log::{error, info}; +use ed25519_dalek::PublicKey; use hyper::Server; #[tokio::main] @@ -30,9 +31,12 @@ async fn start(settings: Settings) { ); let config = Arc::new(settings.config); + let public_key = + Arc::new(PublicKey::from_bytes(&hex::decode(&config.discord.public_key).unwrap()).unwrap()); let server = Server::bind(&addr).serve(MakeSvc { settings: config, nats: Arc::new(settings.nats.into()), + public_key: public_key, }); if let Err(e) = server.await { -- 2.39.5