1mod randomart;
4
5use self::randomart::Randomart;
6use crate::{Error, HashAlg, Result, public};
7use core::{
8 fmt::{self, Display},
9 str::{self, FromStr},
10};
11use encoding::{
12 DigestWriter, Encode,
13 base64::{Base64Unpadded, Encoding},
14};
15use sha2::{Digest, Sha256, Sha512};
16
17#[cfg(feature = "alloc")]
18use alloc::string::{String, ToString};
19#[cfg(all(feature = "alloc", feature = "serde"))]
20use serde::{Deserialize, Serialize, de, ser};
21
22const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";
24
25#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
46#[non_exhaustive]
47pub enum Fingerprint {
48 Sha256([u8; HashAlg::Sha256.digest_size()]),
50
51 Sha512([u8; HashAlg::Sha512.digest_size()]),
53}
54
55impl Fingerprint {
56 const SHA512_BASE64_SIZE: usize = 86;
58
59 #[must_use]
62 #[allow(clippy::missing_panics_doc, reason = "should not panic")]
63 pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
64 match algorithm {
65 HashAlg::Sha256 => {
66 let mut digest = Sha256::new();
67 public_key
68 .encode(&mut DigestWriter(&mut digest))
69 .expect(FINGERPRINT_ERR_MSG);
70 Self::Sha256(digest.finalize().into())
71 }
72 HashAlg::Sha512 => {
73 let mut digest = Sha512::new();
74 public_key
75 .encode(&mut DigestWriter(&mut digest))
76 .expect(FINGERPRINT_ERR_MSG);
77 Self::Sha512(digest.finalize().into())
78 }
79 }
80 }
81
82 #[must_use]
84 pub fn algorithm(self) -> HashAlg {
85 match self {
86 Self::Sha256(_) => HashAlg::Sha256,
87 Self::Sha512(_) => HashAlg::Sha512,
88 }
89 }
90
91 #[must_use]
93 pub fn prefix(self) -> &'static str {
94 match self.algorithm() {
95 HashAlg::Sha256 => "SHA256",
96 HashAlg::Sha512 => "SHA512",
97 }
98 }
99
100 fn footer(self) -> &'static str {
102 match self.algorithm() {
103 HashAlg::Sha256 => "[SHA256]",
104 HashAlg::Sha512 => "[SHA512]",
105 }
106 }
107
108 #[must_use]
110 pub fn as_bytes(&self) -> &[u8] {
111 match self {
112 Self::Sha256(bytes) => bytes.as_slice(),
113 Self::Sha512(bytes) => bytes.as_slice(),
114 }
115 }
116
117 #[must_use]
119 pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
120 match self {
121 Self::Sha256(fingerprint) => Some(fingerprint),
122 _ => None,
123 }
124 }
125
126 #[must_use]
128 pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
129 match self {
130 Self::Sha512(fingerprint) => Some(fingerprint),
131 _ => None,
132 }
133 }
134
135 #[must_use]
137 pub fn is_sha256(self) -> bool {
138 matches!(self, Self::Sha256(_))
139 }
140
141 #[must_use]
143 pub fn is_sha512(self) -> bool {
144 matches!(self, Self::Sha512(_))
145 }
146
147 pub fn fmt_randomart(self, header: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 Randomart::new(header, self).fmt(f)
153 }
154
155 #[cfg(feature = "alloc")]
171 #[must_use]
172 pub fn to_randomart(self, header: &str) -> String {
173 Randomart::new(header, self).to_string()
174 }
175}
176
177impl AsRef<[u8]> for Fingerprint {
178 fn as_ref(&self) -> &[u8] {
179 self.as_bytes()
180 }
181}
182
183impl Display for Fingerprint {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 let prefix = self.prefix();
186
187 let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
189 let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
190 write!(f, "{prefix}:{base64}")
191 }
192}
193
194impl FromStr for Fingerprint {
195 type Err = Error;
196
197 fn from_str(id: &str) -> Result<Self> {
198 let (alg_str, base64) = id.split_once(':').ok_or(Error::AlgorithmUnknown)?;
199
200 let algorithm = match alg_str {
202 "SHA256" => HashAlg::Sha256,
203 "SHA512" => HashAlg::Sha512,
204 _ => return Err(Error::AlgorithmUnknown),
205 };
206
207 let mut buf = [0u8; HashAlg::Sha512.digest_size()];
209 let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
210
211 match algorithm {
212 HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
213 HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
214 }
215 }
216}
217
218#[cfg(all(feature = "alloc", feature = "serde"))]
219impl<'de> Deserialize<'de> for Fingerprint {
220 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
221 where
222 D: de::Deserializer<'de>,
223 {
224 let string = String::deserialize(deserializer)?;
225 string.parse().map_err(de::Error::custom)
226 }
227}
228
229#[cfg(all(feature = "alloc", feature = "serde"))]
230impl Serialize for Fingerprint {
231 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
232 where
233 S: ser::Serializer,
234 {
235 self.to_string().serialize(serializer)
236 }
237}