niom-turn/src/bin/turn_rest_cred.rs

77 lines
2.6 KiB
Rust

use anyhow::Context;
use base64::Engine;
use hmac::{Hmac, Mac};
use sha1::Sha1;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
fn now_unix_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0))
.as_secs()
}
fn turn_rest_password_base64(secret: &[u8], username: &str) -> String {
type HmacSha1 = Hmac<Sha1>;
let mut mac = HmacSha1::new_from_slice(secret).expect("rest secret to build hmac");
mac.update(username.as_bytes());
let bytes = mac.finalize().into_bytes();
base64::engine::general_purpose::STANDARD.encode(bytes)
}
/// Minimal offline TURN REST credential generator.
///
/// Usage:
/// - `cargo run --bin turn_rest_cred -- --secret "..." --user alice --ttl 600`
///
/// Output:
/// - `TURN_USERNAME=...`
/// - `TURN_PASSWORD=...`
fn main() -> anyhow::Result<()> {
let mut secret: Option<String> = None;
let mut user: Option<String> = None;
let mut ttl: u64 = 600;
let mut json = false;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--secret" => secret = Some(args.next().context("--secret requires a value")?),
"--user" => user = Some(args.next().context("--user requires a value")?),
"--ttl" => {
let v = args.next().context("--ttl requires a value")?;
ttl = v.parse::<u64>().context("--ttl must be an integer")?;
}
"--json" => json = true,
"-h" | "--help" => {
println!(
"turn_rest_cred\n\nUSAGE:\n turn_rest_cred --secret <secret> [--user <id>] [--ttl <seconds>] [--json]\n\nNOTES:\n Username format is <expiry>[:<user>]. Password is base64(HMAC-SHA1(secret, username)).\n"
);
return Ok(());
}
other => return Err(anyhow::anyhow!("unknown arg: {}", other)),
}
}
let secret = secret.context("missing --secret")?;
let expiry = now_unix_secs().saturating_add(ttl.max(60));
let username = match user {
Some(u) if !u.is_empty() => format!("{}:{}", expiry, u),
_ => expiry.to_string(),
};
let password = turn_rest_password_base64(secret.as_bytes(), &username);
if json {
println!(
"{{\n \"username\": \"{}\",\n \"credential\": \"{}\",\n \"ttl\": {}\n}}",
username, password, ttl
);
} else {
println!("TURN_USERNAME={}", username);
println!("TURN_PASSWORD={}", password);
println!("TURN_TTL={}", ttl);
}
Ok(())
}