Skip to content

Commit 306257d

Browse files
Fix parsing signature with size-1 trees
The ascii format for Sigsum signatures makes signature with a size-1 trees a special case where the inclusion proof is omitted. This was not supported here, we instead were expecting (or generating) an inclusion proof that would be the single line `leaf_index=0`. We now correctly parse and generate two-parts signature for size-1 trees.
1 parent eff83f3 commit 306257d

1 file changed

Lines changed: 144 additions & 21 deletions

File tree

src/io/ascii/mod.rs

Lines changed: 144 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,12 @@ impl Protoleaf {
113113

114114
impl SigsumSignature {
115115
pub fn from_ascii(input: &str) -> Result<Self> {
116-
let parts: Vec<&str> = input.split("\n\n").collect();
117-
if parts.len() != 3 {
118-
return Err(ParseAsciiError(format!(
119-
"expected 3 parts, got {}",
120-
parts.len()
121-
)));
122-
}
123-
let mut p = Parser::new(parts[0]);
116+
let mut parts = input.split("\n\n");
117+
let mut p = Parser::new(
118+
parts
119+
.next()
120+
.expect("str.split() should return at least one part"),
121+
);
124122
let version: u64 = p.parse(VERSION_FIELD)?;
125123
if version != 2 {
126124
return Err(ParseAsciiError(format!("version {version} not supported")));
@@ -132,8 +130,32 @@ impl SigsumSignature {
132130
"expected an empty line after 'leaf'".into(),
133131
));
134132
}
135-
let sth = SignedTreeHead::from_ascii(parts[1])?;
136-
let proof = InclusionProof::from_ascii(parts[2])?;
133+
134+
// Parsing the tree head part
135+
let Some(sth_part) = parts.next() else {
136+
return Err(ParseAsciiError("missing tree head".into()));
137+
};
138+
let sth = SignedTreeHead::from_ascii(sth_part)?;
139+
140+
// Parsing the inclusion proof iff the tree size is not 1
141+
let proof;
142+
if sth.size > 1 {
143+
let Some(proof_part) = parts.next() else {
144+
return Err(ParseAsciiError("missing inclusion proof".into()));
145+
};
146+
proof = InclusionProof::from_ascii(proof_part)?;
147+
} else if sth.size == 1 {
148+
proof = InclusionProof {
149+
leaf_index: 0,
150+
node_hashes: Vec::new(),
151+
};
152+
} else {
153+
return Err(ParseAsciiError("empty tree".into()));
154+
}
155+
if parts.next().is_some() {
156+
return Err(ParseAsciiError("trailing data".into()));
157+
};
158+
137159
Ok(Self {
138160
log_keyhash,
139161
leaf_keyhash,
@@ -164,10 +186,12 @@ impl SigsumSignature {
164186
cosig.keyhash, cosig.timestamp, cosig.cosignature
165187
);
166188
}
167-
writeln!(ascii);
168-
writeln!(ascii, "{LEAF_INDEX_FIELD}={}", self.proof.leaf_index);
169-
for h in self.proof.node_hashes.iter() {
170-
writeln!(ascii, "{NODE_HASH_FIELD}={h:x}");
189+
if self.sth.size > 1 {
190+
writeln!(ascii);
191+
writeln!(ascii, "{LEAF_INDEX_FIELD}={}", self.proof.leaf_index);
192+
for h in self.proof.node_hashes.iter() {
193+
writeln!(ascii, "{NODE_HASH_FIELD}={h:x}");
194+
}
171195
}
172196
ascii
173197
}
@@ -295,15 +319,70 @@ mod tests {
295319
";
296320

297321
#[test]
298-
fn sigsumsig_from_ascii() {
322+
fn ssig_from_ascii() {
299323
assert_eq!(*SSIG, SigsumSignature::from_ascii(SSIG_ASCII).unwrap());
300324
}
301325

302326
#[test]
303-
fn sigsumsig_to_ascii() {
327+
fn ssig_to_ascii() {
304328
assert_eq!(SSIG_ASCII, (*SSIG).to_ascii());
305329
}
306330

331+
lazy_static! {
332+
static ref SSIG_SIZE1: SigsumSignature = SigsumSignature {
333+
log_keyhash: hex!("4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d")
334+
.into(),
335+
leaf_keyhash: hex!("16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245").into(),
336+
leaf_signature: hex!("e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900").into(),
337+
sth: SignedTreeHead {
338+
size: 1,
339+
root_hash: hex!("8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6").into(),
340+
signature: hex!("e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d").into(),
341+
cosignatures: vec![
342+
WitnessCosignature {
343+
keyhash: hex!("1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de").into(),
344+
timestamp: 1687170591,
345+
cosignature: hex!("cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a").into(),
346+
},
347+
WitnessCosignature {
348+
keyhash: hex!("73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531").into(),
349+
timestamp: 1687170591,
350+
cosignature: hex!("7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f").into(),
351+
},
352+
],
353+
},
354+
proof: InclusionProof {
355+
leaf_index: 0,
356+
node_hashes: vec![],
357+
},
358+
};
359+
}
360+
361+
const SSIG_SIZE1_ASCII:&str = "\
362+
version=2\n\
363+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
364+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
365+
\n\
366+
size=1\n\
367+
root_hash=8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6\n\
368+
signature=e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d\n\
369+
cosignature=1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de 1687170591 cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a\n\
370+
cosignature=73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531 1687170591 7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f\n\
371+
";
372+
373+
#[test]
374+
fn ssig_size1_from_ascii() {
375+
assert_eq!(
376+
*SSIG_SIZE1,
377+
SigsumSignature::from_ascii(SSIG_SIZE1_ASCII).unwrap()
378+
);
379+
}
380+
381+
#[test]
382+
fn ssig_size1_to_ascii() {
383+
assert_eq!(SSIG_SIZE1_ASCII, (*SSIG_SIZE1).to_ascii());
384+
}
385+
307386
macro_rules! test_ssig_parse_error {
308387
($testname:ident, $errormsg:literal, $input:literal) => {
309388
#[test]
@@ -316,9 +395,21 @@ mod tests {
316395
};
317396
}
318397

398+
test_ssig_parse_error! { ssig_empty, "unexpected end of input", "" }
399+
319400
test_ssig_parse_error! {
320-
ssig_missing_part,
321-
"expected 3 parts, got 2",
401+
ssig_missing_sth,
402+
"missing tree head",
403+
"\
404+
version=2\n\
405+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
406+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
407+
"
408+
}
409+
410+
test_ssig_parse_error! {
411+
ssig_missing_proof,
412+
"missing inclusion proof",
322413
"\
323414
version=2\n\
324415
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
@@ -330,23 +421,55 @@ mod tests {
330421
"
331422
}
332423

333-
test_ssig_parse_error! { sigsum_signature_too_many_parts,
334-
"expected 3 parts, got 4",
424+
test_ssig_parse_error! {
425+
ssig_empty_tree,
426+
"empty tree",
335427
"\
336428
version=2\n\
337429
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
338430
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
339431
\n\
340-
size=1\n\
432+
size=0\n\
433+
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
434+
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
435+
"
436+
}
437+
438+
test_ssig_parse_error! {
439+
ssig_trailing_data,
440+
"trailing data",
441+
"\
442+
version=2\n\
443+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
444+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
445+
\n\
446+
size=2\n\
341447
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
342448
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
343449
\n\
344450
leaf_index=1\n\
451+
node_hash=35fd6eb70d46d60679775c346225688e6e84c02c3c7978e5c51daf8decc22d2f\n\
345452
\n\
346453
abc=123\n\
347454
"
348455
}
349456

457+
test_ssig_parse_error! {
458+
ssig_size1_trailing_data,
459+
"trailing data",
460+
"\
461+
version=2\n\
462+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
463+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
464+
\n\
465+
size=1\n\
466+
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
467+
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
468+
\n\
469+
leaf_index=0\n\
470+
"
471+
}
472+
350473
test_ssig_parse_error! { sigsum_signature_version_not_supported,
351474
"version 666 not supported",
352475
"\

0 commit comments

Comments
 (0)