use crate::config::EncryptionMethod;
use crate::error::{PackagerError, PackagerResult};
use bytes::{BufMut, BytesMut};
#[cfg(feature = "encryption")]
use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
#[cfg(feature = "encryption")]
use aes::Aes128;
#[cfg(feature = "encryption")]
use cbc::{Decryptor, Encryptor};
#[derive(Debug, Clone)]
pub struct KeyInfo {
pub key: Vec<u8>,
pub iv: Vec<u8>,
pub uri: Option<String>,
pub format: Option<String>,
pub format_versions: Option<String>,
}
impl KeyInfo {
#[must_use]
pub fn new(key: Vec<u8>, iv: Vec<u8>) -> Self {
Self {
key,
iv,
uri: None,
format: None,
format_versions: None,
}
}
#[must_use]
pub fn with_uri(mut self, uri: String) -> Self {
self.uri = Some(uri);
self
}
#[must_use]
pub fn with_format(mut self, format: String) -> Self {
self.format = Some(format);
self
}
pub fn validate(&self) -> PackagerResult<()> {
if self.key.len() != 16 {
return Err(PackagerError::EncryptionError(
"Key must be 16 bytes for AES-128".to_string(),
));
}
if self.iv.len() != 16 {
return Err(PackagerError::EncryptionError(
"IV must be 16 bytes".to_string(),
));
}
Ok(())
}
}
pub struct EncryptionHandler {
method: EncryptionMethod,
key_info: Option<KeyInfo>,
}
impl EncryptionHandler {
#[must_use]
pub fn new(method: EncryptionMethod) -> Self {
Self {
method,
key_info: None,
}
}
pub fn set_key_info(&mut self, key_info: KeyInfo) -> PackagerResult<()> {
key_info.validate()?;
self.key_info = Some(key_info);
Ok(())
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.method != EncryptionMethod::None
}
#[must_use]
pub fn method(&self) -> EncryptionMethod {
self.method
}
pub fn encrypt(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
if !self.is_enabled() {
return Ok(data.to_vec());
}
match self.method {
EncryptionMethod::None => Ok(data.to_vec()),
EncryptionMethod::Aes128 => self.encrypt_aes128(data),
EncryptionMethod::SampleAes => self.encrypt_sample_aes(data),
EncryptionMethod::Cenc => self.encrypt_cenc(data),
}
}
pub fn decrypt(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
if !self.is_enabled() {
return Ok(data.to_vec());
}
match self.method {
EncryptionMethod::None => Ok(data.to_vec()),
EncryptionMethod::Aes128 => self.decrypt_aes128(data),
EncryptionMethod::SampleAes => self.decrypt_sample_aes(data),
EncryptionMethod::Cenc => self.decrypt_cenc(data),
}
}
#[cfg(feature = "encryption")]
fn encrypt_aes128(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
let key_info = self
.key_info
.as_ref()
.ok_or_else(|| PackagerError::EncryptionError("Key info not set".to_string()))?;
type Aes128CbcEnc = Encryptor<Aes128>;
let cipher = Aes128CbcEnc::new_from_slices(&key_info.key, &key_info.iv)
.map_err(|e| PackagerError::EncryptionError(format!("Failed to create cipher: {e}")))?;
let msg_len = data.len();
let mut buf = vec![0u8; msg_len + 16];
buf[..msg_len].copy_from_slice(data);
let encrypted = cipher
.encrypt_padded::<block_padding::Pkcs7>(&mut buf, msg_len)
.map_err(|e| PackagerError::EncryptionError(format!("Encryption failed: {e}")))?;
Ok(encrypted.to_vec())
}
#[cfg(not(feature = "encryption"))]
fn encrypt_aes128(&self, _data: &[u8]) -> PackagerResult<Vec<u8>> {
Err(PackagerError::EncryptionError(
"Encryption feature not enabled".to_string(),
))
}
#[cfg(feature = "encryption")]
fn decrypt_aes128(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
let key_info = self
.key_info
.as_ref()
.ok_or_else(|| PackagerError::EncryptionError("Key info not set".to_string()))?;
type Aes128CbcDec = Decryptor<Aes128>;
let cipher = Aes128CbcDec::new_from_slices(&key_info.key, &key_info.iv)
.map_err(|e| PackagerError::EncryptionError(format!("Failed to create cipher: {e}")))?;
let mut buf = data.to_vec();
let decrypted = cipher
.decrypt_padded::<block_padding::Pkcs7>(&mut buf)
.map_err(|e| PackagerError::EncryptionError(format!("Decryption failed: {e}")))?;
Ok(decrypted.to_vec())
}
#[cfg(not(feature = "encryption"))]
fn decrypt_aes128(&self, _data: &[u8]) -> PackagerResult<Vec<u8>> {
Err(PackagerError::EncryptionError(
"Encryption feature not enabled".to_string(),
))
}
fn encrypt_sample_aes(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
self.encrypt_aes128(data)
}
fn decrypt_sample_aes(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
self.decrypt_aes128(data)
}
fn encrypt_cenc(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
self.encrypt_aes128(data)
}
fn decrypt_cenc(&self, data: &[u8]) -> PackagerResult<Vec<u8>> {
self.decrypt_aes128(data)
}
pub fn generate_hls_key_tag(&self) -> PackagerResult<String> {
if !self.is_enabled() {
return Ok(String::new());
}
let key_info = self
.key_info
.as_ref()
.ok_or_else(|| PackagerError::EncryptionError("Key info not set".to_string()))?;
let method = match self.method {
EncryptionMethod::Aes128 => "AES-128",
EncryptionMethod::SampleAes => "SAMPLE-AES",
_ => {
return Err(PackagerError::EncryptionError(
"Unsupported method for HLS".to_string(),
))
}
};
let uri = key_info
.uri
.as_ref()
.ok_or_else(|| PackagerError::EncryptionError("Key URI not set".to_string()))?;
let iv_hex = hex::encode(&key_info.iv);
let mut tag = format!("#EXT-X-KEY:METHOD={method},URI=\"{uri}\",IV=0x{iv_hex}");
if let Some(format) = &key_info.format {
tag.push_str(&format!(",KEYFORMAT=\"{format}\""));
}
if let Some(versions) = &key_info.format_versions {
tag.push_str(&format!(",KEYFORMATVERSIONS=\"{versions}\""));
}
Ok(tag)
}
#[must_use]
pub fn key_info(&self) -> Option<&KeyInfo> {
self.key_info.as_ref()
}
}
pub struct KeyGenerator;
impl KeyGenerator {
#[must_use]
pub fn generate_aes128_key() -> Vec<u8> {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let mut key = Vec::with_capacity(16);
for i in 0..16 {
#[allow(clippy::cast_possible_truncation)]
key.push(((now >> (i * 8)) & 0xFF) as u8);
}
key
}
#[must_use]
pub fn generate_iv() -> Vec<u8> {
Self::generate_aes128_key()
}
#[must_use]
pub fn from_passphrase(passphrase: &str) -> Vec<u8> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
passphrase.hash(&mut hasher);
let hash = hasher.finish();
let mut key = Vec::with_capacity(16);
for i in 0..16 {
#[allow(clippy::cast_possible_truncation)]
key.push(((hash >> (i * 4)) & 0xFF) as u8);
}
key
}
}
pub trait DrmProvider {
fn system_id(&self) -> &str;
fn generate_pssh(&self, key_id: &[u8]) -> PackagerResult<Vec<u8>>;
fn license_url(&self) -> Option<String>;
}
pub struct WidevineDrmProvider {
license_url: String,
}
impl WidevineDrmProvider {
#[must_use]
pub fn new(license_url: String) -> Self {
Self { license_url }
}
}
impl DrmProvider for WidevineDrmProvider {
fn system_id(&self) -> &'static str {
"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" }
fn generate_pssh(&self, key_id: &[u8]) -> PackagerResult<Vec<u8>> {
let mut pssh = BytesMut::new();
pssh.put_u32(0); pssh.put_slice(b"pssh");
pssh.put_u32(0);
let system_id = hex::decode(self.system_id().replace('-', ""))
.map_err(|_| PackagerError::DrmFailed("Invalid system ID".to_string()))?;
pssh.put_slice(&system_id);
pssh.put_u32(1);
pssh.put_slice(key_id);
pssh.put_u32(0);
let size = pssh.len();
pssh[0..4].copy_from_slice(&(size as u32).to_be_bytes());
Ok(pssh.to_vec())
}
fn license_url(&self) -> Option<String> {
Some(self.license_url.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_generation() {
let key = KeyGenerator::generate_aes128_key();
assert_eq!(key.len(), 16);
}
#[test]
fn test_key_info_validation() {
let key = vec![0u8; 16];
let iv = vec![0u8; 16];
let key_info = KeyInfo::new(key, iv);
assert!(key_info.validate().is_ok());
}
#[test]
fn test_key_info_invalid_key_size() {
let key = vec![0u8; 8]; let iv = vec![0u8; 16];
let key_info = KeyInfo::new(key, iv);
assert!(key_info.validate().is_err());
}
#[test]
fn test_encryption_handler_creation() {
let handler = EncryptionHandler::new(EncryptionMethod::Aes128);
assert!(handler.is_enabled());
}
#[test]
fn test_hls_key_tag_generation() {
let key = vec![0u8; 16];
let iv = vec![0u8; 16];
let key_info = KeyInfo::new(key, iv).with_uri("https://example.com/key".to_string());
let mut handler = EncryptionHandler::new(EncryptionMethod::Aes128);
handler
.set_key_info(key_info)
.expect("should succeed in test");
let tag = handler
.generate_hls_key_tag()
.expect("should succeed in test");
assert!(tag.contains("AES-128"));
assert!(tag.contains("https://example.com/key"));
}
#[test]
#[cfg(feature = "encryption")]
fn test_aes128_encrypt_decrypt_roundtrip() {
let key = vec![
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
0x4f, 0x3c,
];
let iv = vec![
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
];
let plaintext = b"Hello OxiMedia AES-128 test data";
let key_info = KeyInfo::new(key, iv);
let mut handler = EncryptionHandler::new(EncryptionMethod::Aes128);
handler
.set_key_info(key_info)
.expect("set_key_info should succeed in test");
let ciphertext = handler
.encrypt(plaintext)
.expect("encrypt should succeed in test");
assert_ne!(
&ciphertext[..plaintext.len()],
plaintext.as_ref(),
"ciphertext must differ from plaintext"
);
assert_eq!(ciphertext.len() % 16, 0, "ciphertext must be block-aligned");
let decrypted = handler
.decrypt(&ciphertext)
.expect("decrypt should succeed in test");
assert_eq!(
&decrypted[..plaintext.len()],
plaintext.as_ref(),
"AES-128 round-trip must recover original plaintext"
);
}
}