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 docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The authentication system supports:
- **JWT-based authentication** with query parameter tokens
- **Path-based authorization** with hierarchical permissions
- **Symmetric key cryptography** (HMAC-SHA256/384/512)
- **Asymmetric key cryptography** (RSASSA-PKCS1-SHA256/384/512, RSASSA-PSS-SHA256/384/512, ECDSA-SHA256/384)
- **Anonymous access** for public content
- **Cluster authentication** for relay-to-relay communication

Expand Down
1 change: 1 addition & 0 deletions rs/moq-relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Key features:
- Path-based authorization with `root`, `pub`, and `sub` claims
- Anonymous access support for public content
- Symmetric key cryptography (HMAC-SHA256/384/512)
- Asymmetric key cryptography (RSASSA-PKCS1-SHA256/384/512, RSASSA-PSS-SHA256/384/512, ECDSA-SHA256/384)

Quick example configuration in your `.toml` file:
```toml
Expand Down
2 changes: 1 addition & 1 deletion rs/moq-relay/cfg/prod.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ key = "key.pem"

# See docs/auth.md for more information.
[auth]
key = "root.jwk"
key = "public.jwk"
10 changes: 5 additions & 5 deletions rs/moq-relay/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ pub struct AuthToken {

#[derive(Clone)]
pub struct Auth {
key: Option<Arc<moq_token::Key>>,
key: Option<Arc<moq_token::JWK>>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just keep this as Key? It's fine if it's not backwards compatible.

public: Option<PathOwned>,
}

impl Auth {
pub fn new(config: AuthConfig) -> anyhow::Result<Self> {
let key = match config.key.as_deref() {
Some(path) => Some(moq_token::Key::from_file(path)?),
Some(path) => Some(moq_token::JWK::from_file(path)?),
None => None,
};

Expand Down Expand Up @@ -158,12 +158,12 @@ impl Auth {
#[cfg(test)]
mod tests {
use super::*;
use moq_token::{Algorithm, Key};
use moq_token::{Algorithm, JWK};
use tempfile::NamedTempFile;

fn create_test_key() -> anyhow::Result<(NamedTempFile, Key)> {
fn create_test_key() -> anyhow::Result<(NamedTempFile, JWK)> {
let key_file = NamedTempFile::new()?;
let key = Key::generate(Algorithm::HS256, None);
let key = JWK::generate(Algorithm::HS256, None)?;
key.to_file(key_file.path())?;
Ok((key_file, key))
}
Expand Down
17 changes: 13 additions & 4 deletions rs/moq-token-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

A simple JWT-based authentication scheme for moq-relay.

## Usage
## Quick Usage (symmetric keys)
```bash
# generate secret key
moq-token --key key.jwk generate
# sign a new JWT
moq-token --key key.jwk sign --root demo --publish bbb > token.jwt
# verify the JWT
moq-token --key key.jwk verify < token.jwt
```

## Public Keys
We currently don't support public key cryptography, but we should in the future.
Patches welcome!
## Quick Usage (asymmetric keys)
```bash
# generate private and public keys
moq-token --key private.jwk generate --algorithm RS256 --public public.jwk
# sign a new JWT (using private key)
moq-token --key private.jwk sign --root demo --publish bbb > token.jwt
# verify the JWT (using public key)
moq-token --key public.jwk verify < token.jwt
```
22 changes: 16 additions & 6 deletions rs/moq-token-cli/src/bin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Context;
use clap::{Parser, Subcommand};
use moq_token::Algorithm;
use std::{io, path::PathBuf};

#[derive(Debug, Parser)]
Expand All @@ -23,11 +24,15 @@ enum Commands {
Generate {
/// The algorithm to use.
#[arg(long, default_value = "HS256")]
algorithm: moq_token::Algorithm,
algorithm: Algorithm,

/// An optional key ID, useful for rotating keys.
#[arg(long)]
id: Option<String>,

/// Optional path to save the public key (for asymmetric algorithms).
#[arg(long)]
public: Option<PathBuf>,
},

/// Sign a token to stdout, reading the key from stdin.
Expand Down Expand Up @@ -75,9 +80,14 @@ fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

match cli.command {
Commands::Generate { algorithm, id } => {
let key = moq_token::Key::generate(algorithm, id);
key.to_file(cli.key)?;
Commands::Generate { algorithm, id, public } => {
let key = moq_token::JWK::generate(algorithm, id)?;

if let Some(public) = public {
key.to_public()?.to_file(public)?;
}

key.to_file(&cli.key)?;
}

Commands::Sign {
Expand All @@ -88,7 +98,7 @@ fn main() -> anyhow::Result<()> {
expires,
issued,
} => {
let key = moq_token::Key::from_file(cli.key)?;
let key = moq_token::JWK::from_file(cli.key)?;

let payload = moq_token::Claims {
root,
Expand All @@ -104,7 +114,7 @@ fn main() -> anyhow::Result<()> {
}

Commands::Verify => {
let key = moq_token::Key::from_file(cli.key)?;
let key = moq_token::JWK::from_file(cli.key)?;
let token = io::read_to_string(io::stdin())?.trim().to_string();
let payload = key.decode(&token)?;

Expand Down
4 changes: 4 additions & 0 deletions rs/moq-token/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ edition = "2021"
anyhow = "1"
aws-lc-rs = "1"
base64 = "0.22"
elliptic-curve = { version = "0.13.8", features = ["jwk", "arithmetic", "alloc", "sec1", "pkcs8"] }
jsonwebtoken = "9"
p256 = { version = "0.13.2", features = ["serde", "jwk"] }
p384 = { version = "0.13.1" }
rsa = { version = "0.9.9" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_with = { version = "3", features = ["base64"] }
17 changes: 13 additions & 4 deletions rs/moq-token/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ A simple JWT and JWK based authentication scheme for moq-relay.
For comprehensive documentation including token structure, authorization rules, and examples, see:
**[Authentication Documentation](../../docs/auth.md)**

## Quick Usage
## Quick Usage (symmetric keys)
```bash
# generate secret key
moq-token --key key.jwk generate
# sign a new JWT
moq-token --key key.jwk sign --root demo --publish bbb > token.jwt
# verify the JWT
moq-token --key key.jwk verify < token.jwt
```

## Public Keys
We currently don't support public key cryptography, but we should in the future.
Patches welcome!
## Quick Usage (asymmetric keys)
```bash
# generate private and public keys
moq-token --key private.jwk generate --algorithm RS256 --public public.jwk
# sign a new JWT (using private key)
moq-token --key private.jwk sign --root demo --publish bbb > token.jwt
# verify the JWT (using public key)
moq-token --key public.jwk verify < token.jwt
```
111 changes: 109 additions & 2 deletions rs/moq-token/src/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ use std::{fmt, str::FromStr};
/// We could support all of them, but there's currently no point using public key crypto.
/// The relay can fetch any resource it wants; it doesn't need to forge tokens.
///
/// TODO support public key crypto at some point.
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]
pub enum Algorithm {
HS256,
HS384,
HS512,
ES256,
ES384,
RS256,
RS384,
RS512,
PS256,
PS384,
PS512,
EdDSA,
}

impl From<Algorithm> for jsonwebtoken::Algorithm {
Expand All @@ -19,6 +27,15 @@ impl From<Algorithm> for jsonwebtoken::Algorithm {
Algorithm::HS256 => jsonwebtoken::Algorithm::HS256,
Algorithm::HS384 => jsonwebtoken::Algorithm::HS384,
Algorithm::HS512 => jsonwebtoken::Algorithm::HS512,
Algorithm::ES256 => jsonwebtoken::Algorithm::ES256,
Algorithm::ES384 => jsonwebtoken::Algorithm::ES384,
Algorithm::RS256 => jsonwebtoken::Algorithm::RS256,
Algorithm::RS384 => jsonwebtoken::Algorithm::RS384,
Algorithm::RS512 => jsonwebtoken::Algorithm::RS512,
Algorithm::PS256 => jsonwebtoken::Algorithm::PS256,
Algorithm::PS384 => jsonwebtoken::Algorithm::PS384,
Algorithm::PS512 => jsonwebtoken::Algorithm::PS512,
Algorithm::EdDSA => jsonwebtoken::Algorithm::EdDSA,
}
}
}
Expand All @@ -31,6 +48,15 @@ impl FromStr for Algorithm {
"HS256" => Ok(Algorithm::HS256),
"HS384" => Ok(Algorithm::HS384),
"HS512" => Ok(Algorithm::HS512),
"ES256" => Ok(Algorithm::ES256),
"ES384" => Ok(Algorithm::ES384),
"RS256" => Ok(Algorithm::RS256),
"RS384" => Ok(Algorithm::RS384),
"RS512" => Ok(Algorithm::RS512),
"PS256" => Ok(Algorithm::PS256),
"PS384" => Ok(Algorithm::PS384),
"PS512" => Ok(Algorithm::PS512),
"EdDSA" => Ok(Algorithm::EdDSA),
_ => anyhow::bail!("invalid algorithm: {s}"),
}
}
Expand All @@ -42,6 +68,15 @@ impl fmt::Display for Algorithm {
Algorithm::HS256 => write!(f, "HS256"),
Algorithm::HS384 => write!(f, "HS384"),
Algorithm::HS512 => write!(f, "HS512"),
Algorithm::ES256 => write!(f, "ES256"),
Algorithm::ES384 => write!(f, "ES384"),
Algorithm::RS256 => write!(f, "RS256"),
Algorithm::RS384 => write!(f, "RS384"),
Algorithm::RS512 => write!(f, "RS512"),
Algorithm::PS256 => write!(f, "PS256"),
Algorithm::PS384 => write!(f, "PS384"),
Algorithm::PS512 => write!(f, "PS512"),
Algorithm::EdDSA => write!(f, "EdDSA"),
}
}
}
Expand All @@ -55,12 +90,23 @@ mod tests {
assert_eq!(Algorithm::from_str("HS256").unwrap(), Algorithm::HS256);
assert_eq!(Algorithm::from_str("HS384").unwrap(), Algorithm::HS384);
assert_eq!(Algorithm::from_str("HS512").unwrap(), Algorithm::HS512);
assert_eq!(Algorithm::from_str("ES256").unwrap(), Algorithm::ES256);
assert_eq!(Algorithm::from_str("ES384").unwrap(), Algorithm::ES384);
assert_eq!(Algorithm::from_str("RS256").unwrap(), Algorithm::RS256);
assert_eq!(Algorithm::from_str("RS384").unwrap(), Algorithm::RS384);
assert_eq!(Algorithm::from_str("RS512").unwrap(), Algorithm::RS512);
assert_eq!(Algorithm::from_str("PS256").unwrap(), Algorithm::PS256);
assert_eq!(Algorithm::from_str("PS384").unwrap(), Algorithm::PS384);
assert_eq!(Algorithm::from_str("PS512").unwrap(), Algorithm::PS512);
assert_eq!(Algorithm::from_str("EdDSA").unwrap(), Algorithm::EdDSA);
}

#[test]
fn test_algorithm_from_str_invalid() {
assert!(Algorithm::from_str("HS128").is_err());
assert!(Algorithm::from_str("RS256").is_err());
assert!(Algorithm::from_str("RS128").is_err());
assert!(Algorithm::from_str("ES512").is_err());
assert!(Algorithm::from_str("EDDSA").is_err());
assert!(Algorithm::from_str("invalid").is_err());
assert!(Algorithm::from_str("").is_err());
}
Expand All @@ -70,6 +116,15 @@ mod tests {
assert_eq!(Algorithm::HS256.to_string(), "HS256");
assert_eq!(Algorithm::HS384.to_string(), "HS384");
assert_eq!(Algorithm::HS512.to_string(), "HS512");
assert_eq!(Algorithm::ES256.to_string(), "ES256");
assert_eq!(Algorithm::ES384.to_string(), "ES384");
assert_eq!(Algorithm::RS256.to_string(), "RS256");
assert_eq!(Algorithm::RS384.to_string(), "RS384");
assert_eq!(Algorithm::RS512.to_string(), "RS512");
assert_eq!(Algorithm::PS256.to_string(), "PS256");
assert_eq!(Algorithm::PS384.to_string(), "PS384");
assert_eq!(Algorithm::PS512.to_string(), "PS512");
assert_eq!(Algorithm::EdDSA.to_string(), "EdDSA");
}

#[test]
Expand All @@ -86,6 +141,42 @@ mod tests {
jsonwebtoken::Algorithm::from(Algorithm::HS512),
jsonwebtoken::Algorithm::HS512
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::ES256),
jsonwebtoken::Algorithm::ES256
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::ES384),
jsonwebtoken::Algorithm::ES384
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::RS256),
jsonwebtoken::Algorithm::RS256
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::RS384),
jsonwebtoken::Algorithm::RS384
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::RS512),
jsonwebtoken::Algorithm::RS512
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::PS256),
jsonwebtoken::Algorithm::PS256
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::PS384),
jsonwebtoken::Algorithm::PS384
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::PS512),
jsonwebtoken::Algorithm::PS512
);
assert_eq!(
jsonwebtoken::Algorithm::from(Algorithm::EdDSA),
jsonwebtoken::Algorithm::EdDSA
);
}

#[test]
Expand All @@ -103,6 +194,22 @@ mod tests {
assert_eq!(Algorithm::HS256, Algorithm::HS256);
assert_ne!(Algorithm::HS256, Algorithm::HS384);
assert_ne!(Algorithm::HS384, Algorithm::HS512);
assert_eq!(Algorithm::ES256, Algorithm::ES256);
assert_eq!(Algorithm::ES384, Algorithm::ES384);
assert_ne!(Algorithm::HS384, Algorithm::ES256);
assert_ne!(Algorithm::ES256, Algorithm::ES384);
assert_eq!(Algorithm::RS256, Algorithm::RS256);
assert_eq!(Algorithm::RS384, Algorithm::RS384);
assert_eq!(Algorithm::RS512, Algorithm::RS512);
assert_ne!(Algorithm::RS256, Algorithm::RS512);
assert_ne!(Algorithm::RS256, Algorithm::PS256);
assert_eq!(Algorithm::PS256, Algorithm::PS256);
assert_eq!(Algorithm::PS384, Algorithm::PS384);
assert_eq!(Algorithm::PS512, Algorithm::PS512);
assert_ne!(Algorithm::PS256, Algorithm::PS512);
assert_eq!(Algorithm::EdDSA, Algorithm::EdDSA);
assert_ne!(Algorithm::EdDSA, Algorithm::ES256);
assert_ne!(Algorithm::EdDSA, Algorithm::RS512);
}

#[test]
Expand Down
Loading
Loading