common-access-token 0.1.11

Rust implementation of Common Access Token (CAT) with HMAC signatures
Documentation

Common Access Token (CAT) for Rust

Crate Documentation License: MIT

A Rust implementation of the Common Access Token (CAT) specification with HMAC signatures.

What is CAT?

Common Access Token (CAT) is a token format designed for authorization and authentication in distributed systems, particularly for media and content delivery applications. It provides a secure, compact, and efficient way to represent claims and authorization information between parties.

CAT is built on established standards:

Overview

This library provides a complete implementation for generating and validating Common Access Tokens (CAT) using HMAC signatures. It is designed to be interoperable with other implementations like node-cat.

Key benefits of using CAT tokens:

  • Compact: Binary format results in smaller token sizes compared to text-based formats
  • Efficient: CBOR encoding/decoding is faster and requires less processing power
  • Secure: Built on established cryptographic standards
  • Extensible: Supports custom claims and extensions

Features

  • Token Operations:

    • Generate CAT tokens with HMAC signatures (HS256)
    • Validate CAT tokens with comprehensive security checks
    • Support for token expiration and time-based validation
    • Enhanced token handling with FixCoseMac0 for improved compatibility
  • Claims Support:

    • Standard CWT claims (issuer, subject, audience, expiration, etc.)
    • CAT-specific claims (version, renewal, usage, data, authorization)
    • Support for binary data in claims
    • Custom claim extension capability
  • Integration:

    • Interoperability with other CAT implementations
    • Easy integration with Rust applications
    • Comprehensive documentation and examples

Installation

Add this to your Cargo.toml:

[dependencies]
common-access-token = "0.1"

Usage

Token Generation

This example demonstrates how to create a CAT token with standard claims:

use common_access_token::{Cat, CatGenerateOptions, CatOptions, CatValidationType, Claims};
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Step 1: Set up the cryptographic key
    // This is a sample key - in production, use a secure key management system
    let key = hex::decode("403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388")?;
    let mut keys = HashMap::new();
    keys.insert("Symmetric256".to_string(), key);

    // Step 2: Configure the CAT instance
    // expect_cwt_tag: true means the token will include the CWT tag for compatibility
    let cat = Cat::new(CatOptions {
        keys,
        expect_cwt_tag: true,
    });

    // Step 3: Create claims for the token
    let mut claims = Claims::new();

    // Standard CWT claims
    claims.set_issuer("eyevinn");          // Who issued this token
    claims.set_subject("jonas");           // Who this token refers to
    claims.set_audience("one");            // Who this token is intended for

    // Set time-based claims
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs() as i64;

    claims.set_expiration(now + 120);      // Token expires in 2 minutes
    claims.set_issued_at(now);             // Token was issued now

    // Step 4: Generate the token
    // Option 1: Using the simplified method with string key ID
    let token = cat.generate_with_string_kid(claims, "Symmetric256", true)?;

    // Option 2: Using the full options (more flexible)
    // let token = cat.generate(claims, &CatGenerateOptions {
    //     validation_type: CatValidationType::Mac,  // Use HMAC for signing
    //     alg: "HS256".to_string(),                 // Use HMAC-SHA256 algorithm
    //     kid: "Symmetric256".as_bytes().to_vec(),  // Key ID as binary
    //     generate_cwt_id: true,                    // Generate a unique token ID
    // })?;

    println!("Generated token: {}", token);
    // The token is base64-encoded and can be transmitted over HTTP headers, etc.

    Ok(())
}

Using Enhanced Token Handling with FixCoseMac0

For improved compatibility and robustness, the library provides an enhanced implementation via the FixCoseMac0 struct:

