removing unused signing code and cleaning up from feedback

This commit is contained in:
Michael Neale
2025-11-19 15:09:10 +11:00
parent f434a5ed6c
commit 97e710cecb
5 changed files with 15 additions and 278 deletions
Generated
-58
View File
@@ -1799,33 +1799,6 @@ dependencies = [
"syn 2.0.99",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.99",
]
[[package]]
name = "darling"
version = "0.20.10"
@@ -2129,30 +2102,6 @@ version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
"sha2",
"subtle",
"zeroize",
]
[[package]]
name = "either"
version = "1.15.0"
@@ -2344,12 +2293,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filetime"
version = "0.2.25"
@@ -2929,7 +2872,6 @@ dependencies = [
"chrono",
"clap",
"config",
"ed25519-dalek",
"futures",
"goose",
"goose-mcp",
-1
View File
@@ -44,7 +44,6 @@ url = "2.5.7"
rand = "0.9.2"
hex = "0.4.3"
socket2 = "0.6.1"
ed25519-dalek = "2.1"
sha2 = "0.10.9"
[target.'cfg(windows)'.dependencies]
-176
View File
@@ -1,176 +0,0 @@
use anyhow::Result;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};
pub struct Ed25519Validator {
public_key: VerifyingKey,
}
impl Ed25519Validator {
pub fn new(public_key_hex: &str) -> Result<Self> {
let bytes = hex::decode(public_key_hex)?;
if bytes.len() != 32 {
anyhow::bail!("Public key must be 32 bytes");
}
let public_key =
VerifyingKey::from_bytes(&bytes.try_into().expect("verified 32 bytes above"))?;
Ok(Self { public_key })
}
pub fn verify(
&self,
signature_header: &str,
method: &str,
path: &str,
body: Option<&str>,
) -> Result<()> {
let parts: Vec<&str> = signature_header.split('.').collect();
if parts.len() != 2 {
anyhow::bail!("Invalid signature format");
}
let timestamp = parts[0];
let sig_hex = parts[1];
let timestamp_secs: i64 = timestamp
.parse()
.map_err(|_| anyhow::anyhow!("Invalid timestamp format"))?;
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
let age = now - timestamp_secs;
const MAX_AGE_SECS: i64 = 300;
if age > MAX_AGE_SECS {
anyhow::bail!("Signature expired (age: {}s)", age);
}
if age < -MAX_AGE_SECS {
anyhow::bail!("Signature timestamp too far in future");
}
let body_hash = if let Some(body_content) = body {
let hash = Sha256::digest(body_content.as_bytes());
hex::encode(hash)
} else {
String::new()
};
let message = format!("{}|{}|{}|{}", method, path, timestamp, body_hash);
let sig_bytes = hex::decode(sig_hex)?;
let signature = Signature::from_bytes(
&sig_bytes
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid signature length"))?,
);
self.public_key.verify(message.as_bytes(), &signature)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};
#[test]
#[allow(clippy::zero_prefixed_literal)]
fn test_signature_validation() {
let signing_key = SigningKey::from_bytes(&[
157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068,
073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096,
]);
let verifying_key = signing_key.verifying_key();
let public_key_hex = hex::encode(verifying_key.as_bytes());
let validator = Ed25519Validator::new(&public_key_hex).unwrap();
let method = "POST";
let path = "/api/test";
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_string();
let body = r#"{"test":"data"}"#;
let body_hash = hex::encode(Sha256::digest(body.as_bytes()));
let message = format!("{}|{}|{}|{}", method, path, timestamp, body_hash);
let signature = signing_key.sign(message.as_bytes());
let sig_hex = hex::encode(signature.to_bytes());
let signature_header = format!("{}.{}", timestamp, sig_hex);
assert!(validator
.verify(&signature_header, method, path, Some(body))
.is_ok());
}
#[test]
fn test_invalid_signature() {
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
let verifying_key = signing_key.verifying_key();
let public_key_hex = hex::encode(verifying_key.as_bytes());
let validator = Ed25519Validator::new(&public_key_hex).unwrap();
let signature_header = "1731384000.deadbeef";
assert!(validator
.verify(signature_header, "GET", "/test", None)
.is_err());
}
#[test]
fn test_expired_signature() {
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
let verifying_key = signing_key.verifying_key();
let public_key_hex = hex::encode(verifying_key.as_bytes());
let validator = Ed25519Validator::new(&public_key_hex).unwrap();
let method = "GET";
let path = "/test";
let old_timestamp = "1731384000";
let message = format!("{}|{}|{}|", method, path, old_timestamp);
let signature = signing_key.sign(message.as_bytes());
let sig_hex = hex::encode(signature.to_bytes());
let signature_header = format!("{}.{}", old_timestamp, sig_hex);
let result = validator.verify(&signature_header, method, path, None);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Signature expired"));
}
#[test]
fn test_future_timestamp() {
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
let verifying_key = signing_key.verifying_key();
let public_key_hex = hex::encode(verifying_key.as_bytes());
let validator = Ed25519Validator::new(&public_key_hex).unwrap();
let method = "GET";
let path = "/test";
let future_timestamp = (SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
+ 400)
.to_string();
let message = format!("{}|{}|{}|", method, path, future_timestamp);
let signature = signing_key.sign(message.as_bytes());
let sig_hex = hex::encode(signature.to_bytes());
let signature_header = format!("{}.{}", future_timestamp, sig_hex);
let result = validator.verify(&signature_header, method, path, None);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("too far in future"));
}
}
+1 -28
View File
@@ -111,33 +111,6 @@ fn validate_and_build_request(
anyhow::bail!("Invalid tunnel secret");
}
// Ed25519 signature validation (if configured)
if let Ok(public_key_hex) = std::env::var("GOOSE_TUNNEL_PUBLIC_KEY_AUTH") {
match super::ed25519::Ed25519Validator::new(&public_key_hex) {
Ok(validator) => {
let sig_header = message
.headers
.as_ref()
.and_then(|h| {
h.iter()
.find(|(k, _)| k.eq_ignore_ascii_case("x-corp-signature"))
.map(|(_, v)| v)
})
.ok_or_else(|| anyhow::anyhow!("Missing X-Corp-Signature header"))?;
validator.verify(
sig_header,
&message.method,
&message.path,
message.body.as_deref(),
)?;
}
Err(e) => {
anyhow::bail!("Failed to create Ed25519 validator: {}", e);
}
}
}
let mut request_builder = match message.method.as_str() {
"GET" => client.get(url),
"POST" => client.post(url),
@@ -149,7 +122,7 @@ fn validate_and_build_request(
if let Some(headers) = &message.headers {
for (key, value) in headers {
if key.to_lowercase() == "x-secret-key" {
if key.eq_ignore_ascii_case("x-secret-key") {
continue;
}
request_builder = request_builder.header(key, value);
+14 -15
View File
@@ -1,4 +1,3 @@
pub mod ed25519;
pub mod lapstone;
#[cfg(test)]
@@ -124,7 +123,7 @@ impl TunnelManager {
}
}
pub async fn update_config<F>(&self, f: F)
pub async fn update_config<F>(&self, f: F) -> anyhow::Result<()>
where
F: FnOnce(&mut TunnelConfig),
{
@@ -132,21 +131,21 @@ impl TunnelManager {
f(&mut cfg);
let global_cfg = Config::global();
if let Err(e) = global_cfg.set_param("tunnel_auto_start", cfg.auto_start) {
tracing::error!("Failed to save tunnel config: {}", e);
return;
}
global_cfg
.set_param("tunnel_auto_start", cfg.auto_start)
.map_err(|e| anyhow::anyhow!("Failed to save tunnel config: {}", e))?;
if let Some(secret) = &cfg.secret {
if let Err(e) = global_cfg.set_secret("tunnel_secret", secret) {
tracing::error!("Failed to save tunnel secret: {}", e);
}
global_cfg
.set_secret("tunnel_secret", secret)
.map_err(|e| anyhow::anyhow!("Failed to save tunnel secret: {}", e))?;
}
if let Some(agent_id) = &cfg.agent_id {
if let Err(e) = global_cfg.set_secret("tunnel_agent_id", agent_id) {
tracing::error!("Failed to save tunnel agent_id: {}", e);
}
global_cfg
.set_secret("tunnel_agent_id", agent_id)
.map_err(|e| anyhow::anyhow!("Failed to save tunnel agent_id: {}", e))?;
}
Ok(())
}
async fn start_tunnel_internal(&self) -> anyhow::Result<(TunnelInfo, mpsc::Receiver<()>)> {
@@ -162,7 +161,7 @@ impl TunnelManager {
c.secret = Some(tunnel_secret.clone());
c.agent_id = Some(agent_id.clone());
})
.await;
.await?;
let (restart_tx, restart_rx) = mpsc::channel::<()>(1);
*self.restart_tx.write().await = Some(restart_tx.clone());
@@ -195,7 +194,7 @@ impl TunnelManager {
Ok((info, mut restart_rx)) => {
*self.state.write().await = TunnelState::Running;
*self.info.write().await = Some(info.clone());
self.update_config(|c| c.auto_start = true).await;
let _ = self.update_config(|c| c.auto_start = true).await;
let state = self.state.clone();
let config = self.config.clone();
@@ -267,7 +266,7 @@ impl TunnelManager {
*self.info.write().await = None;
if clear_auto_start {
self.update_config(|c| c.auto_start = false).await;
let _ = self.update_config(|c| c.auto_start = false).await;
}
}
}