Browse Source

Wrote tests for the verify_writecap function.

Matthew Carr 2 years ago
parent
commit
60e4b37b50
2 changed files with 117 additions and 12 deletions
  1. 109 11
      crates/node/src/crypto.rs
  2. 8 1
      crates/node/src/main.rs

+ 109 - 11
crates/node/src/crypto.rs

@@ -599,31 +599,55 @@ pub(crate) fn sign_writecap(write_cap: &mut WriteCap, key: &Key) -> Result<()> {
     Ok(())
 }
 
+/// The types of errors which can occur when verifying a writecap chain is authorized to write to
+/// a given path.
+#[derive(Debug, PartialEq)]
+pub(crate) enum WritecapAuthzErr {
+    /// The chain is not valid for use on the given path.
+    UnauthorizedPath,
+    /// At least one writecap in the chain is expired.
+    Expired,
+    /// The given writecaps do not actually form a chain.
+    NotChained,
+    /// There is an invalid signature in the chain.
+    InvalidSignature,
+    /// The principal the root writecap was issued to does not own the given path.
+    RootDoesNotOwnPath,
+    /// An error occured while serializing a writecap.
+    Serde(String),
+    /// A cryptographic error occurred while attempting to verify a writecap.
+    Crypto(String),
+}
+
 /// Verifies that the given `WriteCap` actually grants permission to write to the given `Path`.
