use std::collections::{BTreeMap, HashMap, HashSet};
use std::time::{SystemTime, UNIX_EPOCH};
use base64::Engine;
use base64::engine::general_purpose;
use ed25519_dalek::{Signer, SigningKey};
use serde_json::{Value, json};
use x25519_dalek::{PublicKey, StaticSecret};
use crate::api::ApiClient;
use crate::base64::{base64url_decode, base64url_encode};
use crate::crypto::aes::aes128_ecb_encrypt_block;
use crate::crypto::key_manager::KeyManager;
use crate::crypto::keyring::{Keyring, encrypt_tlv_records};
use crate::crypto::{AuthRing, AuthState, MegaRsaKey, make_random_key, parse_raw_private_key};
use crate::error::{MegaError, Result};
use crate::fs::Node;
pub(crate) struct Session {
pub(crate) api: ApiClient,
session_id: String,
session_key: Option<[u8; 16]>,
pub(crate) master_key: [u8; 16],
rsa_key: MegaRsaKey,
pub email: String,
pub name: Option<String>,
pub user_handle: String,
pub(crate) nodes: Vec<Node>,
pub(crate) share_keys: HashMap<String, [u8; 16]>,
pub(crate) outshares: HashMap<String, HashSet<String>>,
pub(crate) pending_outshares: HashMap<String, HashSet<String>>,
pub(crate) contacts: HashMap<String, Contact>,
pub(crate) key_manager: KeyManager,
pub(crate) authring_ed: AuthRing,
pub(crate) authring_cu: AuthRing,
pub(crate) backups: Vec<u8>,
pub(crate) warnings: crate::crypto::Warnings,
pub(crate) manual_verification: bool,
pub(crate) user_attr_cache: HashMap<String, Vec<u8>>,
pub(crate) user_attr_versions: HashMap<String, String>,
pub(crate) last_keys_blob_b64: Option<String>,
pub(crate) keys_persist_inflight: bool,
pub(crate) pending_keys_token: Option<String>,
pub(crate) keys_downgrade_detected: bool,
pub(crate) scsn: Option<String>,
pub(crate) wsc_url: Option<String>,
pub(crate) sc_catchup: bool,
pub(crate) current_seqtag: Option<String>,
pub(crate) current_seqtag_seen: bool,
pub(crate) defer_seqtag_wait: bool,
pub(crate) alerts_catchup_pending: bool,
pub(crate) user_alert_lsn: Option<String>,
pub(crate) user_alerts: Vec<Value>,
resume_enabled: bool,
progress_callback: Option<crate::progress::ProgressCallback>,
previews_enabled: bool,
workers: usize,
}
#[derive(Debug, Clone)]
pub struct Contact {
pub handle: String,
pub email: Option<String>,
pub status: i64,
pub last_updated: i64,
}
impl Session {
pub(super) fn new_internal(
api: ApiClient,
session_id: String,
session_key: Option<[u8; 16]>,
master_key: [u8; 16],
rsa_key: MegaRsaKey,
email: String,
name: Option<String>,
user_handle: String,
user_attr_cache: HashMap<String, Vec<u8>>,
user_attr_versions: HashMap<String, String>,
scsn: Option<String>,
) -> Self {
let sc_catchup = scsn.is_some();
let alerts_catchup_pending = scsn.is_some();
Session {
api,
session_id,
session_key,
master_key,
rsa_key,
email,
name,
user_handle,
nodes: Vec::new(),
share_keys: HashMap::new(),
outshares: HashMap::new(),
pending_outshares: HashMap::new(),
contacts: HashMap::new(),
key_manager: KeyManager::default(),
authring_ed: AuthRing::default(),
authring_cu: AuthRing::default(),
backups: Vec::new(),
warnings: crate::crypto::Warnings::default(),
manual_verification: false,
user_attr_cache,
user_attr_versions,
last_keys_blob_b64: None,
keys_persist_inflight: false,
pending_keys_token: None,
keys_downgrade_detected: false,
scsn,
wsc_url: None,
sc_catchup,
current_seqtag: None,
current_seqtag_seen: false,
defer_seqtag_wait: false,
alerts_catchup_pending,
user_alert_lsn: None,
user_alerts: Vec::new(),
resume_enabled: false,
progress_callback: None,
previews_enabled: false,
workers: 1,
}
}
fn build_upv_command(attrs: Vec<(&str, String, Option<String>)>) -> Value {
let mut obj = serde_json::Map::new();
obj.insert("a".into(), Value::from("upv"));
for (name, value, version) in attrs {
if let Some(v) = version {
obj.insert(name.into(), json!([value, v]));
} else {
obj.insert(name.into(), json!([value, 0]));
}
}
Value::Object(obj)
}
fn validate_upv_batch(resp: Value) -> Result<()> {
if let Some(arr) = resp.as_array() {
for item in arr {
if let Some(code) = item.as_i64() {
if code < 0 {
let error_code = crate::api::ApiErrorCode::from(code);
return Err(MegaError::ApiError {
code: code as i32,
message: error_code.description().to_string(),
});
}
}
}
}
Ok(())
}
pub(super) async fn attach_account_keys_if_missing(&mut self) -> Result<()> {
if !self.has_valid_rsa_key() {
return Err(MegaError::Custom(
"Cannot attach account keys without a valid RSA key".to_string(),
));
}
if self.get_user_attribute_raw("^!keys").await?.is_some() {
return Ok(());
}
let existing_keyring = self.get_user_attribute_raw("*keyring").await?;
let (keyring, keyring_enc) = if let Some(enc) = existing_keyring {
(Keyring::from_encrypted(&enc, &self.master_key)?, None)
} else {
let kr = Keyring::generate();
let enc = kr.to_encrypted(&self.master_key)?;
(kr, Some(enc))
};
let ed = keyring
.ed25519
.clone()
.ok_or_else(|| MegaError::Custom("Missing Ed25519 key".to_string()))?;
let cu = keyring
.cu25519
.clone()
.ok_or_else(|| MegaError::Custom("Missing Curve25519 key".to_string()))?;
if ed.len() != 32 || cu.len() != 32 {
return Err(MegaError::Custom(
"Invalid keyring lengths; expected 32-byte keys".to_string(),
));
}
let mut ed_arr = [0u8; 32];
ed_arr.copy_from_slice(&ed);
let signing = SigningKey::from_bytes(&ed_arr);
let pu_ed = signing.verifying_key().to_bytes().to_vec();
let mut cu_arr = [0u8; 32];
cu_arr.copy_from_slice(&cu);
let cu_secret = StaticSecret::from(cu_arr);
let pu_cu = PublicKey::from(&cu_secret).to_bytes().to_vec();
let sig_cu = signing.sign(&pu_cu).to_bytes().to_vec();
let rsa_pub = self.rsa_key.public_key_bytes();
let sig_pubk = signing.sign(&rsa_pub).to_bytes().to_vec();
let mut km = KeyManager::new();
km.set_priv_keys(&ed, &cu);
km.priv_rsa = self.rsa_key.private_key_bytes();
km.generation = 1;
km.creation_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
if let Ok(decoded_handle) = base64url_decode(&self.user_handle) {
let mut ident = [0u8; 8];
let copy_len = decoded_handle.len().min(8);
ident[..copy_len].copy_from_slice(&decoded_handle[..copy_len]);
km.identity = u64::from_le_bytes(ident);
}
let keys_blob = km.encode_container(&self.master_key)?;
let keys_b64 = base64url_encode(&keys_blob);
let mut commands = Vec::new();
let usk_missing = self.get_user_attribute_raw("*~usk").await?.is_none();
let jscd_missing = self.get_user_attribute_raw("*~jscd").await?.is_none();
let attr_versions = self.user_attr_versions.clone();
let version_for = |name: &str| attr_versions.get(name).cloned();
let mut generated_usk: Option<Vec<u8>> = None;
if usk_missing {
let usk = make_random_key();
let usk_b64 = base64url_encode(&usk);
commands.push(Self::build_upv_command(vec![(
"*~usk",
usk_b64,
version_for("*~usk"),
)]));
generated_usk = Some(usk.to_vec());
}
let mut key_attrs = Vec::new();
if let Some(enc) = keyring_enc.as_ref() {
key_attrs.push(("*keyring", base64url_encode(enc), version_for("*keyring")));
}
key_attrs.push(("^!keys", keys_b64, version_for("^!keys")));
key_attrs.push((
"+puEd255",
base64url_encode(&pu_ed),
version_for("+puEd255"),
));
key_attrs.push((
"+puCu255",
base64url_encode(&pu_cu),
version_for("+puCu255"),
));
key_attrs.push((
"+sigCu255",
base64url_encode(&sig_cu),
version_for("+sigCu255"),
));
key_attrs.push((
"+sigPubk",
base64url_encode(&sig_pubk),
version_for("+sigPubk"),
));
commands.push(Self::build_upv_command(key_attrs));
let mut generated_jscd: Option<Vec<u8>> = None;
if jscd_missing {
let mut records = BTreeMap::new();
records.insert("ak".to_string(), make_random_key().to_vec());
records.insert("ck".to_string(), make_random_key().to_vec());
records.insert("fn".to_string(), make_random_key().to_vec());
let jscd = encrypt_tlv_records(&records, &self.master_key)?;
let jscd_b64 = base64url_encode(&jscd);
commands.push(Self::build_upv_command(vec![(
"*~jscd",
jscd_b64,
version_for("*~jscd"),
)]));
generated_jscd = Some(jscd);
}
if !commands.is_empty() {
let resp = self.api.request_batch(commands).await?;
Self::validate_upv_batch(resp.clone())?;
if let Some(ver) = Self::extract_attr_version(&resp, "^!keys") {
self.user_attr_versions.insert("^!keys".to_string(), ver);
}
}
if let Some(enc) = keyring_enc {
self.user_attr_cache.insert("*keyring".to_string(), enc);
}
if let Some(usk) = generated_usk {
self.user_attr_cache.insert("*~usk".to_string(), usk);
}
self.user_attr_cache
.insert("^!keys".to_string(), keys_blob.clone());
self.last_keys_blob_b64 = Some(base64url_encode(&keys_blob));
self.user_attr_cache.insert("+puEd255".to_string(), pu_ed);
self.user_attr_cache.insert("+puCu255".to_string(), pu_cu);
self.user_attr_cache.insert("+sigCu255".to_string(), sig_cu);
self.user_attr_cache
.insert("+sigPubk".to_string(), sig_pubk);
if let Some(jscd) = generated_jscd {
self.user_attr_cache.insert("*~jscd".to_string(), jscd);
}
self.key_manager = km;
self.authring_ed = AuthRing::deserialize_ltlv(&self.key_manager.auth_ed25519);
self.authring_cu = AuthRing::deserialize_ltlv(&self.key_manager.auth_cu25519);
Ok(())
}
async fn upload_rsa_keypair(&mut self, rsa_key: &MegaRsaKey) -> Result<()> {
let privk = rsa_key.encode_private_key(&self.master_key);
let pubk = rsa_key.encode_public_key();
let response = self
.api_mut()
.request(json!({
"a": "up",
"privk": privk,
"pubk": pubk
}))
.await?;
if let Some(err) = response.as_i64().filter(|v| *v < 0) {
let code = crate::api::ApiErrorCode::from(err);
return Err(MegaError::ApiError {
code: err as i32,
message: code.description().to_string(),
});
}
Ok(())
}
pub(super) async fn initialize_account_keys(&mut self) -> Result<()> {
if self.key_manager.is_ready() {
return Ok(());
}
if self.load_keys_attribute().await? {
return Ok(());
}
if !self.has_valid_rsa_key() {
let rsa_key = MegaRsaKey::generate().map_err(|e| {
MegaError::CryptoError(format!("Failed to generate RSA keypair: {e}"))
})?;
self.upload_rsa_keypair(&rsa_key).await?;
self.rsa_key = rsa_key;
}
self.attach_account_keys_if_missing().await?;
if self.key_manager.is_ready() || self.load_keys_attribute().await? {
return Ok(());
}
Err(MegaError::Custom(
"Request incomplete: account keys are not initialized".to_string(),
))
}
pub(crate) async fn ensure_share_keys_ready(&mut self) -> Result<()> {
if self.key_manager.is_ready() || self.load_keys_attribute().await? {
return Ok(());
}
self.initialize_account_keys().await
}
pub fn session_id(&self) -> &str {
&self.session_id
}
pub(crate) fn master_key(&self) -> &[u8; 16] {
&self.master_key
}
pub(crate) fn share_key_from_manager(&self, handle: &str) -> Option<[u8; 16]> {
self.key_manager.get_share_key_from_str(handle)
}
pub(crate) fn rsa_key(&self) -> &MegaRsaKey {
&self.rsa_key
}
pub(crate) fn has_valid_rsa_key(&self) -> bool {
self.rsa_key.is_valid_private_key()
}
pub(crate) fn empty_rsa_key() -> MegaRsaKey {
MegaRsaKey {
p: num_bigint::BigUint::from(0u32),
q: num_bigint::BigUint::from(0u32),
d: num_bigint::BigUint::from(0u32),
u: num_bigint::BigUint::from(0u32),
m: num_bigint::BigUint::from(0u32),
e: num_bigint::BigUint::from(3u32),
}
}
pub(crate) fn api_mut(&mut self) -> &mut ApiClient {
&mut self.api
}
pub fn set_resume(&mut self, enabled: bool) {
self.resume_enabled = enabled;
}
pub fn is_resume_enabled(&self) -> bool {
self.resume_enabled
}
pub fn watch_status(&mut self, callback: crate::progress::ProgressCallback) {
self.progress_callback = Some(callback);
}
pub fn clear_status(&mut self) {
self.progress_callback = None;
}
pub(crate) fn report_progress(&mut self, progress: &crate::progress::TransferProgress) -> bool {
if let Some(ref mut callback) = self.progress_callback {
callback(progress)
} else {
true }
}
pub fn enable_previews(&mut self, enabled: bool) {
self.previews_enabled = enabled;
}
pub fn previews_enabled(&self) -> bool {
self.previews_enabled
}
pub fn set_workers(&mut self, workers: usize) {
self.workers = workers.clamp(1, 16);
}
pub fn workers(&self) -> usize {
self.workers
}
pub fn nodes(&self) -> &[crate::fs::Node] {
&self.nodes
}
pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
let session = self.dump_session()?;
std::fs::write(path, session)
.map_err(|e| MegaError::Custom(format!("Write error: {}", e)))?;
Ok(())
}
pub fn dump_session_blob(&self) -> Result<Vec<u8>> {
let sid_bytes = base64url_decode(&self.session_id)
.map_err(|_| MegaError::Custom("Invalid session id".to_string()))?;
if sid_bytes.is_empty() {
return Err(MegaError::Custom("Session id is empty".to_string()));
}
let mut out = Vec::with_capacity(1 + 16 + sid_bytes.len());
if let Some(sek) = self.session_key {
out.push(1);
let enc = aes128_ecb_encrypt_block(&self.master_key, &sek);
out.extend_from_slice(&enc);
} else {
out.extend_from_slice(&self.master_key);
}
out.extend_from_slice(&sid_bytes);
Ok(out)
}
pub fn dump_session(&self) -> Result<String> {
let blob = self.dump_session_blob()?;
Ok(general_purpose::STANDARD.encode(blob))
}
pub fn parse_session_blob(session_b64: &str) -> Result<SessionBlob> {
let data = general_purpose::STANDARD
.decode(session_b64.as_bytes())
.map_err(|_| MegaError::Custom("Invalid session blob".to_string()))?;
if data.is_empty() {
return Err(MegaError::Custom("Empty session blob".to_string()));
}
if data[0] == 2 {
return Err(MegaError::Custom(
"Folder-link sessions are not supported; use parse_folder_session_blob".to_string(),
));
}
let (session_version, key_bytes, sid_bytes, master_key_encrypted) =
if data[0] == 1 && data.len() >= 17 {
(1u8, &data[1..17], &data[17..], true)
} else if data.len() >= 16 {
(0u8, &data[0..16], &data[16..], false)
} else {
return Err(MegaError::Custom("Invalid session blob length".to_string()));
};
if sid_bytes.is_empty() {
return Err(MegaError::Custom("Session id is empty".to_string()));
}
let mut master_key = [0u8; 16];
master_key.copy_from_slice(key_bytes);
let session_id = base64url_encode(sid_bytes);
Ok(SessionBlob {
session_id,
master_key,
session_version,
master_key_encrypted,
})
}
pub fn parse_folder_session_blob(session_b64: &str) -> Result<FolderSessionBlob> {
let data = general_purpose::STANDARD
.decode(session_b64.as_bytes())
.map_err(|_| MegaError::Custom("Invalid session blob".to_string()))?;
if data.is_empty() {
return Err(MegaError::Custom("Empty session blob".to_string()));
}
if data[0] != 2 {
return Err(MegaError::Custom(
"Not a folder-link session blob".to_string(),
));
}
let mut idx = 1usize;
if idx + 6 + 6 + 16 + 8 > data.len() {
return Err(MegaError::Custom("Invalid folder session blob".to_string()));
}
let public_handle = base64url_encode(&data[idx..idx + 6]);
idx += 6;
let root_handle = base64url_encode(&data[idx..idx + 6]);
idx += 6;
let mut folder_key = [0u8; 16];
folder_key.copy_from_slice(&data[idx..idx + 16]);
idx += 16;
let flags = &data[idx..idx + 8];
idx += 8;
if flags[3..].iter().any(|b| *b != 0) {
return Err(MegaError::Custom(
"Invalid folder session flags".to_string(),
));
}
let has_write = flags[0] != 0;
let has_account = flags[1] != 0;
let has_padding = flags[2] != 0;
let read_string = |buf: &[u8], pos: &mut usize| -> Result<String> {
if *pos + 2 > buf.len() {
return Err(MegaError::Custom("Invalid folder session blob".to_string()));
}
let len = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]) as usize;
*pos += 2;
if *pos + len > buf.len() {
return Err(MegaError::Custom("Invalid folder session blob".to_string()));
}
let out = String::from_utf8(buf[*pos..*pos + len].to_vec())
.map_err(|_| MegaError::Custom("Invalid folder session string".to_string()))?;
*pos += len;
Ok(out)
};
let write_auth = if has_write {
Some(read_string(&data, &mut idx)?)
} else {
None
};
let account_auth = if has_account {
Some(read_string(&data, &mut idx)?)
} else {
None
};
let padding = if has_padding {
Some(read_string(&data, &mut idx)?)
} else {
None
};
if idx != data.len() {
return Err(MegaError::Custom("Invalid folder session blob".to_string()));
}
Ok(FolderSessionBlob {
public_handle,
root_handle,
folder_key,
write_auth,
account_auth,
padding,
})
}
pub fn dump_folder_session_blob(blob: &FolderSessionBlob) -> Result<String> {
let public_bytes = base64url_decode(&blob.public_handle)
.map_err(|_| MegaError::Custom("Invalid public handle".to_string()))?;
let root_bytes = base64url_decode(&blob.root_handle)
.map_err(|_| MegaError::Custom("Invalid root handle".to_string()))?;
if public_bytes.len() != 6 || root_bytes.len() != 6 {
return Err(MegaError::Custom("Invalid handle length".to_string()));
}
let mut out = Vec::new();
out.push(2);
out.extend_from_slice(&public_bytes);
out.extend_from_slice(&root_bytes);
out.extend_from_slice(&blob.folder_key);
let has_write = blob.write_auth.is_some();
let has_account = blob.account_auth.is_some();
let has_padding = true;
let mut flags = [0u8; 8];
flags[0] = if has_write { 1 } else { 0 };
flags[1] = if has_account { 1 } else { 0 };
flags[2] = if has_padding { 1 } else { 0 };
out.extend_from_slice(&flags);
let mut write_string = |s: &str| {
let len = s.len().min(u16::MAX as usize) as u16;
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&s.as_bytes()[..len as usize]);
};
if let Some(auth) = blob.write_auth.as_deref() {
write_string(auth);
}
if let Some(auth) = blob.account_auth.as_deref() {
write_string(auth);
}
let padding = blob.padding.clone().unwrap_or_else(|| "P".to_string());
write_string(&padding);
Ok(general_purpose::STANDARD.encode(out))
}
pub(crate) async fn load_keyring(&mut self) -> Result<Keyring> {
let Some(enc_keyring) = self.get_user_attribute_raw("*keyring").await? else {
return Err(MegaError::Custom("Keyring not found".to_string()));
};
Keyring::from_encrypted(&enc_keyring, &self.master_key)
}
pub async fn get_user_attribute_raw(&mut self, attr: &str) -> Result<Option<Vec<u8>>> {
if let Some(cached) = self.user_attr_cache.get(attr) {
return Ok(Some(cached.clone()));
}
let response = match self.api_mut().get_user_attribute(attr).await {
Ok(v) => v,
Err(MegaError::ApiError { code, .. }) if code == -9 => {
return Ok(None);
}
Err(e) => return Err(e),
};
if let Some(av) = response.get("av").and_then(|v| v.as_str()) {
if av.is_empty() {
return Ok(None);
}
let decoded = base64url_decode(av)?;
self.user_attr_cache
.insert(attr.to_string(), decoded.clone());
if let Some(ver) = response.get("v").and_then(|v| v.as_str()) {
self.user_attr_versions
.insert(attr.to_string(), ver.to_string());
}
return Ok(Some(decoded));
}
if let Some(arr) = response.as_array() {
if let Some(obj) = arr
.iter()
.find(|o| o.get("av").and_then(|v| v.as_str()).is_some())
{
let av = obj.get("av").and_then(|v| v.as_str()).unwrap_or("");
if av.is_empty() {
return Ok(None);
}
let decoded = base64url_decode(av)?;
self.user_attr_cache
.insert(attr.to_string(), decoded.clone());
if let Some(ver) = obj.get("v").and_then(|v| v.as_str()) {
self.user_attr_versions
.insert(attr.to_string(), ver.to_string());
}
return Ok(Some(decoded));
}
}
Ok(None)
}
pub async fn set_private_attribute(
&mut self,
attr: &str,
value_b64: &str,
version: Option<String>,
) -> Result<()> {
let resp = self
.api_mut()
.set_private_attribute(attr, value_b64, version.as_deref())
.await?;
if let Some(err) = resp.as_i64().filter(|v| *v < 0) {
let code = crate::api::ApiErrorCode::from(err);
return Err(MegaError::ApiError {
code: err as i32,
message: code.description().to_string(),
});
}
if let Some(ver) = Self::extract_attr_version(&resp, attr) {
self.user_attr_versions.insert(attr.to_string(), ver);
}
if attr == "^!keys" {
self.last_keys_blob_b64 = Some(value_b64.to_string());
}
Ok(())
}
fn extract_attr_version(resp: &Value, attr: &str) -> Option<String> {
if let Some(obj) = resp.as_object() {
if let Some(v) = obj.get(attr).and_then(|v| v.as_str()) {
return Some(v.to_string());
}
}
if let Some(arr) = resp.as_array() {
for item in arr {
if let Some(v) = Self::extract_attr_version(item, attr) {
return Some(v);
}
}
}
None
}
pub async fn set_authring_ed25519(&mut self, blob: Vec<u8>) -> Result<()> {
self.authring_ed = AuthRing::deserialize_ltlv(&blob);
self.persist_keys_attribute().await
}
pub async fn set_authring_cu25519(&mut self, blob: Vec<u8>) -> Result<()> {
self.authring_cu = AuthRing::deserialize_ltlv(&blob);
self.persist_keys_attribute().await
}
pub fn keys_downgrade_detected(&self) -> bool {
self.keys_downgrade_detected
}
pub async fn set_backups_blob(&mut self, blob: Vec<u8>) -> Result<()> {
self.backups = blob;
self.persist_keys_attribute().await
}
pub async fn set_warnings(&mut self, warnings: crate::crypto::Warnings) -> Result<()> {
self.warnings = warnings;
self.persist_keys_attribute().await
}
pub async fn set_contact_verification_warning(&mut self, enabled: bool) -> Result<()> {
self.warnings.set_cv(enabled);
self.persist_keys_attribute().await
}
pub async fn set_manual_verification(&mut self, enabled: bool) -> Result<()> {
self.manual_verification = enabled;
self.persist_keys_attribute().await
}
pub fn contact_verification_warning(&self) -> bool {
self.warnings.cv_enabled()
}
pub fn authring_state(&self, handle_b64: &str) -> (Option<AuthState>, Option<AuthState>) {
(
self.authring_ed.get_state(handle_b64),
self.authring_cu.get_state(handle_b64),
)
}
pub async fn load_keys_attribute(&mut self) -> Result<bool> {
let Some(enc_keys) = self.get_user_attribute_raw("^!keys").await? else {
return Ok(false);
};
let mut km = KeyManager::new();
km.decode_container(&enc_keys, &self.master_key)?;
if km.is_ready() {
self.last_keys_blob_b64 = Some(base64url_encode(&enc_keys));
for sk in &km.share_keys {
let mut arr: [u8; 16] = [0u8; 16];
arr.copy_from_slice(&sk.key);
let handle_b64 = crate::base64::base64url_encode(&sk.handle);
self.share_keys.entry(handle_b64).or_insert(arr);
}
self.key_manager = km;
if !self.key_manager.priv_rsa.is_empty() {
if let Ok(rsa) = parse_raw_private_key(&self.key_manager.priv_rsa) {
self.rsa_key = rsa;
}
}
self.authring_ed = AuthRing::deserialize_ltlv(&self.key_manager.auth_ed25519);
self.authring_cu = AuthRing::deserialize_ltlv(&self.key_manager.auth_cu25519);
self.backups = self.key_manager.backups.clone();
self.warnings = self.key_manager.warnings.clone();
self.manual_verification = self.key_manager.manual_verification;
self.promote_pending_shares().await?;
if self.clear_inuse_flags_for_missing_shares() {
self.persist_keys_with_retry().await?;
}
return Ok(true);
}
Ok(false)
}
pub async fn ensure_keys_attribute(&mut self) -> Result<()> {
if self.key_manager.is_ready() {
return Ok(());
}
if self.load_keys_attribute().await? {
return Ok(());
}
let keyring = match self.load_keyring().await {
Ok(kr) => kr,
Err(MegaError::Custom(msg)) if msg.contains("Keyring not found") => {
return Ok(());
}
Err(e) => return Err(e),
};
let mut km = KeyManager::new();
km.set_priv_keys(
keyring.ed25519.clone().unwrap_or_default().as_slice(),
keyring.cu25519.clone().unwrap_or_default().as_slice(),
);
km.generation = 1;
km.creation_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
if let Ok(decoded_handle) = base64url_decode(&self.user_handle) {
let mut ident = [0u8; 8];
let copy_len = decoded_handle.len().min(8);
ident[..copy_len].copy_from_slice(&decoded_handle[..copy_len]);
km.identity = u64::from_le_bytes(ident);
}
for (h, k) in self.share_keys.iter() {
km.add_share_key_from_str(h, k);
}
let blob = km.encode_container(&self.master_key)?;
let blob_b64 = base64url_encode(&blob);
self.set_private_attribute("^!keys", &blob_b64, None)
.await?;
self.key_manager = km;
Ok(())
}
pub async fn persist_keys_attribute(&mut self) -> Result<()> {
self.persist_keys_with_retry().await
}
pub async fn load<P: AsRef<std::path::Path>>(path: P) -> Result<Option<Self>> {
Self::load_internal(path, None).await
}
pub async fn load_with_proxy<P: AsRef<std::path::Path>>(
path: P,
proxy: &str,
) -> Result<Option<Self>> {
Self::load_internal(path, Some(proxy)).await
}
async fn load_internal<P: AsRef<std::path::Path>>(
path: P,
proxy: Option<&str>,
) -> Result<Option<Self>> {
let path = path.as_ref();
if !path.exists() {
return Ok(None);
}
let session_b64 = std::fs::read_to_string(path)
.map_err(|e| MegaError::Custom(format!("Read error: {}", e)))?;
let session_b64 = session_b64.trim();
if session_b64.is_empty() {
return Err(MegaError::Custom("Empty session file".to_string()));
}
let session = match Self::login_with_session(session_b64, proxy).await {
Ok(session) => session,
Err(_) => {
let _ = std::fs::remove_file(path);
return Ok(None);
}
};
Ok(Some(session))
}
}
#[derive(Debug, Clone)]
pub struct SessionBlob {
pub session_id: String,
pub master_key: [u8; 16],
pub session_version: u8,
pub master_key_encrypted: bool,
}
#[derive(Debug, Clone)]
pub struct FolderSessionBlob {
pub public_handle: String,
pub root_handle: String,
pub folder_key: [u8; 16],
pub write_auth: Option<String>,
pub account_auth: Option<String>,
pub padding: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::MegaRsaKey;
fn create_dummy_session() -> Session {
Session {
api: ApiClient::new(),
session_id: "dummy_session".to_string(),
session_key: None,
master_key: [0u8; 16],
rsa_key: MegaRsaKey::generate().unwrap(),
email: "test@example.com".to_string(),
name: None,
user_handle: "handle".to_string(),
nodes: Vec::new(),
keys_downgrade_detected: false,
share_keys: HashMap::new(),
outshares: HashMap::new(),
pending_outshares: HashMap::new(),
contacts: HashMap::new(),
key_manager: KeyManager::default(),
authring_ed: AuthRing::default(),
authring_cu: AuthRing::default(),
backups: Vec::new(),
warnings: crate::crypto::Warnings::default(),
manual_verification: false,
user_attr_cache: HashMap::new(),
user_attr_versions: HashMap::new(),
last_keys_blob_b64: None,
keys_persist_inflight: false,
pending_keys_token: None,
scsn: None,
wsc_url: None,
sc_catchup: false,
current_seqtag: None,
current_seqtag_seen: false,
defer_seqtag_wait: false,
alerts_catchup_pending: false,
user_alert_lsn: None,
user_alerts: Vec::new(),
resume_enabled: false,
progress_callback: None,
previews_enabled: false,
workers: 1,
}
}
#[test]
fn test_resume_configuration() {
let mut session = create_dummy_session();
assert!(!session.is_resume_enabled());
session.set_resume(true);
assert!(session.is_resume_enabled());
session.set_resume(false);
assert!(!session.is_resume_enabled());
}
#[test]
fn test_previews_configuration() {
let mut session = create_dummy_session();
assert!(!session.previews_enabled());
session.enable_previews(true);
assert!(session.previews_enabled());
session.enable_previews(false);
assert!(!session.previews_enabled());
}
#[test]
fn test_workers_configuration() {
let mut session = create_dummy_session();
assert_eq!(session.workers(), 1);
session.set_workers(4);
assert_eq!(session.workers(), 4);
session.set_workers(10);
assert_eq!(session.workers(), 10);
}
}