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::{self, FromStr};
34use encoding::{Base64Reader, Decode, Reader};
35
36#[cfg(feature = "alloc")]
37use {
38 crate::{AssociatedHashAlg, Comment, SshSig},
39 alloc::{
40 borrow::ToOwned,
41 string::{String, ToString},
42 vec::Vec,
43 },
44 encoding::Encode,
45 sha2::Digest,
46};
47
48#[cfg(all(feature = "alloc", feature = "serde"))]
49use serde::{Deserialize, Serialize, de, ser};
50
51#[cfg(feature = "std")]
52use std::{fs::File, path::Path};
53
54#[cfg(feature = "std")]
55use std::io::{self, Read, Write};
56
57#[cfg(doc)]
58use crate::PrivateKey;
59
60#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
95pub struct PublicKey {
96 pub(crate) key_data: KeyData,
98
99 #[cfg(feature = "alloc")]
106 pub(crate) comment: Comment,
107}
108
109impl PublicKey {
110 #[cfg(feature = "alloc")]
114 pub fn new(key_data: KeyData, comment: impl Into<Comment>) -> Self {
115 Self {
116 key_data,
117 comment: comment.into(),
118 }
119 }
120
121 pub fn from_openssh(public_key: &str) -> Result<Self> {
129 let encapsulation = SshFormat::decode(public_key.trim_end().as_bytes())?;
130 let mut reader = Base64Reader::new(encapsulation.base64_data)?;
131 let key_data = KeyData::decode(&mut reader)?;
132
133 if encapsulation.algorithm_id != key_data.algorithm().as_str() {
135 return Err(Error::AlgorithmUnknown);
136 }
137
138 let public_key = Self {
139 key_data,
140 #[cfg(feature = "alloc")]
141 comment: encapsulation.comment.to_owned().into(),
142 };
143
144 Ok(reader.finish(public_key)?)
145 }
146
147 pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
149 let reader = &mut bytes;
150 let key_data = KeyData::decode(reader)?;
151 Ok(reader.finish(key_data.into())?)
152 }
153
154 pub fn encode_openssh<'o>(&self, out: &'o mut [u8]) -> Result<&'o str> {
156 #[cfg(not(feature = "alloc"))]
157 let comment = "";
158 #[cfg(feature = "alloc")]
159 let comment = self.comment.as_str_lossy();
160
161 SshFormat::encode(self.algorithm().as_str(), &self.key_data, comment, out)
162 }
163
164 #[cfg(feature = "alloc")]
167 pub fn to_openssh(&self) -> Result<String> {
168 SshFormat::encode_string(
169 self.algorithm().as_str(),
170 &self.key_data,
171 self.comment.as_str_lossy(),
172 )
173 }
174
175 #[cfg(feature = "alloc")]
177 pub fn to_bytes(&self) -> Result<Vec<u8>> {
178 Ok(self.key_data.encode_vec()?)
179 }
180
181 #[cfg_attr(feature = "ed25519", doc = "```")]
203 #[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
204 #[cfg(feature = "alloc")]
236 pub fn verify(&self, namespace: &str, msg: &[u8], signature: &SshSig) -> Result<()> {
237 self.verify_prehash(
238 namespace,
239 signature.hash_alg().digest(msg).as_slice(),
240 signature,
241 )
242 }
243
244 #[cfg(feature = "alloc")]
248 pub fn verify_digest<D: AssociatedHashAlg + Digest>(
249 &self,
250 namespace: &str,
251 digest: D,
252 signature: &SshSig,
253 ) -> Result<()> {
254 if D::HASH_ALG != signature.hash_alg() {
255 return Err(Error::Crypto);
256 }
257
258 self.verify_prehash(namespace, digest.finalize().as_slice(), signature)
259 }
260
261 #[cfg(feature = "alloc")]
266 pub fn verify_prehash(
267 &self,
268 namespace: &str,
269 prehash: &[u8],
270 signature: &SshSig,
271 ) -> Result<()> {
272 if self.key_data() != signature.public_key() {
273 return Err(Error::PublicKey);
274 }
275
276 if namespace != signature.namespace() {
277 return Err(Error::Namespace);
278 }
279
280 signature.verify_prehash(prehash)
281 }
282
283 #[cfg(feature = "std")]
285 pub fn read_openssh(reader: &mut impl Read) -> Result<Self> {
286 let input = io::read_to_string(reader)?;
287 Self::from_openssh(&input)
288 }
289
290 #[cfg(feature = "std")]
292 pub fn read_openssh_file(path: impl AsRef<Path>) -> Result<Self> {
293 let mut file = File::open(path)?;
294 Self::read_openssh(&mut file)
295 }
296
297 #[cfg(feature = "std")]
299 pub fn write_openssh(&self, writer: &mut impl Write) -> Result<()> {
300 let mut encoded = self.to_openssh()?;
301 encoded.push('\n'); writer.write_all(encoded.as_bytes())?;
304 Ok(())
305 }
306
307 #[cfg(feature = "std")]
309 pub fn write_openssh_file(&self, path: impl AsRef<Path>) -> Result<()> {
310 let mut file = File::create(path)?;
311 self.write_openssh(&mut file)
312 }
313
314 pub fn algorithm(&self) -> Algorithm {
316 self.key_data.algorithm()
317 }
318
319 #[cfg(feature = "alloc")]
321 pub fn comment(&self) -> &Comment {
322 &self.comment
323 }
324
325 pub fn key_data(&self) -> &KeyData {
327 &self.key_data
328 }
329
330 pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
334 self.key_data.fingerprint(hash_alg)
335 }
336
337 #[cfg(feature = "alloc")]
339 pub fn set_comment(&mut self, comment: impl Into<Comment>) {
340 self.comment = comment.into();
341 }
342
343 #[cfg(not(feature = "alloc"))]
347 pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
348 reader.drain_prefixed()?;
349 Ok(())
350 }
351
352 #[cfg(feature = "alloc")]
354 pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
355 self.comment = Comment::decode(reader)?;
356 Ok(())
357 }
358}
359
360impl From<KeyData> for PublicKey {
361 fn from(key_data: KeyData) -> PublicKey {
362 PublicKey {
363 key_data,
364 #[cfg(feature = "alloc")]
365 comment: Comment::default(),
366 }
367 }
368}
369
370impl From<PublicKey> for KeyData {
371 fn from(public_key: PublicKey) -> KeyData {
372 public_key.key_data
373 }
374}
375
376impl From<&PublicKey> for KeyData {
377 fn from(public_key: &PublicKey) -> KeyData {
378 public_key.key_data.clone()
379 }
380}
381
382#[cfg(feature = "alloc")]
383impl From<DsaPublicKey> for PublicKey {
384 fn from(public_key: DsaPublicKey) -> PublicKey {
385 KeyData::from(public_key).into()
386 }
387}
388
389#[cfg(feature = "ecdsa")]
390impl From<EcdsaPublicKey> for PublicKey {
391 fn from(public_key: EcdsaPublicKey) -> PublicKey {
392 KeyData::from(public_key).into()
393 }
394}
395
396impl From<Ed25519PublicKey> for PublicKey {
397 fn from(public_key: Ed25519PublicKey) -> PublicKey {
398 KeyData::from(public_key).into()
399 }
400}
401
402#[cfg(feature = "alloc")]
403impl From<RsaPublicKey> for PublicKey {
404 fn from(public_key: RsaPublicKey) -> PublicKey {
405 KeyData::from(public_key).into()
406 }
407}
408
409#[cfg(feature = "ecdsa")]
410impl From<SkEcdsaSha2NistP256> for PublicKey {
411 fn from(public_key: SkEcdsaSha2NistP256) -> PublicKey {
412 KeyData::from(public_key).into()
413 }
414}
415
416impl From<SkEd25519> for PublicKey {
417 fn from(public_key: SkEd25519) -> PublicKey {
418 KeyData::from(public_key).into()
419 }
420}
421
422impl FromStr for PublicKey {
423 type Err = Error;
424
425 fn from_str(s: &str) -> Result<Self> {
426 Self::from_openssh(s)
427 }
428}
429
430#[cfg(feature = "alloc")]
431#[allow(clippy::to_string_trait_impl)]
432impl ToString for PublicKey {
433 fn to_string(&self) -> String {
434 self.to_openssh().expect("SSH public key encoding error")
435 }
436}
437
438#[cfg(all(feature = "alloc", feature = "serde"))]
439impl<'de> Deserialize<'de> for PublicKey {
440 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
441 where
442 D: de::Deserializer<'de>,
443 {
444 if deserializer.is_human_readable() {
445 let string = String::deserialize(deserializer)?;
446 Self::from_openssh(&string).map_err(de::Error::custom)
447 } else {
448 let bytes = Vec::<u8>::deserialize(deserializer)?;
449 Self::from_bytes(&bytes).map_err(de::Error::custom)
450 }
451 }
452}
453
454#[cfg(all(feature = "alloc", feature = "serde"))]
455impl Serialize for PublicKey {
456 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
457 where
458 S: ser::Serializer,
459 {
460 if serializer.is_human_readable() {
461 self.to_openssh()
462 .map_err(ser::Error::custom)?
463 .serialize(serializer)
464 } else {
465 self.to_bytes()
466 .map_err(ser::Error::custom)?
467 .serialize(serializer)
468 }
469 }
470}