ssh_key/
fingerprint.rs
1use crate::{encode::Encode, public, Error, HashAlg, Result};
4use base64ct::{Base64Unpadded, Encoding};
5use core::{
6 fmt::{self, Display},
7 str::{self, FromStr},
8};
9use sha2::{Digest, Sha256, Sha512};
10
11const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";
13
14#[cfg(all(feature = "alloc", feature = "serde"))]
15use {
16 alloc::string::{String, ToString},
17 serde::{de, ser, Deserialize, Serialize},
18};
19
20#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
41#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
42#[non_exhaustive]
43pub enum Fingerprint {
44 Sha256([u8; HashAlg::Sha256.digest_size()]),
46
47 Sha512([u8; HashAlg::Sha512.digest_size()]),
49}
50
51impl Fingerprint {
52 const SHA512_BASE64_SIZE: usize = 86;
54
55 pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
58 match algorithm {
59 HashAlg::Sha256 => {
60 let mut digest = Sha256::new();
61 public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
62 Self::Sha256(digest.finalize().into())
63 }
64 HashAlg::Sha512 => {
65 let mut digest = Sha512::new();
66 public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
67 Self::Sha512(digest.finalize().into())
68 }
69 }
70 }
71
72 pub fn algorithm(self) -> HashAlg {
74 match self {
75 Self::Sha256(_) => HashAlg::Sha256,
76 Self::Sha512(_) => HashAlg::Sha512,
77 }
78 }
79
80 pub fn as_bytes(&self) -> &[u8] {
82 match self {
83 Self::Sha256(bytes) => bytes.as_slice(),
84 Self::Sha512(bytes) => bytes.as_slice(),
85 }
86 }
87
88 pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
90 match self {
91 Self::Sha256(fingerprint) => Some(fingerprint),
92 _ => None,
93 }
94 }
95
96 pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
98 match self {
99 Self::Sha512(fingerprint) => Some(fingerprint),
100 _ => None,
101 }
102 }
103
104 pub fn is_sha256(self) -> bool {
106 matches!(self, Self::Sha256(_))
107 }
108
109 pub fn is_sha512(self) -> bool {
111 matches!(self, Self::Sha512(_))
112 }
113}
114
115impl AsRef<[u8]> for Fingerprint {
116 fn as_ref(&self) -> &[u8] {
117 self.as_bytes()
118 }
119}
120
121impl Display for Fingerprint {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
125 let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
126 write!(f, "{}:{}", self.algorithm(), base64)
127 }
128}
129
130impl FromStr for Fingerprint {
131 type Err = Error;
132
133 fn from_str(id: &str) -> Result<Self> {
134 let (algorithm, base64) = id.split_once(':').ok_or(Error::Algorithm)?;
135
136 let mut buf = [0u8; HashAlg::Sha512.digest_size()];
138 let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
139
140 match algorithm.parse()? {
141 HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
142 HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
143 }
144 }
145}
146
147#[cfg(all(feature = "alloc", feature = "serde"))]
148#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "serde"))))]
149impl<'de> Deserialize<'de> for Fingerprint {
150 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
151 where
152 D: de::Deserializer<'de>,
153 {
154 let string = String::deserialize(deserializer)?;
155 string.parse().map_err(de::Error::custom)
156 }
157}
158
159#[cfg(all(feature = "alloc", feature = "serde"))]
160#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "serde"))))]
161impl Serialize for Fingerprint {
162 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
163 where
164 S: ser::Serializer,
165 {
166 self.to_string().serialize(serializer)
167 }
168}