Skip to content

Commit 1220283

Browse files
authored
Include public key in signature (#224)
1 parent 352a432 commit 1220283

File tree

17 files changed

+163
-1169
lines changed

17 files changed

+163
-1169
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,8 @@ walkdir = "2.5.0"
4242

4343
[dev-dependencies]
4444
pretty_assertions = "1.4.1"
45-
ssh-key = { version = "0.6.0", features = ["ed25519"] }
4645
tempfile = "3.24.0"
4746

48-
[dev-dependencies.sequoia-openpgp]
49-
version = "2.1.0"
50-
default-features = false
51-
features = ["allow-experimental-crypto", "allow-variable-time-crypto", "crypto-rust"]
52-
5347
[lib]
5448
doctest = false
5549

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,7 @@ such as `C:`.
196196
### `notes`
197197

198198
The value of the mandatory `notes` key is an array of signed notes. Notes are
199-
objects containing a single mandatory key `signatures`, an object mapping
200-
public keys to signatures.
199+
objects containing a single mandatory key `signatures`, an array of signatures.
201200

202201
Notes may optionally contain a `time` field whose value is a timestamp given as
203202
the number of nanoseconds after the UNIX epoch.
@@ -228,16 +227,17 @@ signed by the public key
228227
},
229228
"notes": [
230229
{
231-
"signatures": {
232-
"public1a67dndhhmae7p6fsfnj0z37zf78cde6mwqgtms0y87h8ldlvvflyqcxnd63": ""
233-
},
230+
"signatures": [
231+
""
232+
],
234233
"time": 1768531681809767000
235234
}
236235
]
237236
}
238237
```
239238

240-
The signature is elided for brevity.
239+
The signature is elided for brevity. Signatures are bech32m-encoded strings
240+
containing both a public key and an Ed25519 signature.
241241

242242
Keys, Signatures, Fingerprints, and Hashes
243243
------------------------------------------
@@ -431,7 +431,7 @@ filepack sign
431431
```
432432

433433
Which signs the manifest in the current directory with your master key and adds
434-
the signature to the manifest's `signatures` map. Signatures are made over a
434+
the signature to the manifest's `signatures` array. Signatures are made over a
435435
fingerprint hash, recursively calculated from the contents of the manifest.
436436

437437
### Signature Verification

src/bech32_decoder.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,13 @@ pub(crate) struct Bech32Decoder<'a> {
88
}
99

