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"
])
# 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",
)
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",
-use hyper::{Body, Error, Response, StatusCode};
+use hyper::{Body, Response, StatusCode};
pub struct WebhookError {
pub code: StatusCode,
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<Config>,
pub nats: Arc<Connection>,
+ pub public_key: Arc<PublicKey>
}
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,
) {
sync::Arc,
task::{Context, Poll},
};
+use ed25519_dalek::PublicKey;
pub struct MakeSvc {
pub settings: Arc<Config>,
pub nats: Arc<Connection>,
+ pub public_key: Arc<PublicKey>
}
impl<T> Service<T> for MakeSvc {
ready(Ok(HandlerService {
config: self.settings.clone(),
nats: self.nats.clone(),
+ public_key: self.public_key.clone()
}))
}
}
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!(
&["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<u8>, hex_signature: &str) -> bool {
- SIGNATURE_BODY_COUNTER.inc();
+fn demo<T, const N: usize>(v: Vec<T>) -> [T; N] {
+ v.try_into()
+ .unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len()))
+}
+
+pub fn validate_signature(public_key: &PublicKey, data: &Vec<u8>, 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();
};
- static ref KEYPAIR: (String, [u8; 64]) = {
+ static ref TEST_KEYPAIR: ed25519_dalek::Keypair = {
generate_keypair()
};
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,
},
},
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)
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()
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);
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)
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)
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
-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<u8>,
- sk: [u8; libsodium_sys::crypto_sign_ed25519_SECRETKEYBYTES as usize],
+ message: Vec<u8>,
+ 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
use crate::config::Config;\r
use common::config::Settings;\r
use common::log::{error, info};\r
+use ed25519_dalek::PublicKey;\r
use hyper::Server;\r
\r
#[tokio::main]\r
);\r
\r
let config = Arc::new(settings.config);\r
+ let public_key =\r
+ Arc::new(PublicKey::from_bytes(&hex::decode(&config.discord.public_key).unwrap()).unwrap());\r
let server = Server::bind(&addr).serve(MakeSvc {\r
settings: config,\r
nats: Arc::new(settings.nats.into()),\r
+ public_key: public_key,\r
});\r
\r
if let Err(e) = server.await {\r