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
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::{Deserialize, Serialize, de, ser};
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
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
67 .encode(&mut DigestWriter(&mut digest))
68 .expect(FINGERPRINT_ERR_MSG);
69 Self::Sha256(digest.finalize().into())
70 }
71 HashAlg::Sha512 => {
72 let mut digest = Sha512::new();
73 public_key
74 .encode(&mut DigestWriter(&mut digest))
75 .expect(FINGERPRINT_ERR_MSG);
76 Self::Sha512(digest.finalize().into())
77 }
78 }
79 }
80
81 pub fn algorithm(self) -> HashAlg {
83 match self {
84 Self::Sha256(_) => HashAlg::Sha256,
85 Self::Sha512(_) => HashAlg::Sha512,
86 }
87 }
88
89 pub fn prefix(self) -> &'static str {
91 match self.algorithm() {
92 HashAlg::Sha256 => "SHA256",
93 HashAlg::Sha512 => "SHA512",
94 }
95 }
96
97 fn footer(self) -> &'static str {
99 match self.algorithm() {
100 HashAlg::Sha256 => "[SHA256]",
101 HashAlg::Sha512 => "[SHA512]",
102 }
103 }
104
105 pub fn as_bytes(&self) -> &[u8] {
107 match self {
108 Self::Sha256(bytes) => bytes.as_slice(),
109 Self::Sha512(bytes) => bytes.as_slice(),
110 }
111 }
112
113 pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
115 match self {
116 Self::Sha256(fingerprint) => Some(fingerprint),
117 _ => None,
118 }
119 }
120
121 pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
123 match self {
124 Self::Sha512(fingerprint) => Some(fingerprint),
125 _ => None,
126 }
127 }
128
129 pub fn is_sha256(self) -> bool {
131 matches!(self, Self::Sha256(_))
132 }
133
134 pub fn is_sha512(self) -> bool {
136 matches!(self, Self::Sha512(_))
137 }
138
139 pub fn fmt_randomart(self, header: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 Randomart::new(header, self).fmt(f)
142 }
143
144 #[cfg(feature = "alloc")]
160 pub fn to_randomart(self, header: &str) -> String {
161 Randomart::new(header, self).to_string()
162 }
163}
164
165impl AsRef<[u8]> for Fingerprint {
166 fn as_ref(&self) -> &[u8] {
167 self.as_bytes()
168 }
169}
170
171impl Display for Fingerprint {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 let prefix = self.prefix();
174
175 let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
177 let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
178 write!(f, "{prefix}:{base64}")
179 }
180}
181
182impl FromStr for Fingerprint {
183 type Err = Error;
184
185 fn from_str(id: &str) -> Result<Self> {
186 let (alg_str, base64) = id.split_once(':').ok_or(Error::AlgorithmUnknown)?;
187
188 let algorithm = match alg_str {
190 "SHA256" => HashAlg::Sha256,
191 "SHA512" => HashAlg::Sha512,
192 _ => return Err(Error::AlgorithmUnknown),
193 };
194
195 let mut buf = [0u8; HashAlg::Sha512.digest_size()];
197 let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
198
199 match algorithm {
200 HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
201 HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
202 }
203 }
204}
205
206#[cfg(all(feature = "alloc", feature = "serde"))]
207impl<'de> Deserialize<'de> for Fingerprint {
208 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
209 where
210 D: de::Deserializer<'de>,
211 {
212 let string = String::deserialize(deserializer)?;
213 string.parse().map_err(de::Error::custom)
214 }
215}
216
217#[cfg(all(feature = "alloc", feature = "serde"))]
218impl Serialize for Fingerprint {
219 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
220 where
221 S: ser::Serializer,
222 {
223 self.to_string().serialize(serializer)
224 }
225}