From 6c654072a5d627dbe83495f45e2ce805e3f652ae Mon Sep 17 00:00:00 2001 From: Bad Manners Date: Thu, 19 Dec 2024 19:17:21 -0300 Subject: [PATCH] Include server fingerprint in auth responses and rename project --- Cargo.lock | 38 +++++++++++++++++++------------------- Cargo.toml | 4 ++-- Dockerfile | 4 ++-- src/main.rs | 38 +++++++++++++++++++++++++++++--------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65a89f0..90dd686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -83,6 +83,24 @@ dependencies = [ "syn", ] +[[package]] +name = "auth_ssh_games" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "base64 0.22.1", + "clap", + "hmac", + "jsonwebtoken", + "ring", + "serde", + "serde_json", + "sha2", + "tokio", + "uuid", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -847,24 +865,6 @@ dependencies = [ "time", ] -[[package]] -name = "sish_games" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "base64 0.22.1", - "clap", - "hmac", - "jsonwebtoken", - "ring", - "serde", - "serde_json", - "sha2", - "tokio", - "uuid", -] - [[package]] name = "smallvec" version = "1.13.2" diff --git a/Cargo.toml b/Cargo.toml index 33bc521..4802d58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "sish_games" -description = "Server for sish games" +name = "auth_ssh_games" +description = "Authentication server for SSH-based games" version = "0.1.0" edition = "2021" diff --git a/Dockerfile b/Dockerfile index fc49e2a..27fcf04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,5 +5,5 @@ COPY . . RUN cargo build --release FROM alpine:3.20 -COPY --from=builder /usr/src/app/target/release/sish_games /usr/local/bin/sish_games -ENTRYPOINT [ "sish_games" ] \ No newline at end of file +COPY --from=builder /usr/src/app/target/release/auth_ssh_games /usr/local/bin/auth_ssh_games +ENTRYPOINT [ "auth_ssh_games" ] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7840679..db511c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,8 +27,15 @@ struct GenerationRequest { password: String, } +#[derive(Serialize)] +struct GenerationResponse { + jwt: String, + server_fingerprint: String, +} + #[derive(Clone)] struct GenerationState { + server_fingerprint: Arc, encoding_key: Arc, audiences: Arc>, } @@ -49,16 +56,24 @@ struct ValidationState { #[derive(Parser)] #[command(version, about, long_about = None)] struct Args { + /// Fingerprint of the server's SSH key, to avoid MitM attacks. + #[arg(short = 'f', long, value_name = "FINGERPRINT")] + server_fingerprint: String, + + /// Private key for signing JWTs. #[arg(short = 'i', long, value_name = "FILE")] private_key: PathBuf, + /// Public key for validating JWTs. #[arg(short, long, value_name = "FILE")] public_key: PathBuf, - #[arg(short, long, num_args(1..))] + /// Allowed audiences for JWTs. + #[arg(short, long, num_args(1..), value_name = "AUDIENCE")] audiences: Vec, - #[arg(short, long)] + /// Secret token that's part of the URL for authentication. + #[arg(short, long, value_name = "TOKEN")] validation_token: String, } @@ -82,6 +97,7 @@ async fn main() -> Result<()> { let router = Router::new() .route("/", post(generation_handler)) .with_state(GenerationState { + server_fingerprint: Arc::new(args.server_fingerprint), encoding_key, audiences: Arc::new(audiences.iter().cloned().collect()), }) @@ -103,16 +119,16 @@ async fn generation_handler( Json(payload): Json, ) -> impl IntoResponse { if !state.audiences.contains(&payload.audience) { - return StatusCode::FORBIDDEN.into_response(); + return (StatusCode::FORBIDDEN, "invalid payload").into_response(); } let password = match BASE64_STANDARD.decode(payload.password) { Ok(password) => password, - Err(_) => return StatusCode::FORBIDDEN.into_response(), + Err(_) => return (StatusCode::FORBIDDEN, "invalid payload").into_response(), }; let mut hmac = Hmac::::new_from_slice(payload.audience.as_bytes()).unwrap(); hmac.update(payload.user.as_bytes()); if hmac.verify_slice(&password).is_err() { - return StatusCode::FORBIDDEN.into_response(); + return (StatusCode::FORBIDDEN, "invalid payload").into_response(); } let claims = AuthenticationClaims { aud: payload.audience, @@ -125,7 +141,11 @@ async fn generation_handler( &state.encoding_key, ) .unwrap(); - jwt.into_response() + Json(GenerationResponse { + jwt, + server_fingerprint: state.server_fingerprint.as_ref().clone(), + }) + .into_response() } async fn validation_handler( @@ -138,11 +158,11 @@ async fn validation_handler( &state.validation, ) { Ok(token) => token.claims, - Err(_) => return StatusCode::FORBIDDEN, + Err(_) => return (StatusCode::FORBIDDEN, "invalid auth").into_response(), }; if claims.sub == payload.user && Uuid::try_parse(&payload.user).is_ok() { - StatusCode::OK + StatusCode::OK.into_response() } else { - StatusCode::FORBIDDEN + (StatusCode::FORBIDDEN, "invalid auth").into_response() } }