use rand::RngCore;
use serde_json::json;
use crate::api::ApiClient;
use crate::base64::{base64url_decode, base64url_encode};
use crate::crypto::{
MegaRsaKey, aes128_ecb_decrypt, aes128_ecb_encrypt, aes128_ecb_encrypt_block,
make_password_key, make_random_key, make_username_hash,
};
use crate::error::{MegaError, Result};
#[derive(Debug, Clone)]
pub struct RegistrationState {
pub user_handle: String,
pub password_key: [u8; 16],
pub challenge: [u8; 16],
}
impl RegistrationState {
pub fn serialize(&self) -> String {
format!(
"{}:{}:{}",
base64url_encode(&self.password_key),
base64url_encode(&self.challenge),
self.user_handle
)
}
pub fn deserialize(s: &str) -> Result<Self> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 3 {
return Err(MegaError::InvalidState(
"Expected format: pk:challenge:handle".to_string(),
));
}
let password_key_bytes = base64url_decode(parts[0])?;
let challenge_bytes = base64url_decode(parts[1])?;
if password_key_bytes.len() != 16 {
return Err(MegaError::InvalidState(
"Password key must be 16 bytes".to_string(),
));
}
if challenge_bytes.len() != 16 {
return Err(MegaError::InvalidState(
"Challenge must be 16 bytes".to_string(),
));
}
let mut password_key = [0u8; 16];
let mut challenge = [0u8; 16];
password_key.copy_from_slice(&password_key_bytes);
challenge.copy_from_slice(&challenge_bytes);
Ok(Self {
user_handle: parts[2].to_string(),
password_key,
challenge,
})
}
}
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 password_key = make_password_key(password);
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, &password_key);
let response = api
.request(json!({
"a": "up",
"k": base64url_encode(&encrypted_master_key),
"ts": base64url_encode(&ts_data)
}))
.await?;
let user_handle = response
.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?;
api.request(json!({
"a": "up",
"name": name
}))
.await?;
let mut challenge = [0u8; 16];
rand::thread_rng().fill_bytes(&mut challenge[..4]);
rand::thread_rng().fill_bytes(&mut challenge[12..]);
let mut c_data = [0u8; 32];
c_data[..16].copy_from_slice(&master_key);
c_data[16..].copy_from_slice(&challenge);
let encrypted_c = aes128_ecb_encrypt(&c_data, &password_key);
api.request(json!({
"a": "uc",
"c": base64url_encode(&encrypted_c),
"n": base64url_encode(name.as_bytes()),
"m": base64url_encode(email.as_bytes())
}))
.await?;
Ok(RegistrationState {
user_handle,
password_key,
challenge,
})
}
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 rsa_key = MegaRsaKey::generate()
.map_err(|e| MegaError::CryptoError(format!("RSA generation: {}", e)))?;
let response = api
.request(json!({
"a": "us",
"user": &state.user_handle
}))
.await?;
let tsid = response["tsid"]
.as_str()
.ok_or(MegaError::InvalidResponse)?;
api.set_session_id(tsid.to_string());
let response = api
.request(json!({
"a": "ud",
"c": signup_key
}))
.await?;
let arr = response.as_array().ok_or(MegaError::InvalidResponse)?;
if arr.len() != 5 {
return Err(MegaError::InvalidResponse);
}
let b64_email = arr[0].as_str().ok_or(MegaError::InvalidResponse)?;
let _b64_name = arr[1].as_str().ok_or(MegaError::InvalidResponse)?;
let _handle = arr[2].as_str().ok_or(MegaError::InvalidResponse)?;
let b64_master_key = arr[3].as_str().ok_or(MegaError::InvalidResponse)?;
let b64_challenge = arr[4].as_str().ok_or(MegaError::InvalidResponse)?;
let email_bytes = base64url_decode(b64_email)?;
let email = String::from_utf8(email_bytes).map_err(|_| MegaError::InvalidResponse)?;
let encrypted_master_key = base64url_decode(b64_master_key)?;
let encrypted_challenge = base64url_decode(b64_challenge)?;
let master_key = aes128_ecb_decrypt(&encrypted_master_key, &state.password_key);
let challenge = aes128_ecb_decrypt(&encrypted_challenge, &state.password_key);
if challenge != state.challenge {
return Err(MegaError::InvalidChallenge);
}
let email_lower = email.to_lowercase();
let uh = make_username_hash(&email_lower, &state.password_key);
let uh_b64 = base64url_encode(&uh);
api.request(json!({
"a": "up",
"c": signup_key,
"uh": uh_b64
}))
.await?;
let response = api
.request(json!({
"a": "us",
"user": email_lower,
"uh": uh_b64
}))
.await?;
let tsid = response["tsid"]
.as_str()
.ok_or(MegaError::InvalidResponse)?;
api.set_session_id(tsid.to_string());
let master_key_arr: [u8; 16] = master_key
.try_into()
.map_err(|_| MegaError::InvalidResponse)?;
api.request(json!({
"a": "up",
"pubk": rsa_key.encode_public_key(),
"privk": rsa_key.encode_private_key(&master_key_arr)
}))
.await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_serialization_roundtrip() {
let state = RegistrationState {
user_handle: "test_handle_123".to_string(),
password_key: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
challenge: [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
};
let serialized = state.serialize();
let deserialized = RegistrationState::deserialize(&serialized).unwrap();
assert_eq!(deserialized.user_handle, state.user_handle);
assert_eq!(deserialized.password_key, state.password_key);
assert_eq!(deserialized.challenge, state.challenge);
}
#[test]
fn test_state_deserialization_invalid() {
assert!(RegistrationState::deserialize("invalid").is_err());
assert!(RegistrationState::deserialize("a:b").is_err());
assert!(RegistrationState::deserialize("a:b:c:d").is_err());
}
}