Skip to content

Commit

Permalink
feat: rework USB auth (#60)
Browse files Browse the repository at this point in the history
* feat: rework USB auth

* ci: run clippy with `--all-features`
  • Loading branch information
cocool97 authored Nov 18, 2024
1 parent 2f08e5e commit 5077968
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 82 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 8 additions & 6 deletions adb_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
3 changes: 3 additions & 0 deletions adb_client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
165 changes: 93 additions & 72 deletions adb_client/src/usb/adb_rsa_key.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
pub exponent: u32,
}

impl ADBRsaInternalPublicKey {
pub fn new(exponent: &BigUint, modulus: &BigUint) -> Result<Self> {
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<u8> {
let mut bytes: Vec<u8> = 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<Self> {
let mut rng = OsRng;
pub fn new_random() -> Result<Self> {
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<Self> {
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<Self> {
Ok(ADBRsaKey {
private_key: RsaPrivateKey::from_pkcs8_pem(pkcs8_content)?,
})
}

pub fn encoded_public_key(&self) -> Result<String> {
// 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::<LittleEndian>(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::<LittleEndian>(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<String> {
// 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<Vec<u8>> {
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<u8>, 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<u8>) -> 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<Vec<u8>> {
Ok(self
.private_key
.sign(Pkcs1v15Sign::new::<sha1::Sha1>(), msg.as_ref())?)
}
}

fn set_bit(n: usize) -> Result<BigUint> {
Expand Down Expand Up @@ -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\
Expand Down
6 changes: 3 additions & 3 deletions adb_client/src/usb/adb_usb_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct ADBUSBDevice {
fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<ADBRsaKey>> {
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}");
Expand Down Expand Up @@ -121,7 +121,7 @@ impl ADBUSBDevice {
) -> Result<Self> {
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 {
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 5077968

Please sign in to comment.