licenz_core/guard.rs
1//! License guard pattern for enforced validation
2//!
3//! This module provides a type-safe way to ensure licenses are always validated
4//! before their data is accessed.
5
6use crate::error::Result;
7use crate::license::{LicenseData, SignedLicense};
8use crate::verifier::LicenseVerifier;
9use std::ops::Deref;
10use std::path::Path;
11
12/// A validated license guard that ensures the license was verified before access.
13///
14/// This pattern makes it impossible to access license data without first validating it.
15/// The only way to construct a `ValidatedLicense` is through the validation methods,
16/// which guarantees the license has been cryptographically verified.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use licenz_core::{require_license, LicenseVerifier};
22///
23/// // This is the ONLY way to get license data
24/// let license = require_license("license.lic", PUBLIC_KEY)?;
25///
26/// // Now you can safely access the data
27/// println!("Licensed to: {}", license.customer_id);
28/// if license.has_feature("premium") {
29/// enable_premium();
30/// }
31/// ```
32#[derive(Debug, Clone)]
33pub struct ValidatedLicense {
34 /// The verified license
35 inner: SignedLicense,
36
37 /// Validation timestamp
38 validated_at: chrono::DateTime<chrono::Utc>,
39}
40
41impl ValidatedLicense {
42 /// Create a new validated license (internal use only)
43 fn new(license: SignedLicense) -> Self {
44 Self {
45 inner: license,
46 validated_at: chrono::Utc::now(),
47 }
48 }
49
50 /// Get the underlying license data
51 pub fn data(&self) -> &LicenseData {
52 &self.inner.data
53 }
54
55 /// Get when this license was validated
56 pub fn validated_at(&self) -> chrono::DateTime<chrono::Utc> {
57 self.validated_at
58 }
59
60 /// Check if a feature is enabled
61 pub fn has_feature(&self, feature: &str) -> bool {
62 self.inner.data.has_feature(feature)
63 }
64
65 /// Get days remaining
66 pub fn days_remaining(&self) -> i64 {
67 self.inner.data.days_remaining()
68 }
69
70 /// Get the raw signed license (for serialization, etc.)
71 pub fn into_inner(self) -> SignedLicense {
72 self.inner
73 }
74}
75
76// Allow direct access to common fields via Deref
77impl Deref for ValidatedLicense {
78 type Target = LicenseData;
79
80 fn deref(&self) -> &Self::Target {
81 &self.inner.data
82 }
83}
84
85/// Require a valid license to proceed.
86///
87/// This is the primary entry point for license validation. It loads the license
88/// from the specified path, verifies it against the provided public key, and
89/// returns a `ValidatedLicense` that can be used to access the license data.
90///
91/// # Arguments
92///
93/// * `license_path` - Path to the license file
94/// * `public_key_pem` - PEM-encoded public key
95///
96/// # Returns
97///
98/// Returns `Ok(ValidatedLicense)` if the license is valid, or an error if:
99/// - The license file cannot be read
100/// - The public key is invalid
101/// - The signature verification fails
102/// - The license has expired
103/// - Hardware binding doesn't match
104///
105/// # Example
106///
107/// ```rust,ignore
108/// const PUBLIC_KEY: &str = include_str!("../public.pem");
109///
110/// fn main() {
111/// let license = licenz_core::require_license("license.lic", PUBLIC_KEY)
112/// .expect("Valid license required");
113///
114/// println!("Welcome, {}!", license.customer_id);
115/// }
116/// ```
117pub fn require_license(
118 license_path: impl AsRef<Path>,
119 public_key_pem: &str,
120) -> Result<ValidatedLicense> {
121 let verifier = LicenseVerifier::from_pem(public_key_pem)?;
122 let license = verifier.load_and_validate(license_path.as_ref())?;
123 Ok(ValidatedLicense::new(license))
124}
125
126/// Require a valid license with custom verifier options.
127///
128/// Use this when you need more control over the verification process,
129/// such as providing custom hardware info for testing.
130pub fn require_license_with_verifier(
131 license_path: impl AsRef<Path>,
132 verifier: &LicenseVerifier,
133) -> Result<ValidatedLicense> {
134 let license = verifier.load_and_validate(license_path.as_ref())?;
135 Ok(ValidatedLicense::new(license))
136}
137
138/// Validate license bytes directly (for API/network use)
139pub fn validate_license_bytes(
140 license_bytes: &[u8],
141 public_key_pem: &str,
142) -> Result<ValidatedLicense> {
143 let verifier = LicenseVerifier::from_pem(public_key_pem)?;
144 let license = verifier.parse_license(license_bytes)?;
145 verifier.validate(&license)?;
146 Ok(ValidatedLicense::new(license))
147}
148
149/// Macro to load and validate a license at compile time.
150///
151/// This macro ensures that:
152/// 1. The public key exists at compile time (include_str! fails otherwise)
153/// 2. License validation happens early in the program
154///
155/// Returns `Result<ValidatedLicense>` - the caller decides how to handle errors.
156///
157/// # Usage
158///
159/// ```rust,ignore
160/// // In main.rs
161/// let license = licenz_core::load_license!("license.lic")?;
162/// ```
163///
164/// Or with a custom key path:
165///
166/// ```rust,ignore
167/// let license = licenz_core::load_license!("license.lic", "keys/public.pem")?;
168/// ```
169///
170/// # Note
171///
172/// For policy enforcement (exit on failure, custom thresholds, etc.),
173/// use `licenz-policy` crate's `PolicyEnforcer` instead.
174#[macro_export]
175macro_rules! load_license {
176 ($license_path:expr) => {{
177 const PUBLIC_KEY: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/public.pem"));
178 $crate::require_license($license_path, PUBLIC_KEY)
179 }};
180
181 ($license_path:expr, $key_path:expr) => {{
182 const PUBLIC_KEY: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $key_path));
183 $crate::require_license($license_path, PUBLIC_KEY)
184 }};
185}
186
187/// **DEPRECATED**: Use `load_license!` instead, which returns a Result.
188///
189/// This macro calls `std::process::exit(1)` on failure, which is an enforcement
190/// decision. The Security Witness Pattern recommends separating attestation
191/// (this crate) from enforcement (licenz-policy crate).
192#[macro_export]
193#[deprecated(
194 since = "0.2.0",
195 note = "Use load_license! macro or licenz-policy crate for enforcement"
196)]
197macro_rules! require_valid_license {
198 ($license_path:expr) => {{
199 const PUBLIC_KEY: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/public.pem"));
200
201 match $crate::require_license($license_path, PUBLIC_KEY) {
202 Ok(license) => license,
203 Err(e) => {
204 eprintln!("License validation failed: {}", e);
205 eprintln!("Please ensure you have a valid license file.");
206 std::process::exit(1);
207 }
208 }
209 }};
210
211 ($license_path:expr, $key_path:expr) => {{
212 const PUBLIC_KEY: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $key_path));
213
214 match $crate::require_license($license_path, PUBLIC_KEY) {
215 Ok(license) => license,
216 Err(e) => {
217 eprintln!("License validation failed: {}", e);
218 eprintln!("Please ensure you have a valid license file.");
219 std::process::exit(1);
220 }
221 }
222 }};
223}
224
225/// Feature gate macro for conditional code execution.
226///
227/// # Example
228///
229/// ```rust,ignore
230/// let license = require_valid_license!("license.lic");
231///
232/// feature_gate!(license, "premium", {
233/// // This code only runs if "premium" feature is licensed
234/// enable_premium_features();
235/// });
236///
237/// feature_gate!(license, "enterprise", {
238/// enable_sso();
239/// enable_audit_logging();
240/// } else {
241/// show_upgrade_prompt();
242/// });
243/// ```
244#[macro_export]
245macro_rules! feature_gate {
246 ($license:expr, $feature:expr, $enabled:block) => {
247 if $license.has_feature($feature) {
248 $enabled
249 }
250 };
251
252 ($license:expr, $feature:expr, $enabled:block else $disabled:block) => {
253 if $license.has_feature($feature) {
254 $enabled
255 } else {
256 $disabled
257 }
258 };
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::{KeyPair, KeySize, LicenseData, LicenseGenerator};
265
266 fn create_test_license() -> (String, Vec<u8>) {
267 let keypair = KeyPair::generate(KeySize::Bits2048).unwrap();
268 let generator = LicenseGenerator::new(keypair.private_key().clone());
269
270 let data = LicenseData::builder()
271 .id("TEST-001")
272 .serial("SN-12345")
273 .customer_id("Test Customer")
274 .product_id("TestApp")
275 .valid_days(365)
276 .feature("basic")
277 .feature("premium")
278 .build()
279 .unwrap();
280
281 let signed = generator.generate(data).unwrap();
282 let binary = generator.export_binary(&signed).unwrap();
283 let public_key = keypair.export_public_pem().unwrap();
284
285 (public_key, binary)
286 }
287
288 #[test]
289 fn test_validated_license_access() {
290 let (public_key, binary) = create_test_license();
291
292 let license = validate_license_bytes(&binary, &public_key).unwrap();
293
294 // Can access data through guard
295 assert_eq!(license.customer_id, "Test Customer");
296 assert!(license.has_feature("basic"));
297 assert!(license.has_feature("premium"));
298 assert!(!license.has_feature("enterprise"));
299 }
300
301 #[test]
302 fn test_validated_license_deref() {
303 let (public_key, binary) = create_test_license();
304
305 let license = validate_license_bytes(&binary, &public_key).unwrap();
306
307 // Deref allows direct field access
308 assert_eq!(license.product_id, "TestApp");
309 assert_eq!(license.serial, "SN-12345");
310 }
311}