diff --git a/examples/full_test.rs b/examples/full_test.rs index 011aa79..36c56cc 100644 --- a/examples/full_test.rs +++ b/examples/full_test.rs @@ -1,10 +1,11 @@ use std::error::Error; use std::borrow::Cow; +use anyhow::{Context, Result}; use hard_xml::XmlRead; use url::Url; -fn get_pkgs_to_download(resp: &omaha::Response) -> Result)>, Box> { +fn get_pkgs_to_download(resp: &omaha::Response) -> Result)>> { let mut to_download: Vec<(Url, omaha::Hash<_>)> = Vec::new(); for app in &resp.apps { @@ -44,17 +45,23 @@ fn get_pkgs_to_download(resp: &omaha::Response) -> Result Result<(), Box> { let client = reqwest::Client::new(); + const APP_VERSION_DEFAULT: &str = "3340.0.0+nightly-20220823-2100"; + const MACHINE_ID_DEFAULT: &str = "abce671d61774703ac7be60715220bfe"; + const TRACK_DEFAULT: &str = "stable"; + //// // request //// let parameters = ue_rs::request::Parameters { - app_version: Cow::Borrowed("3340.0.0+nightly-20220823-2100"), - machine_id: Cow::Borrowed("abce671d61774703ac7be60715220bfe"), + app_version: Cow::Borrowed(APP_VERSION_DEFAULT), + machine_id: Cow::Borrowed(MACHINE_ID_DEFAULT), - track: Cow::Borrowed("stable"), + track: Cow::Borrowed(TRACK_DEFAULT), }; - let response_text = ue_rs::request::perform(&client, parameters).await?; + let response_text = ue_rs::request::perform(&client, parameters).await.context(format!( + "perform({APP_VERSION_DEFAULT}, {MACHINE_ID_DEFAULT}, {TRACK_DEFAULT}) failed" + ))?; println!("response:\n\t{:#?}", response_text); println!(); @@ -62,9 +69,9 @@ async fn main() -> Result<(), Box> { //// // parse response //// - let resp = omaha::Response::from_str(&response_text)?; + let resp = omaha::Response::from_str(&response_text).context("failed to parse response")?; - let pkgs_to_dl = get_pkgs_to_download(&resp)?; + let pkgs_to_dl = get_pkgs_to_download(&resp).context("failed to get packages to download")?; //// // download @@ -76,7 +83,7 @@ async fn main() -> Result<(), Box> { // std::io::BufWriter wrapping an std::fs::File is probably the right choice. // std::io::sink() is basically just /dev/null let data = std::io::sink(); - let res = ue_rs::download_and_hash(&client, url, data).await?; + let res = ue_rs::download_and_hash(&client, url.clone(), data).await.context(format!("download_and_hash({url:?}) failed"))?; println!("\texpected sha256: {}", expected_sha256); println!("\tcalculated sha256: {}", res.hash); diff --git a/examples/request.rs b/examples/request.rs index e7bf4e8..9468e46 100644 --- a/examples/request.rs +++ b/examples/request.rs @@ -1,20 +1,28 @@ use std::error::Error; use std::borrow::Cow; +use anyhow::Context; + use ue_rs::request; #[tokio::main] async fn main() -> Result<(), Box> { let client = reqwest::Client::new(); + const APP_VERSION_DEFAULT: &str = "3340.0.0+nightly-20220823-2100"; + const MACHINE_ID_DEFAULT: &str = "abce671d61774703ac7be60715220bfe"; + const TRACK_DEFAULT: &str = "stable"; + let parameters = request::Parameters { - app_version: Cow::Borrowed("3340.0.0+nightly-20220823-2100"), - machine_id: Cow::Borrowed("abce671d61774703ac7be60715220bfe"), + app_version: Cow::Borrowed(APP_VERSION_DEFAULT), + machine_id: Cow::Borrowed(MACHINE_ID_DEFAULT), - track: Cow::Borrowed("stable"), + track: Cow::Borrowed(TRACK_DEFAULT), }; - let response = request::perform(&client, parameters).await?; + let response = request::perform(&client, parameters).await.context(format!( + "perform({APP_VERSION_DEFAULT}, {MACHINE_ID_DEFAULT}, {TRACK_DEFAULT}) failed" + ))?; println!("response:\n\t{:#?}", response); diff --git a/examples/response.rs b/examples/response.rs index b86115b..50194dc 100644 --- a/examples/response.rs +++ b/examples/response.rs @@ -1,5 +1,6 @@ use std::error::Error; +use anyhow::Context; use hard_xml::XmlRead; use omaha; @@ -29,7 +30,7 @@ fn main() -> Result<(), Box> { println!("{}", RESPONSE_XML); println!(); - let resp = omaha::Response::from_str(RESPONSE_XML)?; + let resp = omaha::Response::from_str(RESPONSE_XML).context("failed to create response")?; println!("{:#?}", resp); println!(); @@ -66,7 +67,10 @@ fn main() -> Result<(), Box> { println!(" urls:"); for url in &app.update_check.urls { - println!(" {}", url.join(&pkg.name)?); + println!( + " {}", + url.join(&pkg.name).context(format!("failed to join URL with {:?}", pkg.name))? + ); } println!(); diff --git a/omaha/src/hash_types.rs b/omaha/src/hash_types.rs index 35f807f..021187f 100644 --- a/omaha/src/hash_types.rs +++ b/omaha/src/hash_types.rs @@ -1,10 +1,10 @@ use std::fmt; use std::str; +use anyhow::{Error as CodecError, anyhow}; + #[rustfmt::skip] use ct_codecs::{ - Error as CodecError, - Base64, Hex, @@ -74,9 +74,9 @@ impl str::FromStr for Hash { impl Hash { #[inline] - fn decode(hash: &str) -> Result { + fn decode(hash: &str) -> anyhow::Result { let mut digest = T::Output::default(); - D::decode(digest.as_mut(), hash, None)?; + D::decode(digest.as_mut(), hash, None).map_err(|_| anyhow!("decode ({}) failed", hash))?; Ok(Self(digest)) } diff --git a/omaha/src/response.rs b/omaha/src/response.rs index 7094bb7..0e21503 100644 --- a/omaha/src/response.rs +++ b/omaha/src/response.rs @@ -11,10 +11,10 @@ use self::omaha::{Sha1, Sha256}; mod sha256_hex { use crate as omaha; use self::omaha::Sha256; - use ct_codecs::Error; + use anyhow::Error as CodecError; #[inline] - pub(crate) fn from_str(s: &str) -> Result, Error> { + pub(crate) fn from_str(s: &str) -> Result, CodecError> { >::from_hex(s) } } diff --git a/src/bin/download_sysext.rs b/src/bin/download_sysext.rs index aebfeb0..f0f45c6 100644 --- a/src/bin/download_sysext.rs +++ b/src/bin/download_sysext.rs @@ -8,6 +8,7 @@ use std::io; #[macro_use] extern crate log; +use anyhow::{Context, Result, bail}; use globset::{Glob, GlobSet, GlobSetBuilder}; use hard_xml::XmlRead; use argh::FromArgs; @@ -36,13 +37,17 @@ struct Package<'a> { impl<'a> Package<'a> { #[rustfmt::skip] - fn hash_on_disk(&mut self, path: &Path) -> Result, Box> { + fn hash_on_disk(&mut self, path: &Path) -> Result> { use sha2::{Sha256, Digest}; - let mut file = File::open(path)?; + let mut file = File::open(path).context({ + format!("open({}) failed", path.display()) + })?; let mut hasher = Sha256::new(); - io::copy(&mut file, &mut hasher)?; + io::copy(&mut file, &mut hasher).context({ + format!("copy({}) failed", path.display()) + })?; Ok(omaha::Hash::from_bytes( hasher.finalize().into() @@ -50,9 +55,11 @@ impl<'a> Package<'a> { } #[rustfmt::skip] - fn check_download(&mut self, in_dir: &Path) -> Result<(), Box> { + fn check_download(&mut self, in_dir: &Path) -> Result<()> { let path = in_dir.join(&*self.name); - let md = fs::metadata(&path)?; + let md = fs::metadata(&path).context({ + format!("metadata({}) failed", path.display()) + })?; let size_on_disk = md.len() as usize; let expected_size = self.size.bytes(); @@ -68,7 +75,9 @@ impl<'a> Package<'a> { if size_on_disk == expected_size { info!("{}: download complete, checking hash...", path.display()); - let hash = self.hash_on_disk(&path)?; + let hash = self.hash_on_disk(&path).context({ + format!("hash_on_disk({}) failed", path.display()) + })?; if self.verify_checksum(hash) { info!("{}: good hash, will continue without re-download", path.display()); } else { @@ -80,7 +89,7 @@ impl<'a> Package<'a> { Ok(()) } - async fn download(&mut self, into_dir: &Path, client: &reqwest::Client) -> Result<(), Box> { + async fn download(&mut self, into_dir: &Path, client: &reqwest::Client) -> Result<()> { // FIXME: use _range_start for completing downloads let _range_start = match self.status { PackageStatus::ToDownload => 0, @@ -91,9 +100,9 @@ impl<'a> Package<'a> { info!("downloading {}...", self.url); let path = into_dir.join(&*self.name); - let mut file = File::create(path)?; + let mut file = File::create(path.clone()).context(format!("create({}) failed", path.display()))?; - let res = ue_rs::download_and_hash(&client, self.url.clone(), &mut file).await?; + let res = ue_rs::download_and_hash(&client, self.url.clone(), &mut file).await.context(format!("download_and_hash({}) failed", self.url))?; self.verify_checksum(res.hash); Ok(()) @@ -113,23 +122,23 @@ impl<'a> Package<'a> { } } - fn verify_signature_on_disk(&mut self, from_path: &Path, pubkey_path: &str) -> Result<(), Box> { - let upfile = File::open(from_path)?; + fn verify_signature_on_disk(&mut self, from_path: &Path, pubkey_path: &str) -> Result<()> { + let upfile = File::open(from_path).context(format!("open({}) failed", from_path.display()))?; // Read update payload from file, read delta update header from the payload. let res_data = fs::read_to_string(from_path); - let header = delta_update::read_delta_update_header(&upfile)?; + let header = delta_update::read_delta_update_header(&upfile).context(format!("read_delta_update_header({}) failed", from_path.display()))?; // Extract signature from header. - let sigbytes = delta_update::get_signatures_bytes(&upfile, &header)?; + let sigbytes = delta_update::get_signatures_bytes(&upfile, &header).context(format!("get_signatures_bytes({}) failed", from_path.display()))?; // Parse signature data from the signature containing data, version, special fields. let _sigdata = match delta_update::parse_signature_data(res_data.unwrap().as_bytes(), &sigbytes, pubkey_path) { Some(data) => data, _ => { self.status = PackageStatus::BadSignature; - return Err("unable to parse signature data".into()); + bail!("unable to parse signature data"); } }; @@ -142,7 +151,7 @@ impl<'a> Package<'a> { #[rustfmt::skip] fn get_pkgs_to_download<'a>(resp: &'a omaha::Response, glob_set: &GlobSet) - -> Result>, Box> { + -> Result>> { let mut to_download: Vec<_> = Vec::new(); for app in &resp.apps { diff --git a/src/download.rs b/src/download.rs index 56ecb33..4e396ce 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use anyhow::{Context, Result}; use std::io::Write; use std::io; @@ -9,26 +9,29 @@ pub struct DownloadResult { pub data: W, } -pub async fn download_and_hash(client: &reqwest::Client, url: U, mut data: W) -> Result, Box> +pub async fn download_and_hash(client: &reqwest::Client, url: U, mut data: W) -> Result> where - U: reqwest::IntoUrl, + U: reqwest::IntoUrl + Clone, W: io::Write, { + let client_url = url.clone(); + #[rustfmt::skip] let mut res = client.get(url) .send() - .await?; + .await + .context(format!("client get and send({}) failed", client_url.as_str()))?; let mut hasher = Sha256::new(); let mut bytes_read = 0usize; let bytes_to_read = res.content_length().unwrap_or(u64::MAX) as usize; - while let Some(chunk) = res.chunk().await? { + while let Some(chunk) = res.chunk().await.context("response chunk failed")? { bytes_read += chunk.len(); hasher.update(&chunk); - data.write_all(&chunk)?; + data.write_all(&chunk).context("write_all chunk failed")?; // TODO: better way to report progress? print!( @@ -37,10 +40,10 @@ where bytes_to_read, ((bytes_read as f32 / bytes_to_read as f32) * 100.0f32).floor() ); - io::stdout().flush()?; + io::stdout().flush().context("stdout flush failed")?; } - data.flush()?; + data.flush().context("data flush failed")?; println!(); Ok(DownloadResult { diff --git a/src/request.rs b/src/request.rs index de9900e..780ffc5 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,6 @@ -use std::error::Error; use std::borrow::Cow; +use anyhow::{Context, Result}; use hard_xml::XmlWrite; use omaha; @@ -30,7 +30,7 @@ pub struct Parameters<'a> { pub machine_id: Cow<'a, str>, } -pub async fn perform<'a>(client: &reqwest::Client, parameters: Parameters<'a>) -> Result> { +pub async fn perform<'a>(client: &reqwest::Client, parameters: Parameters<'a>) -> Result { let req_body = { let r = omaha::Request { protocol_version: Cow::Borrowed(PROTOCOL_VERSION), @@ -69,7 +69,7 @@ pub async fn perform<'a>(client: &reqwest::Client, parameters: Parameters<'a>) - ], }; - r.to_string()? + r.to_string().context("to_string failed")? }; // TODO: remove @@ -80,7 +80,8 @@ pub async fn perform<'a>(client: &reqwest::Client, parameters: Parameters<'a>) - let resp = client.post(UPDATE_URL) .body(req_body) .send() - .await?; + .await + .context("client post send({UPDATE_URL}) failed")?; - Ok(resp.text().await?) + Ok(resp.text().await.context("response text failed")?) } diff --git a/update-format-crau/src/delta_update.rs b/update-format-crau/src/delta_update.rs index 18704e1..145c02a 100644 --- a/update-format-crau/src/delta_update.rs +++ b/update-format-crau/src/delta_update.rs @@ -1,7 +1,7 @@ use std::io::{Read, Seek, SeekFrom}; -use std::error::Error; use std::fs::File; use log::debug; +use anyhow::{Context, Result, bail}; use protobuf::Message; @@ -29,26 +29,26 @@ impl DeltaUpdateFileHeader { } // Read delta update header from the given file, return DeltaUpdateFileHeader. -pub fn read_delta_update_header(mut f: &File) -> Result> { +pub fn read_delta_update_header(mut f: &File) -> Result { let mut header = DeltaUpdateFileHeader { magic: [0; 4], file_format_version: 0, manifest_size: 0, }; - f.read_exact(&mut header.magic)?; + f.read_exact(&mut header.magic).context("failed to read header magic")?; if header.magic != DELTA_UPDATE_FILE_MAGIC { - return Err("bad file magic".into()); + bail!("bad file magic"); } let mut buf = [0u8; 8]; - f.read_exact(&mut buf)?; + f.read_exact(&mut buf).context("failed to read file format version")?; header.file_format_version = u64::from_be_bytes(buf); if header.file_format_version != 1 { - return Err("unsupported file format version".into()); + bail!("unsupported file format version"); } - f.read_exact(&mut buf)?; + f.read_exact(&mut buf).context("failed to read manifest size")?; header.manifest_size = u64::from_be_bytes(buf); Ok(header) @@ -56,14 +56,14 @@ pub fn read_delta_update_header(mut f: &File) -> Result(mut f: &'a File, header: &'a DeltaUpdateFileHeader) -> Result, Box> { +pub fn get_signatures_bytes<'a>(mut f: &'a File, header: &'a DeltaUpdateFileHeader) -> Result> { let manifest_bytes = { let mut buf = vec![0u8; header.manifest_size as usize]; - f.read_exact(&mut buf)?; + f.read_exact(&mut buf).context("failed to read manifest bytes")?; buf.into_boxed_slice() }; - let manifest = proto::DeltaArchiveManifest::parse_from_bytes(&manifest_bytes)?; + let manifest = proto::DeltaArchiveManifest::parse_from_bytes(&manifest_bytes).context("failed to parse manifest")?; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!! signature offsets are from the END of the manifest !!! @@ -73,10 +73,10 @@ pub fn get_signatures_bytes<'a>(mut f: &'a File, header: &'a DeltaUpdateFileHead let signatures_bytes = match (manifest.signatures_offset, manifest.signatures_size) { (Some(sig_offset), Some(sig_size)) => { - f.seek(SeekFrom::Start(header.translate_offset(sig_offset)))?; + f.seek(SeekFrom::Start(header.translate_offset(sig_offset))).context("failed to seek to start of signature")?; let mut buf = vec![0u8; sig_size as usize]; - f.read_exact(&mut buf)?; + f.read_exact(&mut buf).context("failed to read signature")?; Some(buf.into_boxed_slice()) } _ => None, @@ -125,7 +125,7 @@ pub fn verify_sig_pubkey(testdata: &[u8], sig: &Signature, pubkeyfile: &str) -> debug!("special_fields: {:?}", sig.special_fields()); // verify signature with pubkey - _ = verify_sig::verify_rsa_pkcs(testdata, sig.data(), get_public_key_pkcs_pem(pubkeyfile, KeyTypePkcs8)); + _ = verify_sig::verify_rsa_pkcs(testdata, sig.data(), get_public_key_pkcs_pem(pubkeyfile, KeyTypePkcs8).unwrap()); _ = pubkeyfile; sigvec.cloned() diff --git a/update-format-crau/src/verify_sig.rs b/update-format-crau/src/verify_sig.rs index c64e134..0b625d2 100644 --- a/update-format-crau/src/verify_sig.rs +++ b/update-format-crau/src/verify_sig.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result, bail}; use rsa::{RsaPrivateKey, RsaPublicKey}; use rsa::pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey}; use rsa::pkcs8::{DecodePrivateKey, DecodePublicKey}; @@ -5,7 +6,6 @@ use rsa::pkcs1v15; use rsa::signature::{SignatureEncoding, Signer, Verifier}; use rsa::sha2::Sha256; use std::{fs, str}; -use std::error::Error; #[derive(Debug)] pub enum KeyType { @@ -19,7 +19,7 @@ pub enum KeyType { // Takes a data buffer and a private key, to sign the data // with the private key and verify the data with the public key. -pub fn sign_rsa_pkcs(databuf: &[u8], private_key: RsaPrivateKey) -> Result, Box> { +pub fn sign_rsa_pkcs(databuf: &[u8], private_key: RsaPrivateKey) -> Result> { let signing_key = pkcs1v15::SigningKey::::new(private_key); let signature = signing_key.sign(databuf); @@ -30,44 +30,44 @@ pub fn sign_rsa_pkcs(databuf: &[u8], private_key: RsaPrivateKey) -> Result Result<(), Box> { +pub fn verify_rsa_pkcs(databuf: &[u8], signature: &[u8], public_key: RsaPublicKey) -> Result<()> { // Equivalent of: // openssl rsautl -verify -pubin -key |public_key_path| // - in |sig_data| -out |out_hash_data| let verifying_key = pkcs1v15::VerifyingKey::::new(public_key); - Ok(verifying_key.verify(databuf, &pkcs1v15::Signature::try_from(signature).unwrap())?) + Ok(verifying_key.verify(databuf, &pkcs1v15::Signature::try_from(signature).unwrap()).context(format!("verify of signature ({:?}) failed", signature))?) } -pub fn get_private_key_pkcs_pem(private_key_path: &str, key_type: KeyType) -> RsaPrivateKey { +pub fn get_private_key_pkcs_pem(private_key_path: &str, key_type: KeyType) -> Result { let private_key_buf = fs::read_to_string(private_key_path).unwrap(); let out_key = match key_type { - KeyType::KeyTypePkcs1 => RsaPrivateKey::from_pkcs1_pem(private_key_buf.as_str()).unwrap_or_else(|error| { - panic!("failed to parse PKCS1 PEM message: {:?}", error); + KeyType::KeyTypePkcs1 => RsaPrivateKey::from_pkcs1_pem(private_key_buf.as_str()).or_else(|error| { + bail!("failed to parse PKCS1 PEM message: {:?}", error); }), - KeyType::KeyTypePkcs8 => RsaPrivateKey::from_pkcs8_pem(private_key_buf.as_str()).unwrap_or_else(|error| { - panic!("failed to parse PKCS8 PEM message: {:?}", error); + KeyType::KeyTypePkcs8 => RsaPrivateKey::from_pkcs8_pem(private_key_buf.as_str()).or_else(|error| { + bail!("failed to parse PKCS8 PEM message: {:?}", error); }), KeyType::KeyTypeNone => { - panic!("invalid key type: {:?}", key_type); + bail!("invalid key type: {:?}", key_type); } }; out_key } -pub fn get_public_key_pkcs_pem(public_key_path: &str, key_type: KeyType) -> RsaPublicKey { +pub fn get_public_key_pkcs_pem(public_key_path: &str, key_type: KeyType) -> Result { let public_key_buf = fs::read_to_string(public_key_path).unwrap(); let out_key = match key_type { - KeyType::KeyTypePkcs1 => RsaPublicKey::from_pkcs1_pem(public_key_buf.as_str()).unwrap_or_else(|error| { - panic!("failed to parse PKCS1 PEM message: {:?}", error); + KeyType::KeyTypePkcs1 => RsaPublicKey::from_pkcs1_pem(public_key_buf.as_str()).or_else(|error| { + bail!("failed to parse PKCS1 PEM message: {:?}", error); }), - KeyType::KeyTypePkcs8 => RsaPublicKey::from_public_key_pem(public_key_buf.as_str()).unwrap_or_else(|error| { - panic!("failed to parse PKCS8 PEM message: {:?}", error); + KeyType::KeyTypePkcs8 => RsaPublicKey::from_public_key_pem(public_key_buf.as_str()).or_else(|error| { + bail!("failed to parse PKCS8 PEM message: {:?}", error); }), KeyType::KeyTypeNone => { - panic!("invalid key type: {:?}", key_type); + bail!("invalid key type: {:?}", key_type); } }; @@ -88,30 +88,38 @@ mod tests { #[test] fn test_verify_sig() { // PKCS1 - let signature = sign_rsa_pkcs(TESTDATA.as_bytes(), get_private_key_pkcs_pem(PRIVKEY_PKCS1_PATH, KeyTypePkcs1)).unwrap_or_else(|error| { + let signature = sign_rsa_pkcs( + TESTDATA.as_bytes(), + get_private_key_pkcs_pem(PRIVKEY_PKCS1_PATH, KeyTypePkcs1).unwrap(), + ) + .or_else(|error| { panic!("failed to sign data: {:?}", error); }); _ = verify_rsa_pkcs( TESTDATA.as_bytes(), signature.as_slice(), - get_public_key_pkcs_pem(PUBKEY_PKCS1_PATH, KeyTypePkcs1), + get_public_key_pkcs_pem(PUBKEY_PKCS1_PATH, KeyTypePkcs1).unwrap(), ) - .unwrap_or_else(|error| { + .or_else(|error| { panic!("failed to verify data: {:?}", error); }); // PKCS8 - let signature = sign_rsa_pkcs(TESTDATA.as_bytes(), get_private_key_pkcs_pem(PRIVKEY_PKCS8_PATH, KeyTypePkcs8)).unwrap_or_else(|error| { + let signature = sign_rsa_pkcs( + TESTDATA.as_bytes(), + get_private_key_pkcs_pem(PRIVKEY_PKCS8_PATH, KeyTypePkcs8).unwrap(), + ) + .or_else(|error| { panic!("failed to sign data: {:?}", error); }); _ = verify_rsa_pkcs( TESTDATA.as_bytes(), signature.as_slice(), - get_public_key_pkcs_pem(PUBKEY_PKCS8_PATH, KeyTypePkcs8), + get_public_key_pkcs_pem(PUBKEY_PKCS8_PATH, KeyTypePkcs8).unwrap(), ) - .unwrap_or_else(|error| { + .or_else(|error| { panic!("failed to verify data: {:?}", error); }); }