1#[cfg(feature = "alloc")]
6mod dsa;
7#[cfg(feature = "ecdsa")]
8mod ecdsa;
9mod ed25519;
10mod key_data;
11#[cfg(feature = "alloc")]
12mod opaque;
13#[cfg(feature = "alloc")]
14mod rsa;
15mod sk;
16mod ssh_format;
17
18pub use self::{ed25519::Ed25519PublicKey, key_data::KeyData, sk::SkEd25519};
19
20#[cfg(feature = "alloc")]
21pub use self::{
22 dsa::DsaPublicKey,
23 opaque::{OpaquePublicKey, OpaquePublicKeyBytes},
24 rsa::RsaPublicKey,
25};
26
27#[cfg(feature = "ecdsa")]
28pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};
29
30pub(crate) use self::ssh_format::SshFormat;
31
32use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
33use core::str::FromStr;
34use encoding::{Base64Reader, Decode, Reader};
35
36#[cfg(feature = "alloc")]
37use {
38 crate::SshSig,
39 alloc::{
40 borrow::ToOwned,
41 string::{String, ToString},
42 vec::Vec,
43 },
44 encoding::Encode,
45};
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#[cfg(doc)]
54use crate::PrivateKey;
55
56#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
85pub struct PublicKey {
86 pub(crate) key_data: KeyData,
88
89 #[cfg(feature = "alloc")]
91 pub(crate) comment: String,
92}
93
94impl PublicKey {
95 #[cfg(feature = "alloc")]
99 pub fn new(key_data: KeyData, comment: impl Into<String>) -> Self {
100 Self {
101 key_data,
102 comment: comment.into(),
103 }
104 }
105
106 pub fn from_openssh(public_key: &str) -> Result<Self> {
114 let encapsulation = SshFormat::decode(public_key.trim_end().as_bytes())?;
115 let mut reader = Base64Reader::new(encapsulation.base64_data)?;
116 let key_data = KeyData::decode(&mut reader)?;
117
118 if encapsulation.algorithm_id != key_data.algorithm().as_str() {
120 return Err(Error::AlgorithmUnknown);
121 }
122
123 let public_key = Self {
124 key_data,
125 #[cfg(feature = "alloc")]
126 comment: encapsulation.comment.to_owned(),
127 };
128
129 Ok(reader.finish(public_key)?)
130 }
131
132 pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
134 let reader = &mut bytes;
135 let key_data = KeyData::decode(reader)?;
136 Ok(reader.finish(key_data.into())?)
137 }
138
139 pub fn encode_openssh<'o>(&self, out: &'o mut [u8]) -> Result<&'o str> {
141 SshFormat::encode(
142 self.algorithm().as_str(),
143 &self.key_data,
144 self.comment(),
145 out,
146 )
147 }
148
149 #[cfg(feature = "alloc")]
152 pub fn to_openssh(&self) -> Result<String> {
153 SshFormat::encode_string(self.algorithm().as_str(), &self.key_data, self.comment())
154 }
155
156 #[cfg(feature = "alloc")]
158 pub fn to_bytes(&self) -> Result<Vec<u8>> {
159 let mut public_key_bytes = Vec::new();
160 self.key_data.encode(&mut public_key_bytes)?;
161 Ok(public_key_bytes)
162 }
163
164 #[cfg_attr(feature = "ed25519", doc = "```")]
181 #[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
182 #[cfg(feature = "alloc")]
213 pub fn verify(&self, namespace: &str, msg: &[u8], signature: &SshSig) -> Result<()> {
214 if self.key_data() != signature.public_key() {
215 return Err(Error::PublicKey);
216 }
217
218 if namespace != signature.namespace() {
219 return Err(Error::Namespace);
220 }
221
222 signature.verify(msg)
223 }
224
225 #[cfg(feature = "std")]
227 pub fn read_openssh_file(path: &Path) -> Result<Self> {
228 let input = fs::read_to_string(path)?;
229 Self::from_openssh(&input)
230 }
231
232 #[cfg(feature = "std")]
234 pub fn write_openssh_file(&self, path: &Path) -> Result<()> {
235 let mut encoded = self.to_openssh()?;
236 encoded.push('\n'); fs::write(path, encoded.as_bytes())?;
239 Ok(())
240 }
241
242 pub fn algorithm(&self) -> Algorithm {
244 self.key_data.algorithm()
245 }
246
247 #[cfg(not(feature = "alloc"))]
249 pub fn comment(&self) -> &str {
250 ""
251 }
252
253 #[cfg(feature = "alloc")]
255 pub fn comment(&self) -> &str {
256 &self.comment
257 }
258
259 pub fn key_data(&self) -> &KeyData {
261 &self.key_data
262 }
263
264 pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
268 self.key_data.fingerprint(hash_alg)
269 }
270
271 #[cfg(feature = "alloc")]
273 pub fn set_comment(&mut self, comment: impl Into<String>) {
274 self.comment = comment.into();
275 }
276
277 #[cfg(not(feature = "alloc"))]
281 pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
282 reader.drain_prefixed()?;
283 Ok(())
284 }
285
286 #[cfg(feature = "alloc")]
288 pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
289 self.comment = String::decode(reader)?;
290 Ok(())
291 }
292}
293
294impl From<KeyData> for PublicKey {
295 fn from(key_data: KeyData) -> PublicKey {
296 PublicKey {
297 key_data,
298 #[cfg(feature = "alloc")]
299 comment: String::new(),
300 }
301 }
302}
303
304impl From<PublicKey> for KeyData {
305 fn from(public_key: PublicKey) -> KeyData {
306 public_key.key_data
307 }
308}
309
310impl From<&PublicKey> for KeyData {
311 fn from(public_key: &PublicKey) -> KeyData {
312 public_key.key_data.clone()
313 }
314}
315
316#[cfg(feature = "alloc")]
317impl From<DsaPublicKey> for PublicKey {
318 fn from(public_key: DsaPublicKey) -> PublicKey {
319 KeyData::from(public_key).into()
320 }
321}
322
323#[cfg(feature = "ecdsa")]
324impl From<EcdsaPublicKey> for PublicKey {
325 fn from(public_key: EcdsaPublicKey) -> PublicKey {
326 KeyData::from(public_key).into()
327 }
328}
329
330impl From<Ed25519PublicKey> for PublicKey {
331 fn from(public_key: Ed25519PublicKey) -> PublicKey {
332 KeyData::from(public_key).into()
333 }
334}
335
336#[cfg(feature = "alloc")]
337impl From<RsaPublicKey> for PublicKey {
338 fn from(public_key: RsaPublicKey) -> PublicKey {
339 KeyData::from(public_key).into()
340 }
341}
342
343#[cfg(feature = "ecdsa")]
344impl From<SkEcdsaSha2NistP256> for PublicKey {
345 fn from(public_key: SkEcdsaSha2NistP256) -> PublicKey {
346 KeyData::from(public_key).into()
347 }
348}
349
350impl From<SkEd25519> for PublicKey {
351 fn from(public_key: SkEd25519) -> PublicKey {
352 KeyData::from(public_key).into()
353 }
354}
355
356impl FromStr for PublicKey {
357 type Err = Error;
358
359 fn from_str(s: &str) -> Result<Self> {
360 Self::from_openssh(s)
361 }
362}
363
364#[cfg(feature = "alloc")]
365impl ToString for PublicKey {
366 fn to_string(&self) -> String {
367 self.to_openssh().expect("SSH public key encoding error")
368 }
369}
370
371#[cfg(all(feature = "alloc", feature = "serde"))]
372impl<'de> Deserialize<'de> for PublicKey {
373 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
374 where
375 D: de::Deserializer<'de>,
376 {
377 if deserializer.is_human_readable() {
378 let string = String::deserialize(deserializer)?;
379 Self::from_openssh(&string).map_err(de::Error::custom)
380 } else {
381 let bytes = Vec::<u8>::deserialize(deserializer)?;
382 Self::from_bytes(&bytes).map_err(de::Error::custom)
383 }
384 }
385}
386
387#[cfg(all(feature = "alloc", feature = "serde"))]
388impl Serialize for PublicKey {
389 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
390 where
391 S: ser::Serializer,
392 {
393 if serializer.is_human_readable() {
394 self.to_openssh()
395 .map_err(ser::Error::custom)?
396 .serialize(serializer)
397 } else {
398 self.to_bytes()
399 .map_err(ser::Error::custom)?
400 .serialize(serializer)
401 }
402 }
403}