1010
impl<'a> Bech32Decoder<'a> {
11-
pub(crate) fn byte_array<const LEN: usize>(mut self) -> Result<[u8; LEN], Bech32Error> {
11+
pub(crate) fn byte_array<const LEN: usize>(&mut self) -> Result<[u8; LEN], Bech32Error> {
1212
let mut array = [0; LEN];
1313

1414
for (slot, byte) in array.iter_mut().zip(self.bytes(LEN)?) {
1515
*slot = byte;
1616
}
1717

18-
let excess = self.data.len() - self.i;
19-
20-
ensure! {
21-
excess == 0,
22-
bech32_error::Overlong { excess, ty: self.ty },
23-
}
24-
2518
Ok(array)
2619
}
2720

@@ -41,6 +34,27 @@ impl<'a> Bech32Decoder<'a> {
4134
Ok(fes.fes_to_bytes())
4235
}
4336

37+
pub(crate) fn decode_byte_array<const LEN: usize>(
38+
ty: Bech32Type,
39+
s: &'a str,
40+
) -> Result<[u8; LEN], Bech32Error> {
41+
let mut decoder = Self::new(ty, s)?;
42+
let array = decoder.byte_array()?;
43+
decoder.done()?;
44+
Ok(array)
45+
}
46+
47+
pub(crate) fn done(self) -> Result<(), Bech32Error> {
48+
let excess = self.data.len() - self.i;
49+
50+
ensure! {
51+
excess == 0,
52+
bech32_error::Overlong { excess, ty: self.ty },
53+
}
54+
55+
Ok(())
56+
}
57+
4458
fn fes(
4559
&mut self,
4660
len: usize,
@@ -105,8 +119,7 @@ mod tests {
105119
#[track_caller]
106120
fn case(s: &str, err: &str) {
107121
assert_eq!(
108-
Bech32Decoder::new(Bech32Type::PublicKey, &checksum(s))
109-
.and_then(Bech32Decoder::byte_array::<1>)
122+
Bech32Decoder::decode_byte_array::<1>(Bech32Type::PublicKey, &checksum(s))
110123
.unwrap_err()
111124
.to_string(),
112125
err,

src/fingerprint.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ impl FromStr for Fingerprint {
2323
type Err = Bech32Error;
2424

2525
fn from_str(s: &str) -> Result<Self, Self::Err> {
26-
let decoder = Bech32Decoder::new(Bech32Type::Fingerprint, s)?;
27-
let inner = decoder.byte_array()?;
26+
let inner = Bech32Decoder::decode_byte_array(Bech32Type::Fingerprint, s)?;
2827
Ok(Self(inner.into()))
2928
}
3029
}

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ use {
5858
public_key_error::PublicKeyError,
5959
serialized_message::SerializedMessage,
6060
sign_options::SignOptions,
61+
signature_error::SignatureError,
6162
style::Style,
6263
subcommand::Subcommand,
6364
tag::Tag,
@@ -77,7 +78,9 @@ use {
7778
owo_colors::Styled,
7879
regex::Regex,
7980
serde::{Deserialize, Deserializer, Serialize, Serializer},
80-
serde_with::{DeserializeFromStr, MapPreventDuplicates, SerializeDisplay, serde_as},
81+
serde_with::{
82+
DeserializeFromStr, MapPreventDuplicates, SerializeDisplay, SetPreventDuplicates, serde_as,
83+
},
8184
snafu::{ErrorCompat, OptionExt, ResultExt, Snafu, ensure},
8285
std::{
8386
backtrace::{Backtrace, BacktraceStatus},
@@ -199,6 +202,7 @@ mod relative_path;
199202
mod serialized_message;
200203
mod sign_options;
201204
mod signature;
205+
mod signature_error;
202206
mod style;
203207
mod subcommand;
204208
mod tag;

src/manifest.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,15 @@ impl Manifest {
100100
for note in &mut self.notes {
101101
if note.message(message.fingerprint) == message {
102102
ensure! {
103-
note.signatures.insert(key, signature).is_none() || options.overwrite,
103+
!note.has_signature(key) || options.overwrite,
104104
error::SignatureAlreadyExists { key },
105105
}
106+
note.signatures.insert(signature);
106107
return Ok(());
107108
}
108109
}
109110

110-
self.notes.push(Note::from_message(message, key, signature));
111+
self.notes.push(Note::from_message(message, signature));
111112

112113
Ok(())
113114
}
@@ -128,7 +129,8 @@ impl Manifest {
128129
let mut digests = BTreeMap::new();
129130
let mut signatures = BTreeMap::new();
130131
for (index, note) in self.notes.iter().enumerate() {
131-
for &public_key in note.signatures.keys() {
132+
for signature in &note.signatures {
133+
let public_key = signature.public_key();
132134
if let Some(first) = signatures.insert(public_key, index) {
133135
return Err(
134136
error::DuplicateSignature {

src/note.rs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,25 @@ use super::*;
44
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
55
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
66
pub struct Note {
7-
#[serde_as(as = "MapPreventDuplicates<_, _>")]
8-
pub signatures: BTreeMap<PublicKey, Signature>,
7+
#[serde_as(as = "SetPreventDuplicates<_>")]
8+
pub signatures: BTreeSet<Signature>,
99
#[serde(default, skip_serializing_if = "is_default")]
1010
pub time: Option<u128>,
1111
}
1212

1313
impl Note {
14-
pub(crate) fn from_message(
15-
message: Message,
16-
public_key: PublicKey,
17-
signature: Signature,
18-
) -> Self {
14+
pub(crate) fn from_message(message: Message, signature: Signature) -> Self {
1915
Self {
20-
signatures: [(public_key, signature)].into(),
16+
signatures: [signature].into(),
2117
time: message.time,
2218
}
2319
}
2420

25-
pub(crate) fn has_signature(&self, public_key: PublicKey) -> bool {
26-
self.signatures.contains_key(&public_key)
21+
pub fn has_signature(&self, public_key: PublicKey) -> bool {
22+
self
23+
.signatures
24+
.iter()
25+
.any(|signature| signature.public_key() == public_key)
2726
}
2827

2928
pub(crate) fn message(&self, fingerprint: Fingerprint) -> Message {
@@ -35,8 +34,8 @@ impl Note {
3534

3635
pub(crate) fn verify(&self, fingerprint: Fingerprint) -> Result<u64> {
3736
let serialized = self.message(fingerprint).serialize();
38-
for (public_key, signature) in &self.signatures {
39-
public_key.verify(&serialized, signature)?;
37+
for signature in &self.signatures {
38+
signature.verify(&serialized)?;
4039
}
4140
Ok(self.signatures.len().into_u64())
4241
}
@@ -49,7 +48,7 @@ mod tests {
4948
#[test]
5049
fn duplicate_fields_are_rejected() {
5150
assert_eq!(
52-
serde_json::from_str::<Note>(r#"{"signatures":{},"signatures":{}}"#)
51+
serde_json::from_str::<Note>(r#"{"signatures":[],"signatures":[]}"#)
5352
.unwrap_err()
5453
.to_string(),
5554
"duplicate field `signatures` at line 1 column 29",
@@ -59,28 +58,26 @@ mod tests {
5958
#[test]
6059
fn duplicate_signatures_are_rejected() {
6160
let json = format!(
62-
r#"{{"signatures":{{"{}":"{}","{}":"{}"}}}}"#,
63-
test::PUBLIC_KEY,
61+
r#"{{"signatures":["{}","{}"]}}"#,
6462
test::SIGNATURE,
65-
test::PUBLIC_KEY,
6663
test::SIGNATURE,
6764
);
6865

6966
assert_matches_regex! {
7067
serde_json::from_str::<Note>(&json).unwrap_err().to_string(),
71-
r"invalid entry: found duplicate key at line 1 column \d+",
68+
r"invalid entry: found duplicate value at line 1 column \d+",
7269
}
7370
}
7471

7572
#[test]
7673
fn optional_fields_are_not_serialized() {
7774
assert_eq!(
7875
serde_json::to_string(&Note {
79-
signatures: BTreeMap::new(),
76+
signatures: BTreeSet::new(),
8077
time: None,
8178
})
8279
.unwrap(),
83-
r#"{"signatures":{}}"#,
80+
r#"{"signatures":[]}"#,
8481
);
8582
}
8683
}

src/private_key.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,15 @@ impl PrivateKey {
4949

5050
pub(crate) fn sign(&self, message: &SerializedMessage) -> Signature {
5151
use ed25519_dalek::Signer;
52-
Signature::new(self.0.sign(message.as_bytes()))
52+
Signature::new(self.0.sign(message.as_bytes()), self.public_key())
5353
}
5454
}
5555

5656
impl FromStr for PrivateKey {
5757
type Err = Bech32Error;
5858

5959
fn from_str(key: &str) -> Result<Self, Self::Err> {
60-
let decoder = Bech32Decoder::new(Bech32Type::PrivateKey, key)?;
61-
let inner = decoder.byte_array()?;
60+
let inner = Bech32Decoder::decode_byte_array(Bech32Type::PrivateKey, key)?;
6261
let inner = ed25519_dalek::SigningKey::from_bytes(&inner);
6362
assert!(!inner.verifying_key().is_weak());
6463
Ok(Self(inner))

src/public_key.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ impl PublicKey {
4242

4343
Ok(public_key)
4444
}
45-
46-
pub(crate) fn verify(self, message: &SerializedMessage, signature: &Signature) -> Result {
47-
signature.verify(message, self)
48-
}
4945
}
5046

5147
impl From<PrivateKey> for PublicKey {
@@ -58,8 +54,7 @@ impl FromStr for PublicKey {
5854
type Err = PublicKeyError;
5955

6056
fn from_str(key: &str) -> Result<Self, Self::Err> {
61-
let decoder = Bech32Decoder::new(Bech32Type::PublicKey, key)?;
62-
let inner = decoder.byte_array()?;
57+
let inner = Bech32Decoder::decode_byte_array(Bech32Type::PublicKey, key)?;
6358
Self::from_bytes(inner)
6459
}
6560
}

0 commit comments

Comments
 (0)