diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index bf8d89b..a501df5 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - run: rustup component add clippy - name: Run clippy - run : cargo clippy + run : cargo clippy --all-features fmt: name: "fmt" diff --git a/adb_client/Cargo.toml b/adb_client/Cargo.toml index e8d0e15..bc15dfe 100644 --- a/adb_client/Cargo.toml +++ b/adb_client/Cargo.toml @@ -10,19 +10,21 @@ repository.workspace = true version.workspace = true [dependencies] -base64 = "0.22.1" -bincode = "1.3.3" +base64 = { version = "0.22.1" } +bincode = { version = "1.3.3" } byteorder = { version = "1.5.0" } chrono = { version = "0.4.38" } homedir = { version = "0.3.4" } image = { version = "0.25.4" } lazy_static = { version = "1.5.0" } log = { version = "0.4.22" } -num-bigint = { version = "0.6", package = "num-bigint-dig" } -rand = { version = "0.7.0" } +num-bigint = { version = "0.8.4", package = "num-bigint-dig" } +num-traits = { version = "0.2.19" } +rand = { version = "0.8.5" } regex = { version = "1.11.0", features = ["perf", "std", "unicode"] } -rsa = { version = "0.3.0" } +rsa = { version = "0.9.6" } rusb = { version = "0.9.4", features = ["vendored"] } serde = { version = "1.0.210", features = ["derive"] } -serde_repr = "0.1.19" +serde_repr = { version = "0.1.19" } +sha1 = { version = "0.10.6", features = ["oid"] } thiserror = { version = "2.0.1" } diff --git a/adb_client/src/error.rs b/adb_client/src/error.rs index 2f24bb4..d03eb72 100644 --- a/adb_client/src/error.rs +++ b/adb_client/src/error.rs @@ -90,4 +90,7 @@ pub enum RustADBError { /// Given path does not represent an APK #[error("wrong file extension: {0}")] WrongFileExtension(String), + /// An error occurred with PKCS8 data + #[error("error with pkcs8: {0}")] + RsaPkcs8Error(#[from] rsa::pkcs8::Error), } diff --git a/adb_client/src/usb/adb_rsa_key.rs b/adb_client/src/usb/adb_rsa_key.rs index 3ec06f7..dceed5a 100644 --- a/adb_client/src/usb/adb_rsa_key.rs +++ b/adb_client/src/usb/adb_rsa_key.rs @@ -1,93 +1,114 @@ use crate::{Result, RustADBError}; use base64::{engine::general_purpose::STANDARD, Engine}; -use byteorder::{LittleEndian, WriteBytesExt}; -use num_bigint::traits::ModInverse; -use num_bigint::BigUint; +use num_bigint::{BigUint, ModInverse}; +use num_traits::cast::ToPrimitive; +use num_traits::FromPrimitive; use rand::rngs::OsRng; -use rsa::{Hash, PaddingScheme, PublicKeyParts, RSAPrivateKey}; -use std::convert::TryInto; +use rsa::pkcs8::DecodePrivateKey; +use rsa::traits::PublicKeyParts; +use rsa::{Pkcs1v15Sign, RsaPrivateKey}; + +const ADB_PRIVATE_KEY_SIZE: usize = 2048; +const ANDROID_PUBKEY_MODULUS_SIZE_WORDS: u32 = 64; + +#[repr(C)] +#[derive(Debug, Default)] +/// Internal ADB representation of a public key +struct ADBRsaInternalPublicKey { + pub modulus_size_words: u32, + pub n0inv: u32, + pub modulus: BigUint, + pub rr: Vec, + pub exponent: u32, +} + +impl ADBRsaInternalPublicKey { + pub fn new(exponent: &BigUint, modulus: &BigUint) -> Result { + Ok(Self { + modulus_size_words: ANDROID_PUBKEY_MODULUS_SIZE_WORDS, + exponent: exponent.to_u32().ok_or(RustADBError::ConversionError)?, + modulus: modulus.clone(), + ..Default::default() + }) + } + + pub fn into_bytes(mut self) -> Vec { + let mut bytes: Vec = Vec::new(); + bytes.append(&mut self.modulus_size_words.to_le_bytes().to_vec()); + bytes.append(&mut self.n0inv.to_le_bytes().to_vec()); + bytes.append(&mut self.modulus.to_bytes_le()); + bytes.append(&mut self.rr); + bytes.append(&mut self.exponent.to_le_bytes().to_vec()); + + bytes + } +} -// From project: https://github.com/hajifkd/webadb #[derive(Debug)] pub struct ADBRsaKey { - private_key: RSAPrivateKey, + private_key: RsaPrivateKey, } impl ADBRsaKey { - pub fn random_with_size(size: usize) -> Result { - let mut rng = OsRng; + pub fn new_random() -> Result { Ok(Self { - private_key: RSAPrivateKey::new(&mut rng, size)?, + private_key: RsaPrivateKey::new(&mut OsRng, ADB_PRIVATE_KEY_SIZE)?, }) } - pub fn from_pkcs8(pkcs8_content: &str) -> Result { - let der_encoded = pkcs8_content - .lines() - .filter(|line| !line.starts_with("-") && !line.is_empty()) - .fold(String::new(), |mut data, line| { - data.push_str(line); - data - }); - let der_bytes = STANDARD.decode(&der_encoded)?; - let private_key = RSAPrivateKey::from_pkcs8(&der_bytes)?; - - Ok(ADBRsaKey { private_key }) + pub fn new_from_pkcs8(pkcs8_content: &str) -> Result { + Ok(ADBRsaKey { + private_key: RsaPrivateKey::from_pkcs8_pem(pkcs8_content)?, + }) } - pub fn encoded_public_key(&self) -> Result { - // see https://android.googlesource.com/platform/system/core/+/android-4.4_r1/adb/adb_auth_host.c - // L63 RSA_to_RSAPublicKey - const RSANUMBYTES: u32 = 256; - const RSANUMWORDS: u32 = 64; - let user: String = format!("adb_client@{}", env!("CARGO_PKG_VERSION")); - - let mut result = vec![]; - result.write_u32::(RSANUMWORDS)?; - let r32 = set_bit(32)?; - let n = self.private_key.n(); - let r = set_bit((32 * RSANUMWORDS) as _)?; - // Well, let rr = set_bit((64 * RSANUMWORDS) as _) % n is also fine, since r \sim n. - let rr = r.modpow(&BigUint::from(2u32), n); - let rem = n % &r32; - let n0inv = rem.mod_inverse(&r32); - if let Some(n0inv) = n0inv { - let n0inv = n0inv.to_biguint().ok_or(RustADBError::ConversionError)?; - let n0inv_p: u32 = 1 + !u32::from_le_bytes((&n0inv.to_bytes_le()[..4]).try_into()?); - result.write_u32::(n0inv_p)?; - } else { - return Err(RustADBError::ConversionError); - } - - write_biguint(&mut result, n, RSANUMBYTES as _)?; - write_biguint(&mut result, &rr, RSANUMBYTES as _)?; - write_biguint(&mut result, self.private_key.e(), 4)?; - - let mut encoded = STANDARD.encode(&result); - encoded.push(' '); - encoded.push_str(&user); - Ok(encoded) - } + pub fn android_pubkey_encode(&self) -> Result { + // Helped from project: https://github.com/hajifkd/webadb + // Source code: https://android.googlesource.com/platform/system/core/+/refs/heads/main/libcrypto_utils/android_pubkey.cpp + // Useful function `android_pubkey_encode()` + let mut adb_rsa_pubkey = + ADBRsaInternalPublicKey::new(self.private_key.e(), self.private_key.n())?; - pub fn sign(&self, msg: impl AsRef<[u8]>) -> Result> { - Ok(self.private_key.sign( - PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)), - msg.as_ref(), - )?) + // r32 = 2 ^ 32 + let r32 = BigUint::from_u64(1 << 32).ok_or(RustADBError::ConversionError)?; + + // r = 2 ^ rsa_size = 2 ^ 2048 + let r = set_bit(ADB_PRIVATE_KEY_SIZE)?; + + // rr = r ^ 2 mod N + let rr = r.modpow(&BigUint::from(2u32), &adb_rsa_pubkey.modulus); + adb_rsa_pubkey.rr = rr.to_bytes_le(); + + // rem = N[0] + let rem = &adb_rsa_pubkey.modulus % &r32; + + // n0inv = -1 / rem mod r32 + let n0inv = rem + .mod_inverse(&r32) + .and_then(|v| v.to_biguint()) + .ok_or(RustADBError::ConversionError)?; + + // BN_sub(n0inv, r32, n0inv) + adb_rsa_pubkey.n0inv = (r32 - n0inv) + .to_u32() + .ok_or(RustADBError::ConversionError)?; + + Ok(self.encode_public_key(adb_rsa_pubkey.into_bytes())) } -} -fn write_biguint(writer: &mut Vec, data: &BigUint, n_bytes: usize) -> Result<()> { - for &v in data - .to_bytes_le() - .iter() - .chain(std::iter::repeat(&0)) - .take(n_bytes) - { - writer.write_u8(v)?; + fn encode_public_key(&self, pub_key: Vec) -> String { + let mut encoded = STANDARD.encode(pub_key); + encoded.push(' '); + encoded.push_str(&format!("adb_client@{}", env!("CARGO_PKG_VERSION"))); + + encoded } - Ok(()) + pub fn sign(&self, msg: impl AsRef<[u8]>) -> Result> { + Ok(self + .private_key + .sign(Pkcs1v15Sign::new::(), msg.as_ref())?) + } } fn set_bit(n: usize) -> Result { @@ -133,9 +154,9 @@ CNkECiepaGyquQaffwR1CAi8dH6biJjlTQWQPFcCLA0hvernWo3eaSfiL7fHyym+ ile69MHFENUePSpuRSiF3Z02 -----END PRIVATE KEY-----"; let priv_key = - ADBRsaKey::from_pkcs8(DEFAULT_PRIV_KEY).expect("cannot create rsa key from data"); + ADBRsaKey::new_from_pkcs8(DEFAULT_PRIV_KEY).expect("cannot create rsa key from data"); let pub_key = priv_key - .encoded_public_key() + .android_pubkey_encode() .expect("cannot encode public key"); let pub_key_adb = "\ QAAAAFH/pU9PVrHRgEjMGnpvOr2QzKYCavSE1fcSwvpS1uPn9GTmuyZr7c9up\ diff --git a/adb_client/src/usb/adb_usb_device.rs b/adb_client/src/usb/adb_usb_device.rs index d416ace..dec983f 100644 --- a/adb_client/src/usb/adb_usb_device.rs +++ b/adb_client/src/usb/adb_usb_device.rs @@ -32,7 +32,7 @@ pub struct ADBUSBDevice { fn read_adb_private_key>(private_key_path: P) -> Result> { read_to_string(private_key_path.as_ref()) .map_err(RustADBError::from) - .map(|pk| match ADBRsaKey::from_pkcs8(&pk) { + .map(|pk| match ADBRsaKey::new_from_pkcs8(&pk) { Ok(pk) => Some(pk), Err(e) => { log::error!("Error while create RSA private key: {e}"); @@ -121,7 +121,7 @@ impl ADBUSBDevice { ) -> Result { let private_key = match read_adb_private_key(private_key_path)? { Some(pk) => pk, - None => ADBRsaKey::random_with_size(2048)?, + None => ADBRsaKey::new_random()?, }; let mut s = Self { @@ -202,7 +202,7 @@ impl ADBUSBDevice { return Ok(()); } - let mut pubkey = self.private_key.encoded_public_key()?.into_bytes(); + let mut pubkey = self.private_key.android_pubkey_encode()?.into_bytes(); pubkey.push(b'\0'); let message = ADBUsbMessage::new(USBCommand::Auth, AUTH_RSAPUBLICKEY, 0, pubkey);