Browse Source

Added methods to Hash to allow it to be converted to and from
a string.

Matthew Carr 2 years ago
parent
commit
5913d46501
4 changed files with 139 additions and 2 deletions
  1. 52 0
      crates/node/Cargo.lock
  2. 3 0
      crates/node/Cargo.toml
  3. 79 1
      crates/node/src/crypto.rs
  4. 5 1
      crates/node/src/main.rs

+ 52 - 0
crates/node/Cargo.lock

@@ -8,6 +8,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "base64-url"
+version = "1.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a99c239d0c7e77c85dddfa9cebce48704b3c49550fcd3b84dd637e4484899f"
+dependencies = [
+ "base64",
+]
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -41,6 +56,12 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
 [[package]]
 name = "libc"
 version = "0.2.123"
@@ -51,10 +72,13 @@ checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
 name = "node"
 version = "0.1.0"
 dependencies = [
+ "base64-url",
  "openssl",
  "serde",
  "serde-big-array",
  "serde-block-tree",
+ "strum",
+ "strum_macros",
 ]
 
 [[package]]
@@ -124,6 +148,12 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
+
 [[package]]
 name = "serde"
 version = "1.0.136"
@@ -161,6 +191,28 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "strum"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.86"

+ 3 - 0
crates/node/Cargo.toml

@@ -11,3 +11,6 @@ serde-block-tree = { path = "../serde-block-tree" }
 serde = { version = "^1.0.136", features = ["derive"] }
 serde-big-array = { version = "^0.4.1" }
 openssl = { version = "^0.10.38", features = ["vendored"] }
+base64-url = { version = "^1.4.13" }
+strum = { version = "^0.24.0", features = ["derive"] }
+strum_macros = { version = "^0.24.0" }

+ 79 - 1
crates/node/src/crypto.rs

@@ -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::*;

+ 5 - 1
crates/node/src/main.rs

@@ -145,7 +145,7 @@ impl Path {
     /// The limit, in bytes, of a `Path`'s length.
     const BYTE_LIMIT: usize = 4096;
 
-    /// Returns a result which, when successful, returns the index after the last character in the
+    /// Returns a result which, when successful, contains the index after the last character in the
     /// current path component.
     fn component_end<I: Iterator<Item = (usize, char)>>(
         start: usize, first: char, pairs: &mut I
@@ -233,6 +233,8 @@ enum PathError {
     Empty,
     /// Occurs when a component in a path string was empty.
     EmptyComponent,
+    /// Occurs when the leading component of a path is not in the correct format.
+    InvalidLeadingComponent,
 }
 
 impl Display for PathError {
@@ -245,6 +247,8 @@ impl Display for PathError {
            ),
            PathError::Empty => formatter.write_str("path was empty"),
            PathError::EmptyComponent => formatter.write_str("component of path was empty"),
+           PathError::InvalidLeadingComponent
+            => formatter.write_str("invalid leading path component")
        }
     }
 }