Implement token and key from cloud

This commit is contained in:
hodasemi 2023-09-24 07:10:52 +02:00
parent 99819f36d9
commit 1cf3e17faa
5 changed files with 199 additions and 59 deletions

View file

@ -19,3 +19,4 @@ reqwest = "0.11.20"
base64 = "0.21.4" base64 = "0.21.4"
tokio = { version = "1.32.0", features=["macros", "rt-multi-thread"] } tokio = { version = "1.32.0", features=["macros", "rt-multi-thread"] }
md5 = "0.7.0" md5 = "0.7.0"
futures = "0.3.28"

View file

@ -1,9 +1,10 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
str::FromStr,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use anyhow::{Error, Result}; use anyhow::{anyhow, bail, Error, Result};
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use chrono::Local; use chrono::Local;
@ -101,13 +102,8 @@ impl Cloud {
.collect() .collect()
} }
async fn api_request( async fn api_request(&self, endpoint: &str, dump_data: String) -> Result<String> {
&self, let mut header = HeaderMap::default();
endpoint: &str,
dump_data: String,
header: Option<HeaderMap>,
) -> Result<String> {
let mut header = header.unwrap_or_default();
let url = format!("{}{}", self.api_url, endpoint); let url = format!("{}{}", self.api_url, endpoint);
@ -159,15 +155,11 @@ impl Cloud {
data.insert("userType".to_string(), "0".to_string()); data.insert("userType".to_string(), "0".to_string());
data.insert("userName".to_string(), format!("{}", self.account)); data.insert("userName".to_string(), format!("{}", self.account));
let response = Self::parse_response( let response = Response::from_str(
self.api_request( &self
"/v1/multicloud/platform/user/route", .api_request("/v1/multicloud/platform/user/route", to_string(&data)?)
to_string(&data)?, .await?,
None, )?;
)
.await?,
)
.ok_or(Error::msg("failed parsing response"))?;
if let Some(api_url) = response.normal_data().get("masUrl") { if let Some(api_url) = response.normal_data().get("masUrl") {
self.api_url = api_url.clone(); self.api_url = api_url.clone();
@ -180,11 +172,11 @@ impl Cloud {
let mut data = self.make_general_data(); let mut data = self.make_general_data();
data.insert("loginAccount".to_string(), format!("{}", self.account)); data.insert("loginAccount".to_string(), format!("{}", self.account));
let response = Self::parse_response( let response = Response::from_str(
self.api_request("/v1/user/login/id/get", to_string(&data)?, None) &self
.api_request("/v1/user/login/id/get", to_string(&data)?)
.await?, .await?,
) )?;
.ok_or(Error::msg("failed parsing response"))?;
let login_id = response let login_id = response
.normal_data() .normal_data()
@ -233,57 +225,112 @@ impl Cloud {
let dump_i = to_string(&i).unwrap(); let dump_i = to_string(&i).unwrap();
let response = let response = Response::from_str(&self.api_request("/mj/user/login", dump_i).await?)?;
Self::parse_response(self.api_request("/mj/user/login", dump_i, None).await?)
.ok_or(Error::msg("failed parsing response"))?;
self.uid = Some(response.nested_data().uid.clone()); self.uid = Some(response.login_data().uid.clone());
self.access_token = Some(response.nested_data().mdata.accessToken.clone()); self.access_token = Some(response.login_data().mdata.accessToken.clone());
self.security.set_aes_keys( self.security.set_aes_keys(
&response.nested_data().accessToken, &response.login_data().accessToken,
&response.nested_data().randomData, &response.login_data().randomData,
); );
Ok(()) Ok(())
} }
fn parse_response(s: String) -> Option<Response> { pub async fn keys(&self, device_id: u64) -> Result<(String, String)> {
if let Ok(r) = from_str::<ResponseNumber>(&s) { for method in [1, 2] {
return Some(Response::from(r)); let udp_id = CloudSecurity::udp_id(device_id, method);
let mut data = self.make_general_data();
data.insert("udpid".to_string(), udp_id.clone());
let response = Response::from_str(
&self
.api_request("/v1/iot/secure/getToken", to_string(&data)?)
.await?,
)?;
for token in response.token_list() {
if token.udpId == udp_id {
return Ok((token.token.clone(), token.key.clone()));
}
}
} }
if let Ok(r) = from_str::<ResponseString>(&s) { bail!("no keys found")
return Some(Response::from(r));
}
if let Ok(r) = from_str::<ResponseMap>(&s) {
return Some(Response::from(r));
}
None
} }
} }
#[derive(Debug)] #[derive(Debug)]
enum Response { enum Response {
Normal(ResponseNumber), Normal(ResponseNumber),
NestedMap(ResponseMap), NestedMap(ResponseLogin),
TokenList(ResponseTokenList),
} }
impl Response { impl Response {
pub fn normal_data(&self) -> &HashMap<String, String> { pub fn code(&self) -> i32 {
match self { match self {
Self::Normal(n) => &n.data, Self::Normal(n) => n.code,
Self::NestedMap(_) => panic!(), Self::NestedMap(n) => n.code,
Self::TokenList(n) => n.code.parse().unwrap(),
} }
} }
pub fn nested_data(&self) -> &ResponseMapData { pub fn normal_data(&self) -> &HashMap<String, String> {
match self { match self {
Self::Normal(_) => panic!(), Self::Normal(n) => &n.data,
Self::NestedMap(n) => &n.data, _ => panic!(),
} }
} }
pub fn login_data(&self) -> &ResponseLoginData {
match self {
Self::NestedMap(n) => &n.data,
_ => panic!(),
}
}
pub fn token_list(&self) -> &[Token] {
match self {
Self::TokenList(n) => &n.data.tokenlist,
_ => panic!(),
}
}
}
impl FromStr for Response {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let resp_fn = |s| {
if let Ok(r) = from_str::<ResponseNumber>(s) {
return Ok(Response::from(r));
}
if let Ok(r) = from_str::<ResponseString>(s) {
return Ok(Response::from(r));
}
if let Ok(r) = from_str::<ResponseLogin>(s) {
return Ok(Response::from(r));
}
if let Ok(r) = from_str::<ResponseTokenList>(s) {
return Ok(Response::from(r));
}
Err(Error::msg("failed parsing response"))
};
let response = resp_fn(s)?;
if response.code() != 0 {
bail!("Error return code: {}", response.code());
}
Ok(response)
}
} }
impl From<ResponseNumber> for Response { impl From<ResponseNumber> for Response {
@ -302,12 +349,18 @@ impl From<ResponseString> for Response {
} }
} }
impl From<ResponseMap> for Response { impl From<ResponseLogin> for Response {
fn from(value: ResponseMap) -> Self { fn from(value: ResponseLogin) -> Self {
Self::NestedMap(value) Self::NestedMap(value)
} }
} }
impl From<ResponseTokenList> for Response {
fn from(value: ResponseTokenList) -> Self {
Self::TokenList(value)
}
}
#[allow(unused)] #[allow(unused)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct ResponseNumber { struct ResponseNumber {
@ -325,16 +378,39 @@ struct ResponseString {
#[allow(unused)] #[allow(unused)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct ResponseMap { struct ResponseLogin {
msg: String, msg: String,
code: i32, code: i32,
pub data: ResponseMapData, pub data: ResponseLoginData,
}
#[allow(unused)]
#[derive(Deserialize, Debug)]
struct ResponseTokenList {
msg: String,
code: String,
pub data: TokenList,
}
#[allow(unused)]
#[derive(Deserialize, Debug)]
struct TokenList {
tokenlist: Vec<Token>,
}
#[allow(unused)]
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
struct Token {
udpId: String,
key: String,
token: String,
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(unused)] #[allow(unused)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct ResponseMapData { struct ResponseLoginData {
randomData: String, randomData: String,
uid: String, uid: String,
accountId: String, accountId: String,
@ -376,6 +452,9 @@ struct Data<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use anyhow::Result; use anyhow::Result;
use futures::future::try_join;
use crate::Startup;
use super::Cloud; use super::Cloud;
@ -408,4 +487,17 @@ mod test {
Ok(()) Ok(())
} }
#[tokio::test]
async fn keys() -> Result<()> {
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
let (_, devices) = try_join(cloud.login(), Startup::discover()).await?;
for device_info in devices {
let (_token, _key) = cloud.keys(device_info.id).await?;
}
Ok(())
}
} }

