Browse Source

Refactored the encrypt and decrypt slice methods to support asymmetric algorithms.

Matthew Carr 2 years ago
parent
commit
8ff0f9ed32
3 changed files with 147 additions and 80 deletions
  1. 137 79
      crates/node/src/crypto.rs
  2. 1 1
      crates/node/src/main.rs
  3. 9 0
      crates/node/src/serde_tests.rs

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

@@ -2,7 +2,10 @@
 
 use super::*;
 use openssl::{
-    symm::{Cipher, encrypt as openssl_encrypt, decrypt as openssl_decrypt}
+    error::ErrorStack,
+    encrypt::{Encrypter, Decrypter},
+    pkey::{PKey, Public, Private},
+    symm::{Cipher, encrypt as openssl_encrypt, decrypt as openssl_decrypt},
 };
 use serde_block_tree::{to_vec, from_vec};
 use serde::de::{DeserializeOwned};
@@ -24,6 +27,8 @@ pub trait Hydrate<const N: usize> {
 #[derive(Debug)]
 pub enum Error {
     NoReadCap,
+    NoKeyAvailable,
+    MissingPrivateKey,
     Encryption(String),
     Decryption(String),
 }
@@ -57,84 +62,147 @@ pub enum Key {
     /// 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.
-    #[serde(with = "BigArray")]
-    Aes256Cbc([u8; 32 + 16]),
+    Aes256Cbc {
+        key: [u8; 32],
+        iv: [u8; 16],
+    },
     /// A key for the AES 256 cipher in counter mode.
     Aes256Ctr([u8; 32]),
+    Ed25519 {
+        public: [u8; 32],
+        private: Option<[u8; 32]>
+    },
 }
 
-impl Key {
-    /// Returns the array in this `Key` as a slice.
-    pub fn as_slice(&self) -> &[u8] {
-        match self {
-            Key::Aes256Cbc(array) => array,
-            Key::Aes256Ctr(array) => array,
-        }
-    }
+enum EncryptionAlgo<'a> {
+    Symmetric {
+        cipher: Cipher,
+        key: &'a [u8],
+        iv: Option<&'a [u8]>
+    },
+    Asymmetric(PKey<Public>),
+}
 
-    /// Returns the array in this `Key` as a mutable slice.
-    pub fn as_mut_slice(&mut self) -> &mut [u8] {
-        match self {
-            Key::Aes256Cbc(array) => array,
-            Key::Aes256Ctr(array) => array,
-        }
-    }
+enum DecryptionAlgo<'a> {
+    Symmetric {
+        cipher: Cipher,
+        key: &'a [u8],
+        iv: Option<&'a [u8]>
+    },
+    Asymmetric(PKey<Private>),
+}
 
-    /// Returns the length of the key part of the array in this `Key`. Note that the bytes of the
-    /// key come before the bytes of the IV.
-    const fn key_len(&self) -> usize {
-        let (array_len, key_len) = match self {
-            Key::Aes256Cbc(array) => (array.len(), 32),
-            Key::Aes256Ctr(array) => (array.len(), 32)
-        };
-        debug_assert!(key_len <= array_len);
-        key_len
+impl From<ErrorStack> for Error {
+    fn from(error: ErrorStack) -> Error {
+        Error::Encryption(error.to_string())
     }
+}
 
-    /// Returns the key part of this `Key`.
-    pub fn key_slice(&self) -> &[u8] {
-        let key_len = self.key_len();
-        &self.as_slice()[..key_len]
+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::Ed25519 { private, .. } => match private {
+                Some(key) => Some(key.as_slice()),
+                None => None,
+            },
+        }
     }
 
     /// Returns the IV part of this`Key`.
