Add initialization of crypto values

This commit is contained in:
2022-05-21 22:11:39 +02:00
parent 755acf3c34
commit 9e1f2a462c
4 changed files with 160 additions and 38 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target /target
Cargo.lock

View File

@@ -6,7 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
derive_builder = "0.11"
rand = { version = "0.8", features = ["getrandom"] }
regex = "1.5"
reqwest = { version = "0.11", features = ["json", "cookies"] } reqwest = { version = "0.11", features = ["json", "cookies"] }
thiserror = "1.0"
[[bin]] [[bin]]
name = "vodafone_runner" name = "vodafone_runner"

View File

@@ -1 +1,4 @@
pub mod station; pub mod station;
#[macro_use]
extern crate derive_builder;

View File

@@ -1,43 +1,157 @@
pub mod station { pub mod station {
pub use reqwest::Client; pub use reqwest::Client;
use derive_builder::UninitializedFieldError;
use rand::{rngs::OsRng, RngCore};
use regex::Regex;
use reqwest::header; use reqwest::header;
use std::time::Duration; use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum StationError {
#[error("request error")]
Request(#[from] reqwest::Error),
#[error("could not parse data: {0}")]
Parse(String),
}
#[derive(Debug, Builder)]
#[builder(build_fn(error = "StationError"))]
struct CryptoValues {
session_id: String,
nonce: String,
init_vector: String,
salt: String,
}
impl From<UninitializedFieldError> for StationError {
fn from(ufe: UninitializedFieldError) -> StationError {
StationError::Parse(ufe.to_string())
}
}
#[derive(Debug, Builder)]
struct LoginState {
csrf_nonce: String,
key: String,
}
#[derive(Debug)] #[derive(Debug)]
pub struct VodafoneStation { pub struct VodafoneStation {
host: String, host: String,
client: Client, client: Client,
session_id: String, crypto: Option<CryptoValues>,
nonce: String, state: Option<LoginState>,
csrf_nonce: String,
init_vector: String,
salt: String,
key: String,
cookie: String,
} }
impl VodafoneStation { impl VodafoneStation {
pub fn new(host: String) -> Self { pub fn new(host: &str) -> Self {
let mut headers = header::HeaderMap::new(); let mut headers = header::HeaderMap::new();
headers.insert("X-Requested-With", header::HeaderValue::from_static("XMLHttpRequest")); headers.insert(
headers.insert(header::REFERER, "X-Requested-With",
header::HeaderValue::from_static("XMLHttpRequest"),
);
headers.insert(
header::REFERER,
header::HeaderValue::from_str(&format!("http://{}/?overview", host)) header::HeaderValue::from_str(&format!("http://{}/?overview", host))
.expect("Host name is not valid ASCII!")); .expect("Host name is not valid ASCII!"),
headers.insert(header::ORIGIN, );
headers.insert(
header::ORIGIN,
header::HeaderValue::from_str(&format!("http://{}", host)) header::HeaderValue::from_str(&format!("http://{}", host))
.expect("Host name is not valid ASCII!")); .expect("Host name is not valid ASCII!"),
);
let client = Client::builder() let client = Client::builder()
.default_headers(headers) .default_headers(headers)
.user_agent("Mozilla/5.0 (Windows NT 6.1; rv:91.0) Gecko/20100101 Firefox/91.0") .user_agent("Mozilla/5.0 (Windows NT 6.1; rv:91.0) Gecko/20100101 Firefox/91.0")
.cookie_store(true) .cookie_store(true)
.timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(10))
.build().expect("Could not construct reqwest client."); .build()
.expect("Could not construct reqwest client.");
VodafoneStation { VodafoneStation {
host: host, host: host.to_owned(),
client,
crypto: None,
state: None,
} }
} }
pub async fn connect(&mut self) -> Result<(), StationError> {
let response = self.client.get("/").send().await?;
let text = response.text().await?;
self.init_crypto(&text)?;
Ok(())
}
fn init_crypto(&mut self, response: &str) -> Result<(), StationError> {
let re = Regex::new(
r"(?x) # Ignore whitespace; allow line comments
(\W|^) [[:space:]]* # Start of Javascript statement
var [[:space:]]+ (?P<varName>[[:word:]]+) # variable declaration
[[:space:]]* = [[:space:]]* # Equals sign
'(?P<strval>[^']*)' # String value
[[:space:]]*; # End of statement
",
)
.unwrap();
let mut stbuild = CryptoValuesBuilder::default();
for caps in re.captures_iter(&response) {
match &caps["varName"] {
"currentSessionId" => {
stbuild.session_id(caps["strval"].to_owned());
}
"myIv" => {
stbuild.init_vector(caps["strval"].to_owned());
}
"mySalt" => {
stbuild.salt(caps["strval"].to_owned());
}
_ => (),
}
}
stbuild.nonce(OsRng.next_u32().to_string());
self.crypto = Some(stbuild.build()?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_crypto() {
let mut station = VodafoneStation::new("INV");
let session_id = "cool_session";
let iv = "so_initialized";
let salt = "extremely_salty";
station
.init_crypto(
format!(
r"var currentSessionId = '{}';
var myIv = '{}';
var mySalt = '{}';
",
&session_id, &iv, &salt
)
.as_str(),
)
.expect("Couldn't parse Javascript variable declarations.");
assert!(station.crypto.is_some());
let crypto = station.crypto.unwrap();
assert_eq!(crypto.session_id, session_id);
assert_eq!(crypto.init_vector, iv);
assert_eq!(crypto.salt, salt);
assert!(crypto.nonce.len() > 0);
}
} }
} }