drawbridge_hash/
hash.rs

1// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use core::ops::{Deref, DerefMut};
5use core::str::FromStr;
6
7use serde::{de::Error as _, Deserialize, Serialize};
8use sha2::digest::generic_array::GenericArray;
9use sha2::digest::OutputSizeUser;
10use sha2::{Sha224, Sha256, Sha384, Sha512};
11
12#[derive(Clone, Default)]
13pub(super) struct Buffer<T: OutputSizeUser>(pub GenericArray<u8, T::OutputSize>);
14
15impl<T: OutputSizeUser> Deref for Buffer<T> {
16    type Target = GenericArray<u8, T::OutputSize>;
17
18    fn deref(&self) -> &Self::Target {
19        &self.0
20    }
21}
22
23impl<T: OutputSizeUser> DerefMut for Buffer<T> {
24    fn deref_mut(&mut self) -> &mut Self::Target {
25        &mut self.0
26    }
27}
28
29impl<T: OutputSizeUser> Eq for Buffer<T> {}
30impl<T: OutputSizeUser> PartialEq for Buffer<T> {
31    fn eq(&self, other: &Self) -> bool {
32        self.0 == other.0
33    }
34}
35
36#[derive(Clone, PartialEq, Eq)]
37pub(super) enum Inner {
38    Sha224(Buffer<Sha224>),
39    Sha256(Buffer<Sha256>),
40    Sha384(Buffer<Sha384>),
41    Sha512(Buffer<Sha512>),
42}
43
44impl Inner {
45    fn name(&self) -> &'static str {
46        match self {
47            Self::Sha224(..) => "sha224",
48            Self::Sha256(..) => "sha256",
49            Self::Sha384(..) => "sha384",
50            Self::Sha512(..) => "sha512",
51        }
52    }
53}
54
55impl AsRef<[u8]> for Inner {
56    fn as_ref(&self) -> &[u8] {
57        match self {
58            Self::Sha224(b) => &*b,
59            Self::Sha256(b) => &*b,
60            Self::Sha384(b) => &*b,
61            Self::Sha512(b) => &*b,
62        }
63    }
64}
65
66impl std::fmt::Debug for Inner {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "{}", self)
69    }
70}
71
72impl AsMut<[u8]> for Inner {
73    fn as_mut(&mut self) -> &mut [u8] {
74        match self {
75            Self::Sha224(b) => &mut *b,
76            Self::Sha256(b) => &mut *b,
77            Self::Sha384(b) => &mut *b,
78            Self::Sha512(b) => &mut *b,
79        }
80    }
81}
82
83impl std::fmt::Display for Inner {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        let b64 = base64::encode_config(self.as_ref(), base64::URL_SAFE_NO_PAD);
86        write!(f, "{}:{}", self.name(), b64)
87    }
88}
89
90impl FromStr for Inner {
91    type Err = Error;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        let index = s.find(':').ok_or(Error::MissingColon)?;
95
96        let (alg, b64) = s.split_at(index);
97        let b64 = &b64[1..];
98
99        let mut hash = match alg {
100            "sha224" => Self::Sha224(Default::default()),
101            "sha256" => Self::Sha256(Default::default()),
102            "sha384" => Self::Sha384(Default::default()),
103            "sha512" => Self::Sha512(Default::default()),
104            _ => return Err(Error::UnknownAlgorithm),
105        };
106
107        if b64.len() != (hash.as_ref().len() * 8 + 5) / 6 {
108            return Err(Error::InvalidLength);
109        }
110
111        base64::decode_config_slice(b64, base64::URL_SAFE_NO_PAD, hash.as_mut())?;
112
113        Ok(hash)
114    }
115}
116
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum Error {
119    MissingColon,
120    InvalidLength,
121    UnknownAlgorithm,
122    Decode(base64::DecodeError),
123}
124
125impl std::fmt::Display for Error {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            Self::MissingColon => write!(f, "colon delimiter is missing"),
129            Self::InvalidLength => write!(f, "hash length is invalid"),
130            Self::UnknownAlgorithm => write!(f, "unknown hashing algorithm"),
131            Self::Decode(e) => write!(f, "invalid base64: {}", e),
132        }
133    }
134}
135
136impl From<base64::DecodeError> for Error {
137    fn from(value: base64::DecodeError) -> Self {
138        Self::Decode(value)
139    }
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
143pub struct Hash(pub(super) Inner);
144
145impl std::fmt::Display for Hash {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        write!(f, "{}", self.0)
148    }
149}
150
151impl FromStr for Hash {
152    type Err = Error;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        Inner::from_str(s).map(Self)
156    }
157}
158
159impl Serialize for Hash {
160    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
161        self.to_string().serialize(serializer)
162    }
163}
164
165impl<'de> Deserialize<'de> for Hash {
166    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
167        String::deserialize(deserializer)?
168            .parse()
169            .map_err(|_| D::Error::custom("invalid hash"))
170    }
171}