use common_access_token::cose::FixCoseMac0;
use common_access_token::Claims;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use std::time::{SystemTime, UNIX_EPOCH};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Step 1: Set up the key and claims
    let key = "your-secret-key".as_bytes();
    let kid = "key-1".as_bytes();

    let mut claims = Claims::new();
    claims.set_issuer("my-service");
    claims.set_subject("user-123");

    // Set expiration to 1 hour from now
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)?
        .as_secs() as i64;
    claims.set_expiration(now + 3600);
    claims.set_issued_at(now);

    // Step 2: Create a token using FixCoseMac0
    let token_bytes = FixCoseMac0::create_token(&claims, key, kid, true)?;
    let token = URL_SAFE_NO_PAD.encode(&token_bytes);
    println!("Generated token: {}", token);

    // Step 3: Validate and extract claims from a token
    let received_token_bytes = URL_SAFE_NO_PAD.decode(&token)?;

    // Validate the token
    match FixCoseMac0::verify_token(&received_token_bytes, key) {
        Ok(()) => println!("✅ Token is valid"),
        Err(err) => println!("❌ Token validation failed: {:?}", err),
    }

    // Extract claims from the token
    match FixCoseMac0::extract_claims(&received_token_bytes) {
        Ok(validated_claims) => {
            println!("Issuer: {:?}", validated_claims.get_issuer());
            println!("Subject: {:?}", validated_claims.get_subject());
            println!("Expiration: {:?}", validated_claims.get_expiration());
        },
        Err(err) => println!("Error extracting claims: {:?}", err),
    }

    // Step 4: Re-sign a token with updated claims
    let mut new_claims = claims.clone();
    new_claims.set_expiration(now + 7200); // 2 hours from now

    let new_token_bytes = FixCoseMac0::resign_token(
        &token_bytes, key, kid, &new_claims, true)?;
    let new_token = URL_SAFE_NO_PAD.encode(&new_token_bytes);
    println!("Re-signed token: {}", new_token);

    Ok(())
}

Using CAT-specific Claims

CAT extends the standard CWT claims with additional fields for specific use cases:

use common_access_token::{Cat, CatGenerateOptions, CatOptions, CatValidationType, Claims};
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a CAT instance with a cryptographic key
    let cat = Cat::new(CatOptions {
        keys: HashMap::from([
            ("Symmetric256".to_string(),
             hex::decode("403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388")?),
        ]),
        expect_cwt_tag: true,
    });

    // Create claims with both standard and CAT-specific properties
    let mut claims = Claims::new();

    // Standard claims
    claims.set_issuer("eyevinn");
    claims.set_subject("jonas");

    // Time-based claims
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs() as i64;
    claims.set_expiration(now + 3600);  // 1 hour from now
    claims.set_issued_at(now);

    // CAT-specific claims
    claims.set_cat_version(2);                                        // Protocol version

    // Simple string claims
    claims.set_cat_authorization("full_access");                      // Authorization level

    // Option 1: Set CAT usage as a simple string
    claims.set_cat_usage("streaming");                                // Purpose of the token

    // Option 2: Set CAT usage as a nested structure (recommended for complex data)
    use std::collections::BTreeMap;
    use common_access_token::ClaimValue;

    let mut usage_map = BTreeMap::new();
    usage_map.insert("type".to_string(), ClaimValue::String("streaming".to_string()));
    usage_map.insert("quality".to_string(), ClaimValue::String("hd".to_string()));
    usage_map.insert("bitrate".to_string(), ClaimValue::Integer(5000));
    claims.set_cat_usage_map(usage_map);

    // Option 3: Set CAT renewal from JSON
    let renewal_json = serde_json::json!({
        "url": "https://example.com/renew",
        "ttl": 3600
    });
    claims.set_cat_renewal_from_json(&renewal_json)?;

    // Generate the token using the simplified method
    let token = cat.generate_with_string_kid(claims, "Symmetric256", true)?;

    println!("Generated token with CAT claims: {}", token);
    Ok(())
}

Token Validation

This example shows how to validate a CAT token and extract its claims:

