ssh_key/public/
openssh.rs
1use crate::{writer::Base64Writer, Error, Result};
16use core::str;
17
18#[derive(Clone, Debug, Eq, PartialEq)]
20pub(crate) struct Encapsulation<'a> {
21 pub(crate) algorithm_id: &'a str,
23
24 pub(crate) base64_data: &'a [u8],
26
27 #[cfg_attr(not(feature = "alloc"), allow(dead_code))]
29 pub(crate) comment: &'a str,
30}
31
32impl<'a> Encapsulation<'a> {
33 pub(crate) fn decode(mut bytes: &'a [u8]) -> Result<Self> {
35 let algorithm_id = decode_segment_str(&mut bytes)?;
36 let base64_data = decode_segment(&mut bytes)?;
37 let comment = str::from_utf8(bytes)
38 .map_err(|_| Error::CharacterEncoding)?
39 .trim_end();
40 if algorithm_id.is_empty() || base64_data.is_empty() {
41 return Err(Error::Length);
43 }
44 Ok(Self {
45 algorithm_id,
46 base64_data,
47 comment,
48 })
49 }
50
51 pub(crate) fn encode<'o, F>(
53 out: &'o mut [u8],
54 algorithm_id: &str,
55 comment: &str,
56 f: F,
57 ) -> Result<&'o str>
58 where
59 F: FnOnce(&mut Base64Writer<'_>) -> Result<()>,
60 {
61 let mut offset = 0;
62 encode_str(out, &mut offset, algorithm_id)?;
63 encode_str(out, &mut offset, " ")?;
64
65 let mut writer = Base64Writer::new(&mut out[offset..])?;
66 f(&mut writer)?;
67 let base64_len = writer.finish()?.len();
68
69 offset = offset.checked_add(base64_len).ok_or(Error::Length)?;
70 if !comment.is_empty() {
71 encode_str(out, &mut offset, " ")?;
72 encode_str(out, &mut offset, comment)?;
73 }
74 Ok(str::from_utf8(&out[..offset])?)
75 }
76}
77
78fn decode_segment<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8]> {
80 let start = *bytes;
81 let mut len = 0usize;
82
83 loop {
84 match *bytes {
85 [b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'-' | b'/' | b'=' | b'@' | b'.', rest @ ..] =>
86 {
87 *bytes = rest;
89 len = len.checked_add(1).ok_or(Error::Length)?;
90 }
91 [b' ', rest @ ..] => {
92 *bytes = rest;
94 return start.get(..len).ok_or(Error::Length);
95 }
96 [_, ..] => {
97 return Err(Error::CharacterEncoding);
99 }
100 [] => {
101 return start.get(..len).ok_or(Error::Length);
103 }
104 }
105 }
106}
107
108fn decode_segment_str<'a>(bytes: &mut &'a [u8]) -> Result<&'a str> {
110 str::from_utf8(decode_segment(bytes)?).map_err(|_| Error::CharacterEncoding)
111}
112
113fn encode_str(out: &mut [u8], offset: &mut usize, s: &str) -> Result<()> {
115 let bytes = s.as_bytes();
116
117 if out.len() < offset.checked_add(bytes.len()).ok_or(Error::Length)? {
118 return Err(Error::Length);
119 }
120
121 out[*offset..][..bytes.len()].copy_from_slice(bytes);
122 *offset = offset.checked_add(bytes.len()).ok_or(Error::Length)?;
123 Ok(())
124}
125
126#[cfg(test)]
127mod tests {
128 use super::Encapsulation;
129
130 const EXAMPLE_KEY: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com";
131
132 #[test]
133 fn decode() {
134 let encapsulation = Encapsulation::decode(EXAMPLE_KEY.as_bytes()).unwrap();
135 assert_eq!(encapsulation.algorithm_id, "ssh-ed25519");
136 assert_eq!(
137 encapsulation.base64_data,
138 b"AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"
139 );
140 assert_eq!(encapsulation.comment, "user@example.com");
141 }
142}