diff --git a/ssh-key/src/certificate/options_map.rs b/ssh-key/src/certificate/options_map.rs index e1f34d9..fbc4130 100644 --- a/ssh-key/src/certificate/options_map.rs +++ b/ssh-key/src/certificate/options_map.rs @@ -42,7 +42,13 @@ impl Decode for OptionsMap { while !reader.is_finished() { let name = String::decode(reader)?; - let data = String::decode(reader)?; + let data = reader.read_prefixed(|reader| { + if reader.remaining_len() > 0 { + String::decode(reader) + } else { + Ok(String::default()) + } + })?; // Options must be lexically ordered by "name" if they appear in // the sequence. Each named option may only appear once in a @@ -65,7 +71,16 @@ impl Encode for OptionsMap { fn encoded_len(&self) -> encoding::Result { self.iter() .try_fold(4, |acc, (name, data)| { - [acc, 4, name.len(), 4, data.len()].checked_sum() + [ + acc, + name.encoded_len()?, + if data.is_empty() { + 4 + } else { + data.encoded_len_prefixed()? + }, + ] + .checked_sum() }) .map_err(Into::into) } @@ -78,7 +93,11 @@ impl Encode for OptionsMap { for (name, data) in self.iter() { name.encode(writer)?; - data.encode(writer)?; + if data.is_empty() { + 0usize.encode(writer)?; + } else { + data.encode_prefixed(writer)? + } } Ok(()) diff --git a/ssh-key/tests/certificate.rs b/ssh-key/tests/certificate.rs index 41ae193..632c06f 100644 --- a/ssh-key/tests/certificate.rs +++ b/ssh-key/tests/certificate.rs @@ -139,6 +139,26 @@ fn decode_ed25519_openssh() { assert_eq!(cert.valid_principals()[0], "host.example.com"); } +#[test] +fn decode_ed25519_openssh_with_crit_options() { + let src = "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIBW/4zLqXWROWmN1sPgdySnH1GUsEFBjFrRwKKw71BoBAAAAIH1MFwI1oRdEifXgBQvWQfCBBtA/Pi8YCUE/I3wXFJo2AAAAAAAAAAAAAAABAAAAA2ZvbwAAAAAAAAAAAAAAAH//////////AAAAIwAAABFoZWxsb0BleGFtcGxlLmNvbQAAAAoAAAAGZm9vYmFyAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIH1MFwI1oRdEifXgBQvWQfCBBtA/Pi8YCUE/I3wXFJo2AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDRoPdI48KyoaLgaDZsSGs80qBeYQOXBd84CX8GYzFt/L21rxF1EeuPOkgsx7Q39WllXp+FgMMojsHftK/DJHEN"; + let cert = Certificate::from_str(src).unwrap(); + + assert_eq!(Algorithm::Ed25519, cert.public_key().algorithm()); + + assert_eq!(cert.critical_options().len(), 1); + assert_eq!( + cert.critical_options().get("hello@example.com").unwrap(), + "foobar" + ); + + let openssh = cert.to_openssh().unwrap(); + + assert_eq!(openssh, src); + + assert_eq!(cert, Certificate::from_str(&openssh).unwrap()); +} + #[test] fn decode_rsa_4096_openssh() { let cert = Certificate::from_str(RSA_4096_CERT_EXAMPLE).unwrap();