Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/full_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ fn get_pkgs_to_download(resp: &omaha::Response) -> Result<Vec<(Url, Sha256Digest
for app in &resp.apps {
let manifest = &app.update_check.manifest;

for pkg in &manifest.packages {
for pkg in &manifest.packages.packages {
#[rustfmt::skip]
let hash_sha256 = pkg.hash_sha256
.as_ref()
.or_else(|| {
manifest.actions.iter()
manifest.actions.actions.iter()
.find(|a| a.event == omaha::response::ActionEvent::PostInstall)
.map(|a| &a.sha256)
});
Expand Down
4 changes: 2 additions & 2 deletions examples/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let manifest = &app.update_check.manifest;
println!(" version {}:", manifest.version);

for pkg in &manifest.packages {
for pkg in &manifest.packages.packages {
println!(" package {}:", pkg.name);

#[rustfmt::skip]
Expand All @@ -53,7 +53,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let hash_sha256 = pkg.hash_sha256
.as_ref()
.or_else(|| {
manifest.actions.iter()
manifest.actions.actions.iter()
.find(|a| a.event == omaha::response::ActionEvent::PostInstall)
.map(|a| &a.sha256)
});
Expand Down
1 change: 1 addition & 0 deletions omaha/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ uuid = "1.2"
url = "2"
sha2 = "0.10.8"
sha1 = "0.10.6"
ct-codecs = "1.1.6"

[dependencies.hard-xml]
path = "../vendor/hard-xml"
2 changes: 2 additions & 0 deletions omaha/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::num::ParseIntError;
#[derive(Debug)]
pub enum Error {
TryFromHex(ParseIntError),
TryFromBase64(ct_codecs::Error),
InvalidDigestLength {
expected: usize,
actual: usize,
Expand All @@ -19,6 +20,7 @@ impl Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::TryFromHex(err) => write!(fmt, "failed to convert from hex: {err}"),
Error::TryFromBase64(err) => write!(fmt, "failed to convert from base64: {err}"),
Error::InvalidDigestLength {
expected,
actual,
Expand Down
74 changes: 69 additions & 5 deletions omaha/src/hash_types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::str;
use ct_codecs::{Base64, Decoder};

use sha2::Digest;

Expand Down Expand Up @@ -42,8 +43,12 @@ pub trait Hasher {
fn finalize(self) -> Self::Output;

/// Construct a hash of the output format of the associated hashing
/// algorithm using a provided string.
/// algorithm using a provided hex string.
fn try_from_hex_string(s: &str) -> Result<Self::Output>;

/// Construct a hash of the output format of the associated hashing
/// algorithm using a provided base64 string.
fn try_from_base64_string(s: &str) -> Result<Self::Output>;
}

impl Hasher for Sha1 {
Expand All @@ -67,15 +72,19 @@ impl Hasher for Sha1 {
fn try_from_hex_string(s: &str) -> Result<Self::Output> {
try_from_hex_string::<Self>(s)
}

fn try_from_base64_string(s: &str) -> Result<Self::Output> {
try_from_base64_string::<Self>(s)
}
}

pub(crate) mod sha1_from_str {
pub(crate) mod sha1_from_base64_str {
use crate::{Hasher, Sha1, Sha1Digest};
use crate::Result;

#[inline]
pub(crate) fn from_str(s: &str) -> Result<Sha1Digest> {
<Sha1 as Hasher>::try_from_hex_string(s)
<Sha1 as Hasher>::try_from_base64_string(s)
}
}

Expand All @@ -96,12 +105,17 @@ impl Hasher for Sha256 {
fn finalize(self) -> Self::Output {
self.0.finalize().into()
}

fn try_from_hex_string(s: &str) -> Result<Self::Output> {
try_from_hex_string::<Self>(s)
}

fn try_from_base64_string(s: &str) -> Result<Self::Output> {
try_from_base64_string::<Self>(s)
}
}

pub(crate) mod sha256_from_str {
pub(crate) mod sha256_from_hex_str {
use crate::{Hasher, Sha256, Sha256Digest};
use crate::Result;

Expand All @@ -111,6 +125,16 @@ pub(crate) mod sha256_from_str {
}
}

pub(crate) mod sha256_from_base64_str {
use crate::{Hasher, Sha256, Sha256Digest};
use crate::Result;

#[inline]
pub(crate) fn from_str(s: &str) -> Result<Sha256Digest> {
<Sha256 as Hasher>::try_from_base64_string(s)
}
}

/// Parse a hexadecimal string into the output of the generically typed hashing
/// algorithm.
fn try_from_hex_string<T: Hasher>(s: &str) -> Result<T::Output> {
Expand All @@ -136,10 +160,29 @@ fn try_from_hex_string<T: Hasher>(s: &str) -> Result<T::Output> {
}
}

/// Parse a base64 string into the output of the generically typed hashing
/// algorithm.
fn try_from_base64_string<T: Hasher>(s: &str) -> Result<T::Output> {
let mut bytes = vec![0; s.len()];

let bytes = Base64::decode(bytes.as_mut(), s, None).map_err(Error::TryFromBase64)?;

if bytes.len() == T::FINGERPRINT_SIZE {
let mut digest = T::Output::default();
digest.as_mut().copy_from_slice(bytes);
Ok(digest)
} else {
Err(Error::InvalidDigestLength {
expected: T::FINGERPRINT_SIZE,
actual: bytes.len(),
})
}
}

#[cfg(test)]
mod tests {
use crate::Hasher;
use super::{Sha256, Sha1, try_from_hex_string};
use super::{Sha256, Sha1, try_from_hex_string, try_from_base64_string};
use sha1::Digest;

const TEST_DATA: &[u8] = b"test string";
Expand Down Expand Up @@ -197,4 +240,25 @@ mod tests {
assert!(sha256_digest.is_ok());
assert_eq!(sha256_digest.unwrap(), exp_bytes);
}

#[test]
fn try_from_base64_string_sha1() {
let base64_string = "FF+ci4cThKAdESIk5GbSgrN0Q7A=";
let exp_bytes = [20, 95, 156, 139, 135, 19, 132, 160, 29, 17, 34, 36, 228, 102, 210, 130, 179, 116, 67, 176];
let sha1_digest = try_from_base64_string::<Sha1>(base64_string);

assert!(sha1_digest.is_ok());
assert_eq!(sha1_digest.unwrap(), exp_bytes);
}

#[test]
fn try_from_base64_string_sha256() {
let base64_string = "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";

let exp_bytes = [44, 242, 77, 186, 95, 176, 163, 14, 38, 232, 59, 42, 197, 185, 226, 158, 27, 22, 30, 92, 31, 167, 66, 94, 115, 4, 51, 98, 147, 139, 152, 36];
let sha256_digest = try_from_base64_string::<Sha256>(base64_string);

assert!(sha256_digest.is_ok());
assert_eq!(sha256_digest.unwrap(), &exp_bytes[..]);
}
}
36 changes: 26 additions & 10 deletions omaha/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use url::Url;

use crate::Error::{UnknownActionEvent, UnknownSuccessAction};
use crate::uuid::braced_uuid;
use crate::{Sha1Digest, Sha256Digest, Error, sha1_from_str, sha256_from_str};
use crate::{Sha1Digest, Sha256Digest, Error, sha1_from_base64_str, sha256_from_base64_str, sha256_from_hex_str};

#[derive(XmlRead, Debug)]
#[xml(tag = "response")]
Expand Down Expand Up @@ -129,10 +129,18 @@ pub struct Manifest<'a> {
pub version: Cow<'a, str>,

#[xml(child = "packages")]
pub packages: Vec<Package<'a>>,
pub packages: Packages<'a>,

#[xml(child = "actions")]
pub actions: Vec<Action>,
pub actions: Actions,
}

#[derive(XmlRead, Debug)]
#[xml(tag = "packages")]
#[cfg_attr(test, derive(PartialEq, Default))]
pub struct Packages<'a> {
#[xml(child = "package")]
pub packages: Vec<Package<'a>>,
}

#[derive(XmlRead, Debug)]
Expand All @@ -142,7 +150,7 @@ pub struct Package<'a> {
#[xml(attr = "name")]
pub name: Cow<'a, str>,

#[xml(attr = "hash", with = "sha1_from_str")]
#[xml(attr = "hash", with = "sha1_from_base64_str")]
pub hash: Option<Sha1Digest>,

#[xml(attr = "size")]
Expand All @@ -151,18 +159,26 @@ pub struct Package<'a> {
#[xml(attr = "required")]
pub required: bool,

#[xml(attr = "hash_sha256", with = "sha256_from_str")]
#[xml(attr = "hash_sha256", with = "sha256_from_hex_str")]
pub hash_sha256: Option<Sha256Digest>,
}

#[derive(XmlRead, Debug, Default)]
#[xml(tag = "actions")]
#[cfg_attr(test, derive(PartialEq))]
pub struct Actions {
#[xml(child = "action")]
pub actions: Vec<Action>,
}

#[derive(XmlRead, Debug)]
#[xml(tag = "action")]
#[cfg_attr(test, derive(PartialEq))]
pub struct Action {
#[xml(attr = "event")]
pub event: ActionEvent,

#[xml(attr = "sha256", with = "sha256_from_str")]
#[xml(attr = "sha256", with = "sha256_from_base64_str")]
pub sha256: Sha256Digest,

#[xml(attr = "DisablePayloadBackoff")]
Expand Down Expand Up @@ -317,16 +333,16 @@ mod tests {
#[test]
fn package_xml_read_hashes() {
const NAME: &str = "name";
const SHA1_STR: &str = "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d";
const BASE64_STR: &str = "FF+ci4cThKAdESIk5GbSgrN0Q7A=";
const SIZE: usize = 1;
const REQUIRED: bool = false;
const SHA256_STR: &str = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";

test_xml_read(
format!("<package name=\"{NAME}\" hash=\"{SHA1_STR}\" size=\"{SIZE}\" required=\"{REQUIRED}\" hash_sha256=\"{SHA256_STR}\"/>",).as_str(),
format!("<package name=\"{NAME}\" hash=\"{BASE64_STR}\" size=\"{SIZE}\" required=\"{REQUIRED}\" hash_sha256=\"{SHA256_STR}\"/>",).as_str(),
Package {
name: Cow::Borrowed(NAME),
hash: Some(Sha1::try_from_hex_string(SHA1_STR).unwrap()),
hash: Some(Sha1::try_from_base64_string(BASE64_STR).unwrap()),
size: SIZE,
required: REQUIRED,
hash_sha256: Some(Sha256::try_from_hex_string(SHA256_STR).unwrap()),
Expand All @@ -337,7 +353,7 @@ mod tests {
#[test]
fn app_xml_read() {
test_xml_read(
format!("<app appid=\"{{{TEST_UUID}}}\" status=\"\"><updatecheck status=\"\"><manifest version=\"\"/><urls></urls></updatecheck></app>",).as_str(),
format!("<app appid=\"{{{TEST_UUID}}}\" status=\"\"><updatecheck status=\"\"><manifest version=\"\"><packages/><actions/></manifest><urls></urls></updatecheck></app>",).as_str(),
App {
id: uuid::uuid!(TEST_UUID),
..App::default()
Expand Down
6 changes: 3 additions & 3 deletions src/bin/download_sysext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ fn main() -> Result<(), Box<dyn Error>> {
(Some(_), Some(_)) => {
return Err("only one of the options can be given, --input-xml or --payload-url.".into());
}
(Some(_), None) => return Ok(()),
(None, Some(url)) => &url.clone(),
(Some(_), None) => None,
(None, Some(url)) => Some(url.clone()),
(None, None) => return Err("either --input-xml or --payload-url must be given.".into()),
};

Expand All @@ -104,7 +104,7 @@ fn main() -> Result<(), Box<dyn Error>> {
DownloadVerify::new(args.output_dir, args.pubkey_file, args.take_first_match, glob_set)
.target_filename(args.target_filename)
.input_xml(input_xml.unwrap_or_default())
.payload_url(Some(payload_url.clone()))
.payload_url(payload_url)
.run()?;

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/download/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ fn get_pkgs_to_download<'a>(resp: &'a omaha::Response, glob_set: &GlobSet) -> Re
for app in &resp.apps {
let manifest = &app.update_check.manifest;

for pkg in &manifest.packages {
for pkg in &manifest.packages.packages {
if !glob_set.is_match(&*pkg.name) {
info!("package `{}` doesn't match glob pattern, skipping", pkg.name);
continue;
Expand Down