zipsign_api/sign/
tar.rs

1#![cfg_attr(docsrs, doc(cfg(feature = "sign-tar")))]
2
3use std::io::{Read, Seek, Write, copy};
4
5use base64::Engine;
6use base64::prelude::BASE64_STANDARD;
7use ed25519_dalek::SIGNATURE_LENGTH;
8
9use super::{GatherSignatureDataError, gather_signature_data};
10use crate::constants::{
11    BUF_LIMIT, GZIP_END, GZIP_EXTRA, GZIP_START, HEADER_SIZE, SignatureCountLeInt,
12};
13use crate::{Prehash, SigningKey};
14
15crate::Error! {
16    /// An error returned by [`copy_and_sign_tar()`]
17    pub struct SignTarError(Error) {
18        #[error("could not copy input to output")]
19        Copy(#[source] std::io::Error),
20        #[error("could not read input")]
21        InputRead(#[source] std::io::Error),
22        #[error("could not seek in input")]
23        InputSeek(#[source] std::io::Error),
24        #[error("could not seek in output")]
25        OutputSeek(#[source] std::io::Error),
26        #[error("could not write output")]
27        OutputWrite(#[source] std::io::Error),
28        #[error("could not sign pre-hashed message")]
29        Sign(#[source] GatherSignatureDataError),
30        #[error("too many keys")]
31        TooManyKeys,
32    }
33}
34
35/// Copy a `.tar.gz` file and sign its content
36pub fn copy_and_sign_tar<I, O>(
37    input: &mut I,
38    output: &mut O,
39    keys: &[SigningKey],
40    context: Option<&[u8]>,
41) -> Result<(), SignTarError>
42where
43    I: ?Sized + Seek + Read,
44    O: ?Sized + Seek + Write,
45{
46    if keys.len() > SignatureCountLeInt::MAX as usize {
47        return Err(Error::TooManyKeys.into());
48    }
49    let signature_bytes = SIGNATURE_LENGTH * keys.len() + HEADER_SIZE;
50    if (signature_bytes.saturating_add(2) / 3).saturating_mul(4) > BUF_LIMIT {
51        return Err(Error::TooManyKeys.into());
52    }
53
54    // gather signature
55    let prehashed_message = Prehash::calculate(input).map_err(Error::InputRead)?;
56    let buf = gather_signature_data(keys, &prehashed_message, context).map_err(Error::Sign)?;
57    let buf = BASE64_STANDARD.encode(buf);
58    if buf.len() > BUF_LIMIT {
59        return Err(Error::TooManyKeys.into());
60    }
61
62    // copy input
63    input.rewind().map_err(Error::InputSeek)?;
64    let _: u64 = copy(input, output).map_err(Error::Copy)?;
65
66    // write signature
67    let start = output.stream_position().map_err(Error::OutputSeek)?;
68    let mut start_buf = [0u8; 16];
69    write!(&mut start_buf[..], "{start:016x}").unwrap();
70
71    let mut tail = Vec::with_capacity(GZIP_EXTRA + buf.len());
72    tail.extend(GZIP_START);
73    tail.extend(buf.into_bytes()); // GZIP comment
74    tail.extend(start_buf); // GZIP comment
75    tail.extend(GZIP_END);
76    output.write_all(&tail).map_err(Error::OutputWrite)?;
77
78    Ok(())
79}