View file

@ -89,6 +89,40 @@ impl CloudSecurity {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("") .join("")
} }
pub fn udp_id(device_id: u64, method: u8) -> String {
let bytes_id: Vec<u8> = match method {
0 => device_id.to_be_bytes().into_iter().rev().collect(),
1 => {
let mut v = device_id.to_be_bytes().to_vec();
v.truncate(6);
v
}
1 => {
let mut v = device_id.to_be_bytes().to_vec();
v.truncate(6);
v
}
_ => return String::new(),
};
let mut data = Sha256::digest(&bytes_id);
for i in 0..16 {
data[i] ^= data[i + 16];
}
let mut v: Vec<u8> = data.into_iter().collect();
v.truncate(16);
v.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<String>>()
.join("")
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -78,7 +78,7 @@ impl Startup {
const NUM_RETRIES: u8 = 5; const NUM_RETRIES: u8 = 5;
pub fn discover() -> Result<Vec<DeviceInfo>> { pub async fn discover() -> Result<Vec<DeviceInfo>> {
let socket = UdpSocket::bind("0.0.0.0:0")?; let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.set_broadcast(true)?; socket.set_broadcast(true)?;
socket.set_read_timeout(Some(Duration::from_secs(2)))?; socket.set_read_timeout(Some(Duration::from_secs(2)))?;
@ -184,18 +184,18 @@ mod test {
use super::{Device, Startup}; use super::{Device, Startup};
#[test] #[tokio::test]
fn discover() -> Result<()> { async fn discover() -> Result<()> {
let devices = Startup::discover()?; let devices = Startup::discover().await?;
println!("{devices:#?}"); println!("{devices:#?}");
Ok(()) Ok(())
} }
#[test] #[tokio::test]
fn connect() -> Result<()> { async fn connect() -> Result<()> {
for device_info in Startup::discover()? { for device_info in Startup::discover().await? {
Device::connect(device_info)?; Device::connect(device_info)?;
} }

13
tst.json Normal file
View file

@ -0,0 +1,13 @@
{
"code": "0",
"msg": "ok",
"data": {
"tokenlist": [
{
"udpId": "a79479839b272432f3bbf971f9faed30",
"key": "d9d856b06f5245349e96df80e063af989532e6ef03c34039b74bfa0754a0ddf0",
"token": "4D14C31771A353EB584566243552C5CC2CE55C06162E550E71A805949569F7E8FA659B5B4B6CC097475A1270A4CC3DB75D52782DE3D1E65FEEA0945C6FFC7B3C"
}
]
}
}