New: Offline-first licensing with cryptographic validation. Learn more

Security Model

Licenz uses industry-standard cryptographic techniques to ensure licenses cannot be forged, modified, or misused. This page explains the security architecture and how to leverage its protections in your applications.

Cryptographic Signatures

Every license is cryptographically signed using your private key. Only the corresponding public key can verify the signature, ensuring licenses can only be created by you.

Private Key
Signs licenses
(keep secret)
License + Signature
Distributed to
customers
Public Key
Verifies licenses
(embedded in app)

Supported Algorithms

Licenz supports two signature algorithms, both offering strong security guarantees:

Algorithm Key Size Signature Size Best For
Ed25519 256 bits 64 bytes Modern applications, smaller license files, faster verification
RSA-2048/4096 2048/4096 bits 256/512 bytes Compatibility with older systems, regulatory compliance

Recommendation

We recommend Ed25519 for new products. It provides equivalent security to RSA-3072 with significantly faster verification and smaller signatures.

Signature Verification

use licenz::{License, Verifier, PublicKey, SignatureAlgorithm};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load your public key (Ed25519 or RSA)
    let public_key = PublicKey::from_pem(include_str!("public_key.pem"))?;

    // Create verifier - algorithm is auto-detected from key
    let verifier = Verifier::new(public_key);

    let license = License::from_key("LIC-XXXX-XXXX-XXXX-XXXX")?;

    // Verify cryptographic signature
    match verifier.verify(&license) {
        Ok(result) => {
            println!("Signature valid: {}", result.signature_valid());
            println!("Algorithm: {:?}", result.algorithm()); // Ed25519 or RSA
            println!("Key ID: {}", result.key_id());
        }
        Err(e) => {
            eprintln!("Signature verification failed: {}", e);
        }
    }

    Ok(())
}

Offline Validation

One of Licenz's core strengths is the ability to validate licenses completely offline. The cryptographic signature contains all information needed to verify authenticity without contacting any server.

How Offline Validation Works

  1. Embed public key - Include your public key in your application at compile time
  2. Parse license - Decode the license key to extract the signed data
  3. Verify signature - Use the public key to verify the signature is valid
  4. Check claims - Validate expiration, features, and other constraints
use licenz::{License, Verifier, PublicKey};

// Embed the public key at compile time - no network needed
const PUBLIC_KEY_PEM: &str = include_str!("public_key.pem");

fn validate_offline(license_key: &str) -> Result<bool, Box<dyn std::error::Error>> {
    let public_key = PublicKey::from_pem(PUBLIC_KEY_PEM)?;
    let verifier = Verifier::new(public_key);
    let license = License::from_key(license_key)?;

    // Full validation happens locally
    let result = verifier.verify(&license)?;

    // Check signature, expiration, and features - all offline
    Ok(result.signature_valid()
        && result.is_active()
        && result.has_feature("pro"))
}

Benefits of Offline Validation

  • No internet required - Works in air-gapped environments
  • Zero latency - Instant validation with no network round-trip
  • Privacy - No usage data sent to external servers
  • Reliability - No dependency on server availability

Anti-Tampering Protections

Beyond signature verification, Licenz includes multiple layers of tamper detection to identify modified or corrupted licenses.

Integrity Checks

use licenz::{License, Verifier, IntegrityCheck};

let verifier = Verifier::builder()
    .public_key(public_key)
    .integrity_checks(vec![
        IntegrityCheck::LicenseSignature,    // Verify cryptographic signature
        IntegrityCheck::LicenseStructure,    // Validate JSON structure
        IntegrityCheck::FeatureConsistency,  // Check feature references
        IntegrityCheck::TimestampOrder,      // issued_at < expires_at
    ])
    .build()?;

let result = verifier.verify(&license)?;

// Get detailed integrity report
for check in result.integrity_checks() {
    println!("{}: {}", check.name(), if check.passed() { "PASS" } else { "FAIL" });
}

What's Protected

  • License data - Any modification invalidates the signature
  • Feature flags - Cannot be added or modified without re-signing
  • Expiration dates - Tampering with dates is cryptographically detectable
  • Hardware bindings - Fingerprint is part of the signed data
  • Metadata - Custom fields are included in signature computation

Important: Secure Your Private Key

The security of your licensing system depends entirely on keeping your private key secret. Never embed the private key in your application or share it publicly. Store it securely and rotate it periodically.

Clock Protection

A common attack vector is manipulating the system clock to extend expired licenses. Licenz provides several mechanisms to detect and mitigate clock tampering.

Time Sources

Licenz can cross-reference multiple time sources to detect manipulation:

  • System clock - The primary time source
  • Build timestamp - When your application was compiled
  • License issue time - When the license was created
  • Last validation time - Persisted from previous runs
  • Network time - Optional NTP check when online
use licenz::{License, Verifier, ClockSource};

// Use multiple time sources for tamper detection
let verifier = Verifier::builder()
    .public_key(public_key)
    .clock_source(ClockSource::System)              // System clock
    .clock_source(ClockSource::BuildTime)           // Compile timestamp
    .clock_source(ClockSource::LicenseIssueTime)    // License issue date
    .clock_tolerance(Duration::days(1))             // Allow 1 day drift
    .build()?;

match verifier.verify(&license) {
    Ok(result) => {
        if result.clock_tampering_detected() {
            println!("Warning: System clock may have been manipulated");
            println!("System time: {:?}", result.system_time());
            println!("Expected minimum: {:?}", result.minimum_time());
        }
    }
    Err(e) => eprintln!("Validation error: {}", e),
}

Clock Attack Scenarios

Attack Detection Method Response
Set clock to past date Time before build timestamp Reject or warn
Set clock before license issue Time before issued_at Reject validation
Clock jumped backward Time before last validation Configurable response

Key Management

Proper key management is essential for maintaining security.

Key Generation

# Generate Ed25519 key pair
licenz keys generate --algorithm ed25519

# Generate RSA key pair
licenz keys generate --algorithm rsa --bits 4096

# Output:
# Private key saved to: private_key.pem
# Public key saved to: public_key.pem
# Key ID: key_abc123

Key Rotation

Licenz supports key rotation to maintain security over time. When you rotate keys:

  1. Generate a new key pair
  2. Add the new public key to your application (alongside the old one)
  3. Start signing new licenses with the new private key
  4. Existing licenses remain valid (verified by old public key)
  5. After transition period, remove the old public key
# Rotate to a new key
licenz keys rotate --new-algorithm ed25519

# List all active keys
licenz keys list

# Deprecate old key (new licenses won't use it)
licenz keys deprecate key_old123

Security Best Practices

  • Use Ed25519 for new deployments - faster and equally secure
  • Embed public key at compile time - prevents runtime substitution
  • Enable all integrity checks - multiple layers of protection
  • Use hardware binding for high-value software
  • Implement clock protection for time-limited licenses
  • Rotate keys periodically - annually or after any suspected compromise
  • Monitor for anomalies - unusual activation patterns may indicate piracy

Security audit? Licenz's cryptographic implementation has been reviewed by third-party security researchers. Contact us for the full audit report.

Next Steps