-    pub fn iv_slice(&self) -> Option<&[u8]> {
-        let key_len = self.key_len();
-        let slice = self.as_slice();
-        if key_len == slice.len() {
-            None
-        }
-        else {
-            Some(&slice[key_len..])
+    fn iv_slice(&self) -> Option<&[u8]> {
+        match self {
+            Key::Aes256Cbc { iv, .. } => Some(iv),
+            _ => None,
         }
     }
+}
 
-    /// Packs the given key and IV into the given output buffer.
-    pub fn pack(output: &mut [u8], key: &[u8], iv: &[u8]) {
-        debug_assert_eq!(output.len(), key.len() + iv.len());
-        let key_len = key.len();
-        output[..key_len].copy_from_slice(key);
-        output[key_len..].copy_from_slice(iv);
+impl<'a> TryFrom<&'a Key> for EncryptionAlgo<'a> {
+    type Error = Error;
+    fn try_from(key: &'a Key) -> Result<EncryptionAlgo<'a>> {
+        match key {
+            Key::Aes256Cbc { key: key_slice, iv } => Ok(EncryptionAlgo::Symmetric {
+                cipher: Cipher::aes_256_cbc(),
+                key: key_slice,
+                iv: Some(iv),
+            }),
+            Key::Aes256Ctr(key_slice) => Ok(EncryptionAlgo::Symmetric {
+                cipher: Cipher::aes_256_ctr(),
+                key: key_slice,
+                iv: None
+            }),
+            Key::Ed25519 { public, .. } => {
+                let pkey = PKey::public_key_from_der(public.as_slice())
+                    .map_err(|err| Error::Encryption(err.to_string()));
+                Ok(EncryptionAlgo::Asymmetric(pkey?))
+            },
+        }
     }
+}
 
-    /// Returns the cipher that this key is for.
-    fn cipher(&self) -> Cipher {
+impl<'a> EncryptionAlgo<'a> {
+    fn encrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
         match self {
-            Key::Aes256Cbc(_) => Cipher::aes_256_cbc(),
-            Key::Aes256Ctr(_) => Cipher::aes_256_ctr(),
+            EncryptionAlgo::Symmetric { cipher, key, iv } => {
+                openssl_encrypt(*cipher, key, *iv, slice).map_err(Error::from)
+            },
+            EncryptionAlgo::Asymmetric(pkey) => {
+                let encrypter = Encrypter::new(pkey).map_err(Error::from)?;
+                let buffer_len = encrypter.encrypt_len(slice).map_err(Error::from)?;
+                let mut ciphertext = vec![0; buffer_len];
+                let ciphertext_len = encrypter.encrypt(slice, &mut ciphertext)
+                    .map_err(Error::from)?;
+                ciphertext.truncate(ciphertext_len);
+                Ok(ciphertext)
+            }
         }
     }
 }
 
-impl AsRef<[u8]> for Key {
-    fn as_ref(&self) -> &[u8] {
-        self.as_slice()
+impl<'a> TryFrom<&'a Key> for DecryptionAlgo<'a> {
+    type Error = Error;
+    fn try_from(key: &'a Key) -> Result<DecryptionAlgo<'a>> {
+        match key {
+            Key::Aes256Cbc { key: key_slice, iv } => Ok(DecryptionAlgo::Symmetric {
+                cipher: Cipher::aes_256_cbc(),
+                key: key_slice,
+                iv: Some(iv),
+            }),
+            Key::Aes256Ctr(key_slice) => Ok(DecryptionAlgo::Symmetric {
+                cipher: Cipher::aes_256_ctr(),
+                key: key_slice,
+                iv: None
+            }),
+            Key::Ed25519 { private, .. } => {
+                let private = private.ok_or(Error::MissingPrivateKey)?;
+                let pkey = PKey::private_key_from_der(private.as_slice())
+                    .map_err(|err| Error::Decryption(err.to_string()));
+                Ok(DecryptionAlgo::Asymmetric(pkey?))
+            }
+        }
     }
 }
 
-impl AsMut<[u8]> for Key {
-    fn as_mut(&mut self) -> &mut [u8] {
-        self.as_mut_slice()
+impl<'a> DecryptionAlgo<'a> {
+    fn decrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
+        match self {
+            DecryptionAlgo::Symmetric { cipher, key, iv } => {
+                openssl_decrypt(*cipher, key, *iv, slice).map_err(Error::from)
+            },
+            DecryptionAlgo::Asymmetric(pkey) => {
+                let decrypter = Decrypter::new(pkey).map_err(Error::from)?;
+                let buffer_len = decrypter.decrypt_len(slice).map_err(Error::from)?;
+                let mut plaintext = vec![0; buffer_len];
+                let plaintext_len = decrypter.decrypt(slice, &mut plaintext)
+                    .map_err(Error::from)?;
+                plaintext.truncate(plaintext_len);
+                Ok(plaintext)
+            },
+        }
     }
 }
 
@@ -176,7 +244,7 @@ pub(crate) fn decrypt_block(
 
 fn encrypt<T: Serialize>(value: &T, key: &Key) -> Result<Cryptotext<T>> {
     let data = to_vec(value).map_err(|err| Error::Encryption(err.to_string()))?;
-    let vec = encrypt_slice(data.as_slice(), key);
+    let vec = encrypt_slice(&data, key);
     Ok(Cryptotext::Cipher(vec?))
 }
 
@@ -185,25 +253,18 @@ fn decrypt<T: Serialize + DeserializeOwned>(cryptotext: Cryptotext<T>, key: &Key
         Cryptotext::Plain(value) => return Ok(value),
         Cryptotext::Cipher(data) => data
     };
-    let vec = decrypt_slice(data.as_ref(), key);
-    let value = from_vec(&vec?).map_err(|err| Error::Decryption(err.to_string()));
-    value
+    let vec = decrypt_slice(&data, key);
+    from_vec(&vec?).map_err(|err| Error::Decryption(err.to_string()))
 }
 
 fn encrypt_slice(plaintext: &[u8], key: &Key) -> Result<Vec<u8>> {
-    let cipher = key.cipher();
-    let key_slice = key.key_slice();
-    let iv_slice = key.iv_slice();
-    openssl_encrypt(cipher, key_slice, iv_slice, plaintext)
-        .map_err(|err| Error::Encryption(err.to_string()))
+   let algo = EncryptionAlgo::try_from(key)?;
+   algo.encrypt(plaintext)
 }
 
 fn decrypt_slice(ciphertext: &[u8], key: &Key) -> Result<Vec<u8>> {
-    let cipher = key.cipher();
-    let key_slice = key.key_slice();
-    let iv_slice = key.iv_slice();
-    openssl_decrypt(cipher, key_slice, iv_slice, ciphertext)
-        .map_err(|err| Error::Decryption(err.to_string()))
+    let algo = DecryptionAlgo::try_from(key)?;
+    algo.decrypt(ciphertext)
 }
 
 #[cfg(test)]
@@ -222,19 +283,16 @@ mod tests {
     ];
 
     #[test]
-    fn key_aes256cbc_key_part_is_correct() {
-        let mut array = [0u8; KEY.len() + IV.len()];
-        Key::pack(&mut array, &KEY, &IV);
-        let key = Key::Aes256Cbc(array);
-        let key_part = key.key_slice();
-        assert_eq!(KEY, key_part);
+    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_part_is_correct() {
-        let mut array = [0u8; KEY.len() + IV.len()];
-        Key::pack(&mut array, &KEY, &IV);
-        let key = Key::Aes256Cbc(array);
+    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);
     }

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

@@ -67,7 +67,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: Key,
     /// 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.

+ 9 - 0
crates/node/src/serde_tests.rs

@@ -32,6 +32,13 @@ static KEY: [u8; 32] = [
     0xBB, 0xF9, 0xFE, 0xD0, 0xC1, 0xF7, 0x90, 0x34, 0x69, 0xB7, 0xE7, 0xC6, 0x1C, 0x46, 0x85, 0x48,
 ];
 
+static LONG_KEY: [u8; 64] = [
+    0x65, 0x4E, 0x78, 0x5C, 0x8E, 0xEE, 0x9E, 0x57, 0x58, 0x8D, 0xDE, 0x9E, 0x6D, 0x84, 0xF4, 0x68,
+    0x7F, 0x9E, 0x48, 0xC4, 0x6F, 0x1F, 0x4F, 0x83, 0x46, 0x68, 0xB2, 0xC1, 0x46, 0xD0, 0x06, 0xF2,
+    0x23, 0xB9, 0xD4, 0x66, 0x4B, 0xED, 0x65, 0x92, 0xF1, 0xA2, 0x04, 0x1A, 0xD9, 0xF6, 0xA2, 0xAE,
+    0x87, 0x3E, 0xBF, 0x10, 0x4A, 0x29, 0x28, 0x83, 0x2A, 0x8F, 0xC5, 0x30, 0x86, 0x11, 0x48, 0x52,
+];
+
 #[test]
 fn roundtrip_fragment_record() -> Result<()> {
     let expected = FragmentRecord::new(229, Hash::Sha2_256(PRINCIPAL));
@@ -75,12 +82,14 @@ fn make_write_cap() -> Result<WriteCap> {
         issued_by: Principal(Hash::Sha2_256(PRINCIPAL)),
         path: Path::try_from("contacts/emergency").map_err(|err| Error::Message(err.to_string()))?,
         expires: Epoch(1649904316),
+        signing_key: Key::Ed25519 { public: KEY, private: None },
         signature: Signature::Ed25519(SIGNATURE),
         next: Some(Box::from(WriteCap {
             issued_to: Principal(Hash::Sha2_256(PRINCIPAL)),
             issued_by: Principal(Hash::Sha2_256(PRINCIPAL)),
             path: Path::try_from("contacts").map_err(|err| Error::Message(err.to_string()))?,
             expires: Epoch(1649994316),
+            signing_key: Key::Ed25519 { public: KEY, private: None },
             signature: Signature::Ed25519(SIGNATURE),
             next: None,
         }))