zipsign_api/
verify_unsign_tar.rs

1use std::io::{Read, Seek, SeekFrom};
2use std::mem::size_of;
3
4use base64::Engine;
5use base64::prelude::BASE64_STANDARD;
6use ed25519_dalek::{SIGNATURE_LENGTH, Signature, SignatureError};
7
8use crate::constants::{
9    BUF_LIMIT, GZIP_END, GZIP_START, HEADER_SIZE, MAGIC_HEADER, SignatureCountLeInt,
10};
11
12#[derive(Debug, thiserror::Error)]
13pub(crate) enum TarFindDataStartAndLenError {
14    #[error("the expected last GZIP block was missing or corrupted")]
15    Gzip,
16    #[error("could not read input")]
17    Read(#[source] std::io::Error),
18    #[error("could not seek inside the input")]
19    Seek(#[source] std::io::Error),
20    #[error("too many signatures in input")]
21    TooManySignatures,
22}
23
24pub(crate) fn tar_find_data_start_and_len<I>(
25    input: &mut I,
26) -> Result<(u64, usize), TarFindDataStartAndLenError>
27where
28    I: ?Sized + Read + Seek,
29{
30    let mut tail = [0; u64::BITS as usize / 4 + GZIP_END.len()];
31    let data_end = input
32        .seek(SeekFrom::End(-(tail.len() as i64)))
33        .map_err(TarFindDataStartAndLenError::Seek)?;
34
35    input
36        .read_exact(&mut tail)
37        .map_err(TarFindDataStartAndLenError::Read)?;
38    if tail[u64::BITS as usize / 4..] != *GZIP_END {
39        return Err(TarFindDataStartAndLenError::Gzip);
40    }
41    let Ok(gzip_start) = std::str::from_utf8(&tail[..16]) else {
42        return Err(TarFindDataStartAndLenError::Gzip);
43    };
44    let Ok(gzip_start) = u64::from_str_radix(gzip_start, 16) else {
45        return Err(TarFindDataStartAndLenError::Gzip);
46    };
47    let Some(data_start) = gzip_start.checked_add(10) else {
48        return Err(TarFindDataStartAndLenError::Gzip);
49    };
50    let Some(data_len) = data_end.checked_sub(data_start) else {
51        return Err(TarFindDataStartAndLenError::Gzip);
52    };
53    let Ok(data_len) = usize::try_from(data_len) else {
54        return Err(TarFindDataStartAndLenError::Gzip);
55    };
56    if data_len > BUF_LIMIT {
57        return Err(TarFindDataStartAndLenError::TooManySignatures);
58    }
59
60    Ok((gzip_start, data_len + GZIP_START.len()))
61}
62
63#[derive(Debug, thiserror::Error)]
64pub(crate) enum TarReadSignaturesError {
65    #[error("the input contained invalid base64 encoded data")]
66    Base64,
67    #[error("the input contained no signatures")]
68    Empty,
69    #[error("the expected last GZIP block was missing or corrupted")]
70    Gzip,
71    #[error("the encoded length did not fit the expected length")]
72    LengthMismatch,
73    #[error("the expected magic header was missing or corrupted")]
74    MagicHeader,
75    #[error("could not read input")]
76    Read(#[source] std::io::Error),
77    #[error("the input contained an illegal signature at index #{1}")]
78    Signature(#[source] SignatureError, usize),
79}
80
81pub(crate) fn tar_read_signatures<I>(
82    data_start: u64,
83    data_len: usize,
84    input: &mut I,
85) -> Result<Vec<Signature>, TarReadSignaturesError>
86where
87    I: ?Sized + Read + Seek,
88{
89    let _: u64 = input
90        .seek(SeekFrom::Start(data_start))
91        .map_err(TarReadSignaturesError::Read)?;
92
93    let mut data = vec![0; data_len];
94    input
95        .read_exact(&mut data)
96        .map_err(TarReadSignaturesError::Read)?;
97
98    if data[..GZIP_START.len()] != *GZIP_START {
99        return Err(TarReadSignaturesError::Gzip);
100    }
101    let Ok(data) = BASE64_STANDARD.decode(&data[GZIP_START.len()..]) else {
102        return Err(TarReadSignaturesError::Base64);
103    };
104    if data.len() < HEADER_SIZE {
105        return Err(TarReadSignaturesError::MagicHeader);
106    }
107    if data[..MAGIC_HEADER.len()] != *MAGIC_HEADER {
108        return Err(TarReadSignaturesError::MagicHeader);
109    }
110
111    let signature_count = data[MAGIC_HEADER.len()..][..size_of::<SignatureCountLeInt>()]
112        .try_into()
113        .unwrap();
114    let signature_count = SignatureCountLeInt::from_le_bytes(signature_count) as usize;
115    if signature_count == 0 {
116        return Err(TarReadSignaturesError::Empty);
117    }
118    if data.len() != HEADER_SIZE + signature_count * SIGNATURE_LENGTH {
119        return Err(TarReadSignaturesError::LengthMismatch);
120    }
121
122    let signatures = data[HEADER_SIZE..]
123        .chunks_exact(SIGNATURE_LENGTH)
124        .enumerate()
125        .map(|(idx, bytes)| {
126            Signature::from_slice(bytes).map_err(|err| TarReadSignaturesError::Signature(err, idx))
127        })
128        .collect::<Result<Vec<_>, _>>()?;
129    Ok(signatures)
130}