mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
removing unused signing code and cleaning up from feedback
This commit is contained in:
Generated
-58
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user