Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop store content in memory for RPMBuilder #100

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
//! let raw_secret_key = std::fs::read("./test_assets/secret_key.asc")?;
//! // It's recommended to use timestamp of last commit in your VCS
//! let source_date = 1_600_000_000;
//! let pkg = rpm::RPMBuilder::new("test", "1.0.0", "MIT", "x86_64", "some awesome package")
//! let mut pkg = rpm::RPMBuilder::new("test", "1.0.0", "MIT", "x86_64", "some awesome package")
//! .compression(rpm::CompressionType::Gzip)
//! .with_file(
//! "./test_assets/awesome.toml",
Expand Down Expand Up @@ -67,7 +67,7 @@
//!
//! // reading
//! let raw_pub_key = std::fs::read("test_assets/public_key.asc")?;
//! let pkg = rpm::RPMPackage::open("/tmp/awesome.rpm")?;
//! let mut pkg = rpm::RPMPackage::open("/tmp/awesome.rpm")?;
//! // verifying
//! pkg.verify_signature(Verifier::load_from_asc_bytes(&raw_pub_key)?)?;
//! # }
Expand Down
23 changes: 13 additions & 10 deletions src/rpm/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryInto;

use std::fs;
#[cfg(feature = "signature-meta")]
use std::io;
use std::io::{Read, Write};
use std::io::{self, Read, Seek, Write};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

