use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LcpUser {
pub id: Option<String>,
pub email: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LcpRights {
pub print: Option<u64>,
pub copy: Option<u64>,
pub start: Option<String>,
pub end: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LcpEncryption {
pub profile: String,
pub user_key: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LcpLicense {
pub id: String,
pub provider: String,
pub issued: Option<String>,
pub updated: Option<String>,
pub user: Option<LcpUser>,
pub rights: Option<LcpRights>,
pub encryption: Option<LcpEncryption>,
}
impl LcpLicense {
pub fn parse(lcpl_json: &str) -> Result<Self, String> {
serde_json::from_str(lcpl_json).map_err(|e| {
format!(
"Failed to parse Readium LCP license JSON (license.lcpl): {}",
e
)
})
}
pub fn is_expired(&self, current_iso_date: &str) -> bool {
if let Some(ref rights) = self.rights {
if let Some(ref end) = rights.end {
return current_iso_date > end.as_str();
}
}
false
}
}
pub struct LcpDecryptor;
impl LcpDecryptor {
pub fn decrypt_bytes(
encrypted_bytes: &[u8],
user_passphrase: &str,
license: &LcpLicense,
) -> Result<Vec<u8>, String> {
if encrypted_bytes.is_empty() {
return Ok(Vec::new());
}
if user_passphrase.trim().is_empty() {
return Err("Readium LCP passphrase cannot be empty".to_string());
}
let raw_hash = sha256_hash(user_passphrase.as_bytes());
let passphrase_hash: String = raw_hash.iter().map(|b| format!("{:02x}", b)).collect();
if let Some(ref enc) = license.encryption {
if enc.profile.is_empty() {
return Err("Invalid LCP encryption profile".to_string());
}
}
let mut decrypted = Vec::with_capacity(encrypted_bytes.len());
let key_bytes = passphrase_hash.as_bytes();
for (i, &byte) in encrypted_bytes.iter().enumerate() {
let key_byte = key_bytes[i % key_bytes.len()];
decrypted.push(byte ^ key_byte ^ ((i % 255) as u8));
}
Ok(decrypted)
}
}
fn sha256_hash(input: &[u8]) -> [u8; 32] {
let mut state: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
0x5be0cd19,
];
for (i, chunk) in input.chunks(64).enumerate() {
let mut w = [0u32; 64];
for (j, &byte) in chunk.iter().enumerate() {
w[j / 4] |= (byte as u32) << (24 - 8 * (j % 4));
}
for j in chunk.len()..64 {
w[j / 4] |= 0x80 << (24 - 8 * (j % 4));
}
let mut a = state[0];
let mut b = state[1];
let mut c = state[2];
let mut d = state[3];
let mut e = state[4];
let mut f = state[5];
let mut g = state[6];
let mut h = state[7];
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
let maj = (a & b) ^ (a & c) ^ (b & c);
let t2 = s0.wrapping_add(maj);
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let ch = (e & f) ^ (!e & g);
let t1 = h
.wrapping_add(s1)
.wrapping_add(ch)
.wrapping_add(0x428a2f98)
.wrapping_add(w[i % 64]);
h = g;
g = f;
f = e;
e = d.wrapping_add(t1);
d = c;
c = b;
b = a;
a = t1.wrapping_add(t2);
state[0] = state[0].wrapping_add(a);
state[1] = state[1].wrapping_add(b);
state[2] = state[2].wrapping_add(c);
state[3] = state[3].wrapping_add(d);
state[4] = state[4].wrapping_add(e);
state[5] = state[5].wrapping_add(f);
state[6] = state[6].wrapping_add(g);
state[7] = state[7].wrapping_add(h);
}
let mut out = [0u8; 32];
for (i, val) in state.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&val.to_be_bytes());
}
out
}