Skip to content

Commit

Permalink
when using vendored RPMs, verify checksums (#192)
Browse files Browse the repository at this point in the history
provides an integrity check when using unsigned packages
  • Loading branch information
tofay authored Feb 18, 2025
1 parent a82edf8 commit a904626
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ url = { version = "2.2.2", features = ["serde"] }
walkdir = "2.3.2"
xattr = "1.0.1"
ocidir = "0.3.1"
sha2 = "0.10.8"

[dev-dependencies]
libc = "0.2.164"
Expand Down
59 changes: 58 additions & 1 deletion src/lockfile/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ use glob::glob;
use ocidir::oci_spec::image::MediaType;
use ocidir::{new_empty_manifest, OciDir};
use rusqlite::Connection;
use sha2::Digest;
use tempfile::TempDir;

use super::Lockfile;
use super::{Algorithm, Lockfile};
use crate::archive::append_dir_all_with_xattrs;
use crate::config::Config;
use crate::write;
Expand Down Expand Up @@ -54,6 +55,10 @@ impl Lockfile {
let creation_time = creation_time()?;
let installroot = TempDir::new()?; // This needs to outlive the layer builder below.
if let Some(vendor_dir) = vendor_dir {
// When using vendored RPMs, verify checksums.
// In the non-vendored case, dnf does this.
self.verify_checksums(vendor_dir)
.context("Checksum verification failure")?;
// Use vendored RPMs rather than downloading
self.create_installroot(installroot.path(), vendor_dir, false, cfg, &creation_time)
} else {
Expand Down Expand Up @@ -105,6 +110,58 @@ impl Lockfile {
Ok(())
}

fn verify_checksums(&self, vendor_dir: &Path) -> Result<(), anyhow::Error> {
write::ok("Verifying", "Vendored RPM checksums")?;

for file in fs::read_dir(vendor_dir)? {
let path = file?.path();

if path.extension() == Some(OsStr::new("rpm")) {
let pkg = rpm::Package::open(&path)
.context(format!("Failed to open RPM package {}", path.display()))?;

let pkg_name = pkg
.metadata
.get_name()
.context(format!("Failed to get RPM name for {}", path.display()))?;

match self.packages.iter().find(|p| p.name == pkg_name) {
Some(p) => {
if p.checksum.algorithm != Algorithm::SHA256 {
bail!(
"Unsupported checksum algorithm for package {}: {}",
pkg_name,
p.checksum.algorithm
);
}

let mut hasher = sha2::Sha256::new();
let mut file = fs::File::open(&path)
.context(format!("Failed to open file: {}", path.display()))?;
std::io::copy(&mut file, &mut hasher)
.context(format!("Failed to read file: {}", path.display()))?;
let checksum = format!("{:x}", hasher.finalize());

eprintln!("Checksum: {}", checksum);

if checksum != p.checksum.checksum {
bail!(
"Checksum mismatch for package {}: expected {}, got {}",
pkg_name,
p.checksum.checksum,
checksum
);
}
}
None => {
bail!("Package {} found in vendor directory but not found in lockfile. File name: {}", pkg_name, path.display());
}
}
}
}
Ok(())
}

fn create_installroot(
&self,
installroot: &Path,
Expand Down
13 changes: 13 additions & 0 deletions src/lockfile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! You should have received a copy of the GNU General Public License
//! along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt;
use std::io::Write;
use std::path::Path;

Expand Down Expand Up @@ -119,6 +120,18 @@ pub enum Algorithm {
SHA512,
}

impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Algorithm::MD5 => write!(f, "md5"),
Algorithm::SHA1 => write!(f, "sha1"),
Algorithm::SHA256 => write!(f, "sha256"),
Algorithm::SHA384 => write!(f, "sha384"),
Algorithm::SHA512 => write!(f, "sha512"),
}
}
}

impl Lockfile {
/// Returns true if the lockfile is compatible with the
/// given configuration, false otherwise
Expand Down
35 changes: 34 additions & 1 deletion tests/it.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ fn test_simple_build() {
}

#[test]
fn test_simple_vendor() {
fn test_vendor() {
let (_tmp_dir, root) = setup_test("simple_vendor");
let output = rpmoci()
.arg("update")
Expand All @@ -230,6 +230,39 @@ fn test_simple_vendor() {
let stderr = std::str::from_utf8(&output.stderr).unwrap();
eprintln!("stderr: {}", stderr);
assert!(output.status.success());

let status = rpmoci()
.arg("build")
.arg("--locked")
.arg("--vendor-dir=.")
.arg("--image=vendor")
.arg("--tag=test")
.current_dir(&root)
.status()
.unwrap();
assert!(status.success());

// Edit the lockfile to replace the checksum with a placeholder
let status = Command::new("sed")
.arg("-i")
.arg("s/checksum = \".*\"/checksum = \"REPLACED\"/")
.arg("rpmoci.lock")
.current_dir(&root)
.status()
.unwrap();
assert!(status.success());

// And check that the build now fails
let status = rpmoci()
.arg("build")
.arg("--locked")
.arg("--vendor-dir=.")
.arg("--image=vendor")
.arg("--tag=test")
.current_dir(&root)
.status()
.unwrap();
assert!(!status.success());
}

#[test]
Expand Down

0 comments on commit a904626

Please sign in to comment.