3 Commits

Author SHA1 Message Date
cocool97
152836fe54 feat: add framebuffer_bytes method (#61)
* feat: add `framebuffer_bytes` method
2024-11-18 17:15:11 +01:00
cocool97
507796887b feat: rework USB auth (#60)
* feat: rework USB auth

* ci: run clippy with `--all-features`
2024-11-18 14:43:40 +01:00
cocool97
2f08e5e16d fix: pub export AdbStatResponse (#59)
* chore: clarify licensing

* fix: pub export AdbStatResponse
2024-11-17 18:16:20 +01:00
12 changed files with 139 additions and 93 deletions

View File

@@ -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"

View File

@@ -9,7 +9,7 @@ homepage = "https://github.com/cocool97/adb_client"
keywords = ["adb", "android", "tcp", "usb"]
license = "MIT"
repository = "https://github.com/cocool97/adb_client"
version = "2.0.3"
version = "2.0.5"
# To build locally when working on a new release
[patch.crates-io]

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) [year] [fullname]
Copyright (c) 2023-2024 Corentin LIAUD
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -11,6 +11,9 @@
<a href="https://deps.rs/repo/github/cocool97/adb_client">
<img alt="dependency status" src="https://deps.rs/repo/github/cocool97/adb_client/status.svg"/>
</a>
<a href="https://opensource.org/licenses/MIT">
<img alt="dependency status" src="https://img.shields.io/badge/License-MIT-yellow.svg"/>
</a>
</p>
</p>

View File

@@ -10,7 +10,7 @@ repository.workspace = true
version.workspace = true
[dependencies]
adb_client = { version = "2.0.1" }
adb_client = { version = "2.0.5" }
anyhow = { version = "1.0.89" }
clap = { version = "4.5.18", features = ["derive"] }
env_logger = { version = "0.11.5" }

View File

@@ -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" }

View File

@@ -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),
}

View File

@@ -16,7 +16,7 @@ mod utils;
pub use adb_device_ext::ADBDeviceExt;
pub use error::{Result, RustADBError};
pub use models::{AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType};
pub use models::{AdbStatResponse, AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType};
pub use server::*;
pub use transports::*;
pub use usb::ADBUSBDevice;

View File

@@ -8,10 +8,14 @@ use std::{
use byteorder::LittleEndian;
use serde::{Deserialize, Serialize};
/// Represents a `stat` response
#[derive(Debug, Deserialize, Serialize)]
pub struct AdbStatResponse {
/// File permissions
pub file_perm: u32,
/// File size, in bytes
pub file_size: u32,
/// File modification time
pub mod_time: u32,
}

View File

@@ -1,7 +1,12 @@
use std::{io::Read, iter::Map, path::Path, slice::ChunksExact};
use std::{
io::{Read, Seek, Write},
iter::Map,
path::Path,
slice::ChunksExact,
};
use byteorder::{LittleEndian, ReadBytesExt};
use image::{ImageBuffer, Rgba};
use image::{ImageBuffer, ImageFormat, Rgba};
use crate::{models::AdbServerCommand, utils, ADBServerDevice, Result, RustADBError};
@@ -98,13 +103,21 @@ impl TryFrom<[u8; std::mem::size_of::<Self>()]> for FrameBufferInfoV2 {
}
impl ADBServerDevice {
/// Dump framebuffer of this device
/// Dump framebuffer of this device into given ['path']
/// Big help from source code (https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp)
pub fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let img = self.framebuffer_inner()?;
Ok(img.save(path.as_ref())?)
}
/// Dump framebuffer of this device and return corresponding bytes.
///
/// Output data format is currently only `PNG`.
pub fn framebuffer_bytes<W: Write + Seek>(&mut self, mut writer: W) -> Result<()> {
let img = self.framebuffer_inner()?;
Ok(img.write_to(&mut writer, ImageFormat::Png)?)
}
/// Inner method requesting framebuffer from Android device
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
let serial: String = self.identifier.clone();

View File

@@ -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};
// From project: https://github.com/hajifkd/webadb
#[derive(Debug)]
pub struct ADBRsaKey {
private_key: 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 ADBRsaKey {
pub fn random_with_size(size: usize) -> Result<Self> {
let mut rng = OsRng;
impl ADBRsaInternalPublicKey {
pub fn new(exponent: &BigUint, modulus: &BigUint) -> Result<Self> {
Ok(Self {
private_key: RSAPrivateKey::new(&mut rng, size)?,
modulus_size_words: ANDROID_PUBKEY_MODULUS_SIZE_WORDS,
exponent: exponent.to_u32().ok_or(RustADBError::ConversionError)?,
modulus: modulus.clone(),
..Default::default()
})
}
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)?;
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());
Ok(ADBRsaKey { private_key })
}
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 sign(&self, msg: impl AsRef<[u8]>) -> Result<Vec<u8>> {
Ok(self.private_key.sign(
PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)),
msg.as_ref(),
)?)
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)?;
#[derive(Debug)]
pub struct ADBRsaKey {
private_key: RsaPrivateKey,
}
impl ADBRsaKey {
pub fn new_random() -> Result<Self> {
Ok(Self {
private_key: RsaPrivateKey::new(&mut OsRng, ADB_PRIVATE_KEY_SIZE)?,
})
}
Ok(())
pub fn new_from_pkcs8(pkcs8_content: &str) -> Result<Self> {
Ok(ADBRsaKey {
private_key: RsaPrivateKey::from_pkcs8_pem(pkcs8_content)?,
})
}
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())?;
// 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 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
}
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> {
@@ -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\

View File

@@ -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}");
@@ -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 {
@@ -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);