From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 618171FF0AA for ; Tue, 03 Nov 2026 16:13:05 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id CDB67218C6; Tue, 03 Nov 2026 16:13:02 +0100 (CET) From: Filip Schauer To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox 1/3] oci: add a digest helper module Date: Tue, 3 Nov 2026 16:11:44 +0100 Message-ID: <20261103151153.290754-2-f.schauer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261103151153.290754-1-f.schauer@proxmox.com> References: <20261103151153.290754-1-f.schauer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793718778904 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.334 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: KSS5E44Q5WA33T5FSQUH37K5IC7WVWX6 X-Message-ID-Hash: KSS5E44Q5WA33T5FSQUH37K5IC7WVWX6 X-MailFrom: f.schauer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Filip Schauer --- proxmox-oci/src/digest.rs | 94 +++++++++++++++++++++++++++++++++++++++ proxmox-oci/src/lib.rs | 2 + 2 files changed, 96 insertions(+) create mode 100644 proxmox-oci/src/digest.rs diff --git a/proxmox-oci/src/digest.rs b/proxmox-oci/src/digest.rs new file mode 100644 index 00000000..b2170b87 --- /dev/null +++ b/proxmox-oci/src/digest.rs @@ -0,0 +1,94 @@ +use std::io::{self, Read}; +use std::str::FromStr; + +use oci_spec::image::{Digest, DigestAlgorithm}; +use sha2::Digest as _; +use sha2::digest::DynDigest; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum DigestError { + #[error("Unsupported digest algorithm: {0}")] + UnsupportedAlgorithm(String), + #[error("Digest mismatch. Expected {expected}, got {actual}")] + Mismatch { expected: Digest, actual: Digest }, +} + +struct Digester(Box); + +impl Digester { + fn new(digest_algorithm: &DigestAlgorithm) -> Result { + let hasher: Box = match digest_algorithm { + DigestAlgorithm::Sha256 => Box::new(sha2::Sha256::new()), + DigestAlgorithm::Sha384 => Box::new(sha2::Sha384::new()), + DigestAlgorithm::Sha512 => Box::new(sha2::Sha512::new()), + other => return Err(DigestError::UnsupportedAlgorithm(other.to_string())), + }; + Ok(Self(hasher)) + } + + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + + fn finalize(&mut self) -> Digest { + let sha_bit_count = self.0.output_size() * 8; + let digest_str = self + .0 + .finalize_reset() + .iter() + .map(|b| format!("{b:02x}")) + .collect::(); + Digest::from_str(&format!("sha{sha_bit_count}:{digest_str}")).expect("valid digest") + } +} + +pub struct VerifyingReader { + inner: R, + digester: Digester, + expected_digest: Digest, + eof: bool, +} + +impl VerifyingReader { + pub fn new(inner: R, expected_digest: Digest) -> Result { + Ok(Self { + inner, + digester: Digester::new(expected_digest.algorithm())?, + expected_digest, + eof: false, + }) + } + + fn verify(&mut self) -> io::Result<()> { + let actual = self.digester.finalize(); + + if actual != self.expected_digest { + return Err(io::Error::other(DigestError::Mismatch { + expected: self.expected_digest.clone(), + actual, + })); + } + + Ok(()) + } +} + +impl Read for VerifyingReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if buf.is_empty() { + return Ok(0); + } + + let n = self.inner.read(buf)?; + if n == 0 { + if !self.eof { + self.eof = true; + self.verify()?; + } + return Ok(0); + } + self.digester.update(&buf[..n]); + Ok(n) + } +} diff --git a/proxmox-oci/src/lib.rs b/proxmox-oci/src/lib.rs index 78045f49..71970cd8 100644 --- a/proxmox-oci/src/lib.rs +++ b/proxmox-oci/src/lib.rs @@ -15,6 +15,8 @@ use sha2::{Digest, Sha256}; use tar::{Archive, EntryType}; use thiserror::Error; +mod digest; + mod oci_tar_image; use oci_tar_image::{OciTarImage, OciTarImageBlob}; -- 2.47.3