Skip to content

Commit 9805518

Browse files
authored
Store signatures in manifest (#65)
1 parent 4b860f5 commit 9805518

13 files changed

Lines changed: 422 additions & 275 deletions

File tree

Cargo.lock

Lines changed: 293 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ owo-colors = "4"
2626
rand = "0.8.5"
2727
serde = { version = "1.0.209", features = ["derive"] }
2828
serde_json = "1.0.127"
29+
serde_with = "3.11.0"
2930
serde_yaml = "0.9.34"
3031
snafu = "0.8.4"
3132
walkdir = "2.5.0"

src/error.rs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -129,39 +129,17 @@ pub(crate) enum Error {
129129
},
130130
#[snafu(display("root hash mismatch"))]
131131
RootHashMismatch { backtrace: Option<Backtrace> },
132-
#[snafu(display("signature `{path}` already exists"))]
133-
SignatureAlreadyExists {
134-
backtrace: Option<Backtrace>,
135-
path: DisplayPath,
136-
},
137-
#[snafu(display("invalid signature filename: `{path}`"))]
138-
SignatureFilename {
139-
backtrace: Option<Backtrace>,
140-
path: DisplayPath,
141-
},
142-
#[snafu(display("invalid signature: `{path}`"))]
132+
#[snafu(display("invalid signature for public key `{public_key}`"))]
143133
SignatureInvalid {
144134
backtrace: Option<Backtrace>,
145-
path: DisplayPath,
135+
public_key: PublicKey,
146136
source: SignatureError,
147137
},
148-
#[snafu(display("malformed signature: `{path}`"))]
149-
SignatureMalformed {
150-
backtrace: Option<Backtrace>,
151-
path: DisplayPath,
152-
source: signature::Error,
153-
},
154138
#[snafu(display("no signature found for key {key}"))]
155139
SignatureMissing {
156140
backtrace: Option<Backtrace>,
157141
key: PublicKey,
158142
},
159-
#[snafu(display("invalid signature public key: `{path}`"))]
160-
SignaturePublicKey {
161-
backtrace: Option<Backtrace>,
162-
path: DisplayPath,
163-
source: public_key::Error,
164-
},
165143
#[snafu(display("I/O error reading standard input"))]
166144
StandardInputIo {
167145
backtrace: Option<Backtrace>,

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ use {
1414
lexiclean::Lexiclean,
1515
owo_colors::Styled,
1616
serde::{Deserialize, Deserializer, Serialize, Serializer},
17+
serde_with::{DeserializeFromStr, SerializeDisplay},
1718
snafu::{ensure, ErrorCompat, OptionExt, ResultExt, Snafu},
1819
std::{
1920
array::TryFromSliceError,
2021
backtrace::{Backtrace, BacktraceStatus},
2122
cmp::Ordering,
22-
collections::{BTreeMap, BTreeSet, HashMap},
23+
collections::{BTreeMap, HashMap},
2324
env,
2425
fmt::{self, Display, Formatter},
2526
fs::{self, File},
@@ -63,7 +64,6 @@ type Result<T = (), E = Error> = std::result::Result<T, E>;
6364

6465
const MASTER_PRIVATE_KEY: &str = "master.private";
6566
const MASTER_PUBLIC_KEY: &str = "master.public";
66-
const SIGNATURES: &str = "signatures";
6767

6868
fn current_dir() -> Result<Utf8PathBuf> {
6969
Utf8PathBuf::from_path_buf(env::current_dir().context(error::CurrentDir)?)

src/manifest.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use super::*;
22

3-
#[derive(Debug, Deserialize, Serialize)]
3+
#[derive(Debug, Deserialize, PartialEq, Serialize)]
44
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
55
pub(crate) struct Manifest {
6+
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
67
pub(crate) files: BTreeMap<RelativePath, Entry>,
8+
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
9+
pub(crate) signatures: BTreeMap<PublicKey, Signature>,
710
}
811

912
impl Manifest {
@@ -37,4 +40,15 @@ mod tests {
3740
serde_json::from_str::<Manifest>(&capture[1]).unwrap();
3841
}
3942
}
43+
44+
#[test]
45+
fn empty_manifest_serialization() {
46+
let manifest = Manifest {
47+
files: BTreeMap::new(),
48+
signatures: BTreeMap::new(),
49+
};
50+
let json = serde_json::to_string(&manifest).unwrap();
51+
assert_eq!(json, "{}");
52+
assert_eq!(serde_json::from_str::<Manifest>(&json).unwrap(), manifest);
53+
}
4054
}

src/private_key.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use super::*;
33
#[derive(Debug, Snafu)]
44
#[snafu(context(suffix(Error)))]
55
pub(crate) enum Error {
6-
#[snafu(display("invalid hex"))]
6+
#[snafu(display("invalid private key hex"))]
77
Hex { source: hex::FromHexError },
8-
#[snafu(display("invalid byte length {length}"))]
8+
#[snafu(display("invalid private key byte length {length}"))]
99
Length { length: usize },
10-
#[snafu(display("weak key"))]
10+
#[snafu(display("weak private key"))]
1111
Weak,
1212
}
1313

@@ -141,15 +141,15 @@ mod tests {
141141
fn parse_hex_error() {
142142
assert_eq!(
143143
"xyz".parse::<PrivateKey>().unwrap_err().to_string(),
144-
"invalid hex"
144+
"invalid private key hex"
145145
);
146146
}
147147

148148
#[test]
149149
fn parse_length_error() {
150150
assert_eq!(
151151
"0123".parse::<PrivateKey>().unwrap_err().to_string(),
152-
"invalid byte length 2"
152+
"invalid private key byte length 2"
153153
);
154154
}
155155
}

src/public_key.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ use super::*;
33
#[derive(Debug, Snafu)]
44
#[snafu(context(suffix(Error)))]
55
pub(crate) enum Error {
6-
#[snafu(display("invalid hex: `{key}`"))]
6+
#[snafu(display("invalid public key hex: `{key}`"))]
77
Hex {
88
key: String,
99
source: hex::FromHexError,
1010
},
11-
#[snafu(display("invalid key: `{key}`"))]
11+
#[snafu(display("invalid public key: `{key}`"))]
1212
Key { key: String, source: SignatureError },
13-
#[snafu(display("invalid byte length {length}: `{key}`"))]
13+
#[snafu(display("invalid public key byte length {length}: `{key}`"))]
1414
Length {
1515
key: String,
1616
length: usize,
1717
source: TryFromSliceError,
1818
},
19-
#[snafu(display("weak key: `{key}`"))]
19+
#[snafu(display("weak public key: `{key}`"))]
2020
Weak { key: String },
2121
}
2222

23-
#[derive(Clone, Debug, Eq, PartialEq)]
23+
#[derive(Clone, Debug, DeserializeFromStr, Eq, PartialEq, SerializeDisplay)]
2424
pub(crate) struct PublicKey(ed25519_dalek::VerifyingKey);
2525

2626
impl PublicKey {
@@ -149,15 +149,15 @@ mod tests {
149149
fn parse_hex_error() {
150150
assert_eq!(
151151
"xyz".parse::<PublicKey>().unwrap_err().to_string(),
152-
"invalid hex: `xyz`"
152+
"invalid public key hex: `xyz`"
153153
);
154154
}
155155

156156
#[test]
157157
fn parse_length_error() {
158158
assert_eq!(
159159
"0123".parse::<PublicKey>().unwrap_err().to_string(),
160-
"invalid byte length 2: `0123`"
160+
"invalid public key byte length 2: `0123`"
161161
);
162162
}
163163

src/signature.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ use super::*;
33
#[derive(Debug, Snafu)]
44
#[snafu(context(suffix(Error)))]
55
pub(crate) enum Error {
6-
#[snafu(display("invalid public key hex: `{key}`"))]
6+
#[snafu(display("invalid signature hex: `{signature}`"))]
77
Hex {
8-
key: String,
8+
signature: String,
99
source: hex::FromHexError,
1010
},
11-
#[snafu(display("invalid public key length {length}: `{key}`"))]
11+
#[snafu(display("invalid signature byte length {length}: `{signature}`"))]
1212
Length {
13-
key: String,
13+
signature: String,
1414
length: usize,
1515
source: TryFromSliceError,
1616
},
1717
}
1818

19-
#[derive(PartialEq)]
19+
#[derive(DeserializeFromStr, PartialEq, SerializeDisplay)]
2020
pub(crate) struct Signature(ed25519_dalek::Signature);
2121

2222
impl Signature {
@@ -59,11 +59,11 @@ impl fmt::Debug for Signature {
5959
impl FromStr for Signature {
6060
type Err = Error;
6161

62-
fn from_str(key: &str) -> Result<Self, Self::Err> {
63-
let bytes = hex::decode(key).context(HexError { key })?;
62+
fn from_str(signature: &str) -> Result<Self, Self::Err> {
63+
let bytes = hex::decode(signature).context(HexError { signature })?;
6464

6565
let array: [u8; Self::LEN] = bytes.as_slice().try_into().context(LengthError {
66-
key,
66+
signature,
6767
length: bytes.len(),
6868
})?;
6969

src/subcommand/create.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,10 @@ impl Create {
173173
bar.inc(entry.size);
174174
}
175175

176-
let manifest = Manifest { files };
177-
178-
let json = serde_json::to_string(&manifest).unwrap();
179-
180-
fs::write(&manifest_path, &json).context(error::Io {
181-
path: manifest_path,
182-
})?;
176+
let mut manifest = Manifest {
177+
files,
178+
signatures: BTreeMap::new(),
179+
};
183180

184181
if self.sign {
185182
let private_key_path = options.key_dir()?.join(MASTER_PRIVATE_KEY);
@@ -190,23 +187,14 @@ impl Create {
190187

191188
let public_key = private_key.public_key();
192189

193-
let signature_dir = root.join(SIGNATURES);
194-
195-
let signature_path = signature_dir.join(format!("{public_key}.signature"));
196-
197-
ensure! {
198-
!signature_path.try_exists().context(error::Io { path: &signature_path, })?,
199-
error::SignatureAlreadyExists { path: signature_path },
200-
}
190+
manifest.signatures.insert(public_key, signature);
191+
}
201192

202-
fs::create_dir_all(&signature_dir).context(error::Io {
203-
path: &signature_dir,
204-
})?;
193+
let json = serde_json::to_string(&manifest).unwrap();
205194

206-
fs::write(&signature_path, signature.to_string()).context(error::Io {
207-
path: signature_path,
208-
})?;
209-
}
195+
fs::write(&manifest_path, &json).context(error::Io {
196+
path: manifest_path,
197+
})?;
210198

211199
Ok(())
212200
}

src/subcommand/verify.rs

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,6 @@ mismatched file: `{path}`
128128

129129
let mut dirs = Vec::new();
130130

131-
let mut signatures = BTreeSet::new();
132-
133131
for entry in WalkDir::new(&root) {
134132
let entry = entry?;
135133

@@ -158,37 +156,6 @@ mismatched file: `{path}`
158156

159157
let path = path.strip_prefix(&root).unwrap();
160158

161-
if path == SIGNATURES {
162-
continue;
163-
}
164-
165-
if path.starts_with(SIGNATURES) {
166-
ensure! {
167-
path.extension() == Some("signature"),
168-
error::SignatureFilename { path },
169-
}
170-
171-
let pubkey = path
172-
.file_stem()
173-
.context(error::SignatureFilename { path })?
174-
.parse::<PublicKey>()
175-
.context(error::SignaturePublicKey { path })?;
176-
177-
let signature = fs::read_to_string(entry.path()).context(error::Io { path })?;
178-
179-
let signature = signature
180-
.parse::<Signature>()
181-
.context(error::SignatureMalformed { path })?;
182-
183-
pubkey
184-
.verify(root_hash.as_bytes(), &signature)
185-
.context(error::SignatureInvalid { path })?;
186-
187-
signatures.insert(pubkey);
188-
189-
continue;
190-
}
191-
192159
let path = RelativePath::try_from(path).context(error::Path { path })?;
193160

194161
ensure! {
@@ -209,9 +176,17 @@ mismatched file: `{path}`
209176
}
210177
}
211178

179+
for (public_key, signature) in &manifest.signatures {
180+
public_key
181+
.verify(root_hash.as_bytes(), signature)
182+
.context(error::SignatureInvalid {
183+
public_key: public_key.clone(),
184+
})?;
185+
}
186+
212187
if let Some(key) = self.key {
213188
ensure! {
214-
signatures.contains(&key),
189+
manifest.signatures.contains_key(&key),
215190
error::SignatureMissing { key },
216191
}
217192
}

0 commit comments

Comments
 (0)