diff --git a/Cargo.lock b/Cargo.lock index 1bf7ed37..65e65911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,7 @@ dependencies = [ "buildpacks-jvm-shared", "buildpacks-jvm-shared-test", "fs_extra", + "hex", "indoc", "inventory", "libcnb", diff --git a/buildpacks/jvm/CHANGELOG.md b/buildpacks/jvm/CHANGELOG.md index 7296ba7e..5653a53b 100644 --- a/buildpacks/jvm/CHANGELOG.md +++ b/buildpacks/jvm/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Checksum validation of downloaded OpenJDK distribution files. ([#680](https://github.com/heroku/buildpacks-jvm/pull/680)) + ### Changed - Some error messages have changed so they longer suggest to open a Heroku support ticket. Instead, users are now provided with a link to create an issue on GitHub. ([#674](https://github.com/heroku/buildpacks-jvm/pull/674)) diff --git a/buildpacks/jvm/Cargo.toml b/buildpacks/jvm/Cargo.toml index 0cb52333..bba9c8ef 100644 --- a/buildpacks/jvm/Cargo.toml +++ b/buildpacks/jvm/Cargo.toml @@ -19,6 +19,7 @@ nom = "7" inventory = { git = "https://github.com/Malax/inventory", features = ["sha2"] } thiserror = "1" sha2 = "0.10" +hex = "0.4" [dev-dependencies] buildpacks-jvm-shared-test.workspace = true diff --git a/buildpacks/jvm/openjdk_inventory.toml b/buildpacks/jvm/openjdk_inventory.toml index 05d2323d..f86f2277 100644 --- a/buildpacks/jvm/openjdk_inventory.toml +++ b/buildpacks/jvm/openjdk_inventory.toml @@ -291,7 +291,7 @@ version = "11.0.23" os = "linux" arch = "arm64" url = "https://heroku-buildpacks-jvm.s3.us-east-1.amazonaws.com/openjdk/zulu/arm64/11.0.23.tar.gz" -checksum = "sha256:7ab8f4ff3f1c675d8ca9a904f5d3657afcb59b6d852c4c1b2c5988a2854896cd" +checksum = "sha256:5b8b551785c4f23417c10feba5e0342af300f491cb7f4c62c7ed84fd37857960" metadata.distribution = "zulu" [[artifacts]] @@ -299,7 +299,7 @@ version = "11.0.23" os = "linux" arch = "amd64" url = "https://heroku-buildpacks-jvm.s3.us-east-1.amazonaws.com/openjdk/zulu/amd64/11.0.23.tar.gz" -checksum = "sha256:5b8b551785c4f23417c10feba5e0342af300f491cb7f4c62c7ed84fd37857960" +checksum = "sha256:7ab8f4ff3f1c675d8ca9a904f5d3657afcb59b6d852c4c1b2c5988a2854896cd" metadata.distribution = "zulu" [[artifacts]] diff --git a/buildpacks/jvm/src/errors.rs b/buildpacks/jvm/src/errors.rs index 812944a7..db1bab21 100644 --- a/buildpacks/jvm/src/errors.rs +++ b/buildpacks/jvm/src/errors.rs @@ -1,6 +1,6 @@ use crate::openjdk_artifact::HerokuOpenJdkVersionRequirement; use crate::{OpenJdkArtifactRequirementParseError, OpenJdkBuildpackError}; -use buildpacks_jvm_shared::log::log_please_try_again_error; +use buildpacks_jvm_shared::log::{log_please_try_again, log_please_try_again_error}; use buildpacks_jvm_shared::system_properties::ReadSystemPropertiesError; use indoc::formatdoc; use libherokubuildpack::log::log_error; @@ -64,9 +64,9 @@ pub(crate) fn on_error_jvm_buildpack(error: OpenJdkBuildpackError) { "Could not copy the contents of the application's JDK overlay.", error, ), - OpenJdkBuildpackError::CannotOpenOpenJdkTarball(error) => log_please_try_again_error( + OpenJdkBuildpackError::CannotReadOpenJdkTarball(error) => log_please_try_again_error( "Unexpected IO error", - "Could not open downloaded OpenJDK tarball file.", + "Could not read downloaded OpenJDK tarball file.", error, ), OpenJdkBuildpackError::CannotDecompressOpenJdkTarball(error) => log_please_try_again_error( @@ -120,6 +120,15 @@ pub(crate) fn on_error_jvm_buildpack(error: OpenJdkBuildpackError) { Details: {error} ", error = error }, + ), + OpenJdkBuildpackError::OpenJdkTarballChecksumError { expected, actual } => log_please_try_again( + "Corrupted OpenJDK download", + formatdoc! {" + The validation of the downloaded OpenJDK distribution failed due to a checksum mismatch. + + Expected: {expected} + Actual: {actual} + ", expected = hex::encode(expected), actual = hex::encode(actual) } ) } } diff --git a/buildpacks/jvm/src/layers/openjdk.rs b/buildpacks/jvm/src/layers/openjdk.rs index 72509ff2..6b52dd26 100644 --- a/buildpacks/jvm/src/layers/openjdk.rs +++ b/buildpacks/jvm/src/layers/openjdk.rs @@ -1,5 +1,6 @@ use crate::openjdk_artifact::OpenJdkArtifactMetadata; use crate::openjdk_version::OpenJdkVersion; +use crate::util::digest; use crate::{ util, OpenJdkBuildpack, OpenJdkBuildpackError, JAVA_TOOL_OPTIONS_ENV_VAR_DELIMITER, JAVA_TOOL_OPTIONS_ENV_VAR_NAME, JDK_OVERLAY_DIR_NAME, @@ -57,7 +58,23 @@ impl<'a> Layer for OpenJdkLayer<'a> { .map_err(OpenJdkBuildpackError::OpenJdkDownloadError)?; std::fs::File::open(&path) - .map_err(OpenJdkBuildpackError::CannotOpenOpenJdkTarball) + .map_err(OpenJdkBuildpackError::CannotReadOpenJdkTarball) + .and_then(|file| { + digest::(file).map_err(OpenJdkBuildpackError::CannotReadOpenJdkTarball) + }) + .and_then(|downloaded_file_digest| { + if downloaded_file_digest.as_slice() == self.artifact.checksum.value { + Ok(()) + } else { + Err(OpenJdkBuildpackError::OpenJdkTarballChecksumError { + expected: self.artifact.checksum.value.clone(), + actual: downloaded_file_digest.to_vec(), + }) + } + })?; + + std::fs::File::open(&path) + .map_err(OpenJdkBuildpackError::CannotReadOpenJdkTarball) .and_then(|mut file| { libherokubuildpack::tar::decompress_tarball(&mut file, layer_path) .map_err(OpenJdkBuildpackError::CannotDecompressOpenJdkTarball) diff --git a/buildpacks/jvm/src/main.rs b/buildpacks/jvm/src/main.rs index fbe8e122..1bda932f 100644 --- a/buildpacks/jvm/src/main.rs +++ b/buildpacks/jvm/src/main.rs @@ -42,7 +42,8 @@ enum OpenJdkBuildpackError { UnsupportedOpenJdkVersion(OpenJdkArtifactRequirement), OpenJdkDownloadError(DownloadError), CannotCreateOpenJdkTempDir(std::io::Error), - CannotOpenOpenJdkTarball(std::io::Error), + CannotReadOpenJdkTarball(std::io::Error), + OpenJdkTarballChecksumError { expected: Vec, actual: Vec }, CannotDecompressOpenJdkTarball(std::io::Error), ReadSystemPropertiesError(ReadSystemPropertiesError), OpenJdkArtifactRequirementParseError(OpenJdkArtifactRequirementParseError), diff --git a/buildpacks/jvm/src/util.rs b/buildpacks/jvm/src/util.rs index 3a82389d..8277fead 100644 --- a/buildpacks/jvm/src/util.rs +++ b/buildpacks/jvm/src/util.rs @@ -1,6 +1,28 @@ +use sha2::digest::{FixedOutput, Output, Update}; use std::fs::DirEntry; +use std::io::Read; use std::path::{Path, PathBuf}; +pub(crate) fn digest(mut input: impl Read) -> Result, std::io::Error> +where + D: Default + Update + FixedOutput, +{ + let mut digest = D::default(); + + let mut buffer = [0x00; 1024]; + loop { + let bytes_read = input.read(&mut buffer)?; + + if bytes_read > 0 { + digest.update(&buffer[..bytes_read]); + } else { + break; + } + } + + Ok(digest.finalize_fixed()) +} + pub(crate) fn list_directory_contents>(path: P) -> std::io::Result> { std::fs::read_dir(path.as_ref()) .and_then(Iterator::collect::>>)