Browse Source

Implemented symmentric encryption and decryption via openssl.

Matthew Carr 2 years ago
parent
commit
43d8e65ba9
4 changed files with 161 additions and 77 deletions
  1. 3 0
      crates/node/.vscode/settings.json
  2. 150 66
      crates/node/src/crypto.rs
  3. 7 10
      crates/node/src/main.rs
  4. 1 1
      crates/node/src/serde_tests.rs

+ 3 - 0
crates/node/.vscode/settings.json

@@ -3,7 +3,10 @@
         "Asym",
         "blocktree",
         "bodhi",
+        "Crypter",
         "Cryptotext",
+        "decrypter",
+        "encrypter",
         "Hashable",
         "newtype",
         "Xsalsa"

+ 150 - 66
crates/node/src/crypto.rs

@@ -2,12 +2,30 @@
 
 use super::*;
 use openssl::{
-    symm::{Cipher, Crypter, Mode}
+    symm::{Cipher, encrypt as openssl_encrypt, decrypt as openssl_decrypt}
 };
+use serde_block_tree::{to_vec, from_vec};
+use serde::de::{DeserializeOwned};
+
+/// Data that may or may not be encrypted.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub enum Cryptotext<T: Serialize> {
+    /// The inner value of `T` is in plaintext.
+    Plain(T),
+    /// The inner value of `T` is in ciphertext.
+    Cipher(Vec<u8>),
+}
+
+pub trait Hydrate<const N: usize> {
+    fn hydrate(template: Self, data: [u8; N]) -> Self;
+}
 
 /// Errors that can occur during cryptographic operations.
+#[derive(Debug)]
 pub enum Error {
-    NoReadCap
+    NoReadCap,
+    Encryption(String),
+    Decryption(String),
 }
 
 pub type Result<T> = std::result::Result<T, Error>;
@@ -33,20 +51,78 @@ pub enum Signature {
 #[allow(dead_code)]
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub enum Key {
-    Xsalsa20Poly1305([u8; 32]),
+    // For keys that contain IVs, the key always comes first. The function `key_len` can be used
+    // to get the length of the key part of a variant's array.
+
+    /// 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]),
+    /// A key for the AES 256 cipher in counter mode.
+    Aes256Ctr([u8; 32]),
 }
 
 impl Key {
-    /// Returns the data in the key as a slice.
-    fn as_slice(&self) -> &[u8] {
-        let Key::Xsalsa20Poly1305(array) = self;
-        array
+    /// 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,
+        }
     }
 
-    /// Returns the data in the key as a mutable slice.
-    fn as_mut_slice(&mut self) -> &mut [u8] {
-        let Key::Xsalsa20Poly1305(array) = self;
-        array
+    /// 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,
+        }
+    }
+
+    /// 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
+    }
+
+    /// Returns the key part of this `Key`.
+    pub fn key_slice(&self) -> &[u8] {
+        let key_len = self.key_len();
+        &self.as_slice()[..key_len]
+    }
+
+    /// 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..])
+        }
+    }
+
+    /// 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);
+    }
+
+    /// Returns the cipher that this key is for.
+    fn cipher(&self) -> Cipher {
+        match self {
+            Key::Aes256Cbc(_) => Cipher::aes_256_cbc(),
+            Key::Aes256Ctr(_) => Cipher::aes_256_ctr(),
+        }
     }
 }
 
