Skip to content

Commit 96acaa6

Browse files
committed
Verify whole ZIP file
1 parent 0351f45 commit 96acaa6

File tree

4 files changed

+110
-34
lines changed

4 files changed

+110
-34
lines changed

Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ regex = "1"
2727
log = "0.4"
2828
urlencoding = "2.1"
2929
self-replace = "1"
30-
ed25519-dalek = { version = "2", optional = true }
31-
memmap2 = { version = "0.7", optional = true }
30+
ed25519-dalek = { version = "2", features = ["digest"], optional = true }
3231

3332
[features]
3433
default = ["reqwest/default-tls"]
@@ -38,7 +37,7 @@ compression-zip-deflate = ["zip/deflate"] #
3837
archive-tar = ["tar"]
3938
compression-flate2 = ["flate2", "either"] #
4039
rustls = ["reqwest/rustls-tls"]
41-
signatures = ["ed25519-dalek", "memmap2"]
40+
signatures = ["ed25519-dalek"]
4241

4342
[package.metadata.docs.rs]
4443
# Whether to pass `--all-features` to Cargo (default: false)

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ use std::path;
141141
#[macro_use]
142142
extern crate log;
143143

144+
#[cfg(feature = "signatures")]
145+
mod signatures;
144146
#[macro_use]
145147
mod macros;
146148
pub mod backends;

src/signatures.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use std::convert::TryInto;
2+
use std::fs::File;
3+
use std::io::{copy, Read, Seek, SeekFrom};
4+
use std::path::Path;
5+
6+
use ed25519_dalek::{Digest, Sha512, Signature, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
7+
8+
use crate::errors::Error;
9+
use crate::{detect_archive, ArchiveKind};
10+
11+
const MAGIC_HEADER: &[u8; 14] = b"\x0c\x04\x01ed25519ph\x00\x00";
12+
const HEADER_SIZE: usize = 16;
13+
type SignatureCountLeInt = u16;
14+
15+
pub(crate) fn verify(archive_path: &Path, keys: &[[u8; PUBLIC_KEY_LENGTH]]) -> crate::Result<()> {
16+
if keys.is_empty() {
17+
return Ok(());
18+
}
19+
20+
println!("Verifying downloaded file...");
21+
22+
let keys = keys
23+
.into_iter()
24+
.map(VerifyingKey::from_bytes)
25+
.collect::<Result<Vec<_>, _>>()
26+
.map_err(|_| Error::NoValidSignature)?;
27+
let file_name = archive_path
28+
.file_name()
29+
.and_then(|s| s.to_str())
30+
.map(|s| s.as_bytes())
31+
.ok_or(Error::NoValidSignature)?;
32+
let archive_kind = detect_archive(&archive_path)?;
33+
34+
let mut exe = File::open(&archive_path)?;
35+
36+
match archive_kind {
37+
ArchiveKind::Plain(_) => {
38+
unimplemented!("Can only check signatures for .zip and .tar* files.")
39+
}
40+
#[cfg(feature = "archive-tar")]
41+
ArchiveKind::Tar(_) => do_verify(&mut exe, &keys, file_name, true),
42+
#[cfg(feature = "archive-zip")]
43+
ArchiveKind::Zip => do_verify(&mut exe, &keys, file_name, false),
44+
}
45+
}
46+
47+
fn do_verify(
48+
exe: &mut File,
49+
keys: &[VerifyingKey],
50+
context: &[u8],
51+
signature_at_eof: bool,
52+
) -> Result<(), Error> {
53+
if signature_at_eof {
54+
exe.seek(SeekFrom::End(-(HEADER_SIZE as i64)))?;
55+
}
56+
57+
let mut header = [0; HEADER_SIZE];
58+
exe.read_exact(&mut header)?;
59+
if header[..MAGIC_HEADER.len()] != MAGIC_HEADER[..] {
60+
println!("Signature header was not found.");
61+
return Err(Error::NoValidSignature);
62+
}
63+
let signature_count = header[MAGIC_HEADER.len()..].try_into().unwrap();
64+
let signature_count = SignatureCountLeInt::from_le_bytes(signature_count) as usize;
65+
let signature_size = signature_count * SIGNATURE_LENGTH;
66+
67+
let content_size = match signature_at_eof {
68+
false => 0,
69+
true => exe.seek(SeekFrom::End(-((HEADER_SIZE + signature_size) as i64)))?
70+
};
71+
72+
let mut signatures = vec![0; signature_size];
73+
exe.read_exact(&mut signatures)?;
74+
let signatures = signatures
75+
.chunks_exact(SIGNATURE_LENGTH)
76+
.map(Signature::from_slice)
77+
.collect::<Result<Vec<_>, _>>()
78+
.map_err(|_| Error::NoValidSignature)?;
79+
80+
let mut prehashed_message = Sha512::new();
81+
match signature_at_eof {
82+
false => {
83+
copy(exe, &mut prehashed_message)?;
84+
}
85+
true => {
86+
exe.seek(SeekFrom::Start(0))?;
87+
copy(&mut exe.take(content_size), &mut prehashed_message)?;
88+
}
89+
}
90+
91+
for key in keys {
92+
for signature in &signatures {
93+
if key
94+
.verify_prehashed_strict(prehashed_message.clone(), Some(context), signature)
95+
.is_ok()
96+
{
97+
println!("OK");
98+
return Ok(());
99+
}
100+
}
101+
}
102+
Err(Error::NoValidSignature)
103+
}

src/update.rs

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -233,43 +233,15 @@ pub trait ReleaseUpdate {
233233

234234
download.download_to(&mut tmp_archive)?;
235235

236+
#[cfg(feature = "signatures")]
237+
crate::signatures::verify(&tmp_archive_path, self.verifying_keys())?;
238+
236239
print_flush(show_output, "Extracting archive... ")?;
237240
let bin_path_in_archive = self.bin_path_in_archive();
238241
Extract::from_source(&tmp_archive_path)
239242
.extract_file(tmp_archive_dir.path(), &bin_path_in_archive)?;
240243
let new_exe = tmp_archive_dir.path().join(&bin_path_in_archive);
241244

242-
#[cfg(feature = "signatures")]
243-
{
244-
use std::io::Read;
245-
246-
let verifying_keys = self.verifying_keys();
247-
if !verifying_keys.is_empty() {
248-
// TODO: FIXME: this only works for signed .zip files, not .tar
249-
let mut signature = [0; ed25519_dalek::SIGNATURE_LENGTH];
250-
fs::File::open(&tmp_archive_path)?.read_exact(&mut signature)?;
251-
let signature = ed25519_dalek::Signature::from_bytes(&signature);
252-
253-
let exe = fs::File::open(&new_exe)?;
254-
let exe = unsafe { memmap2::Mmap::map(&exe)? };
255-
256-
let mut valid_signature = false;
257-
for (idx, bytes) in verifying_keys.into_iter().enumerate() {
258-
let key = match ed25519_dalek::VerifyingKey::from_bytes(&bytes) {
259-
Ok(key) => key,
260-
Err(_) => panic!("Key #{} is invalid", idx),
261-
};
262-
if key.verify_strict(&exe, &signature).is_ok() {
263-
valid_signature = true;
264-
break;
265-
}
266-
}
267-
if !valid_signature {
268-
return Err(Error::NoValidSignature);
269-
}
270-
}
271-
}
272-
273245
println(show_output, "Done");
274246

275247
print_flush(show_output, "Replacing binary file... ")?;

0 commit comments

Comments
 (0)