mod randomart;
use self::randomart::Randomart;
use crate::{public, Error, HashAlg, Result};
use core::{
fmt::{self, Display},
str::{self, FromStr},
};
use encoding::{
base64::{Base64Unpadded, Encoding},
Encode,
};
use sha2::{Digest, Sha256, Sha512};
const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Fingerprint {
Sha256([u8; HashAlg::Sha256.digest_size()]),
Sha512([u8; HashAlg::Sha512.digest_size()]),
}
impl Fingerprint {
const SHA512_BASE64_SIZE: usize = 86;
pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
match algorithm {
HashAlg::Sha256 => {
let mut digest = Sha256::new();
public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
Self::Sha256(digest.finalize().into())
}
HashAlg::Sha512 => {
let mut digest = Sha512::new();
public_key.encode(&mut digest).expect(FINGERPRINT_ERR_MSG);
Self::Sha512(digest.finalize().into())
}
}
}
pub fn algorithm(self) -> HashAlg {
match self {
Self::Sha256(_) => HashAlg::Sha256,
Self::Sha512(_) => HashAlg::Sha512,
}
}
pub fn prefix(self) -> &'static str {
match self.algorithm() {
HashAlg::Sha256 => "SHA256",
HashAlg::Sha512 => "SHA512",
}
}
fn footer(self) -> &'static str {
match self.algorithm() {
HashAlg::Sha256 => "[SHA256]",
HashAlg::Sha512 => "[SHA512]",
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Sha256(bytes) => bytes.as_slice(),
Self::Sha512(bytes) => bytes.as_slice(),
}
}
pub fn sha256(self) -> Option<[u8; HashAlg::Sha256.digest_size()]> {
match self {
Self::Sha256(fingerprint) => Some(fingerprint),
_ => None,
}
}
pub fn sha512(self) -> Option<[u8; HashAlg::Sha512.digest_size()]> {
match self {
Self::Sha512(fingerprint) => Some(fingerprint),
_ => None,
}
}
pub fn is_sha256(self) -> bool {
matches!(self, Self::Sha256(_))
}
pub fn is_sha512(self) -> bool {
matches!(self, Self::Sha512(_))
}
pub fn fmt_randomart(self, header: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Randomart::new(header, self).fmt(f)
}
#[cfg(feature = "alloc")]
pub fn to_randomart(self, header: &str) -> String {
Randomart::new(header, self).to_string()
}
}
impl AsRef<[u8]> for Fingerprint {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = self.prefix();
let mut buf = [0u8; Self::SHA512_BASE64_SIZE];
let base64 = Base64Unpadded::encode(self.as_bytes(), &mut buf).map_err(|_| fmt::Error)?;
write!(f, "{prefix}:{base64}")
}
}
impl FromStr for Fingerprint {
type Err = Error;
fn from_str(id: &str) -> Result<Self> {
let (alg_str, base64) = id.split_once(':').ok_or(Error::AlgorithmUnknown)?;
let algorithm = match alg_str {
"SHA256" => HashAlg::Sha256,
"SHA512" => HashAlg::Sha512,
_ => return Err(Error::AlgorithmUnknown),
};
let mut buf = [0u8; HashAlg::Sha512.digest_size()];
let decoded_bytes = Base64Unpadded::decode(base64, &mut buf)?;
match algorithm {
HashAlg::Sha256 => Ok(Self::Sha256(decoded_bytes.try_into()?)),
HashAlg::Sha512 => Ok(Self::Sha512(decoded_bytes.try_into()?)),
}
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl<'de> Deserialize<'de> for Fingerprint {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
string.parse().map_err(de::Error::custom)
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
impl Serialize for Fingerprint {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.to_string().serialize(serializer)
}
}