@@ -67,16 +143,16 @@ pub(crate) fn encrypt_block(
     versioned_block: VersionedBlock, principal: &Principal, key: &Key
 ) -> Result<VersionedBlock> {
     let VersionedBlock::V0(mut block) = versioned_block;
-    let mut body = match block.body {
+    let body = match block.body {
         Cryptotext::Cipher(_) => return Ok(VersionedBlock::V0(block)),
         Cryptotext::Plain(body) => body
     };
     let (principal_owned, read_cap) = block.read_caps.remove_entry(principal)
         .ok_or(Error::NoReadCap)?;
     let block_key = decrypt(read_cap, key)?;
-    encrypt_in_place(&mut body, &block_key)?;
-    block.body = Cryptotext::Cipher(body);
-    block.read_caps.insert(principal_owned, encrypt(block_key, key)?);
+    let new_body = encrypt_slice(&body, &block_key)?;
+    block.body = Cryptotext::Cipher(new_body);
+    block.read_caps.insert(principal_owned, encrypt(&block_key, key)?);
     Ok(VersionedBlock::V0(block))
 }
 
@@ -85,90 +161,98 @@ pub(crate) fn decrypt_block(
     versioned_block: VersionedBlock, principal: &Principal, key: &Key
 ) -> Result<VersionedBlock> {
     let VersionedBlock::V0(mut block) = versioned_block;
-    let mut body = match block.body {
+    let body = match block.body {
         Cryptotext::Plain(_) => return Ok(VersionedBlock::V0(block)),
         Cryptotext::Cipher(body) => body
     };
     let (principal_owned, read_cap) = block.read_caps.remove_entry(principal)
         .ok_or(Error::NoReadCap)?;
     let block_key = decrypt(read_cap, key)?;
-    decrypt_in_place(&mut body, &block_key)?;
-    block.body = Cryptotext::Plain(body);
+    let new_body = decrypt_slice(&body, &block_key)?;
+    block.body = Cryptotext::Plain(new_body);
     block.read_caps.insert(principal_owned, Cryptotext::Plain(block_key));
     Ok(VersionedBlock::V0(block))
 }
 
-fn encrypt<T: AsRef<[u8]> + AsMut<[u8]>>(mut data: T, key: &Key) -> Result<Cryptotext<T>> {
-    encrypt_in_place(data.as_mut(), key)?;
-    Ok(Cryptotext::Cipher(data))
+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);
+    Ok(Cryptotext::Cipher(vec?))
 }
 
-fn decrypt<T: AsRef<[u8]> + AsMut<[u8]>>(cryptotext: Cryptotext<T>, key: &Key) -> Result<T> {
-    let mut data = match cryptotext {
-        Cryptotext::Plain(data) => return Ok(data),
+fn decrypt<T: Serialize + DeserializeOwned>(cryptotext: Cryptotext<T>, key: &Key) -> Result<T> {
+    let data = match cryptotext {
+        Cryptotext::Plain(value) => return Ok(value),
         Cryptotext::Cipher(data) => data
     };
-    decrypt_in_place(data.as_mut(), key)?;
-    Ok(data)
+    let vec = decrypt_slice(data.as_ref(), key);
+    let value = from_vec(&vec?).map_err(|err| Error::Decryption(err.to_string()));
+    value
 }
 
-fn encrypt_in_place(_plaintext: &mut [u8], _key: &Key) -> Result<()> {
-    unimplemented!();
+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()))
 }
 
-fn decrypt_in_place(_ciphertext: &mut [u8], _key: &Key) -> Result<()> {
-    unimplemented!();
+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()))
 }
 
-/// Tests that validate the dependencies of this module.
 #[cfg(test)]
