Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ And it's ready to take contestants, the hackathon can finally start!
## Security

- We use [`Argon2`] for password-hashing.
- Puzzle solutions are also stored as Argon2 hashes (not raw plaintext).
- State saving is encrypted with [`chacha20poly1305`] (based on [this great guide])

> [!note]
Expand Down
41 changes: 31 additions & 10 deletions src/backend/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ use dioxus::prelude::*;
#[cfg(feature = "server")]
use {
super::logic::*,
chacha20poly1305::aead::{OsRng, rand_core::RngCore},
dioxus::fullstack::{Cookie, TypedHeader},
uuid::Uuid,
zeroize::Zeroize,
};

#[cfg(feature = "server")]
fn hash_puzzle_solution(raw_solution: &str) -> Result<PuzzleSolutionHash, HttpError> {
let mut salt = [0u8; 32];
OsRng.fill_bytes(&mut salt);
let hash = argon2::hash_encoded(raw_solution.as_bytes(), &salt, &ARGON2CONF)
.inspect_err(|e| error!("nem sikerült feladatmegoldást hasítani: {e}"))
.or_internal_server_error("nem sikerült feladatmegoldást hasítani");
salt.zeroize();
hash
}

#[get("/api/event_title")]
pub async fn event_title() -> Result<String> {
Ok(EVENT_TITLE.clone()?)
Expand Down Expand Up @@ -157,7 +169,7 @@ pub async fn set_passwd(init_password: String, mut password: String) -> Result<S
/// NOTE: if any of the solutions is incorrect, none will be saved
#[post("/api/set_solution")]
pub async fn set_solution(
puzzle_solutions: PuzzleSolutions,
mut puzzle_solutions: PuzzleSolutions,
mut password: String,
) -> Result<String, HttpError> {
// submitting as admin
Expand All @@ -175,6 +187,12 @@ pub async fn set_solution(
.or_forbidden("legalább egy feladat már be van állítva")?;
drop(puzzles_lock);

for puzzle in puzzle_solutions.values_mut() {
let solution_hash = hash_puzzle_solution(&puzzle.solution)?;
puzzle.solution.zeroize();
puzzle.solution = solution_hash;
}

PUZZLES.write().await.extend(puzzle_solutions);

#[cfg(feature = "server_state_save")]
Expand All @@ -191,7 +209,7 @@ pub async fn set_solution(
#[post("/api/submit", cookies: TypedHeader<Cookie>)]
pub async fn submit_solution(
puzzle_id: PuzzleId,
solution: PuzzleSolution,
mut solution: PuzzleSolution,
) -> Result<String, HttpError> {
check_admin_pwd()?;
let uuid = extract_sid_cookie(cookies).await?;
Expand All @@ -202,14 +220,17 @@ pub async fn submit_solution(
.or_not_found("nincs ezzel az azonosítóval csapat")?
.clone(); // PERF: rather clone than lock

PUZZLES
.read()
.await
.get(&puzzle_id)
.or_not_found("nincs ezzel az azonosítóval feladat")?
.solution
.eq(&solution)
.or_forbidden("érvénytelen megoldás ehhez a feladathoz")?;
let is_solution_valid = {
let puzzles_lock = PUZZLES.read().await;
let puzzle = puzzles_lock
.get(&puzzle_id)
.or_not_found("nincs ezzel az azonosítóval feladat")?;
argon2::verify_encoded(&puzzle.solution, solution.as_bytes())
.inspect_err(|e| error!("nem sikerült ellenőrizni a feladatmegoldást: {e}"))
.or_internal_server_error("nem sikerült ellenőrizni a feladatmegoldást")?
};
solution.zeroize();
is_solution_valid.or_forbidden("érvénytelen megoldás ehhez a feladathoz")?;

let mut teams_lock = TEAMS.write().await;

Expand Down
4 changes: 3 additions & 1 deletion src/backend/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::collections::{HashMap, HashSet};
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(crate = "dioxus::fullstack::serde")]
pub struct Puzzle {
pub solution: PuzzleSolution,
/// argon2-encoded solution hash (not the raw solution)
pub solution: PuzzleSolutionHash,
/// how much it's worth
pub value: PuzzleValue,
}
Expand All @@ -14,6 +15,7 @@ pub type PuzzleId = String;
/// how much points you get for solving a puzzle
pub type PuzzleValue = u32;
pub type PuzzleSolution = String;
pub type PuzzleSolutionHash = String;
/// all the known puzzles with their values
pub type PuzzlesExisting = HashMap<PuzzleId, PuzzleValue>;
/// all the puzzles with their values and solutions
Expand Down
Loading