feat: fetches pubkeys, boxes messages

This commit is contained in:
Aaron Global 2025-11-17 21:22:05 -05:00
parent 6f49740c39
commit 9ed30871c1
10 changed files with 352 additions and 42 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
/target /target
/web-build
.local

116
Cargo.lock generated
View file

@ -242,6 +242,7 @@ name = "cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"message-tools",
] ]
[[package]] [[package]]
@ -343,6 +344,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.31" version = "0.3.31"
@ -384,6 +391,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -424,6 +443,12 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "http-range-header"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@ -504,6 +529,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.177" version = "0.2.177"
@ -543,7 +574,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2",
"chacha20poly1305", "chacha20poly1305",
"getrandom", "getrandom 0.2.16",
"serde", "serde",
"serde_json", "serde_json",
"wasm-bindgen", "wasm-bindgen",
@ -556,6 +587,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.1.0" version = "1.1.0"
@ -655,13 +696,19 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.16",
] ]
[[package]] [[package]]
@ -777,7 +824,12 @@ name = "server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"getrandom 0.3.4",
"lazy_static",
"message-tools",
"serde_json",
"tokio", "tokio",
"tower-http",
] ]
[[package]] [[package]]
@ -862,6 +914,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tokio-util"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@ -878,6 +943,32 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower-http"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.3" version = "0.3.3"
@ -916,6 +1007,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.22" version = "1.0.22"
@ -950,6 +1047,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.105" version = "0.2.105"
@ -1084,6 +1190,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]] [[package]]
name = "x25519-dalek" name = "x25519-dalek"
version = "2.0.1" version = "2.0.1"

View file

@ -1,2 +1,5 @@
# zachery-lol # zachery-lol
Code and assets for my website. Code and assets for my website.
### usage
`cargo run -- --help`

View file

@ -6,3 +6,6 @@ default-run = "cli"
[dependencies] [dependencies]
clap = { version = "4.5.52", features = ["derive"] } clap = { version = "4.5.52", features = ["derive"] }
# local
message-tools = { path = "../message-tools" }

View file

