Skip to content

Commit 01a9eba

Browse files
authored
ssh-key: fix certificate::OptionsMap encoding (#207)
The values in an `OptionsMap` are encoded with a nested length prefix. https://bugzilla.mindrot.org/show_bug.cgi?id=2389
1 parent d611814 commit 01a9eba

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

ssh-key/src/certificate/options_map.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ impl Decode for OptionsMap {
4242

4343
while !reader.is_finished() {
4444
let name = String::decode(reader)?;
45-
let data = String::decode(reader)?;
45+
let data = reader.read_prefixed(|reader| {
46+
if reader.remaining_len() > 0 {
47+
String::decode(reader)
48+
} else {
49+
Ok(String::default())
50+
}
51+
})?;
4652

4753
// Options must be lexically ordered by "name" if they appear in
4854
// the sequence. Each named option may only appear once in a
@@ -65,7 +71,16 @@ impl Encode for OptionsMap {
6571
fn encoded_len(&self) -> encoding::Result<usize> {
6672
self.iter()
6773
.try_fold(4, |acc, (name, data)| {
68-
[acc, 4, name.len(), 4, data.len()].checked_sum()
74+
[
75+
acc,
76+
name.encoded_len()?,
77+
if data.is_empty() {
78+
4
79+
} else {
80+
data.encoded_len_prefixed()?
81+
},
82+
]
83+
.checked_sum()
6984
})
7085
.map_err(Into::into)
7186
}
@@ -78,7 +93,11 @@ impl Encode for OptionsMap {
7893

7994
for (name, data) in self.iter() {
8095
name.encode(writer)?;
81-
data.encode(writer)?;
96+
if data.is_empty() {
97+
0usize.encode(writer)?;
98+
} else {
99+
data.encode_prefixed(writer)?
100+
}
82101
}
83102

84103
Ok(())

ssh-key/tests/certificate.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@ fn decode_ed25519_openssh() {
139139
assert_eq!(cert.valid_principals()[0], "host.example.com");
140140
}
141141

142+
#[test]
143+
fn decode_ed25519_openssh_with_crit_options() {
144+
let src = "[email protected] AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIBW/4zLqXWROWmN1sPgdySnH1GUsEFBjFrRwKKw71BoBAAAAIH1MFwI1oRdEifXgBQvWQfCBBtA/Pi8YCUE/I3wXFJo2AAAAAAAAAAAAAAABAAAAA2ZvbwAAAAAAAAAAAAAAAH//////////AAAAIwAAABFoZWxsb0BleGFtcGxlLmNvbQAAAAoAAAAGZm9vYmFyAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIH1MFwI1oRdEifXgBQvWQfCBBtA/Pi8YCUE/I3wXFJo2AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDRoPdI48KyoaLgaDZsSGs80qBeYQOXBd84CX8GYzFt/L21rxF1EeuPOkgsx7Q39WllXp+FgMMojsHftK/DJHEN";
145+
let cert = Certificate::from_str(src).unwrap();
146+
147+
assert_eq!(Algorithm::Ed25519, cert.public_key().algorithm());
148+
149+
assert_eq!(cert.critical_options().len(), 1);
150+
assert_eq!(
151+
cert.critical_options().get("[email protected]").unwrap(),
152+
"foobar"
153+
);
154+
155+
let openssh = cert.to_openssh().unwrap();
156+
157+
assert_eq!(openssh, src);
158+
159+
assert_eq!(cert, Certificate::from_str(&openssh).unwrap());
160+
}
161+
142162
#[test]
143163
fn decode_rsa_4096_openssh() {
144164
let cert = Certificate::from_str(RSA_4096_CERT_EXAMPLE).unwrap();

0 commit comments

Comments
 (0)