rust_license_key/
parser.rs

1//! License parsing and decoding functionality.
2//!
3//! This module handles the loading and decoding of signed licenses.
4//! It verifies cryptographic signatures and extracts the license payload.
5//!
6//! # Client-Side Usage
7//!
8//! This module is intended for use in client applications to load and
9//! verify licenses issued by the software publisher.
10
11use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
12use base64::Engine;
13
14use crate::crypto::PublicKey;
15use crate::error::{LicenseError, Result};
16use crate::models::{
17    LicensePayload, SignedLicense, MAX_SUPPORTED_LICENSE_VERSION, MIN_SUPPORTED_LICENSE_VERSION,
18};
19
20// =============================================================================
21// License Parser
22// =============================================================================
23
24/// Parser for loading and verifying signed licenses.
25///
26/// The parser takes a public key and uses it to verify license signatures.
27/// Only licenses signed by the corresponding private key will be accepted.
28///
29/// # Security
30///
31/// The parser only accepts licenses with valid signatures. Any tampering
32/// with the license payload will cause signature verification to fail.
33///
34/// # Example
35///
36/// ```
37/// use rust_license_key::parser::LicenseParser;
38/// use rust_license_key::crypto::PublicKey;
39///
40/// // The public key embedded in your application
41/// let public_key_base64 = "..."; // Your public key here
42///
43/// // In a real application:
44/// // let public_key = PublicKey::from_base64(public_key_base64).unwrap();
45/// // let parser = LicenseParser::new(public_key);
46/// // let license = parser.parse_json(&license_file_contents).unwrap();
47/// ```
48#[derive(Debug, Clone)]
49pub struct LicenseParser {
50    /// The public key used to verify license signatures.
51    public_key: PublicKey,
52}
53
54impl LicenseParser {
55    /// Creates a new license parser with the given public key.
56    ///
57    /// # Arguments
58    ///
59    /// * `public_key` - The publisher's public key for signature verification.
60    pub fn new(public_key: PublicKey) -> Self {
61        Self { public_key }
62    }
63
64    /// Creates a new license parser from a base64-encoded public key.
65    ///
66    /// # Arguments
67    ///
68    /// * `public_key_base64` - The base64-encoded public key string.
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if the public key is invalid or malformed.
73    pub fn from_public_key_base64(public_key_base64: &str) -> Result<Self> {
74        let public_key = PublicKey::from_base64(public_key_base64)?;
75        Ok(Self::new(public_key))
76    }
77
78    /// Parses a signed license from a JSON string.
79    ///
80    /// This method:
81    /// 1. Parses the JSON structure.
82    /// 2. Verifies the cryptographic signature.
83    /// 3. Decodes the license payload.
84    /// 4. Validates the license format version.
85    ///
86    /// # Arguments
87    ///
88    /// * `json` - The JSON string containing the signed license.
89    ///
90    /// # Returns
91    ///
92    /// The verified and decoded license payload.
93    ///
94    /// # Errors
95    ///
96    /// Returns an error if:
97    /// - The JSON is malformed.
98    /// - The signature is invalid.
99    /// - The payload cannot be decoded.
100    /// - The license version is not supported.
101    pub fn parse_json(&self, json: &str) -> Result<LicensePayload> {
102        // Step 1: Parse the signed license structure
103        let signed_license = SignedLicense::from_json(json).map_err(|e| {
104            LicenseError::JsonDeserializationFailed {
105                reason: e.to_string(),
106            }
107        })?;
108
109        self.parse_signed_license(&signed_license)
110    }
111
112    /// Parses a `SignedLicense` structure directly.
113    ///
114    /// Use this when you already have a `SignedLicense` object,
115    /// for example from custom deserialization logic.
116    ///
117    /// # Arguments
118    ///
119    /// * `signed_license` - The signed license to verify and decode.
120    ///
121    /// # Returns
122    ///
123    /// The verified and decoded license payload.
124    pub fn parse_signed_license(&self, signed_license: &SignedLicense) -> Result<LicensePayload> {
125        // Step 2: Verify the signature
126        // The signature is computed over the base64-encoded payload
127        self.public_key
128            .verify_base64(
129                signed_license.encoded_payload.as_bytes(),
130                &signed_license.encoded_signature,
131            )
132            .map_err(|_| LicenseError::InvalidSignature)?;
133
134        // Step 3: Decode the payload from base64
135        let payload_bytes = BASE64_STANDARD
136            .decode(&signed_license.encoded_payload)
137            .map_err(|e| LicenseError::Base64DecodingFailed {
138                reason: e.to_string(),
139            })?;
140
141        // Step 4: Parse the JSON payload
142        let payload: LicensePayload = serde_json::from_slice(&payload_bytes).map_err(|e| {
143            LicenseError::JsonDeserializationFailed {
144                reason: e.to_string(),
145            }
146        })?;
147
148        // Step 5: Validate the license format version
149        if payload.format_version < MIN_SUPPORTED_LICENSE_VERSION {
150            return Err(LicenseError::UnsupportedLicenseVersion {
151                found: payload.format_version,
152                supported: format!(
153                    "{} to {}",
154                    MIN_SUPPORTED_LICENSE_VERSION, MAX_SUPPORTED_LICENSE_VERSION
155                ),
156            });
157        }
158
159        if payload.format_version > MAX_SUPPORTED_LICENSE_VERSION {
160            return Err(LicenseError::UnsupportedLicenseVersion {
161                found: payload.format_version,
162                supported: format!(
163                    "{} to {}",
164                    MIN_SUPPORTED_LICENSE_VERSION, MAX_SUPPORTED_LICENSE_VERSION
165                ),
166            });
167        }
168
169        Ok(payload)
170    }
171
172    /// Attempts to decode a license without signature verification.
173    ///
174    /// # Warning
175    ///
176    /// This method bypasses security and should only be used for debugging
177    /// or inspection purposes. Never use the returned payload for access
178    /// control decisions.
179    ///
180    /// # Arguments
181    ///
182    /// * `json` - The JSON string containing the signed license.
183    ///
184    /// # Returns
185    ///
186    /// The decoded payload and a boolean indicating if the signature was valid.
187    pub fn decode_unverified(&self, json: &str) -> Result<(LicensePayload, bool)> {
188        // Parse the signed license structure
189        let signed_license = SignedLicense::from_json(json).map_err(|e| {
190            LicenseError::JsonDeserializationFailed {
191                reason: e.to_string(),
192            }
193        })?;
194
195        // Check signature validity
196        let signature_valid = self
197            .public_key
198            .verify_base64(
199                signed_license.encoded_payload.as_bytes(),
200                &signed_license.encoded_signature,
201            )
202            .is_ok();
203
204        // Decode the payload regardless of signature
205        let payload_bytes = BASE64_STANDARD
206            .decode(&signed_license.encoded_payload)
207            .map_err(|e| LicenseError::Base64DecodingFailed {
208                reason: e.to_string(),
209            })?;
210
211        let payload: LicensePayload = serde_json::from_slice(&payload_bytes).map_err(|e| {
212            LicenseError::JsonDeserializationFailed {
213                reason: e.to_string(),
214            }
215        })?;
216
217        Ok((payload, signature_valid))
218    }
219
220    /// Returns a reference to the parser's public key.
221    pub fn public_key(&self) -> &PublicKey {
222        &self.public_key
223    }
224}
225
226// =============================================================================
227// Convenience Functions
228// =============================================================================
229
230/// Parses a signed license using a base64-encoded public key.
231///
232/// This is a convenience function for one-shot license parsing.
233/// For multiple license parsing operations, create a `LicenseParser` instance.
234///
235/// # Arguments
236///
237/// * `license_json` - The JSON string containing the signed license.
238/// * `public_key_base64` - The base64-encoded public key.
239///
240/// # Returns
241///
242/// The verified and decoded license payload.
243pub fn parse_license(license_json: &str, public_key_base64: &str) -> Result<LicensePayload> {
244    let parser = LicenseParser::from_public_key_base64(public_key_base64)?;
245    parser.parse_json(license_json)
246}
247
248/// Extracts the raw payload from a signed license without verification.
249///
250/// # Warning
251///
252/// This function is for inspection only. Never trust unverified payload data.
253///
254/// # Arguments
255///
256/// * `license_json` - The JSON string containing the signed license.
257///
258/// # Returns
259///
260/// The raw payload JSON as a string.
261pub fn extract_payload_unverified(license_json: &str) -> Result<String> {
262    let signed_license = SignedLicense::from_json(license_json).map_err(|e| {
263        LicenseError::JsonDeserializationFailed {
264            reason: e.to_string(),
265        }
266    })?;
267
268    let payload_bytes = BASE64_STANDARD
269        .decode(&signed_license.encoded_payload)
270        .map_err(|e| LicenseError::Base64DecodingFailed {
271            reason: e.to_string(),
272        })?;
273
274    String::from_utf8(payload_bytes).map_err(|e| LicenseError::InvalidLicenseFormat {
275        reason: format!("payload is not valid UTF-8: {}", e),
276    })
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use crate::builder::LicenseBuilder;
283    use crate::crypto::KeyPair;
284    use chrono::Duration;
285
286    fn create_test_license(key_pair: &KeyPair) -> String {
287        LicenseBuilder::new()
288            .license_id("TEST-LIC-001")
289            .customer_id("TEST-CUST-001")
290            .customer_name("Test Customer")
291            .expires_in(Duration::days(30))
292            .allowed_feature("premium")
293            .build_and_sign_to_json(key_pair)
294            .expect("Should create test license")
295    }
296
297    #[test]
298    fn test_parse_valid_license() {
299        let key_pair = KeyPair::generate().expect("Key generation should succeed");
300        let license_json = create_test_license(&key_pair);
301
302        let parser = LicenseParser::new(key_pair.public_key());
303        let payload = parser
304            .parse_json(&license_json)
305            .expect("Should parse license");
306
307        assert_eq!(payload.license_id, "TEST-LIC-001");
308        assert_eq!(payload.customer_id, "TEST-CUST-001");
309        assert_eq!(payload.customer_name.as_deref(), Some("Test Customer"));
310    }
311
312    #[test]
313    fn test_parse_with_wrong_key_fails() {
314        let key_pair_1 = KeyPair::generate().expect("Key generation should succeed");
315        let key_pair_2 = KeyPair::generate().expect("Key generation should succeed");
316
317        // Create license with key_pair_1
318        let license_json = create_test_license(&key_pair_1);
319
320        // Try to parse with key_pair_2's public key
321        let parser = LicenseParser::new(key_pair_2.public_key());
322        let result = parser.parse_json(&license_json);
323
324        assert!(result.is_err());
325        assert!(matches!(
326            result.unwrap_err(),
327            LicenseError::InvalidSignature
328        ));
329    }
330
331    #[test]
332    fn test_parse_tampered_license_fails() {
333        let key_pair = KeyPair::generate().expect("Key generation should succeed");
334        let license_json = create_test_license(&key_pair);
335
336        // Parse the license JSON to get the structure
337        let mut signed: SignedLicense = serde_json::from_str(&license_json).expect("Should parse");
338
339        // Tamper with the payload (modify a character in the base64)
340        let mut chars: Vec<char> = signed.encoded_payload.chars().collect();
341        if let Some(c) = chars.get_mut(10) {
342            *c = if *c == 'A' { 'B' } else { 'A' };
343        }
344        signed.encoded_payload = chars.into_iter().collect();
345
346        // Serialize back to JSON
347        let tampered_json = serde_json::to_string(&signed).expect("Should serialize");
348
349        // Try to parse the tampered license
350        let parser = LicenseParser::new(key_pair.public_key());
351        let result = parser.parse_json(&tampered_json);
352
353        assert!(result.is_err());
354    }
355
356    #[test]
357    fn test_parse_invalid_json() {
358        let key_pair = KeyPair::generate().expect("Key generation should succeed");
359        let parser = LicenseParser::new(key_pair.public_key());
360
361        let result = parser.parse_json("not valid json");
362        assert!(matches!(
363            result.unwrap_err(),
364            LicenseError::JsonDeserializationFailed { .. }
365        ));
366    }
367
368    #[test]
369    fn test_decode_unverified() {
370        let key_pair = KeyPair::generate().expect("Key generation should succeed");
371        let license_json = create_test_license(&key_pair);
372
373        let parser = LicenseParser::new(key_pair.public_key());
374        let (payload, signature_valid) = parser
375            .decode_unverified(&license_json)
376            .expect("Should decode");
377
378        assert!(signature_valid);
379        assert_eq!(payload.license_id, "TEST-LIC-001");
380    }
381
382    #[test]
383    fn test_decode_unverified_with_wrong_key() {
384        let key_pair_1 = KeyPair::generate().expect("Key generation should succeed");
385        let key_pair_2 = KeyPair::generate().expect("Key generation should succeed");
386
387        let license_json = create_test_license(&key_pair_1);
388
389        let parser = LicenseParser::new(key_pair_2.public_key());
390        let (payload, signature_valid) = parser
391            .decode_unverified(&license_json)
392            .expect("Should decode");
393
394        // Signature should be invalid but payload still decoded
395        assert!(!signature_valid);
396        assert_eq!(payload.license_id, "TEST-LIC-001");
397    }
398
399    #[test]
400    fn test_extract_payload_unverified() {
401        let key_pair = KeyPair::generate().expect("Key generation should succeed");
402        let license_json = create_test_license(&key_pair);
403
404        let payload_json = extract_payload_unverified(&license_json).expect("Should extract");
405
406        // Verify it's valid JSON containing expected fields
407        let value: serde_json::Value =
408            serde_json::from_str(&payload_json).expect("Should be valid JSON");
409        assert_eq!(value["id"], "TEST-LIC-001");
410    }
411
412    #[test]
413    fn test_from_public_key_base64() {
414        let key_pair = KeyPair::generate().expect("Key generation should succeed");
415        let public_key_base64 = key_pair.public_key_base64();
416
417        let parser = LicenseParser::from_public_key_base64(&public_key_base64)
418            .expect("Should create parser");
419
420        let license_json = create_test_license(&key_pair);
421        let payload = parser.parse_json(&license_json).expect("Should parse");
422
423        assert_eq!(payload.license_id, "TEST-LIC-001");
424    }
425
426    #[test]
427    fn test_parse_license_convenience_function() {
428        let key_pair = KeyPair::generate().expect("Key generation should succeed");
429        let public_key_base64 = key_pair.public_key_base64();
430        let license_json = create_test_license(&key_pair);
431
432        let payload =
433            parse_license(&license_json, &public_key_base64).expect("Should parse license");
434
435        assert_eq!(payload.license_id, "TEST-LIC-001");
436    }
437}