77 lines
2.6 KiB
Rust
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(())
|
|
}
|