use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::Engine;
use crate::crypto::PublicKey;
use crate::error::{LicenseError, Result};
use crate::models::{
LicensePayload, SignedLicense, MAX_SUPPORTED_LICENSE_VERSION, MIN_SUPPORTED_LICENSE_VERSION,
};
#[derive(Debug, Clone)]
pub struct LicenseParser {
public_key: PublicKey,
}
impl LicenseParser {
pub fn new(public_key: PublicKey) -> Self {
Self { public_key }
}
pub fn from_public_key_base64(public_key_base64: &str) -> Result<Self> {
let public_key = PublicKey::from_base64(public_key_base64)?;
Ok(Self::new(public_key))
}
pub fn parse_json(&self, json: &str) -> Result<LicensePayload> {
let signed_license = SignedLicense::from_json(json).map_err(|e| {
LicenseError::JsonDeserializationFailed {
reason: e.to_string(),
}
})?;
self.parse_signed_license(&signed_license)
}
pub fn parse_signed_license(&self, signed_license: &SignedLicense) -> Result<LicensePayload> {
self.public_key
.verify_base64(
signed_license.encoded_payload.as_bytes(),
&signed_license.encoded_signature,
)
.map_err(|_| LicenseError::InvalidSignature)?;
let payload_bytes = BASE64_STANDARD
.decode(&signed_license.encoded_payload)
.map_err(|e| LicenseError::Base64DecodingFailed {
reason: e.to_string(),
})?;
let payload: LicensePayload = serde_json::from_slice(&payload_bytes).map_err(|e| {
LicenseError::JsonDeserializationFailed {
reason: e.to_string(),
}
})?;
if payload.format_version < MIN_SUPPORTED_LICENSE_VERSION {
return Err(LicenseError::UnsupportedLicenseVersion {
found: payload.format_version,
supported: format!(
"{} to {}",
MIN_SUPPORTED_LICENSE_VERSION, MAX_SUPPORTED_LICENSE_VERSION
),
});
}
if payload.format_version > MAX_SUPPORTED_LICENSE_VERSION {
return Err(LicenseError::UnsupportedLicenseVersion {
found: payload.format_version,
supported: format!(
"{} to {}",
MIN_SUPPORTED_LICENSE_VERSION, MAX_SUPPORTED_LICENSE_VERSION
),
});
}
Ok(payload)
}
pub fn decode_unverified(&self, json: &str) -> Result<(LicensePayload, bool)> {
let signed_license = SignedLicense::from_json(json).map_err(|e| {
LicenseError::JsonDeserializationFailed {
reason: e.to_string(),
}
})?;
let signature_valid = self
.public_key
.verify_base64(
signed_license.encoded_payload.as_bytes(),
&signed_license.encoded_signature,
)
.is_ok();
let payload_bytes = BASE64_STANDARD
.decode(&signed_license.encoded_payload)
.map_err(|e| LicenseError::Base64DecodingFailed {
reason: e.to_string(),
})?;
let payload: LicensePayload = serde_json::from_slice(&payload_bytes).map_err(|e| {
LicenseError::JsonDeserializationFailed {
reason: e.to_string(),
}
})?;
Ok((payload, signature_valid))
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
pub fn parse_license(license_json: &str, public_key_base64: &str) -> Result<LicensePayload> {
let parser = LicenseParser::from_public_key_base64(public_key_base64)?;
parser.parse_json(license_json)
}
pub fn extract_payload_unverified(license_json: &str) -> Result<String> {
let signed_license = SignedLicense::from_json(license_json).map_err(|e| {
LicenseError::JsonDeserializationFailed {
reason: e.to_string(),
}
})?;
let payload_bytes = BASE64_STANDARD
.decode(&signed_license.encoded_payload)
.map_err(|e| LicenseError::Base64DecodingFailed {
reason: e.to_string(),
})?;
String::from_utf8(payload_bytes).map_err(|e| LicenseError::InvalidLicenseFormat {
reason: format!("payload is not valid UTF-8: {}", e),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::LicenseBuilder;
use crate::crypto::KeyPair;
use chrono::Duration;
fn create_test_license(key_pair: &KeyPair) -> String {
LicenseBuilder::new()
.license_id("TEST-LIC-001")
.customer_id("TEST-CUST-001")
.customer_name("Test Customer")
.expires_in(Duration::days(30))
.allowed_feature("premium")
.build_and_sign_to_json(key_pair)
.expect("Should create test license")
}
#[test]
fn test_parse_valid_license() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let license_json = create_test_license(&key_pair);
let parser = LicenseParser::new(key_pair.public_key());
let payload = parser
.parse_json(&license_json)
.expect("Should parse license");
assert_eq!(payload.license_id, "TEST-LIC-001");
assert_eq!(payload.customer_id, "TEST-CUST-001");
assert_eq!(payload.customer_name.as_deref(), Some("Test Customer"));
}
#[test]
fn test_parse_with_wrong_key_fails() {
let key_pair_1 = KeyPair::generate().expect("Key generation should succeed");
let key_pair_2 = KeyPair::generate().expect("Key generation should succeed");
let license_json = create_test_license(&key_pair_1);
let parser = LicenseParser::new(key_pair_2.public_key());
let result = parser.parse_json(&license_json);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LicenseError::InvalidSignature
));
}
#[test]
fn test_parse_tampered_license_fails() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let license_json = create_test_license(&key_pair);
let mut signed: SignedLicense = serde_json::from_str(&license_json).expect("Should parse");
let mut chars: Vec<char> = signed.encoded_payload.chars().collect();
if let Some(c) = chars.get_mut(10) {
*c = if *c == 'A' { 'B' } else { 'A' };
}
signed.encoded_payload = chars.into_iter().collect();
let tampered_json = serde_json::to_string(&signed).expect("Should serialize");
let parser = LicenseParser::new(key_pair.public_key());
let result = parser.parse_json(&tampered_json);
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_json() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let parser = LicenseParser::new(key_pair.public_key());
let result = parser.parse_json("not valid json");
assert!(matches!(
result.unwrap_err(),
LicenseError::JsonDeserializationFailed { .. }
));
}
#[test]
fn test_decode_unverified() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let license_json = create_test_license(&key_pair);
let parser = LicenseParser::new(key_pair.public_key());
let (payload, signature_valid) = parser
.decode_unverified(&license_json)
.expect("Should decode");
assert!(signature_valid);
assert_eq!(payload.license_id, "TEST-LIC-001");
}
#[test]
fn test_decode_unverified_with_wrong_key() {
let key_pair_1 = KeyPair::generate().expect("Key generation should succeed");
let key_pair_2 = KeyPair::generate().expect("Key generation should succeed");
let license_json = create_test_license(&key_pair_1);
let parser = LicenseParser::new(key_pair_2.public_key());
let (payload, signature_valid) = parser
.decode_unverified(&license_json)
.expect("Should decode");
assert!(!signature_valid);
assert_eq!(payload.license_id, "TEST-LIC-001");
}
#[test]
fn test_extract_payload_unverified() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let license_json = create_test_license(&key_pair);
let payload_json = extract_payload_unverified(&license_json).expect("Should extract");
let value: serde_json::Value =
serde_json::from_str(&payload_json).expect("Should be valid JSON");
assert_eq!(value["id"], "TEST-LIC-001");
}
#[test]
fn test_from_public_key_base64() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let public_key_base64 = key_pair.public_key_base64();
let parser = LicenseParser::from_public_key_base64(&public_key_base64)
.expect("Should create parser");
let license_json = create_test_license(&key_pair);
let payload = parser.parse_json(&license_json).expect("Should parse");
assert_eq!(payload.license_id, "TEST-LIC-001");
}
#[test]
fn test_parse_license_convenience_function() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let public_key_base64 = key_pair.public_key_base64();
let license_json = create_test_license(&key_pair);
let payload =
parse_license(&license_json, &public_key_base64).expect("Should parse license");
assert_eq!(payload.license_id, "TEST-LIC-001");
}
}