ssh_key/
public.rs
1#[cfg(feature = "alloc")]
6mod dsa;
7#[cfg(feature = "ecdsa")]
8mod ecdsa;
9mod ed25519;
10mod key_data;
11mod openssh;
12#[cfg(feature = "alloc")]
13mod rsa;
14mod sk;
15
16pub use self::{ed25519::Ed25519PublicKey, key_data::KeyData, sk::SkEd25519};
17
18#[cfg(feature = "alloc")]
19pub use self::{dsa::DsaPublicKey, rsa::RsaPublicKey};
20
21#[cfg(feature = "ecdsa")]
22pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};
23
24pub(crate) use self::openssh::Encapsulation;
25
26use crate::{
27 decode::Decode,
28 encode::Encode,
29 reader::{Base64Reader, Reader},
30 Algorithm, Error, Result,
31};
32use core::str::FromStr;
33
34#[cfg(feature = "alloc")]
35use {
36 crate::{checked::CheckedSum, writer::base64_len},
37 alloc::{
38 borrow::ToOwned,
39 string::{String, ToString},
40 vec::Vec,
41 },
42};
43
44#[cfg(feature = "fingerprint")]
45use crate::{Fingerprint, HashAlg};
46
47#[cfg(all(feature = "alloc", feature = "serde"))]
48use serde::{de, ser, Deserialize, Serialize};
49
50#[cfg(feature = "std")]
51use std::{fs, path::Path};
52
53#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
82pub struct PublicKey {
83 pub(crate) key_data: KeyData,
85
86 #[cfg(feature = "alloc")]
88 pub(crate) comment: String,
89}
90
91impl PublicKey {
92 #[cfg(feature = "alloc")]
96 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
97 pub fn new(key_data: KeyData, comment: impl Into<String>) -> Self {
98 Self {
99 key_data,
100 comment: comment.into(),
101 }
102 }
103
104 pub fn from_openssh(public_key: &str) -> Result<Self> {
112 let encapsulation = Encapsulation::decode(public_key.trim_end().as_bytes())?;
113 let mut reader = Base64Reader::new(encapsulation.base64_data)?;
114 let key_data = KeyData::decode(&mut reader)?;
115
116 if encapsulation.algorithm_id != key_data.algorithm().as_str() {
118 return Err(Error::Algorithm);
119 }
120
121 let public_key = Self {
122 key_data,
123 #[cfg(feature = "alloc")]
124 comment: encapsulation.comment.to_owned(),
125 };
126
127 reader.finish(public_key)
128 }
129
130 pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
132 let reader = &mut bytes;
133 let key_data = KeyData::decode(reader)?;
134 reader.finish(key_data.into())
135 }
136
137 pub fn encode_openssh<'o>(&self, out: &'o mut [u8]) -> Result<&'o str> {
139 Encapsulation::encode(out, self.algorithm().as_str(), self.comment(), |writer| {
140 self.key_data.encode(writer)
141 })
142 }
143
144 #[cfg(feature = "alloc")]
147 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
148 pub fn to_openssh(&self) -> Result<String> {
149 let encoded_len = [
150 2, self.algorithm().as_str().len(),
152 base64_len(self.key_data.encoded_len()?),
153 self.comment.len(),
154 ]
155 .checked_sum()?;
156
157 let mut buf = vec![0u8; encoded_len];
158 let actual_len = self.encode_openssh(&mut buf)?.len();
159 buf.truncate(actual_len);
160 Ok(String::from_utf8(buf)?)
161 }
162
163 #[cfg(feature = "alloc")]
165 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
166 pub fn to_bytes(&self) -> Result<Vec<u8>> {
167 let mut public_key_bytes = Vec::new();
168 self.key_data.encode(&mut public_key_bytes)?;
169 Ok(public_key_bytes)
170 }
171
172 #[cfg(feature = "std")]
174 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
175 pub fn read_openssh_file(path: &Path) -> Result<Self> {
176 let input = fs::read_to_string(path)?;
177 Self::from_openssh(&*input)
178 }
179
180 #[cfg(feature = "std")]
182 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
183 pub fn write_openssh_file(&self, path: &Path) -> Result<()> {
184 let encoded = self.to_openssh()?;
185 fs::write(path, encoded.as_bytes())?;
186 Ok(())
187 }
188
189 pub fn algorithm(&self) -> Algorithm {
191 self.key_data.algorithm()
192 }
193
194 #[cfg(not(feature = "alloc"))]
196 pub fn comment(&self) -> &str {
197 ""
198 }
199
200 #[cfg(feature = "alloc")]
202 pub fn comment(&self) -> &str {
203 &self.comment
204 }
205
206 pub fn key_data(&self) -> &KeyData {
208 &self.key_data
209 }
210
211 #[cfg(feature = "fingerprint")]
215 #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
216 pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
217 self.key_data.fingerprint(hash_alg)
218 }
219
220 #[cfg(feature = "alloc")]
222 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
223 pub fn set_comment(&mut self, comment: impl Into<String>) {
224 self.comment = comment.into();
225 }
226
227 #[cfg(not(feature = "alloc"))]
231 pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
232 reader.drain_prefixed()?;
233 Ok(())
234 }
235
236 #[cfg(feature = "alloc")]
238 pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
239 self.comment = String::decode(reader)?;
240 Ok(())
241 }
242}
243
244impl From<KeyData> for PublicKey {
245 fn from(key_data: KeyData) -> PublicKey {
246 PublicKey {
247 key_data,
248 #[cfg(feature = "alloc")]
249 comment: String::new(),
250 }
251 }
252}
253
254impl From<PublicKey> for KeyData {
255 fn from(public_key: PublicKey) -> KeyData {
256 public_key.key_data
257 }
258}
259
260impl From<&PublicKey> for KeyData {
261 fn from(public_key: &PublicKey) -> KeyData {
262 public_key.key_data.clone()
263 }
264}
265
266#[cfg(feature = "alloc")]
267#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
268impl From<DsaPublicKey> for PublicKey {
269 fn from(public_key: DsaPublicKey) -> PublicKey {
270 KeyData::from(public_key).into()
271 }
272}
273
274#[cfg(feature = "ecdsa")]
275#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
276impl From<EcdsaPublicKey> for PublicKey {
277 fn from(public_key: EcdsaPublicKey) -> PublicKey {
278 KeyData::from(public_key).into()
279 }
280}
281
282impl From<Ed25519PublicKey> for PublicKey {
283 fn from(public_key: Ed25519PublicKey) -> PublicKey {
284 KeyData::from(public_key).into()
285 }
286}
287
288#[cfg(feature = "alloc")]
289#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
290impl From<RsaPublicKey> for PublicKey {
291 fn from(public_key: RsaPublicKey) -> PublicKey {
292 KeyData::from(public_key).into()
293 }
294}
295
296#[cfg(feature = "ecdsa")]
297#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
298impl From<SkEcdsaSha2NistP256> for PublicKey {
299 fn from(public_key: SkEcdsaSha2NistP256) -> PublicKey {
300 KeyData::from(public_key).into()
301 }
302}
303
304impl From<SkEd25519> for PublicKey {
305 fn from(public_key: SkEd25519) -> PublicKey {
306 KeyData::from(public_key).into()
307 }
308}
309
310impl FromStr for PublicKey {
311 type Err = Error;
312
313 fn from_str(s: &str) -> Result<Self> {
314 Self::from_openssh(s)
315 }
316}
317
318#[cfg(feature = "alloc")]
319impl ToString for PublicKey {
320 fn to_string(&self) -> String {
321 self.to_openssh().expect("SSH public key encoding error")
322 }
323}
324
325#[cfg(all(feature = "alloc", feature = "serde"))]
326#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "serde"))))]
327impl<'de> Deserialize<'de> for PublicKey {
328 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
329 where
330 D: de::Deserializer<'de>,
331 {
332 if deserializer.is_human_readable() {
333 let string = String::deserialize(deserializer)?;
334 Self::from_openssh(&string).map_err(de::Error::custom)
335 } else {
336 let bytes = Vec::<u8>::deserialize(deserializer)?;
337 Self::from_bytes(&bytes).map_err(de::Error::custom)
338 }
339 }
340}
341
342#[cfg(all(feature = "alloc", feature = "serde"))]
343#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "serde"))))]
344impl Serialize for PublicKey {
345 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
346 where
347 S: ser::Serializer,
348 {
349 if serializer.is_human_readable() {
350 self.to_openssh()
351 .map_err(ser::Error::custom)?
352 .serialize(serializer)
353 } else {
354 self.to_bytes()
355 .map_err(ser::Error::custom)?
356 .serialize(serializer)
357 }
358 }
359}