Skip to content

Commit

Permalink
Use sha2 to validate Python release checksums (#783)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnpryer authored Oct 21, 2023
1 parent b2510ac commit d4de466
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 30 deletions.
80 changes: 79 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion crates/huak_python_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ license.workspace = true
anyhow = "1.0.75"
clap = { workspace = true }
colored = { workspace = true }
hex = "0.4.3"
huak_home = { version = "0.0.0", path = "../huak_home" }
human-panic = { workspace = true }
reqwest = { version = "0.11.22", features = ["blocking", "json"] }
sha2 = "0.10.8"
tar = "0.4.40"
tempfile = { workspace = true }
zstd = "0.13.0"

[lints]
Expand Down
43 changes: 15 additions & 28 deletions crates/huak_python_manager/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,45 @@ use crate::{
};
use anyhow::{bail, Context, Error, Ok}; // TODO(cnpryer): Use thiserror in library code.
use huak_home::huak_home_dir;
use std::{fs::File, path::PathBuf};
use sha2::{Digest, Sha256};
use tar::Archive;
use tempfile::TempDir;
use zstd::decode_all;
use zstd::stream::read::Decoder;

/// Install a Python release to `~/.huak/bin/`.
pub(crate) fn install_to_home(strategy: &Strategy) -> Result<(), Error> {
let release = resolve_release(strategy).context("requested release data")?;
let tmp_dir = TempDir::new()?;
let tmp_name = "tmp.tar.zst";
let tmp_path = tmp_dir.path().join(tmp_name);
let target_dir = huak_home_dir()
.context("requested huak's home directory")?
.join("toolchains")
.join(format!("huak-{}-{}", release.kind, release.version));

download_release(&release, &tmp_path)?;
let buffer = download_release(&release)?;
validate_checksum(&buffer, release.checksum)?;

let mut archive = File::open(tmp_path)?;
let decoded = decode_all(&mut archive)?;
// TODO(cnpryer): Support more archive formats.
let decoded = Decoder::with_buffer(buffer.as_slice())?;
let mut archive = Archive::new(decoded);

let mut archive = Archive::new(decoded.as_slice());
Ok(archive.unpack(target_dir)?)
}

/// Download the release to a temporary archive file (`to`).
fn download_release(release: &Release, to: &PathBuf) -> Result<(), Error> {
validate_release(release)?;

fn download_release(release: &Release) -> Result<Vec<u8>, Error> {
let mut response = reqwest::blocking::get(release.url)?;

if !response.status().is_success() {
bail!("failed to download file from {}", release.url);
}

let mut file = File::create(to)?;
response.copy_to(&mut file)?;
let mut contents = Vec::new();
response.copy_to(&mut contents)?;

Ok(())
Ok(contents)
}

/// Validation for release installation. The following is verified prior to installation:
/// - checksum
fn validate_release(release: &Release) -> Result<(), Error> {
let url = format!("{}.sha256", release.url);
let response = reqwest::blocking::get(&url)?;

if !response.status().is_success() {
bail!("failed to fetch checksum from {url}");
}
fn validate_checksum(bytes: &[u8], checksum: &str) -> Result<(), Error> {
let mut hasher = Sha256::new();
hasher.update(bytes);

if response.text()?.strip_suffix('\n') != Some(release.checksum) {
if !hex::encode(hasher.finalize()).eq_ignore_ascii_case(checksum) {
bail!("failed to validate checksum");
}

Expand Down

0 comments on commit d4de466

Please sign in to comment.