Browse Source

Refactored the `Key` type to make it less easy to make security
critical mistakes.

Matthew Carr 2 years ago
parent
commit
9e8b7b84b1
3 changed files with 265 additions and 197 deletions
  1. 209 152
      crates/node/src/crypto.rs
  2. 22 5
      crates/node/src/main.rs
  3. 34 40
      crates/node/src/test_helpers.rs

+ 209 - 152
crates/node/src/crypto.rs

@@ -4,7 +4,7 @@ use super::*;
 use openssl::{
     error::ErrorStack,
     encrypt::{Encrypter, Decrypter},
-    pkey::{PKey, Public, Private},
+    pkey::{PKey, Public as OsslPublic, Private as OsslPrivate},
     symm::{Cipher, encrypt as openssl_encrypt, decrypt as openssl_decrypt},
     rand::rand_bytes,
     rsa::{Rsa, Padding as OpensslPadding},
@@ -77,10 +77,20 @@ pub enum Hash {
     Sha2_512([u8; 64]),
 }
 
+impl Default for HashType {
+    fn default() -> HashType {
+        HashType::Sha2_256
+    }
+}
+
 impl Hash {
     /// The character that's used to separate a hash type from its value in its string
     /// representation.
     const HASH_SEP: char = ':';
+
+    fn kind(&self) -> HashType {
+        HashType::from(self)
+    }
 }
 
 impl From<HashType> for Hash {
@@ -201,10 +211,9 @@ impl From<RsaPadding> for OpensslPadding {
     }
 }
 
-/// A cryptographic key.
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone, EnumDiscriminants)]
-#[strum_discriminants(name(KeyType))]
-pub enum Key {
+#[strum_discriminants(name(SymKeyType))]
+pub enum SymKey {
     /// A key for the AES 256 cipher in Cipher Block Chaining mode. Note that this includes the
     /// initialization vector, so that a value of this variant contains all the information needed
     /// to fully initialize a cipher context.
@@ -217,71 +226,115 @@ pub enum Key {
         key: [u8; 32],
         iv: [u8; 16]
     },
-    Rsa {
-        public: Vec<u8>,
-        private: Option<Vec<u8>>,
-        padding: RsaPadding,
-    },
 }
 
-impl Key {
-    /// Returns the key part of this `Key`. Note that if this `Key` is for an asymmetric algorithm,
-    /// then this returns the *private* key (if it is present).
-    fn key_slice(&self) -> Option<&[u8]> {
-        match self {
-            Key::Aes256Cbc { key, .. } => Some(key),
-            Key::Aes256Ctr { key, .. } => Some(key),
-            Key::Rsa { private, .. } => match private {
-                Some(key) => Some(key.as_slice()),
-                None => None,
+fn generate_key_and_iv<const KEY_LEN: usize, const IV_LEN: usize>(
+) -> Result<([u8; KEY_LEN], [u8; IV_LEN])> {
+    let mut key = [0; KEY_LEN];
+    let mut iv = [0; IV_LEN];
+    rand_bytes(&mut key).map_err(Error::from)?;
+    rand_bytes(&mut iv).map_err(Error::from)?;
+    Ok((key, iv))
+}
+
+/// Returns an array of the given length filled with cryptographically random data.
+fn rand_array<const LEN: usize>() -> Result<[u8; LEN]> {
+    let mut array = [0; LEN];
+    rand_bytes(&mut array).map_err(Error::from)?;
+    Ok(array)
+}
+
+impl SymKey {
+    pub fn generate(kind: SymKeyType) -> Result<SymKey> {
+        match kind {
+            SymKeyType::Aes256Cbc => {
+                Ok(SymKey::Aes256Cbc { key: rand_array()?, iv: rand_array()? })
+            },
+            SymKeyType::Aes256Ctr => {
+                Ok(SymKey::Aes256Ctr { key: rand_array()?, iv: rand_array()? })
             },
         }
     }
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+pub struct Public;
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+pub struct Private;
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, EnumDiscriminants)]
+#[strum_discriminants(name(AsymKeyType))]
+pub enum AsymKey<T> {
+    Rsa {
+        der: Vec<u8>,
+        padding: RsaPadding,
+        kind: T,
+    }
+}
 
-    /// Returns the IV part of this`Key`.
-    fn iv_slice(&self) -> Option<&[u8]> {
+impl<T> AsymKey<T> {
+    pub fn as_slice(&self) -> &[u8] {
         match self {
-            Key::Aes256Cbc { iv, .. } => Some(iv),
-            _ => None,
+            AsymKey::Rsa { der, .. } => der.as_slice()
         }
     }
+}
 
-    pub fn generate(id: KeyType) -> Result<Key> {
-        match id {
-            KeyType::Aes256Cbc => {
-                let mut key = [0; 32];
-                let mut iv = [0; 16];
-                rand_bytes(&mut key).map_err(Error::from)?;
-                rand_bytes(&mut iv).map_err(Error::from)?;
-                Ok(Key::Aes256Cbc { key, iv })
-            },
-            KeyType::Aes256Ctr => {
-                let mut key = [0; 32];
-                let mut iv = [0; 16];
-                rand_bytes(&mut key).map_err(Error::from)?;
-                rand_bytes(&mut iv).map_err(Error::from)?;
-                Ok(Key::Aes256Ctr { key, iv })
+impl<T> Owned for AsymKey<T> {
+    fn owner_of_kind(&self, kind: HashType) -> Principal {
+        let slice = match self {
+            AsymKey::Rsa { der, .. } => der.as_slice(),
+        };
+        match kind {
+            HashType::Sha2_256 => {
+                let mut buf = [0; 32];
+                let bytes = hash(MessageDigest::sha256(), slice).unwrap();
+                buf.copy_from_slice(&*bytes);
+                Principal(Hash::Sha2_256(buf))
             },
-            KeyType::Rsa => {
+            HashType::Sha2_512 => {
+                let mut buf = [0; 64];
+                let bytes = hash(MessageDigest::sha512(), slice).unwrap();
+                buf.copy_from_slice(&*bytes);
+                Principal(Hash::Sha2_512(buf))
+            }
+        }
+    }
+}
+
+pub struct KeyPair {
+    pub public: AsymKey<Public>,
+    pub private: AsymKey<Private>,
+}
+
+impl KeyPair {
+    pub fn generate(kind: AsymKeyType) -> Result<KeyPair> {
+        match kind {
+            AsymKeyType::Rsa => {
                 let key = PKey::from_rsa(Rsa::generate(4096)?)?;
-                let public= key.public_key_to_der().map_err(Error::from)?;
-                let private = Some(key.private_key_to_der().map_err(Error::from)?);
-                Ok(Key::Rsa { public, private, padding: RsaPadding::Pkcs1 })
+                let public = key.public_key_to_der().map_err(Error::from)?;
+                let private = key.private_key_to_der().map_err(Error::from)?;
+                Ok(KeyPair {
+                    public: AsymKey::Rsa {
+                        der: public,
+                        padding: RsaPadding::default(),
+                        kind: Public,
+                    },
+                    private: AsymKey::Rsa {
+                        der: private,
+                        padding: RsaPadding::default(),
+                        kind: Private,
+                    },
+                })
             },
         }
     }
+}
 
-    /// Hashes this key and returns the `Principal` it is for.
-    pub fn to_principal(&self) -> Principal {
-        let slice = match self {
-            Key::Rsa { public, .. } => public.as_slice(),
-            Key::Aes256Cbc { key, .. } => key,
-            Key::Aes256Ctr { key, .. } => key,
-        };
-        let mut buf = [0; 32];
-        let bytes = hash(MessageDigest::sha256(), slice).unwrap();
-        buf.copy_from_slice(&*bytes);
-        Principal(Hash::Sha2_256(buf))
+impl Owned for KeyPair {
+    fn owner_of_kind(&self, kind: HashType) -> Principal {
+        self.public.owner_of_kind(kind)
     }
 }
 
@@ -292,7 +345,7 @@ enum EncryptionAlgo<'a> {
         iv: Option<&'a [u8]>
     },
     Asymmetric {
-        key: PKey<Public>,
+        key: PKey<OsslPublic>,
         rsa_padding: Option<OpensslPadding>,
     }
 }
@@ -319,22 +372,30 @@ impl<'a> EncryptionAlgo<'a> {
     }
 }
 
-impl<'a> TryFrom<&'a Key> for EncryptionAlgo<'a> {
+impl<'a> TryFrom<&'a SymKey> for EncryptionAlgo<'a> {
     type Error = Error;
-    fn try_from(key: &'a Key) -> Result<EncryptionAlgo<'a>> {
+    fn try_from(key: &'a SymKey) -> Result<EncryptionAlgo<'a>> {
         match key {
-            Key::Aes256Cbc { key: key_slice, iv } => Ok(EncryptionAlgo::Symmetric {
+            SymKey::Aes256Cbc { key: key_slice, iv } => Ok(EncryptionAlgo::Symmetric {
                 cipher: Cipher::aes_256_cbc(),
                 key: key_slice,
                 iv: Some(iv),
             }),
-            Key::Aes256Ctr { key: key_slice, iv } => Ok(EncryptionAlgo::Symmetric {
+            SymKey::Aes256Ctr { key: key_slice, iv } => Ok(EncryptionAlgo::Symmetric {
                 cipher: Cipher::aes_256_ctr(),
                 key: key_slice,
                 iv: Some(iv),
             }),
-            Key::Rsa { public, padding, .. } => {
-                let pkey = PKey::public_key_from_der(public.as_slice())
+        }
+    }
+}
+
+impl<'a> TryFrom<&'a AsymKey<Public>> for EncryptionAlgo<'a> {
+    type Error = Error;
+    fn try_from(key: &'a AsymKey<Public>) -> Result<EncryptionAlgo<'a>> {
+        match key {
+            AsymKey::Rsa { der, padding, .. } => {
+                let pkey = PKey::public_key_from_der(der.as_slice())
                     .map_err(|err| Error::Message(err.to_string()));
                 Ok(EncryptionAlgo::Asymmetric { key: pkey?, rsa_padding: Some((*padding).into()) })
             },
@@ -348,7 +409,7 @@ enum DecryptionAlgo<'a> {
         key: &'a [u8],
         iv: Option<&'a [u8]>
     },
-    Asymmetric(PKey<Private>),
+    Asymmetric(PKey<OsslPrivate>),
 }
 
 impl<'a> DecryptionAlgo<'a> {
@@ -370,23 +431,30 @@ impl<'a> DecryptionAlgo<'a> {
     }
 }
 
-impl<'a> TryFrom<&'a Key> for DecryptionAlgo<'a> {
+impl<'a> TryFrom<&'a SymKey> for DecryptionAlgo<'a> {
     type Error = Error;
-    fn try_from(key: &'a Key) -> Result<DecryptionAlgo<'a>> {
+    fn try_from(key: &'a SymKey) -> Result<DecryptionAlgo<'a>> {
         match key {
-            Key::Aes256Cbc { key: key_slice, iv } => Ok(DecryptionAlgo::Symmetric {
+            SymKey::Aes256Cbc { key: key_slice, iv } => Ok(DecryptionAlgo::Symmetric {
                 cipher: Cipher::aes_256_cbc(),
                 key: key_slice,
                 iv: Some(iv),
             }),
-            Key::Aes256Ctr { key: key_slice, iv } => Ok(DecryptionAlgo::Symmetric {
+            SymKey::Aes256Ctr { key: key_slice, iv } => Ok(DecryptionAlgo::Symmetric {
                 cipher: Cipher::aes_256_ctr(),
                 key: key_slice,
                 iv: Some(iv),
             }),
-            Key::Rsa { private, .. } => {
-                let private = private.as_ref().ok_or(Error::MissingPrivateKey)?;
-                let pkey = PKey::private_key_from_der(private.as_slice()).map_err(Error::from);
+        }
+    }
+}
+
+impl<'a> TryFrom<&'a AsymKey<Private>> for DecryptionAlgo<'a> {
+    type Error = Error;
+    fn try_from(key: &'a AsymKey<Private>) -> Result<DecryptionAlgo<'a>> {
+        match key {
+            AsymKey::Rsa { der, .. } => {
+                let pkey = PKey::private_key_from_der(der.as_slice()).map_err(Error::from);
                 Ok(DecryptionAlgo::Asymmetric(pkey?))
             }
         }
@@ -394,7 +462,7 @@ impl<'a> TryFrom<&'a Key> for DecryptionAlgo<'a> {
 }
 
 struct SignAlgo {
-    key: PKey<Private>,
+    key: PKey<OsslPrivate>,
     digest: MessageDigest,
     signature: Signature
 }
@@ -411,25 +479,24 @@ impl SignAlgo {
     }
 }
 
-impl TryFrom<&Key> for SignAlgo {
+impl TryFrom<&AsymKey<Private>> for SignAlgo {
     type Error = Error;
-    fn try_from(key: &Key) -> Result<SignAlgo> {
+    fn try_from(key: &AsymKey<Private>) -> Result<SignAlgo> {
         match key {
-            Key::Rsa { private: Some(private), .. } => {
-                let rsa = Rsa::private_key_from_der(private.as_slice()).map_err(Error::from)?;
+            AsymKey::Rsa { der, .. } => {
+                let rsa = Rsa::private_key_from_der(der.as_slice()).map_err(Error::from)?;
                 Ok(SignAlgo {
                     key: PKey::from_rsa(rsa).map_err(Error::from)?,
                     digest: MessageDigest::sha256(),
                     signature: Signature::new(SignatureId::Rsa)
                 })
             },
-            _ => Err(Error::KeyVariantUnsupported)
         }
     }
 }
 
 struct VerifyAlgo {
-    key: PKey<Public>,
+    key: PKey<OsslPublic>,
     digest: MessageDigest,
 }
 
@@ -445,24 +512,23 @@ impl VerifyAlgo {
     }
 }
 
-impl TryFrom<&Key> for VerifyAlgo {
+impl TryFrom<&AsymKey<Public>> for VerifyAlgo {
     type Error = Error;
-    fn try_from(key: &Key) -> Result<VerifyAlgo> {
+    fn try_from(key: &AsymKey<Public>) -> Result<VerifyAlgo> {
         match key {
-            Key::Rsa { public, .. } => {
-                let rsa = Rsa::public_key_from_der(public.as_slice()).map_err(Error::from)?;
+            AsymKey::Rsa { der, .. } => {
+                let rsa = Rsa::public_key_from_der(der.as_slice()).map_err(Error::from)?;
                 Ok(VerifyAlgo {
                     key: PKey::from_rsa(rsa).map_err(Error::from)?,
                     digest: MessageDigest::sha256()
                 })
             },
-            _ => Err(Error::KeyVariantUnsupported)
         }
     }
 }
 
 pub(crate) fn encrypt_block(
-    versioned_block: VersionedBlock, principal: &Principal, key: &Key
+    versioned_block: VersionedBlock, principal: &Principal, key: &KeyPair
 ) -> Result<VersionedBlock> {
     let VersionedBlock::V0(mut block) = versioned_block;
     let body = match block.body {
@@ -471,15 +537,15 @@ pub(crate) fn encrypt_block(
     };
     let (principal_owned, read_cap) = block.readcaps.remove_entry(principal)
         .ok_or(Error::NoReadCap)?;
-    let block_key = decrypt(read_cap, key)?;
+    let block_key = decrypt(read_cap, &key.private)?;
     let new_body = encrypt_slice(&body, &block_key)?;
     block.body = Cryptotext::Cipher(new_body);
-    block.readcaps.insert(principal_owned, encrypt(&block_key, key)?);
+    block.readcaps.insert(principal_owned, encrypt(&block_key, &key.public)?);
     Ok(VersionedBlock::V0(block))
 }
 
 pub(crate) fn decrypt_block(
-    versioned_block: VersionedBlock, principal: &Principal, key: &Key
+    versioned_block: VersionedBlock, principal: &Principal, key: &AsymKey<Private>
 ) -> Result<VersionedBlock> {
     let VersionedBlock::V0(mut block) = versioned_block;
     let body = match block.body {
@@ -495,13 +561,17 @@ pub(crate) fn decrypt_block(
     Ok(VersionedBlock::V0(block))
 }
 
-fn encrypt<T: Serialize>(value: &T, key: &Key) -> Result<Cryptotext<T>> {
+fn encrypt<'a, K: TryInto<EncryptionAlgo<'a>, Error = Error>, T: Serialize>(
+    value: &T, key: K
+) -> Result<Cryptotext<T>> {
     let data = to_vec(value).map_err(Error::from)?;
     let vec = encrypt_slice(&data, key);
     Ok(Cryptotext::Cipher(vec?))
 }
 
-fn decrypt<T: Serialize + DeserializeOwned>(cryptotext: Cryptotext<T>, key: &Key) -> Result<T> {
+fn decrypt<'a, K: TryInto<DecryptionAlgo<'a>, Error = Error>, T: Serialize + DeserializeOwned>(
+    cryptotext: Cryptotext<T>, key: K
+) -> Result<T> {
     let data = match cryptotext {
         Cryptotext::Plain(value) => return Ok(value),
         Cryptotext::Cipher(data) => data
@@ -510,20 +580,24 @@ fn decrypt<T: Serialize + DeserializeOwned>(cryptotext: Cryptotext<T>, key: &Key
     from_vec(&vec?).map_err(Error::from)
 }
 
-fn encrypt_slice(plaintext: &[u8], key: &Key) -> Result<Vec<u8>> {
-   let algo = EncryptionAlgo::try_from(key)?;
-   algo.encrypt(plaintext)
+fn encrypt_slice<'a, K: TryInto<EncryptionAlgo<'a>, Error = Error>>(
+    plaintext: &[u8], key: K
+) -> Result<Vec<u8>> {
+    let algo: EncryptionAlgo<'a> = key.try_into()?;
+    algo.encrypt(plaintext)
 }
 
-fn decrypt_slice(ciphertext: &[u8], key: &Key) -> Result<Vec<u8>> {
-    let algo = DecryptionAlgo::try_from(key)?;
+fn decrypt_slice<'a, K: TryInto<DecryptionAlgo<'a>, Error = Error>>(
+    ciphertext: &[u8], key: K
+) -> Result<Vec<u8>> {
+    let algo: DecryptionAlgo<'a> = key.try_into()?;
     algo.decrypt(ciphertext)
 }
 
 #[derive(Serialize)]
 struct SigHeader<'a> {
     path: &'a Path,
-    readcaps: &'a HashMap<Principal, Cryptotext<Key>>,
+    readcaps: &'a HashMap<Principal, Cryptotext<SymKey>>,
     writecap: &'a Writecap,
 }
 
@@ -547,13 +621,15 @@ fn get_body(block: &Block) -> Result<&[u8]> {
     Ok(body)
 }
 
-pub(crate) fn sign_block(versioned_block: &mut VersionedBlock, writecap: Writecap) -> Result<()> {
+pub(crate) fn sign_block(
+    versioned_block: &mut VersionedBlock, writecap: Writecap, priv_key: AsymKey<Private>
+) -> Result<()> {
     let VersionedBlock::V0(block) = versioned_block;
     block.writecap = writecap;
     let body = get_body(block)?;
     let sig_header = SigHeader::from(&*block);
     let header = to_vec(&sig_header)?;
-    let mut sign_algo = SignAlgo::try_from(&block.writecap.signing_key)?;
+    let mut sign_algo = SignAlgo::try_from(&priv_key)?;
     sign_algo.sign([header.as_slice(), body].into_iter())?;
     block.signature = sign_algo.signature;
     Ok(())
@@ -577,7 +653,7 @@ struct WritecapSig<'a> {
     issued_to: &'a Principal,
     path: &'a Path,
     expires: &'a Epoch,
-    signing_key: &'a Key,
+    signing_key: &'a AsymKey<Public>,
 }
 
 impl<'a> From<&'a Writecap> for WritecapSig<'a> {
@@ -591,8 +667,8 @@ impl<'a> From<&'a Writecap> for WritecapSig<'a> {
     }
 }
 
-pub(crate) fn sign_writecap(writecap: &mut Writecap, key: &Key) -> Result<()> {
-    let mut sign_algo = SignAlgo::try_from(key)?;
+pub(crate) fn sign_writecap(writecap: &mut Writecap, priv_key: &AsymKey<Private>) -> Result<()> {
+    let mut sign_algo = SignAlgo::try_from(priv_key)?;
     let sig_input = to_vec(&WritecapSig::from(&*writecap))?;
     sign_algo.sign([sig_input.as_slice()].into_iter())?;
     writecap.signature = sign_algo.signature;
@@ -634,7 +710,7 @@ pub(crate) fn verify_writecap(
             return Err(WritecapAuthzErr::Expired);
         }
         if let Some(prev) = &prev {
-            if prev.signing_key.to_principal() != writecap.issued_to {
+            if prev.signing_key.owner_of_kind(writecap.issued_to.kind()) != writecap.issued_to {
                 return Err(WritecapAuthzErr::NotChained);
             }
         }
@@ -657,7 +733,7 @@ pub(crate) fn verify_writecap(
             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.
-                if writecap.signing_key.to_principal() == path.owner {
+                if writecap.signing_key.owner_of_kind(path.owner.kind()) == path.owner {
                     return Ok(());
                 }
                 else {
@@ -673,32 +749,17 @@ mod tests {
     use super::*;
     use test_helpers::*;
 
-    #[test]
-    fn key_aes256cbc_key_slice_is_correct() -> Result<()> {
-        let key = Key::Aes256Cbc { key: KEY, iv: IV };
-        let key_part = key.key_slice().ok_or(Error::NoKeyAvailable);
-        assert_eq!(KEY, key_part?);
-        Ok(())
-    }
-
-    #[test]
-    fn key_aes256cbc_iv_slice_is_correct() {
-        let key = Key::Aes256Cbc { key: KEY, iv: IV };
-        let iv_part = key.iv_slice().unwrap();
-        assert_eq!(IV, iv_part);
-    }
-
     fn encrypt_decrypt_block_test_case(
-        mut actual: VersionedBlock, principal: &Principal, key: &Key
+        mut actual: VersionedBlock, principal: &Principal, key: &KeyPair
     ) -> Result<()> {
         let expected = actual.clone();
         actual = encrypt_block(actual, principal, key)?;
-        actual = decrypt_block(actual, principal, key)?;
+        actual = decrypt_block(actual, principal, &key.private)?;
         assert_eq!(expected, actual);
         Ok(())
     }
 
-    fn make_versioned_block(principal: Principal, block_key: Key) -> Result<VersionedBlock> {
+    fn make_versioned_block(principal: Principal, block_key: SymKey) -> Result<VersionedBlock> {
         let mut block = make_block()?;
         block.readcaps.clear();
         block.readcaps.insert(principal, Cryptotext::Plain(block_key));
@@ -708,38 +769,29 @@ mod tests {
     #[test]
     fn encrypt_decrypt_block_aes256cbc() -> Result<()> {
         let principal = make_principal();
-        let block_key = Key::Aes256Cbc { key: BLOCK_KEY, iv: BLOCK_IV };
+        let block_key = SymKey::Aes256Cbc { key: BLOCK_KEY, iv: BLOCK_IV };
         let block = make_versioned_block(principal.clone(), block_key)?;
-        let key = Key::Aes256Cbc { key: KEY, iv: IV };
+        let key = make_key_pair();
         encrypt_decrypt_block_test_case(block, &principal, &key)
     }
 
     #[test]
     fn encrypt_decrypt_block_aes256ctr() -> Result<()> {
         let principal = make_principal();
-        let block_key = Key::Aes256Ctr { key: BLOCK_KEY, iv: BLOCK_IV };
-        let block = make_versioned_block(principal.clone(), block_key)?;
-        let key = Key::Aes256Ctr { key: KEY, iv: IV };
-        encrypt_decrypt_block_test_case(block, &principal, &key)
-    }
-
-    #[test]
-    fn encrypt_decrypt_block_rsa() -> Result<()> {
-        let principal = make_principal();
-        let block_key = Key::Aes256Ctr { key: BLOCK_KEY, iv: BLOCK_IV };
+        let block_key = SymKey::Aes256Ctr { key: BLOCK_KEY, iv: BLOCK_IV };
         let block = make_versioned_block(principal.clone(), block_key)?;
-        let key = Key::generate(KeyType::Rsa)?;
+        let key = make_key_pair();
         encrypt_decrypt_block_test_case(block, &principal, &key)
     }
 
     #[test]
     fn rsa_sign_and_verify() -> Result<()> {
-        let key = Key::generate(KeyType::Rsa)?;
+        let key = make_key_pair();
         let header = b"About: lyrics".as_slice();
         let message = b"Everything that feels so good is bad bad bad.".as_slice();
-        let mut signer = SignAlgo::try_from(&key)?;
+        let mut signer = SignAlgo::try_from(&key.private)?;
         signer.sign([header, message].into_iter())?;
-        let verifier = VerifyAlgo::try_from(&key)?;
+        let verifier = VerifyAlgo::try_from(&key.public)?;
         let verified = verifier.verify([header, message].into_iter(), signer.signature.as_slice())?;
         assert_eq!(true, verified);
         Ok(())
@@ -748,19 +800,19 @@ mod tests {
     #[test]
     fn sign_verify_block_rsa() -> Result<()> {
         let principal = make_principal();
-        let block_key = Key::Aes256Ctr { key: BLOCK_KEY, iv: BLOCK_IV };
+        let block_key = SymKey::Aes256Ctr { key: BLOCK_KEY, iv: BLOCK_IV };
         let mut block = make_versioned_block(principal.clone(), block_key)?;
-        let key = Key::generate(KeyType::Rsa)?;
+        let key = make_key_pair();
         let writecap = Writecap {
             issued_to: Principal(Hash::Sha2_256(PRINCIPAL)),
             path: make_path(vec!["contacts", "emergency"]),
             expires: Epoch(1649904316),
-            signing_key: key,
+            signing_key: key.public.clone(),
             signature: Signature::Rsa(SIGNATURE),
             next: None,
         };
-        block = encrypt_block(block, &principal, &writecap.signing_key)?;
-        sign_block(&mut block, writecap)?;
+        block = encrypt_block(block, &principal, &key)?;
+        sign_block(&mut block, writecap, key.private)?;
         assert_eq!(true, verify_block(&block)?);
         Ok(())
     }
@@ -823,12 +875,15 @@ mod tests {
     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
+        sign_writecap(&mut root_writecap, &root_key.private)?;
+        let node_key = AsymKey::Rsa {
+            der: Vec::from(NODE_PUBLIC_KEY),
+            padding: RsaPadding::Pkcs1,
+            kind: Public {},
         };
+        let node_principal = node_key.owner();
         let writecap = make_writecap_trusted_by(
-            root_writecap, &root_key, &node_key, vec!["apps", "contacts"])?;
+            root_writecap, root_key, node_principal, vec!["apps", "contacts"])?;
         let result = verify_writecap(&writecap, &writecap.path);
         assert_eq!(Err(WritecapAuthzErr::NotChained), result);
         Ok(())
@@ -839,12 +894,15 @@ mod tests {
         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
+        sign_writecap(&mut root_writecap, &root_key.private)?;
+        let node_key = AsymKey::Rsa {
+            der: Vec::from(NODE_PUBLIC_KEY),
+            padding: RsaPadding::Pkcs1,
+            kind: Public {}
         };
+        let node_owner = node_key.owner();
         let writecap = make_writecap_trusted_by(
-            root_writecap, &root_key, &node_key, vec!["apps", "contacts"])?;
+            root_writecap, root_key, node_owner, vec!["apps", "contacts"])?;
         let result = verify_writecap(&writecap, &writecap.path);
         assert_eq!(Err(WritecapAuthzErr::RootDoesNotOwnPath), result);
         Ok(())
@@ -898,13 +956,12 @@ mod tests {
         #[test]
         fn rsa_signature_len() -> Result<()> {
             use openssl::rsa::Rsa;
-            let key = Key::generate(KeyType::Rsa)?;
-            let sign_algo = SignAlgo::try_from(&key)?;
-            let private = match &key {
-                Key::Rsa { private: Some(private), .. } => private,
-                _ => return Err(Error::Message("Incorrect key returned.".to_string()))
+            let key = make_key_pair();
+            let sign_algo = SignAlgo::try_from(&key.private)?;
+            let der = match &key.private {
+                AsymKey::Rsa { der, .. } => der,
             };
-            let rsa = Rsa::private_key_from_der(private)?;
+            let rsa = Rsa::private_key_from_der(der)?;
             let pkey = PKey::from_rsa(rsa)?;
             let signer = Signer::new(sign_algo.digest, &pkey)?;
             assert_eq!(RSA_SIG_LEN, signer.len()?);

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

@@ -20,7 +20,7 @@ mod test_helpers;
 mod serde_tests;
 
 mod crypto;
-use crypto::{Hash, Signature, Key, Cryptotext};
+use crypto::{Hash, HashType, Signature, SymKey, AsymKey, Public, Cryptotext};
 
 /// A Block tagged with its version number.
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@@ -36,7 +36,7 @@ struct Block {
     path: Path,
     /// This field contains a collection of `Readcap`s indexed by the principal who holds them.
     /// `Readcap`s are envelopments of the key used to encrypt this block.
-    readcaps: HashMap<Principal, Cryptotext<Key>>,
+    readcaps: HashMap<Principal, Cryptotext<SymKey>>,
     /// This field is used to verify that the signer of this block had permission to write it.
     /// It contains a certificate chain that must lead back to the root key for the tree this block
     /// is part of.
@@ -53,11 +53,11 @@ struct Readcap {
     /// The principal this `Readcap` was issued to.
     issued_to: Principal,
     /// An encipherment of a block key using the public key of the principal.
-    key: Cryptotext<Key>,
+    key: Cryptotext<SymKey>,
 }
 
 impl Readcap {
-    fn new(issued_to: Hash, key: Cryptotext<Key>) -> Readcap {
+    fn new(issued_to: Hash, key: Cryptotext<SymKey>) -> Readcap {
         Readcap { issued_to: Principal(issued_to), key }
     }
 }
@@ -72,7 +72,7 @@ struct Writecap {
     /// The point in time after which this write cap is no longer valid.
     expires: Epoch,
     /// The public key used to sign this write cap.
-    signing_key: Key,
+    signing_key: AsymKey<Public>,
     /// A digital signature which covers all of the fields in the write cap except for next.
     signature: Signature,
     /// The next write cap in the chain leading back to the root.
@@ -137,6 +137,23 @@ impl FragmentRecord {
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone)]
 pub struct Principal(Hash);
 
+impl Principal {
+    fn kind(&self) -> HashType {
+        HashType::from(&self.0)
+    }
+}
+
+/// Trait for types which are owned by a `Principal`.
+pub trait Owned {
+    /// Returns the `Principal` that owns `self`, using the given hash algorithm.
+    fn owner_of_kind(&self, kind: HashType) -> Principal;
+
+    /// Returns the `Principal` that owns `self`, using the default hash algorithm.
+    fn owner(&self) -> Principal {
+        self.owner_of_kind(HashType::default())
+    }
+}
+
 /// An identifier for a block in a tree.
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
 struct Path {

+ 34 - 40
crates/node/src/test_helpers.rs

@@ -1,7 +1,7 @@
 /// Test data and functions to help with testing.
 
 use super::*;
-use crypto::{ KeyType, RsaPadding };
+use crypto::*;
 use serde_block_tree::{Error, Result};
 use std::{ fs::File, io::Write, fmt::Write as FmtWrite };
 
@@ -481,69 +481,67 @@ pub(crate) fn make_path(rel_components: Vec<&str>) -> Path {
 
 pub(crate) fn make_writecap() -> Result<Writecap> {
     let (root_writecap, root_key) = make_self_signed_writecap()?;
-    let public_key = Key::Rsa {
-        public: Vec::from(NODE_PUBLIC_KEY),
-        private: None,
-        padding: RsaPadding::Pkcs1,
-    };
+    let issued_to = Principal(Hash::Sha2_256(PRINCIPAL));
     make_writecap_trusted_by(
-        root_writecap, &root_key, &public_key, vec!["contacts", "emergency"])
+        root_writecap, root_key, issued_to, vec!["contacts", "emergency"])
 }
 
 pub(crate) fn make_writecap_trusted_by(
-    next: Writecap, signing_key: &Key, public_key: &Key, path_components: Vec<&str>
+    next: Writecap, trusting_key: KeyPair, issued_to: Principal, path_components: Vec<&str>
 ) -> Result<Writecap> {
     let hour_hence = Epoch::now() + Duration::from_secs(3600);
-    let mut signing_public_key = signing_key.clone();
-    match &mut signing_public_key {
-        Key::Rsa { private, .. } => private.take(),
-        _ => return Err(Error::Message("unexpected key type".to_string())),
-    };
     let mut writecap = Writecap {
-        issued_to: public_key.to_principal(),
+        issued_to,
         path: make_path_with_owner(next.path.owner.clone(), path_components),
         expires: hour_hence,
-        signing_key: signing_public_key,
+        signing_key: trusting_key.public,
         signature: Signature::default(),
         next: Some(Box::from(next)),
     };
-    crypto::sign_writecap(&mut writecap, signing_key).map_err(|e| Error::Message(e.to_string()))?;
+    crypto::sign_writecap(&mut writecap, &trusting_key.private)
+        .map_err(|e| Error::Message(e.to_string()))?;
     Ok(writecap)
 }
 
-pub(crate) fn make_self_signed_writecap() -> Result<(Writecap, Key)> {
-    let private_key = Key::Rsa {
-        public: Vec::from(ROOT_PUBLIC_KEY),
-        private: Some(Vec::from(ROOT_PRIVATE_KEY)),
-        padding: RsaPadding::Pkcs1,
-    };
-    Ok((make_self_signed_writecap_with(&private_key)?, private_key))
+pub(crate) fn make_key_pair() -> KeyPair {
+    KeyPair {
+        public: AsymKey::Rsa {
+            der: Vec::from(ROOT_PUBLIC_KEY),
+            padding: RsaPadding::default(),
+            kind: Public
+        },
+        private: AsymKey::Rsa {
+            der: Vec::from(ROOT_PRIVATE_KEY),
+            padding: RsaPadding::default(),
+            kind: Private,
+        },
+    }
 }
 
-pub(crate) fn make_self_signed_writecap_with(private_key: &Key) -> Result<Writecap> {
-    let mut public_key = private_key.clone();
-    match &mut public_key {
-        Key::Rsa { private, .. } => private.take(),
-        _ => return Err(Error::Message("unexpected key type".to_string())),
-    };
-    let root_principal = public_key.to_principal();
+pub(crate) fn make_self_signed_writecap() -> Result<(Writecap, KeyPair)> {
+    let key = make_key_pair();
+    Ok((make_self_signed_writecap_with(&key)?, key))
+}
+
+pub(crate) fn make_self_signed_writecap_with(key: &KeyPair) -> Result<Writecap> {
+    let root_principal = key.owner();
     let hour_hence = Epoch::now() + Duration::from_secs(3600);
     let mut writecap = Writecap {
         issued_to: root_principal.clone(),
         path: make_path_with_owner(root_principal, vec![]),
         expires: hour_hence,
-        signing_key: public_key,
+        signing_key: key.public.clone(),
         signature: Signature::default(),
         next: None,
     };
-    crypto::sign_writecap(&mut writecap, &private_key)
+    crypto::sign_writecap(&mut writecap, &key.private)
         .map_err(|e| Error::Message(e.to_string()))?;
     Ok(writecap)
 }
 
 pub(crate) fn make_readcap() -> Readcap {
     Readcap::new(
-        Hash::Sha2_256(PRINCIPAL), Cryptotext::Plain(Key::Aes256Ctr { key: KEY, iv: IV })
+        Hash::Sha2_256(PRINCIPAL), Cryptotext::Plain(SymKey::Aes256Ctr { key: KEY, iv: IV })
     )
 }
 
@@ -581,14 +579,10 @@ impl<'a> NamedSlice<'a> {
 }
 
 fn write_rsa_keys_to_file(path: &str) -> Result<()> {
-    let key = Key::generate(KeyType::Rsa).map_err(|e| Error::Message(e.to_string()))?;
-    let (public, private) = match key {
-        Key::Rsa { public, private, .. } => (public, private.unwrap()),
-        _ => return Err(Error::Message("unexpected key type".to_string())),
-    };
+    let key = KeyPair::generate(AsymKeyType::Rsa).map_err(|e| Error::Message(e.to_string()))?;
     let slices = [
-        NamedSlice::new("PUBLIC", public.as_slice()),
-        NamedSlice::new("PRIVATE", private.as_slice())
+        NamedSlice::new("PUBLIC", key.public.as_slice()),
+        NamedSlice::new("PRIVATE", key.private.as_slice())
     ];
     write_to_file(path, slices.into_iter())
 }