use common_access_token::{Cat, CatOptions, CatValidationOptions, CatValidationType};
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Step 1: Get the token to validate (typically from a request header)
    let token = "..."; // Base64 encoded token received from client

    // Step 2: Set up the cryptographic key for validation
    // This must be the same key used to sign the token
    let key = hex::decode("403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388")?;
    let mut keys = HashMap::new();
    keys.insert("Symmetric256".to_string(), key);

    // Step 3: Create a CAT instance
    let cat = Cat::new(CatOptions {
        keys,
        expect_cwt_tag: true,
    });

    // Step 4: Define validation criteria
    let validation_options = CatValidationOptions {
        issuer: "eyevinn".to_string(),  // Only accept tokens from this issuer
        audience: None,                 // Don't validate audience (or specify allowed audiences)
    };

    // Step 5: Validate the token
    match cat.validate(token, CatValidationType::Mac, &validation_options) {
        Ok(claims) => {
            // Token is valid! Extract and use the claims
            println!("✅ Token is valid!");

            // Standard CWT claims
            println!("Issuer: {:?}", claims.get_issuer());
            println!("Subject: {:?}", claims.get_subject());
            println!("Audience: {:?}", claims.get_audience());
            println!("Expiration: {:?}", claims.get_expiration());
            println!("Issued At: {:?}", claims.get_issued_at());

            // CAT-specific claims
            println!("CAT Version: {:?}", claims.get_cat_version());

            // Access CAT usage - check if it's a string or a map
            if let Some(usage_str) = claims.get_cat_usage_string() {
                println!("CAT Usage (string): {}", usage_str);
            } else if let Some(usage_map) = claims.get_cat_usage_map() {
                println!("CAT Usage (map):");
                if let Some(type_val) = usage_map.get("type").and_then(|v| v.as_string()) {
                    println!("  Type: {}", type_val);
                }
                if let Some(quality_val) = usage_map.get("quality").and_then(|v| v.as_string()) {
                    println!("  Quality: {}", quality_val);
                }
                if let Some(bitrate_val) = usage_map.get("bitrate").and_then(|v| v.as_integer()) {
                    println!("  Bitrate: {}", bitrate_val);
                }
            }

            // Access CAT renewal - check if it's a string or a map
            if let Some(renewal_str) = claims.get_cat_renewal_string() {
                println!("CAT Renewal (string): {}", renewal_str);
            } else if let Some(renewal_map) = claims.get_cat_renewal_map() {
                println!("CAT Renewal (map):");
                if let Some(url_val) = renewal_map.get("url").and_then(|v| v.as_string()) {
                    println!("  URL: {}", url_val);
                }
                if let Some(ttl_val) = renewal_map.get("ttl").and_then(|v| v.as_integer()) {
                    println!("  TTL: {}", ttl_val);
                }
            }

            println!("CAT Data: {:?}", claims.get_cat_data_string());
            println!("CAT Authorization: {:?}", claims.get_cat_authorization_string());

            // Now you can use these claims to make authorization decisions
        }
        Err(err) => {
            // Token validation failed
            eprintln!("❌ Token validation failed: {}", err);
            // Handle the error (e.g., return 401 Unauthorized)
        }
    }

    Ok(())
}

Examples

The library includes several ready-to-use examples in the examples/ directory:

  • generate.rs: Demonstrates basic token generation with standard claims
  • validate.rs: Shows how to validate tokens and extract claims
  • interop.rs: Tests interoperability with the NodeJS implementation
  • cat_claims.rs: Demonstrates using CAT-specific claims for advanced use cases, including nested claim structures
  • analyze_token.rs: Analyzes a token's structure, extracts claims, and demonstrates re-signing
  • debug_token.rs: Provides detailed debugging information for troubleshooting token issues
  • binary_claims_test.rs: Shows how to include binary data in token claims
  • binary_token_validation.rs: Demonstrates working with binary tokens directly
  • fix_validation.rs: Demonstrates using the enhanced FixCoseMac0 implementation for improved token validation

To run an example:

# Generate a token
cargo run --example generate

# Validate a token (replace <token> with an actual token)
cargo run --example validate <token>

# Test interoperability with NodeJS implementation
cargo run --example interop [<token>]

# Use CAT-specific claims
cargo run --example cat_claims

# Analyze a token's structure and claims
cargo run --example analyze_token [<token> [<key_hex>]]

# Debug token issues
cargo run --example debug_token <token>

Security Considerations

When using CAT tokens in your applications, keep these security best practices in mind:

  1. Key Management:

    • Store signing keys securely
    • Rotate keys periodically
    • Use different keys for different environments
  2. Token Validation:

    • Always validate tokens before trusting their contents
    • Check expiration times
    • Verify the issuer and audience claims
  3. Token Lifetime:

    • Use short-lived tokens when possible
    • For longer sessions, consider refresh token patterns
  4. Claims:

    • Only include necessary information in tokens
    • Be cautious with sensitive data in claims

Compatibility and Implementation Details

This library is designed to be interoperable with other CAT implementations:

The library includes two complementary implementations:

  1. Standard Implementation (CoseMac0):

    • Follows the COSE specification exactly
    • Suitable for most standard token operations
    • Used by default in the high-level Cat API
  2. Enhanced Implementation (FixCoseMac0):

    • More robust handling of edge cases
    • Improved interoperability with non-standard token formats
    • Raw header preservation for more reliable verification
    • Specialized methods for token inspection, creation, and re-signing
    • Available via the direct cose::FixCoseMac0 API

Both implementations can be used interchangeably, and the Cat API will automatically try both approaches when validating tokens, ensuring maximum compatibility.