-mod dependency_tests {
+mod tests {
     use super::*;
 
-    static KEY: [u8; 32] = [
+    const KEY: [u8; 32] = [
         0xB2, 0xB3, 0xDA, 0x5A, 0x1A, 0xF6, 0xB3, 0x78, 0x30, 0xAB, 0x1D, 0x33, 0x33, 0xE7, 0xE3,
         0x5B, 0xBB, 0xF9, 0xFE, 0xD0, 0xC1, 0xF7, 0x90, 0x34, 0x69, 0xB7, 0xE7, 0xC6, 0x1C, 0x46,
         0x85, 0x48,
     ];
 
-    static IV: [u8; 16] = [
+    const IV: [u8; 16] = [
         0x60, 0xE0, 0xBE, 0x45, 0xA9, 0xAB, 0x5D, 0x99, 0x3B, 0x3A, 0x1E, 0x54, 0x18, 0xE0, 0x46,
         0xDE,
     ];
 
-    /// This test validates that data decrypted with the `Crypter` API matches data that was
-    /// previously encrypted using it.
     #[test]
-    fn cryptor() {
-        let expected = b"We attack at the crack of noon!";
-
-        let cipher = Cipher::aes_256_cbc();
-        let mut encrypter = Crypter::new(
-            cipher,
-            Mode::Encrypt,
-            &KEY,
-            Some(&IV)).unwrap();
-        let mut ciphertext = vec![0; expected.len() + cipher.block_size()];
-        let mut written = encrypter.update(expected.as_slice(), &mut ciphertext).unwrap();
-        written += encrypter.finalize(&mut ciphertext[written..]).unwrap();
-        ciphertext.truncate(written);
-
-        let mut actual = vec![0u8; ciphertext.len() + cipher.block_size()];
-        let mut decrypter = Crypter::new(
-            cipher,
-            Mode::Decrypt,
-            &KEY,
-            Some(&IV)).unwrap();
-        let mut written = decrypter.update(&ciphertext, &mut actual).unwrap();
-        written += decrypter.finalize(&mut actual[written..]).unwrap();
-        actual.truncate(written);
-
-        assert_eq!(expected, actual.as_slice());
+    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);
     }
-}
 
-/// Tests for the code that is actually in this module.
-#[cfg(test)]
-mod tests {
+    #[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);
+        let iv_part = key.iv_slice().unwrap();
+        assert_eq!(IV, iv_part);
+    }
+
+    /// Tests that validate the dependencies of this module.
+    mod dependency_tests {
+        /// This test validates that data decrypted with the `Crypter` API matches data that was
+        /// previously encrypted using it.
+        #[test]
+        fn crypter() {
+            use super::*;
+            let expected = b"We attack at the crack of noon!";
+            let cipher = Cipher::aes_256_cbc();
 
-}
+            let ciphertext = openssl_encrypt(cipher, &KEY, Some(&IV), expected).unwrap();
+            let actual = openssl_decrypt(cipher, &KEY, Some(&IV), ciphertext.as_slice()).unwrap();
+
+            assert_eq!(expected, actual.as_slice());
+        }
+    }
+}

+ 7 - 10
crates/node/src/main.rs

@@ -1,3 +1,7 @@
+// The dead code warnings create too much noise during wire-framing.
+// TODO: Delete this prior to release.
+#![allow(dead_code)]
+
 use std::{
     collections::HashMap,
     convert::TryFrom,
@@ -11,7 +15,7 @@ use serde_big_array::BigArray;
 mod serde_tests;
 
 mod crypto;
-use crypto::{Hash, Signature, Key};
+use crypto::{Hash, Signature, Key, Cryptotext};
 
 /// A Block tagged with its version number.
 #[allow(dead_code)]
@@ -62,6 +66,8 @@ struct WriteCap {
     path: Path,
     /// 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,
     /// 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.
@@ -105,15 +111,6 @@ struct FragmentRecord {
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
 struct Principal(Hash);
 
-/// Data that may or may not be encrypted.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-enum Cryptotext<T: AsRef<[u8]> + AsMut<[u8]>> {
-    /// The inner value of `T` is in plaintext.
-    Plain(T),
-    /// The inner value of `T` is in ciphertext.
-    Cipher(T),
-}
-
 /// An identifier for a block in a tree.
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 struct Path(Vec<String>);

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

@@ -97,7 +97,7 @@ fn roundtrip_write_cap() -> Result<()> {
 }
 
 fn make_read_cap() -> ReadCap {
-    ReadCap::new(Hash::Sha2_256(PRINCIPAL), Cryptotext::Plain(Key::Xsalsa20Poly1305(KEY)))
+    ReadCap::new(Hash::Sha2_256(PRINCIPAL), Cryptotext::Plain(Key::Aes256Ctr(KEY)))
 }
 
 #[test]