@ -1,6 +1,7 @@
use std::process::Command;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::{os::unix::process::ExitStatusExt, process::{Command, ExitStatus}};
const WEB_BUILD_DIR: &str = "web-build";
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -14,23 +15,36 @@ enum Commands {
/// Builds the website and server, including wasm files /// Builds the website and server, including wasm files
Build, Build,
/// Starts the server /// Runs the server locally
Run, Start {
/// Whether to build again or not
#[arg(long)]
rebuild: bool,
},
/// Generates new encryption keys /// Generates new encryption keys
GenKeys { GenKeys {
/// How many keypairs to generate /// How many keypairs to generate
#[arg(short, long, default_value = "100")] #[arg(short, long, default_value = "100")]
number: Option<usize>, number: usize,
/// Directory to put the generated secret key files
#[arg(short, long, default_value = ".local/secrets.json")]
sk_out_dir: String,
/// Directory to put the generated pubklic key pool
#[arg(long, default_value = "target/public_keys.json")]
pk_out_dir: String,
}, },
/// Reads fetched messages /// Reads fetched messages
ReadMessages { ReadMessages {
/// Optional source file path /// Source file path
#[arg(short, long)] #[arg(short, long, default_value = ".local/messages")]
source: Option<String>, source: String,
/// Optional output file path /// Optional output file path for decoded messages
/// Default: stdout
#[arg(short, long)] #[arg(short, long)]
out_dir: Option<String>, out_dir: Option<String>,
}, },
@ -42,29 +56,67 @@ fn main() {
match &cli.command { match &cli.command {
Commands::Build => { Commands::Build => {
println!("Executing build command..."); println!("Executing build command...");
build(); build();
} }
Commands::Run => {
println!("Executing run command..."); Commands::Start { rebuild } => {
// Add your run logic here println!("Starting server...");
if *rebuild {
println!("Rebuilding web pages...");
build();
} // Change the current working directory of the running Rust program
println!("Changing directory to {WEB_BUILD_DIR}...");
let cd_result = std::env::set_current_dir(WEB_BUILD_DIR);
// Use the existing helper to check the result
handle_command_status(
cd_result.map(|_| std::process::ExitStatus::from_raw(0)),
"cd build dir",
);
println!("Running server executable...");
// Now run the server executable from within the new current directory
handle_command_status(Command::new("./server").status(), "run server");
} }
Commands::GenKeys { .. } => { Commands::GenKeys {
number,
sk_out_dir,
pk_out_dir,
} => {
println!("Executing gen-keys command..."); println!("Executing gen-keys command...");
// Add your key generation logic here message_tools::gen_keys::generate_keys_to_file(pk_out_dir, sk_out_dir, *number)
.expect("write key files");
} }
Commands::ReadMessages { source, .. } => { Commands::ReadMessages { .. } => {
println!("Executing read-messages command..."); println!("Executing read-messages command...");
match source {
Some(s) => println!("Reading from source: {}", s),
None => println!("Reading from default source."),
}
// Add your message reading logic here // Add your message reading logic here
} }
} }
} }
// A helper function to process the Result<ExitStatus, io::Error>
fn handle_command_status(status_result: std::io::Result<ExitStatus>, command_name: &str) {
match status_result {
Ok(exit_status) => {
if !exit_status.success() {
eprintln!(
"\nCommand '{}' failed with exit code: {:?}",
command_name,
exit_status.code()
);
std::process::exit(exit_status.code().unwrap_or(1));
}
}
Err(e) => {
eprintln!("\nFailed to execute command '{}': {}", command_name, e);
std::process::exit(1); // Exit with an error code
}
}
}
fn build() { fn build() {
println!("Building wasm package...");
// build the wasm package
let status = Command::new("wasm-pack") let status = Command::new("wasm-pack")
.arg("build") .arg("build")
.arg("--out-dir") .arg("--out-dir")
@ -73,20 +125,55 @@ fn build() {
.arg("--target") .arg("--target")
.arg("no-modules") .arg("no-modules")
.status(); .status();
handle_command_status(status, "wasm-pack build");
match status { println!("Building server executable...");
Ok(exit_status) => { // build the server executable
if exit_status.success() { let status = Command::new("cargo")
println!("\nBuild successful!"); .arg("build")
} else { .arg("--release")
// Print the exit code if the build failed .args(["-p", "server"])
eprintln!("\nBuild failed with exit code: {:?}", exit_status.code()); .args(["--target", "x86_64-unknown-linux-musl"])
std::process::exit(exit_status.code().unwrap_or(1)); .status();
} handle_command_status(status, "cargo build server");
}
Err(e) => { let server_bin = "target/x86_64-unknown-linux-musl/release/server";
eprintln!("\nFailed to execute cargo build command: {}", e);
std::process::exit(1); // Exit with an error code println!("Creating build directory...");
} // make webpage build directory if it doesn't already exist
let status = Command::new("mkdir").args(["-p", WEB_BUILD_DIR]).status();
handle_command_status(status, "mkdir web-build");
println!("Copying files...");
// copy relevant files
let status = Command::new("cp")
.args(["-R", "routes"])
.arg(format!("{WEB_BUILD_DIR}/routes"))
.status();
handle_command_status(status, "cp routes");
let status = Command::new("cp")
.arg(server_bin)
.arg(format!("{WEB_BUILD_DIR}"))
.status();
handle_command_status(status, "cp server bin");
let pubkey_file = "target/public_keys.json";
if !std::path::Path::new(pubkey_file).exists() {
panic!("No public key file. Please generate keys first.");
} }
let status = Command::new("mkdir")
.args(["-p", &format!("{WEB_BUILD_DIR}/pubkeys")])
.status();
handle_command_status(status, "mkdir web-build");
let status = Command::new("cp")
.arg("target/public_keys.json")
.arg(format!("{WEB_BUILD_DIR}/pubkeys/"))
.status();
handle_command_status(status, "cp pubkeys");
println!("\nBuild successful!");
} }

View file

@ -0,0 +1,48 @@
use std::io::Write;
use std::{fs::OpenOptions, path::Path};
use serde::Serialize;
use x25519_dalek::{PublicKey, StaticSecret};
#[derive(Serialize)]
pub struct Keypair {
sk: Vec<u8>,
pk: Vec<u8>,
}
fn generate_keypair() -> Keypair {
let sk = StaticSecret::random();
let pk = PublicKey::from(&sk);
Keypair {
sk: sk.as_bytes().to_vec(),
pk: pk.as_bytes().to_vec(),
}
}
pub fn generate_keys_to_file(
pks_path: impl AsRef<Path>,
sks_path: impl AsRef<Path>,
num: usize,
) -> std::io::Result<()> {
let mut pks_file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(pks_path)?;
let mut sks_file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(sks_path)?;
for _ in 0..num {
let kp = generate_keypair();
writeln!(pks_file, "{}", serde_json::to_string(&kp.pk)?)?;
writeln!(sks_file, "{}", serde_json::to_string(&kp.sk)?)?;
}
Ok(())
}

View file

@ -4,8 +4,10 @@ use chacha20poly1305::{
aead::{Aead, OsRng}, aead::{Aead, OsRng},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::StaticSecret;
pub mod gen_keys;
pub use x25519_dalek::PublicKey;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub mod wasm; pub mod wasm;

View file

@ -10,3 +10,9 @@ path = "bin/main.rs"
[dependencies] [dependencies]
axum = "0.8.7" axum = "0.8.7"
tokio = { version = "1.48.0", features = ["full"] } tokio = { version = "1.48.0", features = ["full"] }
tower-http = { version = "0.6.6", features = ["fs"] }
serde_json = "1.0.145"
message-tools = { path = "../message-tools" }
getrandom = "0.3.4"
lazy_static = "1.5.0"

View file

@ -1,18 +1,60 @@
use axum::{Router, response::Html, routing::get}; use axum::{Router, response::Html, routing::get};
use std::{fs::OpenOptions, io::Read}; use std::{fs::OpenOptions, io::{BufRead, Read}, path::Path};
use tower_http::services::ServeDir;
const PUBKEY_PATH: &str = "./pubkeys/public_keys.json";
lazy_static::lazy_static! {
static ref PUBLIC_KEYS: Vec<Vec<u8>> = load_public_keys(PUBKEY_PATH).expect("static path");
}
type Error = Box<dyn std::error::Error>; type Error = Box<dyn std::error::Error>;
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
fn load_public_keys(path: impl AsRef<Path>) -> std::io::Result<Vec<Vec<u8>>> {
// Open the file
let f = OpenOptions::new()
.read(true)
.write(false)
.open(path)?;
// Wrap it in a BufReader for efficient line-by-line reading
let reader = std::io::BufReader::new(f);
let mut public_keys = Vec::new();
// Iterate over each line in the file
for line_result in reader.lines() {
let line = line_result?; // Handle potential IO errors during reading
// Skip empty lines or lines with only whitespace
let trimmed_line = line.trim();
if trimmed_line.is_empty() {
continue;
}
// Deserialize the line (which is a single JSON array) into a Vec<u8>
let key: Vec<u8> = serde_json::from_str(trimmed_line).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to parse JSON line: {}", e),
)
})?;
public_keys.push(key);
}
Ok(public_keys)
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
// build our application with a single route // build our application with a single route
let app = Router::new() let app = Router::new()
.route("/", get(serve_path("./routes/root/index.html")?)) .route("/", get(serve_path("./routes/root/index.html")?))
.route("/who", get(serve_path("./routes/who/index.html")?)) .route("/who", get(serve_path("./routes/who/index.html")?))
.route("/api/pubkey", get(async || select_key(&PUBLIC_KEYS)))
.route("/contact", get(serve_path("./routes/contact/index.html")?)) .route("/contact", get(serve_path("./routes/contact/index.html")?))
// .route("/api/pubkey", get(todo!())); .nest_service("/routes", ServeDir::new("./routes"));
;
// run our app with hyper, listening globally on port 3000 // run our app with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
@ -21,6 +63,11 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn select_key(pks: &Vec<Vec<u8>>) -> String {
let idx = getrandom::u64().expect("random integer") as usize % pks.len();
serde_json::to_string(&pks[idx]).expect("serialize vec of bytes")
}
fn serve_path(path: impl AsRef<std::path::Path>) -> Result<Html<String>> { fn serve_path(path: impl AsRef<std::path::Path>) -> Result<Html<String>> {
let mut s = String::new(); let mut s = String::new();
OpenOptions::new() OpenOptions::new()

View file

@ -88,7 +88,7 @@
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
} }
</style> </style>
<script src="pkg/message_tools.js"></script> <script src="./routes/contact/pkg/message_tools.js"></script>
<script> <script>
const { make_salt, make_key_bytes, box_message } = wasm_bindgen; const { make_salt, make_key_bytes, box_message } = wasm_bindgen;
var serverPublicKey; var serverPublicKey;