-fn verify_write_cap(mut write_cap: &WriteCap, path: &Path) -> Result<bool> {
+pub(crate) fn verify_writecap(
+    mut write_cap: &WriteCap, path: &Path
+) -> std::result::Result<(), WritecapAuthzErr> {
     let mut prev: Option<&WriteCap> = None;
     let mut sig_input = Vec::new();
+    let now = Epoch::now();
     loop {
         if !write_cap.path.contains(path) {
-            return Ok(false);
+            return Err(WritecapAuthzErr::UnauthorizedPath);
         }
-        let now = Epoch::now();
         if write_cap.expires <= now {
-            return Ok(false);
+            return Err(WritecapAuthzErr::Expired);
         }
         if let Some(prev) = &prev {
             if prev.signing_key.to_principal() != write_cap.issued_to {
-                return Ok(false);
+                return Err(WritecapAuthzErr::NotChained);
             }
         }
         let sig = WriteCapSig::from(write_cap);
         sig_input.clear();
-        write_to(&sig, &mut sig_input.as_mut_slice())?;
-        let verify_algo = VerifyAlgo::try_from(&write_cap.signing_key)?;
-        let valid = verify_algo.verify(
-            [sig_input.as_slice()].into_iter(), write_cap.signature.as_slice())?;
+        write_to(&sig, &mut sig_input).map_err(|e| WritecapAuthzErr::Serde(e.to_string()))?;
+        let verify_algo = VerifyAlgo::try_from(&write_cap.signing_key)
+            .map_err(|e| WritecapAuthzErr::Crypto(e.to_string()))?;
+        let valid = verify_algo
+            .verify([sig_input.as_slice()].into_iter(), write_cap.signature.as_slice())
+            .map_err(|e| WritecapAuthzErr::Crypto(e.to_string()))?;
         if !valid {
-            return Ok(false);
+            return Err(WritecapAuthzErr::InvalidSignature);
         }
         match &write_cap.next {
             Some(next) => {
@@ -633,7 +657,12 @@ fn verify_write_cap(mut write_cap: &WriteCap, path: &Path) -> Result<bool> {
             None => {
                 // We're at the root key. As long as the signer of this writecap is the owner of
                 // the path, then the writecap is valid.
-                return Ok(write_cap.signing_key.to_principal() == path.owner);
+                if write_cap.signing_key.to_principal() == path.owner {
+                    return Ok(());
+                }
+                else {
+                    return Err(WritecapAuthzErr::RootDoesNotOwnPath)
+                }
             }
         }
     }
@@ -752,6 +781,75 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn verify_writecap_valid() -> Result<()> {
+        let writecap = make_writecap()?;
+        let result = verify_writecap(&writecap, &writecap.path);
+        assert_eq!(Ok(()), result);
+        Ok(())
+    }
+
+    #[test]
+    fn verify_writecap_invalid_signature() -> Result<()> {
+        let mut writecap = make_writecap()?;
+        writecap.signature = Signature::default();
+        let result = verify_writecap(&writecap, &writecap.path);
+        assert_eq!(Err(WritecapAuthzErr::InvalidSignature), result);
+        Ok(())
+    }
+
+    #[test]
+    fn verify_writecap_invalid_path_not_contained() -> Result<()> {
+        let writecap = make_writecap()?;
+        let mut path = writecap.path.clone();
+        path.components.pop();
+        // `path` is now a superpath of `writecap.path`, thus the writecap is not authorized to
+        // write to it.
+        let result = verify_writecap(&writecap, &path);
+        assert_eq!(Err(WritecapAuthzErr::UnauthorizedPath), result);
+        Ok(())
+    }
+
+    #[test]
+    fn verify_writecap_invalid_expired() -> Result<()> {
+        let mut writecap = make_writecap()?;
+        writecap.expires = Epoch::now() - Duration::from_secs(1);
+        let result = verify_writecap(&writecap, &writecap.path);
+        assert_eq!(Err(WritecapAuthzErr::Expired), result);
+        Ok(())
+    }
+
+    #[test]
+    fn verify_writecap_invalid_not_chained() -> Result<()> {
+        let (mut root_writecap, root_key) = make_self_signed_writecap()?;
+        root_writecap.issued_to = Principal(Hash::Sha2_256([0; 32]));
+        sign_writecap(&mut root_writecap, &root_key)?;
+        let node_key = Key::Rsa {
+            public: Vec::from(NODE_PUBLIC_KEY), private: None, padding: RsaPadding::Pkcs1
+        };
+        let writecap = make_writecap_trusted_by(
+            root_writecap, &root_key, &node_key, vec!["apps", "contacts"])?;
+        let result = verify_writecap(&writecap, &writecap.path);
+        assert_eq!(Err(WritecapAuthzErr::NotChained), result);
+        Ok(())
+    }
+
+    #[test]
+    fn verify_writecap_invalid_root_doesnt_own_path() -> Result<()> {
+        let (mut root_writecap, root_key) = make_self_signed_writecap()?;
+        let owner = Principal(Hash::Sha2_256([0; 32]));
+        root_writecap.path = make_path_with_owner(owner, vec![]);
+        sign_writecap(&mut root_writecap, &root_key)?;
+        let node_key = Key::Rsa {
+            public: Vec::from(NODE_PUBLIC_KEY), private: None, padding: RsaPadding::Pkcs1
+        };
+        let writecap = make_writecap_trusted_by(
+            root_writecap, &root_key, &node_key, vec!["apps", "contacts"])?;
+        let result = verify_writecap(&writecap, &writecap.path);
+        assert_eq!(Err(WritecapAuthzErr::RootDoesNotOwnPath), result);
+        Ok(())
+    }
+
     /// Tests that validate the dependencies of this module.
     mod dependency_tests {
         use super::*;

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

@@ -8,7 +8,7 @@ use std::{
     hash::Hash as Hashable,
     fmt::{self, Display, Formatter},
     time::{Duration, SystemTime},
-    ops::Add,
+    ops::{Add, Sub}
 };
 use serde::{Serialize, Deserialize};
 use serde_big_array::BigArray;
@@ -306,6 +306,13 @@ impl Add<Duration> for Epoch {
     }
 }
 
+impl Sub<Duration> for Epoch {
+    type Output = Self;
+    fn sub(self, other: Duration) -> Self {
+        Epoch(self.0 - other.as_secs())
+    }
+}
+
 /// The serial number of a block fragment.
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
 struct FragmentSerial(u32);