use serde_json::json;
use sha2::{Digest, Sha256};
use crate::api::ApiClient;
use crate::base64::{base64url_decode, base64url_encode};
use crate::crypto::{aes128_ecb_encrypt, aes128_ecb_encrypt_block, derive_key_v2, make_random_key};
use crate::error::{MegaError, Result};
#[derive(Debug, Clone)]
pub struct RegistrationState {
pub session_key: String,
}
impl RegistrationState {
pub fn serialize(&self) -> String {
self.session_key.clone()
}
pub fn deserialize(s: &str) -> Result<Self> {
let parts: Vec<&str> = s.split('#').collect();
if parts.len() != 2 {
return Err(MegaError::InvalidState(
"Expected format: handle#key".to_string(),
));
}
let handle_bytes = base64url_decode(parts[0])?;
if handle_bytes.len() != 8 {
return Err(MegaError::InvalidState(
"User handle must be 8 bytes".to_string(),
));
}
let key_bytes = base64url_decode(parts[1])?;
if key_bytes.len() != 16 {
return Err(MegaError::InvalidState(
"Resume key must be 16 bytes".to_string(),
));
}
Ok(Self {
session_key: s.to_string(),
})
}
}
fn build_signup_payload_v2(
password: &str,
master_key: &[u8; 16],
) -> Result<(String, String, String, [u8; 32])> {
let client_random = make_random_key();
let mut buffer = b"mega.nz".to_vec();
buffer.resize(200, b'P');
buffer.extend_from_slice(&client_random);
let salt = Sha256::digest(&buffer);
let derived = derive_key_v2(password, salt.as_slice())?;
let password_key: [u8; 16] = derived[..16].try_into().unwrap();
let auth_key = &derived[16..32];
let encrypted_master_key = aes128_ecb_encrypt(master_key, &password_key);
let mut hasher = Sha256::new();
hasher.update(auth_key);
let hashed = hasher.finalize();
let hak = &hashed[..16];
Ok((
base64url_encode(&client_random),
base64url_encode(&encrypted_master_key),
base64url_encode(hak),
derived,
))
}
fn extract_confirm_code(signup_key: &str) -> Result<Vec<u8>> {
let fragment = if let Some(pos) = signup_key.find("confirm") {
&signup_key[pos + "confirm".len()..]
} else {
signup_key
};
let decoded = base64url_decode(fragment)?;
if !decoded
.windows(b"ConfirmCodeV2".len())
.any(|window| window == b"ConfirmCodeV2")
{
return Err(MegaError::Custom("Invalid confirmation link".to_string()));
}
Ok(decoded)
}
pub async fn register(
email: &str,
password: &str,
name: &str,
proxy: Option<&str>,
) -> Result<RegistrationState> {
let mut api = match proxy {
Some(url) => ApiClient::with_proxy(url)?,
None => ApiClient::new(),
};
let master_key = make_random_key();
let ephemeral_key = make_random_key();
let ssc = make_random_key();
let encrypted_ssc = aes128_ecb_encrypt_block(&ssc, &master_key);
let mut ts_data = [0u8; 32];
ts_data[..16].copy_from_slice(&ssc);
ts_data[16..].copy_from_slice(&encrypted_ssc);
let encrypted_master_key = aes128_ecb_encrypt(&master_key, &ephemeral_key);
let response = api
.request(json!({
"a": "up",
"k": base64url_encode(&encrypted_master_key),
"ts": base64url_encode(&ts_data)
}))
.await?;
let arr = response.as_array().ok_or(MegaError::InvalidResponse)?;
if arr.len() < 2 {
return Err(MegaError::InvalidResponse);
}
let user_handle = arr[1]
.as_str()
.ok_or(MegaError::InvalidResponse)?
.to_string();
let response = api
.request(json!({
"a": "us",
"user": &user_handle
}))
.await?;
let tsid = response["tsid"]
.as_str()
.ok_or(MegaError::InvalidResponse)?;
api.set_session_id(tsid.to_string());
api.request(json!({"a": "ug"})).await?;
let (crv, emk, hak, derived_key) = build_signup_payload_v2(password, &master_key)?;
api.request(json!({
"a": "uc2",
"n": base64url_encode(name.as_bytes()),
"m": base64url_encode(email.as_bytes()),
"crv": crv,
"hak": hak,
"k": emk,
"v": 2
}))
.await?;
let session_key = format!("{}#{}", user_handle, base64url_encode(&derived_key[..16]));
Ok(RegistrationState { session_key })
}
pub async fn verify_registration(
_state: &RegistrationState,
signup_key: &str,
proxy: Option<&str>,
) -> Result<()> {
let mut api = match proxy {
Some(url) => ApiClient::with_proxy(url)?,
None => ApiClient::new(),
};
let code = extract_confirm_code(signup_key)?;
let response = api
.request(json!({
"a": "ud2",
"c": base64url_encode(&code)
}))
.await?;
let wrapper = response.as_array().ok_or(MegaError::InvalidResponse)?;
if wrapper.len() != 2 || !wrapper[0].is_string() {
return Err(MegaError::InvalidResponse);
}
let payload = wrapper[1].as_array().ok_or(MegaError::InvalidResponse)?;
if payload.len() != 4 {
return Err(MegaError::InvalidResponse);
}
let _b64_email = payload[0].as_str().ok_or(MegaError::InvalidResponse)?;
let _b64_name = payload[1].as_str().ok_or(MegaError::InvalidResponse)?;
let _handle = payload[2].as_str().ok_or(MegaError::InvalidResponse)?;
let version = payload[3].as_i64().ok_or(MegaError::InvalidResponse)?;
if version != 2 {
return Err(MegaError::InvalidResponse);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_serialization_roundtrip() {
let handle = [1u8; 8];
let key = [2u8; 16];
let session_key = format!("{}#{}", base64url_encode(&handle), base64url_encode(&key));
let state = RegistrationState { session_key };
let serialized = state.serialize();
let deserialized = RegistrationState::deserialize(&serialized).unwrap();
assert_eq!(deserialized.session_key, state.session_key);
}
#[test]
fn test_state_deserialization_invalid() {
assert!(RegistrationState::deserialize("").is_err());
assert!(RegistrationState::deserialize("invalid").is_err());
assert!(RegistrationState::deserialize("a:b").is_err());
assert!(RegistrationState::deserialize("a:b:c:d").is_err());
assert!(RegistrationState::deserialize("abcd#efgh").is_err());
}
}