|
@@ -13,6 +13,8 @@ use openssl::{
|
|
|
};
|
|
|
use serde_block_tree::{self, to_vec, from_vec, write_to};
|
|
|
use serde::de::{DeserializeOwned};
|
|
|
+use std::str::FromStr;
|
|
|
+use strum_macros::{EnumString, EnumDiscriminants, Display};
|
|
|
|
|
|
/// Data that may or may not be encrypted.
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
|
@@ -31,6 +33,7 @@ pub enum Error {
|
|
|
MissingPrivateKey,
|
|
|
KeyVariantUnsupported,
|
|
|
BlockNotEncrypted,
|
|
|
+ InvalidFormat,
|
|
|
Message(String),
|
|
|
Serde(serde_block_tree::Error),
|
|
|
}
|
|
@@ -50,13 +53,72 @@ impl From<serde_block_tree::Error> for Error {
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
|
/// A cryptographic hash.
|
|
|
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone)]
|
|
|
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone, EnumDiscriminants)]
|
|
|
+#[strum_discriminants(derive(EnumString, Display))]
|
|
|
pub enum Hash {
|
|
|
Sha2_256([u8; 32]),
|
|
|
#[serde(with = "BigArray")]
|
|
|
Sha2_512([u8; 64]),
|
|
|
}
|
|
|
|
|
|
+impl Hash {
|
|
|
+ /// The character that's used to separate a hash type from its value in the leading component
|
|
|
+ /// of a path.
|
|
|
+ const HASH_SEP: char = ':';
|
|
|
+}
|
|
|
+
|
|
|
+impl From<HashDiscriminants> for Hash {
|
|
|
+ fn from(discriminant: HashDiscriminants) -> Hash {
|
|
|
+ match discriminant {
|
|
|
+ HashDiscriminants::Sha2_512 => Hash::Sha2_512([0u8; 64]),
|
|
|
+ HashDiscriminants::Sha2_256 => Hash::Sha2_256([0u8; 32])
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsRef<[u8]> for Hash {
|
|
|
+ fn as_ref(&self) -> &[u8] {
|
|
|
+ match self {
|
|
|
+ Hash::Sha2_256(arr) => arr,
|
|
|
+ Hash::Sha2_512(arr) => arr
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsMut<[u8]> for Hash {
|
|
|
+ fn as_mut(&mut self) -> &mut [u8] {
|
|
|
+ match self {
|
|
|
+ Hash::Sha2_256(arr) => arr,
|
|
|
+ Hash::Sha2_512(arr) => arr
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<&str> for Hash {
|
|
|
+ type Error = Error;
|
|
|
+ fn try_from(leading: &str) -> Result<Hash> {
|
|
|
+ let mut split: Vec<&str> = leading.split(Self::HASH_SEP).collect();
|
|
|
+ if split.len() != 2 {
|
|
|
+ return Err(Error::InvalidFormat)
|
|
|
+ };
|
|
|
+ let second = split.pop().ok_or(Error::InvalidFormat)?;
|
|
|
+ let first = split.pop().ok_or(Error::InvalidFormat)?;
|
|
|
+ let mut hash = Hash::from(HashDiscriminants::from_str(first)
|
|
|
+ .map_err(|_| Error::InvalidFormat)?);
|
|
|
+ base64_url::decode_to_slice(second, hash.as_mut())
|
|
|
+ .map_err(|_| Error::InvalidFormat)?;
|
|
|
+ Ok(hash)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Display for Hash {
|
|
|
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
+ let hash_type: HashDiscriminants = self.into();
|
|
|
+ let hash_data = base64_url::encode(self.as_ref());
|
|
|
+ write!(f, "{}{}{}", hash_type, Hash::HASH_SEP, hash_data)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const RSA_SIG_LEN: usize = 512;
|
|
|
|
|
|
/// A cryptographic signature.
|
|
@@ -659,6 +721,22 @@ mod tests {
|
|
|
assert_eq!(expected, actual);
|
|
|
}
|
|
|
|
|
|
+ #[test]
|
|
|
+ fn hash_to_string() {
|
|
|
+ let hash = test_helpers::make_principal().0;
|
|
|
+ let string = hash.to_string();
|
|
|
+ assert_eq!("Sha2_256:dSip4J0kurN5VhVo_aTipM-ywOOWrqJuRRVQ7aa-bew", string)
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn hash_to_string_round_trip() -> Result<()> {
|
|
|
+ let expected = test_helpers::make_principal().0;
|
|
|
+ let string = expected.to_string();
|
|
|
+ let actual = Hash::try_from(string.as_str())?;
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
/// Tests that validate the dependencies of this module.
|
|
|
mod dependency_tests {
|
|
|
use super::*;
|