Skip to main content

licenz_core/
lib.rs

1//! # Licenz Core
2//!
3//! A powerful offline software license management library for Rust.
4//!
5//! ## Security Witness Pattern
6//!
7//! This library follows the Security Witness Pattern, separating:
8//!
9//! - **Attestation** (this crate): Observes, measures, and reports facts about licenses
10//! - **Enforcement** (licenz-policy): Decides and enforces based on attestations
11//!
12//! The core library is open source and auditable. All verification logic is transparent.
13//! Policy enforcement is handled by the separate `licenz-policy` crate.
14//!
15//! ## Features
16//!
17//! - **Offline License Validation**: Generate licenses that can be verified without internet connectivity
18//! - **Hardware Binding**: Bind licenses to specific hardware identifiers (MAC address, disk ID, hostname)
19//! - **Digital Signatures**: Secure licenses with RSA-SHA256 cryptographic signatures
20//! - **Expiration Management**: Set and enforce license expiration dates
21//! - **Binary Format**: Compact, tamper-resistant binary license format
22//! - **JSON Support**: Legacy JSON format for backward compatibility
23//! - **Security Witness**: Comprehensive attestation of license and system state
24//!
25//! ## Quick Start
26//!
27//! ### Generating a License (Server-Side)
28//!
29//! ```rust,no_run
30//! use licenz_core::{KeyPair, KeySize, LicenseGenerator, LicenseData};
31//!
32//! // Generate RSA key pair
33//! let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
34//!
35//! // Create a license generator
36//! let generator = LicenseGenerator::new(keypair.into_private_key());
37//!
38//! // Build license data
39//! let license_data = LicenseData::builder()
40//!     .id("LIC-001")
41//!     .serial("SN-12345")
42//!     .customer_id("ACME-CORP")
43//!     .product_id("MY-APP")
44//!     .valid_days(365)
45//!     .feature("basic")
46//!     .feature("premium")
47//!     .build()
48//!     .unwrap();
49//!
50//! // Generate signed license
51//! let signed_license = generator.generate(license_data).unwrap();
52//!
53//! // Save to binary file
54//! generator.save_binary(&signed_license, "license.lic".as_ref()).unwrap();
55//! ```
56//!
57//! ### Attestation (Recommended - Security Witness Pattern)
58//!
59//! ```rust,ignore
60//! use licenz_core::{SecurityWitness, WitnessConfig};
61//!
62//! // Public key embedded at compile time
63//! const PUBLIC_KEY: &str = include_str!("../keys/public.pem");
64//!
65//! fn main() {
66//!     let witness = SecurityWitness::new(PUBLIC_KEY).unwrap();
67//!     let attestation = witness.attest("license.lic", &WitnessConfig::default()).unwrap();
68//!
69//!     // Attestation provides facts - your app decides what to do
70//!     println!("Signature valid: {}", attestation.signature_valid);
71//!     println!("Days remaining: {}", attestation.expiration.days_remaining);
72//!     println!("Anomalies: {:?}", attestation.anomalies);
73//!
74//!     // Pass to licenz-policy for enforcement, or handle yourself
75//!     if !attestation.is_valid {
76//!         eprintln!("License invalid");
77//!         std::process::exit(1);
78//!     }
79//! }
80//! ```
81//!
82//! ### Legacy: Direct Validation (Deprecated)
83//!
84//! ```rust,ignore
85//! use licenz_core::require_license;
86//!
87//! // This pattern is deprecated - use SecurityWitness + licenz-policy instead
88//! let license = require_license("license.lic", PUBLIC_KEY)
89//!     .expect("Valid license required to run");
90//! ```
91//!
92//! ## Feature Flags
93//!
94//! - `cloud-metadata`: Enable cloud container detection (AWS, GCP, Azure)
95//! - `post-quantum`: Enable post-quantum cryptography (ML-DSA-65/FIPS 204, ML-KEM-768/FIPS 203)
96
97pub mod anti_tamper;
98pub mod container;
99pub mod crypto;
100pub mod encrypted_store;
101pub mod error;
102pub mod generator;
103pub mod guard;
104pub mod hardware;
105pub mod keys;
106pub mod license;
107pub mod sneakernet;
108pub mod state_manager;
109pub mod support_bundle;
110pub mod unlock;
111pub mod verifier;
112pub mod witness;
113
114#[cfg(feature = "online-check")]
115pub mod online_check;
116
117// Re-export main types
118pub use anti_tamper::{
119    ClockStatus, HardwareFingerprint, LicenseState, MatchResult, STATE_HMAC_PREFIX,
120};
121pub use container::{ContainerBinding, InstanceIdSource, RuntimeEnvironment};
122pub use encrypted_store::{
123    validate_passphrase, EncryptedKeyStore, ENCRYPTED_STORE_VERSION, MIN_PASSPHRASE_LENGTH,
124};
125pub use error::{LicenseError, Result};
126pub use generator::{CryptoGenerator, LicenseGenerator};
127pub use guard::{
128    require_license, require_license_with_verifier, validate_license_bytes, ValidatedLicense,
129};
130pub use hardware::{
131    default_hardware_environment, detect_hardware, DefaultHardwareEnvironment,
132    FixedHardwareEnvironment, HardwareEnvironment, HardwareInfo,
133};
134pub use keys::{parse_private_key, parse_public_key, CryptoKeyPair, KeyPair, KeySize};
135pub use license::{HardwareBinding, LicenseData, LicenseDataBuilder, LicenseFormat, SignedLicense};
136pub use state_manager::{StateManager, StateObservations};
137pub use verifier::{detect_license_format, CryptoVerifier, LicenseVerifier, ValidationResult};
138
139// Cryptographic algorithm exports
140pub use crypto::{algorithm_ids, CryptoRegistry, EncryptionAlgorithm, SignatureAlgorithm};
141
142// Security Witness Pattern exports
143pub use witness::{
144    ClockAttestation, ClockStatusAttestation, EnvironmentAttestation, ExpirationAttestation,
145    ExpirationIssue, HardwareAttestation, SecurityAnomaly, SecurityAttestation, SecurityWitness,
146    StateFileAttestation, StateFileObservation, StateFileStatus, WitnessConfig,
147};
148
149// Sneakernet (offline activation) exports
150pub use sneakernet::{
151    detect_format as detect_sneakernet_format, ActivationRequest, ActivationRequestBuilder,
152    ActivationResponse, SneakernetFormat, MAX_SNEAKERNET_JSON_PAYLOAD, REQUEST_MAGIC,
153    REQUEST_TEXT_PREFIX, REQUEST_TEXT_SUFFIX, REQUEST_VERSION, RESPONSE_MAGIC,
154    RESPONSE_TEXT_PREFIX, RESPONSE_TEXT_SUFFIX, RESPONSE_VERSION,
155};
156
157// Support bundle exports
158pub use support_bundle::{
159    ClockState, ClockStatusSummary, EnvironmentInfo, HardwareMatchStatus, HardwareSummary,
160    LicenseStatusSummary, RuntimeEnvironmentSummary, StateFileLocation, StateFileLocationStatus,
161    StateFileSummary, SupportBundle, SupportBundleBuilder, VerificationEvent,
162    VerificationEventType, BUNDLE_VERSION, ENCRYPTED_BUNDLE_MAGIC,
163};
164
165// Admin unlock exports
166pub use unlock::{
167    generate_challenge_from_state, get_lockout_status, validate_response_code, LockoutStatus,
168    UnlockChallenge, UnlockResult, UnlockType,
169};
170
171#[cfg(feature = "online-check")]
172pub use online_check::{
173    check_revocation, check_revocation_batch, check_revocation_by_serial, sync_report,
174    OnlineCheckConfig, RevocationCheckResult, RevocationStatus, SyncReport, SyncResponse,
175};
176
177/// Library version
178pub const VERSION: &str = env!("CARGO_PKG_VERSION");
179
180/// Get a public key embedded at compile time via environment variable.
181///
182/// Use this with cargo build flags:
183/// ```bash
184/// LICENZ_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..." cargo build
185/// ```
186///
187/// Returns `None` if `LICENZ_PUBLIC_KEY` was not set during compilation.
188pub fn embedded_public_key() -> Option<&'static str> {
189    option_env!("LICENZ_PUBLIC_KEY")
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_full_workflow() {
198        // Generate keys
199        let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
200
201        // Create generator and verifier
202        let generator = LicenseGenerator::new(keypair.private_key().clone());
203        let verifier = LicenseVerifier::new(keypair.public_key);
204
205        // Create license
206        let data = LicenseData::builder()
207            .id("TEST-001")
208            .serial("SN-12345")
209            .customer_id("TEST-CUSTOMER")
210            .product_id("TEST-PRODUCT")
211            .valid_days(365)
212            .feature("basic")
213            .feature("premium")
214            .build()
215            .unwrap();
216
217        // Generate signed license
218        let signed = generator.generate(data).unwrap();
219
220        // Verify
221        assert!(verifier.validate(&signed).is_ok());
222
223        // Check features
224        assert!(signed.data.has_feature("basic"));
225        assert!(signed.data.has_feature("PREMIUM")); // Case insensitive
226        assert!(!signed.data.has_feature("enterprise"));
227    }
228
229    #[test]
230    fn test_hardware_binding() {
231        let binding = HardwareBinding::new()
232            .with_mac_address("AA:BB:CC:DD:EE:FF")
233            .with_hostname("test-server")
234            .with_disk_id("DISK-001");
235
236        assert!(!binding.is_empty());
237        assert_eq!(binding.mac_addresses.len(), 1);
238        assert_eq!(binding.hostnames.len(), 1);
239        assert_eq!(binding.disk_ids.len(), 1);
240    }
241
242    #[test]
243    fn test_validated_license_guard() {
244        let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
245        let generator = LicenseGenerator::new(keypair.private_key().clone());
246
247        let data = LicenseData::builder()
248            .id("GUARD-TEST")
249            .serial("SN-GUARD")
250            .customer_id("Guard Customer")
251            .product_id("GuardApp")
252            .valid_days(365)
253            .feature("test_feature")
254            .build()
255            .unwrap();
256
257        let signed = generator.generate(data).unwrap();
258        let binary = generator.export_binary(&signed).unwrap();
259        let public_key = keypair.export_public_pem().unwrap();
260
261        // Use the guard pattern
262        let validated = validate_license_bytes(&binary, &public_key).unwrap();
263
264        assert_eq!(validated.customer_id, "Guard Customer");
265        assert!(validated.has_feature("test_feature"));
266    }
267
268    #[test]
269    fn test_crypto_workflow_ed25519() {
270        // Generate Ed25519 keys using the new crypto module
271        let keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
272
273        // Create generator using the new CryptoGenerator
274        let generator = CryptoGenerator::from_keypair(&keypair);
275
276        // Create license
277        let data = LicenseData::builder()
278            .id("ED25519-TEST")
279            .serial("SN-ED25519")
280            .customer_id("Ed25519 Customer")
281            .product_id("Ed25519 Product")
282            .valid_days(365)
283            .feature("feature1")
284            .feature("feature2")
285            .build()
286            .unwrap();
287
288        // Generate signed license
289        let signed = generator.generate(data).unwrap();
290        assert_eq!(signed.algorithm, algorithm_ids::ED25519);
291
292        // Export to binary
293        let binary = generator.export_binary(&signed).unwrap();
294
295        // Create verifier with Ed25519 public key
296        let mut keys = std::collections::HashMap::new();
297        keys.insert(
298            algorithm_ids::ED25519.to_string(),
299            keypair.public_key_pem.clone(),
300        );
301        let verifier = CryptoVerifier::new(keys);
302
303        // Parse and validate
304        let parsed = verifier.parse_license(&binary).unwrap();
305        assert!(verifier.validate(&parsed).is_ok());
306
307        // Verify features
308        assert!(parsed.data.has_feature("feature1"));
309        assert!(parsed.data.has_feature("FEATURE2")); // Case insensitive
310    }
311
312    #[test]
313    fn test_crypto_workflow_multi_algorithm() {
314        // Test that a system can handle licenses from multiple algorithms
315
316        // Generate keys for both algorithms
317        let rsa_keypair = CryptoKeyPair::generate(algorithm_ids::RSA_SHA256).unwrap();
318        let ed25519_keypair = CryptoKeyPair::generate(algorithm_ids::ED25519).unwrap();
319
320        // Create a verifier that supports both
321        let mut keys = std::collections::HashMap::new();
322        keys.insert(
323            algorithm_ids::RSA_SHA256.to_string(),
324            rsa_keypair.public_key_pem.clone(),
325        );
326        keys.insert(
327            algorithm_ids::ED25519.to_string(),
328            ed25519_keypair.public_key_pem.clone(),
329        );
330        let verifier = CryptoVerifier::new(keys);
331
332        // Generate RSA license (legacy customer)
333        let rsa_generator = CryptoGenerator::from_keypair(&rsa_keypair);
334        let rsa_data = LicenseData::builder()
335            .id("RSA-LEGACY-001")
336            .serial("SN-RSA-LEGACY")
337            .customer_id("Legacy RSA Customer")
338            .product_id("PROD-001")
339            .valid_days(365)
340            .build()
341            .unwrap();
342        let rsa_license = rsa_generator.generate(rsa_data).unwrap();
343
344        // Generate Ed25519 license (new customer)
345        let ed25519_generator = CryptoGenerator::from_keypair(&ed25519_keypair);
346        let ed25519_data = LicenseData::builder()
347            .id("ED25519-NEW-001")
348            .serial("SN-ED25519-NEW")
349            .customer_id("New Ed25519 Customer")
350            .product_id("PROD-001")
351            .valid_days(365)
352            .build()
353            .unwrap();
354        let ed25519_license = ed25519_generator.generate(ed25519_data).unwrap();
355
356        // Both licenses should validate with the same verifier
357        assert!(verifier.validate(&rsa_license).is_ok());
358        assert!(verifier.validate(&ed25519_license).is_ok());
359
360        // Algorithm should be correctly identified
361        assert_eq!(rsa_license.algorithm, algorithm_ids::RSA_SHA256);
362        assert_eq!(ed25519_license.algorithm, algorithm_ids::ED25519);
363    }
364
365    #[test]
366    fn test_backward_compatibility_rsa() {
367        // Ensure the legacy API still works for RSA
368        let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
369        let generator = LicenseGenerator::new(keypair.private_key().clone());
370        let verifier = LicenseVerifier::new(keypair.public_key);
371
372        let data = LicenseData::builder()
373            .id("LEGACY-RSA-TEST")
374            .serial("SN-LEGACY-RSA")
375            .customer_id("Legacy Customer")
376            .product_id("Legacy Product")
377            .valid_days(365)
378            .build()
379            .unwrap();
380
381        let signed = generator.generate(data).unwrap();
382
383        // Should use RSA-SHA256
384        assert_eq!(signed.algorithm, "RSA-SHA256");
385
386        // Legacy verifier should still work
387        assert!(verifier.validate(&signed).is_ok());
388
389        // Binary round-trip should work
390        let binary = generator.export_binary(&signed).unwrap();
391        let parsed = verifier.parse_license(&binary).unwrap();
392        assert!(verifier.validate(&parsed).is_ok());
393    }
394
395    #[test]
396    fn test_algorithm_registry() {
397        // Verify the algorithm registry works correctly
398        let supported = CryptoRegistry::supported_signature_algorithms();
399        assert!(supported.contains(&algorithm_ids::RSA_SHA256));
400        assert!(supported.contains(&algorithm_ids::ED25519));
401
402        // Get algorithms by ID
403        let rsa = CryptoRegistry::get_signature_algorithm(algorithm_ids::RSA_SHA256).unwrap();
404        assert_eq!(rsa.algorithm_id(), algorithm_ids::RSA_SHA256);
405
406        let ed25519 = CryptoRegistry::get_signature_algorithm(algorithm_ids::ED25519).unwrap();
407        assert_eq!(ed25519.algorithm_id(), algorithm_ids::ED25519);
408
409        // Default should be RSA for backward compatibility
410        let default = CryptoRegistry::default_signature_algorithm();
411        assert_eq!(default.algorithm_id(), algorithm_ids::RSA_SHA256);
412    }
413}