use crate::{cache::CachePath, SECRETS}; use aes_gcm_siv::{ aead::{generic_array::GenericArray, Aead}, Aes256GcmSiv, KeyInit, }; use anyhow::{anyhow, bail, Context}; use base64::Engine; use bincode::{Decode, Encode}; use jellycommon::Asset; use log::warn; use std::{path::PathBuf, sync::LazyLock}; const VERSION: u32 = 3; static ASSET_KEY: LazyLock = LazyLock::new(|| { if let Some(sk) = &SECRETS.session_key { let r = base64::engine::general_purpose::STANDARD .decode(sk) .expect("key invalid; should be valid base64"); aes_gcm_siv::Aes256GcmSiv::new_from_slice(&r) .expect("key has the wrong length; should be 32 bytes") } else { warn!("session_key not configured; generating a random one."); aes_gcm_siv::Aes256GcmSiv::new_from_slice(&[(); 32].map(|_| rand::random())).unwrap() } }); #[derive(Debug, Encode, Decode)] pub enum AssetInner { Federated { host: String, asset: Vec }, Cache(CachePath), Assets(PathBuf), Library(PathBuf), } impl AssetInner { pub fn ser(&self) -> Asset { let mut plaintext = Vec::new(); plaintext.extend(u32::to_le_bytes(VERSION)); plaintext.extend(bincode::encode_to_vec(&self, bincode::config::standard()).unwrap()); while plaintext.len() % 16 == 0 { plaintext.push(0); } let nonce = [(); 12].map(|_| rand::random()); let mut ciphertext = ASSET_KEY .encrypt(&GenericArray::from(nonce), plaintext.as_slice()) .unwrap(); ciphertext.extend(nonce); Asset(base64::engine::general_purpose::URL_SAFE.encode(&ciphertext)) } pub fn deser(s: &str) -> anyhow::Result { let ciphertext = base64::engine::general_purpose::URL_SAFE.decode(&s)?; let (ciphertext, nonce) = ciphertext.split_at(ciphertext.len() - 12); let plaintext = ASSET_KEY .decrypt(nonce.into(), ciphertext) .map_err(|_| anyhow!("asset token decrypt failed"))?; let version = u32::from_le_bytes(plaintext[0..4].try_into().unwrap()); if version != VERSION { bail!("asset token version mismatch"); } let (data, _): (AssetInner, _) = bincode::decode_from_slice(&plaintext[4..], bincode::config::standard()) .context("asset token has invalid format")?; Ok(data) } }