Add signature verification
This commit is contained in:
parent
d4c9a80ebe
commit
d7626063f7
@ -8,9 +8,13 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.69"
|
anyhow = "1.0.69"
|
||||||
axum = { version = "0.6.11", features = ["json"] }
|
axum = { version = "0.6.11", features = ["json"] }
|
||||||
|
p384 = { version = "0.13.0", features = ["ecdsa", "serde"] }
|
||||||
serde = { version = "1.0.155", features = ["derive"] }
|
serde = { version = "1.0.155", features = ["derive"] }
|
||||||
serde_json = "1.0.94"
|
serde_json = "1.0.94"
|
||||||
tokio = { version = "1.26.0", features = ["full"] }
|
tokio = { version = "1.26.0", features = ["full"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-subscriber = "0.3.16"
|
||||||
uuid = { version = "1.3.0", features = ["v4", "serde"] }
|
uuid = { version = "1.3.0", features = ["v4", "serde"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
uuid = { version = "1.3.0", features = ["v4"] }
|
||||||
|
84
src/lib.rs
84
src/lib.rs
@ -1,8 +1,11 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::{http::StatusCode, Json};
|
use axum::{http::StatusCode, Json};
|
||||||
|
use p384::ecdsa::signature::Verifier;
|
||||||
|
use p384::ecdsa::{Signature, VerifyingKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::io::{self, AsyncWriteExt};
|
use tokio::io::{self, AsyncWriteExt};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
@ -12,7 +15,16 @@ use uuid::Uuid;
|
|||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
pub struct ProxyCommand {
|
pub struct ProxyCommand {
|
||||||
command: Command,
|
command: Command,
|
||||||
signature: [u8; 32],
|
signature: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyCommand {
|
||||||
|
fn verify_signature(&self, verifying_key: &VerifyingKey) -> bool {
|
||||||
|
let message = serde_json::to_string(&self.command).unwrap();
|
||||||
|
verifying_key
|
||||||
|
.verify(message.as_bytes(), &self.signature)
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
@ -41,12 +53,14 @@ pub struct ProxyResponse {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GlobalState {
|
pub struct GlobalState {
|
||||||
proxies: Mutex<HashMap<Uuid, ProxyState>>,
|
proxies: Mutex<HashMap<Uuid, ProxyState>>,
|
||||||
|
verifying_key: VerifyingKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalState {
|
impl GlobalState {
|
||||||
pub fn new() -> Self {
|
pub fn new(verifying_key: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
proxies: Mutex::new(HashMap::new()),
|
proxies: Mutex::new(HashMap::new()),
|
||||||
|
verifying_key: VerifyingKey::from_str(verifying_key).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +80,15 @@ pub async fn process_command(
|
|||||||
Json(payload): Json<ProxyCommand>,
|
Json(payload): Json<ProxyCommand>,
|
||||||
) -> (StatusCode, Json<ProxyResponse>) {
|
) -> (StatusCode, Json<ProxyResponse>) {
|
||||||
tracing::info!("Received payload: {:?}", payload);
|
tracing::info!("Received payload: {:?}", payload);
|
||||||
// TODO: verify signature
|
|
||||||
|
if !payload.verify_signature(&state.verifying_key) {
|
||||||
|
return (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(ProxyResponse {
|
||||||
|
message: "Invalid signature".to_string(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
match payload.command {
|
match payload.command {
|
||||||
Command::New {
|
Command::New {
|
||||||
incoming_port,
|
incoming_port,
|
||||||
@ -108,7 +130,7 @@ pub async fn process_command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
StatusCode::CREATED,
|
StatusCode::ACCEPTED,
|
||||||
Json(ProxyResponse {
|
Json(ProxyResponse {
|
||||||
message: "Success".to_string(),
|
message: "Success".to_string(),
|
||||||
}),
|
}),
|
||||||
@ -224,10 +246,16 @@ mod tests {
|
|||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
use crate::{Command, ProxyCommand};
|
use crate::{Command, ProxyCommand};
|
||||||
|
use p384::{
|
||||||
|
ecdsa::{signature::Signer, Signature, SigningKey, VerifyingKey},
|
||||||
|
elliptic_curve::rand_core::OsRng,
|
||||||
|
};
|
||||||
use uuid::uuid;
|
use uuid::uuid;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_proxy_command_new() {
|
fn serialize_proxy_command_new() {
|
||||||
|
let key = SigningKey::from_slice(&[1; 48]).unwrap();
|
||||||
|
let signature = key.sign(&[]); // Not a valid signature
|
||||||
let proxy_command = ProxyCommand {
|
let proxy_command = ProxyCommand {
|
||||||
command: Command::New {
|
command: Command::New {
|
||||||
incoming_port: 5555,
|
incoming_port: 5555,
|
||||||
@ -235,24 +263,62 @@ mod tests {
|
|||||||
destination_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
destination_ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||||
id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
|
id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
|
||||||
},
|
},
|
||||||
signature: [0u8; 32],
|
signature,
|
||||||
};
|
};
|
||||||
let expected = "{\"command\":{\"New\":{\"incoming_port\":5555,\"destination_port\":6666,\"\
|
let expected = "{\"command\":{\"New\":{\"incoming_port\":5555,\"destination_port\":6666,\"\
|
||||||
destination_ip\":\"127.0.0.1\",\"id\":\"67e55044-10b1-426f-9247-bb680e5fe0c8\"}},\
|
destination_ip\":\"127.0.0.1\",\"id\":\"67e55044-10b1-426f-9247-bb680e5fe0c8\"}},\
|
||||||
\"signature\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}";
|
\"signature\":\"\
|
||||||
|
5C912C4B3BFF2ADB49885DCBDB53D6D3041D0632E498CDFF\
|
||||||
|
2114CD2DCAC936AB0901B47C411E5BB57FE77BEF96044940\
|
||||||
|
81680ADAD0775CD144E2D2678537F621ED587E13EB430126\
|
||||||
|
C7A757AEC99CE08A2D0F3A5C9FB45E9349F36408DFD7BA17\"}";
|
||||||
|
|
||||||
assert_eq!(serde_json::to_string(&proxy_command).unwrap(), expected);
|
assert_eq!(serde_json::to_string(&proxy_command).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_proxy_command_delete() {
|
fn serialize_proxy_command_delete() {
|
||||||
|
let key = SigningKey::from_slice(&[1; 48]).unwrap();
|
||||||
|
let signature = key.sign(&[]); // Not a valid signature
|
||||||
let proxy_command = ProxyCommand {
|
let proxy_command = ProxyCommand {
|
||||||
command: Command::Delete {
|
command: Command::Delete {
|
||||||
id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
|
id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
|
||||||
},
|
},
|
||||||
signature: [0u8; 32],
|
signature,
|
||||||
};
|
};
|
||||||
let expected = "{\"command\":{\"Delete\":{\"id\":\"67e55044-10b1-426f-9247-bb680e5fe0c8\"}},\
|
let expected =
|
||||||
\"signature\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}";
|
"{\"command\":{\"Delete\":{\"id\":\"67e55044-10b1-426f-9247-bb680e5fe0c8\"}},\
|
||||||
|
\"signature\":\"\
|
||||||
|
5C912C4B3BFF2ADB49885DCBDB53D6D3041D0632E498CDFF\
|
||||||
|
2114CD2DCAC936AB0901B47C411E5BB57FE77BEF96044940\
|
||||||
|
81680ADAD0775CD144E2D2678537F621ED587E13EB430126\
|
||||||
|
C7A757AEC99CE08A2D0F3A5C9FB45E9349F36408DFD7BA17\"}";
|
||||||
|
|
||||||
assert_eq!(serde_json::to_string(&proxy_command).unwrap(), expected);
|
assert_eq!(serde_json::to_string(&proxy_command).unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_signature() {
|
||||||
|
let command = Command::New {
|
||||||
|
incoming_port: 4567,
|
||||||
|
destination_port: 7654,
|
||||||
|
destination_ip: IpAddr::V4(Ipv4Addr::new(123, 23, 76, 21)),
|
||||||
|
id: uuid::Uuid::new_v4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create signed message
|
||||||
|
let signing_key = SigningKey::random(&mut OsRng);
|
||||||
|
let message = serde_json::to_string(&command).unwrap();
|
||||||
|
let signature: Signature = signing_key.sign(message.as_bytes());
|
||||||
|
let bytes = signature.to_bytes();
|
||||||
|
assert_eq!(bytes.len(), 96);
|
||||||
|
let proxy_command = ProxyCommand {
|
||||||
|
command,
|
||||||
|
signature: bytes.as_slice().try_into().unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify signed message
|
||||||
|
let verifying_key = VerifyingKey::from(&signing_key);
|
||||||
|
assert!(proxy_command.verify_signature(&verifying_key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@ async fn main() {
|
|||||||
|
|
||||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||||
|
|
||||||
let shared_state = Arc::new(GlobalState::new());
|
let verifying_key = std::env::args().nth(1).expect("No verifying key provided");
|
||||||
|
|
||||||
|
let shared_state = Arc::new(GlobalState::new(&verifying_key));
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
// `GET /` goes to `root`
|
// `GET /` goes to `root`
|
||||||
|
Loading…
Reference in New Issue
Block a user