Expand Down Expand Up @@ -461,21 +459,23 @@ impl RPMBuilder {
/// build without a signature
///
/// ignores a present key, if any
pub fn build(self) -> Result<RPMPackage, RPMError> {
pub fn build(self) -> Result<RPMPackage<io::Cursor<Vec<u8>>>, RPMError> {
let (lead, header_idx_tag, content) = self.prepare_data()?;

let mut content = io::Cursor::new(content);
let mut header = Vec::with_capacity(128);
header_idx_tag.write(&mut header)?;

let digest_header = {
let header = header;
let header_and_content_len = header.len() + content.len();
let header_and_content_len = header.len() as u64 + content.get_ref().len() as u64;

let Digests {
header_and_content_digest: header_and_content_digest_md5,
header_digest_sha1,
header_digest_sha256,
} = RPMPackage::create_sig_header_digests(header.as_slice(), content.as_slice())?;
} = RPMPackage::create_sig_header_digests(header.as_slice(), &mut content)?;
content.rewind()?;

Header::<IndexSignatureTag>::builder()
.add_digest(
Expand All @@ -499,24 +499,26 @@ impl RPMBuilder {
///
/// See `signature::Signing` for more details.
#[cfg(feature = "signature-meta")]
pub fn build_and_sign<S>(self, signer: S) -> Result<RPMPackage, RPMError>
pub fn build_and_sign<S>(self, signer: S) -> Result<RPMPackage<io::Cursor<Vec<u8>>>, RPMError>
where
S: signature::Signing<signature::algorithm::RSA>,
{
let source_date = self.source_date;
let (lead, header_idx_tag, content) = self.prepare_data()?;

let mut content = io::Cursor::new(content);
let mut header = Vec::with_capacity(128);
header_idx_tag.write(&mut header)?;
let header = header;

let header_and_content_len = header.len() + content.len();
let header_and_content_len = header.len() as u64 + content.get_ref().len() as u64;

let Digests {
header_and_content_digest: header_and_content_digest_md5,
header_digest_sha1,
header_digest_sha256,
} = RPMPackage::create_sig_header_digests(header.as_slice(), content.as_slice())?;
} = RPMPackage::create_sig_header_digests(header.as_slice(), &mut content)?;
content.rewind()?;

let builder = Header::<IndexSignatureTag>::builder().add_digest(
header_digest_sha1.as_str(),
Expand All @@ -532,8 +534,9 @@ impl RPMBuilder {
};
let rsa_sig_header_only = signer.sign(header.as_slice(), t)?;

let cursor = io::Cursor::new(header).chain(io::Cursor::new(&content));
let cursor = io::Cursor::new(header).chain(&mut content);
let rsa_sig_header_and_archive = signer.sign(cursor, t)?;
content.rewind()?;

builder
.add_signature(
Expand Down
4 changes: 2 additions & 2 deletions src/rpm/headers/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ impl Header<IndexSignatureTag> {
/// Please use the [`builder`](Self::builder()) which has modular and safe API.
#[cfg(feature = "signature-meta")]
pub(crate) fn new_signature_header(
headers_plus_payload_size: usize,
headers_plus_payload_size: u64,
md5sum: &[u8],
sha1: &str,
sha256: &str,
Expand Down Expand Up @@ -956,7 +956,7 @@ mod test {
};

let built = Header::<IndexSignatureTag>::new_signature_header(
size as usize,
size as u64,
md5sum,
sha1,
sha256,
Expand Down
4 changes: 2 additions & 2 deletions src/rpm/headers/signature_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ where
T: ConstructionStage,
{
/// Construct the complete signature header.
pub fn build(mut self, headers_plus_payload_size: usize) -> Header<IndexSignatureTag> {
pub fn build(mut self, headers_plus_payload_size: u64) -> Header<IndexSignatureTag> {
let entry = match headers_plus_payload_size.try_into() {
Ok(size) => IndexEntry::new(
IndexSignatureTag::RPMSIGTAG_SIZE,
Expand All @@ -66,7 +66,7 @@ where
Err(_) => IndexEntry::new(
IndexSignatureTag::RPMSIGTAG_LONGSIZE,
0i32,
IndexData::Int64(vec![headers_plus_payload_size as u64]),
IndexData::Int64(vec![headers_plus_payload_size]),
),
};
self.entries.insert(0, entry);
Expand Down
1 change: 1 addition & 0 deletions src/rpm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod package;
mod timestamp;

pub mod signature;
mod skip_reader;

pub use headers::*;

Expand Down
103 changes: 65 additions & 38 deletions src/rpm/package.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
#[cfg(feature = "signature-meta")]
use std::io::Read;
use std::{
fs, io,
fmt, fs,
io::{self, Seek},
path::{Path, PathBuf},
str::FromStr,
};

use digest::Digest;
use num_traits::FromPrimitive;

use crate::rpm::skip_reader::SkipReader;
use crate::{constants::*, errors::*, CompressionType};

#[cfg(feature = "signature-meta")]
use crate::{signature, Timestamp};
#[cfg(feature = "signature-meta")]
use std::{fmt::Debug, io::Read};

use super::headers::*;
use super::Lead;
Expand All @@ -36,53 +37,71 @@ pub struct Digests {
///
/// Can either be created using the [`RPMBuilder`](super::builder::RPMBuilder)
/// or used with [`parse`](`self::RPMPackage::parse`) to obtain from a file.
#[derive(Debug)]
pub struct RPMPackage {
pub struct RPMPackage<R> {
/// Header and metadata structures.
///
/// Contains the constant lead as well as the metadata store.
pub metadata: RPMPackageMetadata,
/// The compressed or uncompressed files.
pub content: Vec<u8>,
pub content: R,
}

#[cfg(feature = "signature-meta")]
fn stream_len(mut stream: impl Seek) -> io::Result<u64> {
let old_pos = stream.stream_position()?;
let len = stream.seek(io::SeekFrom::End(0))?;

// Avoid seeking a third time when we were already at the end of the
// stream. The branch is usually way cheaper than a seek operation.
if old_pos != len {
stream.seek(io::SeekFrom::Start(old_pos))?;
}

Ok(len)
}

impl RPMPackage {
impl RPMPackage<()> {
/// Open and parse a file at the provided path as an RPM package
pub fn open(path: impl AsRef<Path>) -> Result<Self, RPMError> {
pub fn open(
path: impl AsRef<Path>,
) -> Result<RPMPackage<SkipReader<io::BufReader<fs::File>>>, RPMError> {
let rpm_file = fs::File::open(path.as_ref())?;
let mut buf_reader = io::BufReader::new(rpm_file);
Self::parse(&mut buf_reader)
let buf_reader = io::BufReader::new(rpm_file);
RPMPackage::parse(buf_reader)
}
}

impl<R: io::BufRead + Seek> RPMPackage<R> {
/// Parse an RPM package from an existing buffer
pub fn parse(input: &mut impl io::BufRead) -> Result<Self, RPMError> {
let metadata = RPMPackageMetadata::parse(input)?;
let mut content = Vec::new();
input.read_to_end(&mut content)?;
pub fn parse(mut input: R) -> Result<RPMPackage<SkipReader<R>>, RPMError> {
let metadata = RPMPackageMetadata::parse(&mut input)?;
let content = SkipReader::new(input)?;
Ok(RPMPackage { metadata, content })
}

/// Write the RPM package to a buffer
pub fn write(&self, out: &mut impl io::Write) -> Result<(), RPMError> {
pub fn write(&mut self, out: &mut impl io::Write) -> Result<(), RPMError> {
self.metadata.write(out)?;
out.write_all(&self.content)?;
io::copy(&mut self.content, out)?;
self.content.rewind()?;
Ok(())
}

/// Write the RPM package to a file
pub fn write_file(&self, path: impl AsRef<Path>) -> Result<(), RPMError> {
pub fn write_file(&mut self, path: impl AsRef<Path>) -> Result<(), RPMError> {
self.write(&mut io::BufWriter::new(fs::File::create(path)?))
}

/// Prepare both header and content digests as used by the `SignatureIndex`.
pub(crate) fn create_sig_header_digests(
header: &[u8],
payload: &[u8],
payload: &mut R,
) -> Result<Digests, RPMError> {
let digest_md5 = {
let mut hasher = md5::Md5::default();
hasher.update(header);
hasher.update(payload);
io::copy(payload, &mut hasher)?;
payload.rewind()?;
let hash_result = hasher.finalize();
hash_result.to_vec()
};
Expand Down Expand Up @@ -127,7 +146,7 @@ impl RPMPackage {
pub fn sign_with_timestamp<S>(
&mut self,
signer: S,
t: impl TryInto<Timestamp, Error = impl Debug>,
t: impl TryInto<Timestamp, Error = impl fmt::Debug>,
) -> Result<(), RPMError>
where
S: signature::Signing<signature::algorithm::RSA, Signature = Vec<u8>>,
Expand All @@ -139,17 +158,17 @@ impl RPMPackage {
// make sure to not hash any previous signatures in the header
self.metadata.header.write(&mut header_bytes)?;

let header_and_content_len = header_bytes.len() + self.content.len();
let content_len = stream_len(&mut self.content)?;
let header_and_content_len = header_bytes.len() as u64 + content_len;

let Digests {
header_digest_sha256,
header_digest_sha1,
header_and_content_digest,
} = Self::create_sig_header_digests(header_bytes.as_slice(), &self.content)?;
} = Self::create_sig_header_digests(header_bytes.as_slice(), &mut self.content)?;

let rsa_signature_spanning_header_only = signer.sign(header_bytes.as_slice(), t)?;
let mut header_and_content_cursor =
io::Cursor::new(header_bytes).chain(io::Cursor::new(&self.content));
let mut header_and_content_cursor = io::Cursor::new(header_bytes).chain(&mut self.content);

let rsa_signature_spanning_header_and_archive =
signer.sign(&mut header_and_content_cursor, t)?;
Expand All @@ -173,7 +192,7 @@ impl RPMPackage {

/// Verify the signature as present within the RPM package.
#[cfg(feature = "signature-meta")]
pub fn verify_signature<V>(&self, verifier: V) -> Result<(), RPMError>
pub fn verify_signature<V>(&mut self, verifier: V) -> Result<(), RPMError>
where
V: signature::Verifying<signature::algorithm::RSA, Signature = Vec<u8>>,
{
Expand All @@ -186,6 +205,9 @@ impl RPMPackage {
.get_entry_data_as_binary(IndexSignatureTag::RPMSIGTAG_RSA)?;

signature::echo_signature("signature_header(header only)", signature_header_only);
verifier.verify(header_bytes.as_slice(), signature_header_only)?;

self.verify_digests()?;

let signature_header_and_content = self
.metadata
Expand All @@ -196,25 +218,21 @@ impl RPMPackage {
"signature_header(header and content)",
signature_header_and_content,
);

verifier.verify(header_bytes.as_slice(), signature_header_only)?;
self.verify_digests()?;

let header_and_content_cursor =
io::Cursor::new(header_bytes).chain(io::Cursor::new(&self.content));
let header_and_content_cursor = io::Cursor::new(header_bytes).chain(&mut self.content);
verifier.verify(header_and_content_cursor, signature_header_and_content)?;
self.content.rewind()?;

Ok(())
}

/// Verify any digests which may be present in the RPM headers
pub fn verify_digests(&self) -> Result<(), RPMError> {
pub fn verify_digests(&mut self) -> Result<(), RPMError> {
let mut header = Vec::<u8>::with_capacity(1024);
// make sure to not hash any previous signatures in the header
self.metadata.header.write(&mut header)?;

let pkg_actual_digests =
Self::create_sig_header_digests(header.as_slice(), self.content.as_slice())?;
Self::create_sig_header_digests(header.as_slice(), &mut self.content)?;

let md5 = self
.metadata
Expand Down Expand Up @@ -273,7 +291,8 @@ impl RPMPackage {
// At the present moment even rpmbuild only supports sha256
};
let payload_digest = {
hasher.update(self.content.as_slice());
io::copy(&mut self.content, &mut hasher)?;
self.content.rewind()?;
hex::encode(hasher.finalize())
};
if payload_digest != payload_digest_val[0] {
Expand All @@ -285,6 +304,14 @@ impl RPMPackage {
}
}

impl<R> fmt::Debug for RPMPackage<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RPMPackage")
.field("metadata", &self.metadata)
.finish()
}
}

#[derive(PartialEq, Debug)]
pub struct RPMPackageMetadata {
pub lead: Lead,
Expand All @@ -301,12 +328,12 @@ impl RPMPackageMetadata {
}

/// Parse RPMPackageMetadata from the provided reader
pub fn parse(input: &mut impl io::BufRead) -> Result<Self, RPMError> {
pub fn parse(mut input: impl io::BufRead) -> Result<Self, RPMError> {
let mut lead_buffer = [0; LEAD_SIZE as usize];
input.read_exact(&mut lead_buffer)?;
let lead = Lead::parse(&lead_buffer)?;
let signature_header = Header::parse_signature(input)?;
let header = Header::parse(input)?;
let signature_header = Header::parse_signature(&mut input)?;
let header = Header::parse(&mut input)?;

Ok(RPMPackageMetadata {
lead,
Expand Down
Loading