1mod randomart;
4
5use self::randomart::Randomart;
6use crate::{public, Error, HashAlg, Result};
7use core::{
8 fmt::{self, Display},
9 str::{self, FromStr},
10};
11use encoding::{
12 base64::{Base64Unpadded, Encoding},
13 Encode,
14};
15use sha2::{Digest, Sha256, Sha512};
16
17const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";
19
20#[cfg(feature = "alloc")]
21use alloc::string::{String, ToString};
22
23#[cfg(all(feature = "alloc", feature = "serde"))]
24use serde::{de, ser, Deserialize, Serialize};
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
47#[non_exhaustive]
48pub enum Fingerprint {
49 Sha256([u8; HashAlg::Sha256.digest_size()]),
51
52 Sha512([u8; HashAlg::Sha512.digest_size()]),
54}
55
56impl Fingerprint {
57 const SHA512_BASE64_SIZE: usize = 86;
59
60 pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
63 match algorithm {
64 HashAlg::Sha256 => {
65 let mut digest = Sha256::new();
66 public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
67 Self::Sha256(digest.finalize().into())
68 }
69 HashAlg::Sha512 => {
70 let mut digest = Sha512::new();
71 public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
72 Self::Sha512(digest.finalize().into())
73 }
74 }
75 }
76
77 pub fn algorithm(self) -> HashAlg {
79 match self {
80 Self::Sha256(_) => HashAlg::Sha256,
81 Self::Sha512(_) => HashAlg::Sha512,
82 }
83 }
84
85 pub fn prefix(self) -> &'static str {
87 match self.algorithm() {
88 HashAlg::Sha256 => "SHA256",
89 HashAlg::Sha512 => "SHA512",
90 }
91 }
92
93 fn footer(self) -> &'static str {
95 match self.algorithm() {
96 HashAlg::Sha256 => "[SHA256]",
97 HashAlg::Sha512 => "[SHA512]",
98 }
99 }
100
101 pub fn as_bytes(&self) -> &[u8] {
103 match self {
104 Self::Sha256(bytes) => bytes.as_slice(),
105 Self::Sha512(bytes) => bytes.as_slice(),
106 }
107 }
108
109 pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
111 match self {
112 Self::Sha256(fingerprint) => Some(fingerprint),
113 _ => None,
114 }
115 }
116
117 pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
119 match self {
120 Self::Sha512(fingerprint) => Some(fingerprint),
121 _ => None,
122 }
123 }
124
125 pub fn is_sha256(self) -> bool {
127 matches!(self, Self::Sha256(_))
128 }
129
130 pub fn is_sha512(self) -> bool {
132 matches!(self, Self::Sha512(_))
133 }
134
135 pub fn fmt_randomart(self, header: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 Randomart::new(header, self).fmt(f)
138 }
139
140 #[cfg(feature = "alloc")]
156 pub fn to_randomart(self, header: &str) -> String {
157 Randomart::new(header, self).to_string()
158 }
159}
160
161impl AsRef<[u8]> for Fingerprint {
162 fn as_ref(&self) -> &[u8] {
163 self.as_bytes()
164 }
165}
166
167impl Display for Fingerprint {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 let prefix = self.prefix();
170
171 let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
173 let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
174 write!(f, "{prefix}:{base64}")
175 }
176}
177
178impl FromStr for Fingerprint {
179 type Err = Error;
180
181 fn from_str(id: &str) -> Result<Self> {
182 let (alg_str, base64) = id.split_once(':').ok_or(Error::AlgorithmUnknown)?;
183
184 let algorithm = match alg_str {
186 "SHA256" => HashAlg::Sha256,
187 "SHA512" => HashAlg::Sha512,
188 _ => return Err(Error::AlgorithmUnknown),
189 };
190
191 let mut buf = [0u8; HashAlg::Sha512.digest_size()];
193 let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
194
195 match algorithm {
196 HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
197 HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
198 }
199 }
200}
201
202#[cfg(all(feature = "alloc", feature = "serde"))]
203impl<'de> Deserialize<'de> for Fingerprint {
204 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
205 where
206 D: de::Deserializer<'de>,
207 {
208 let string = String::deserialize(deserializer)?;
209 string.parse().map_err(de::Error::custom)
210 }
211}
212
213#[cfg(all(feature = "alloc", feature = "serde"))]
214impl Serialize for Fingerprint {
215 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
216 where
217 S: ser::Serializer,
218 {
219 self.to_string().serialize(serializer)
220 }
221}