[wip] WIP on wrapping AES-256-XTS for Rust with cxx

Change-Id: Iad41c36b326979d13422fec3a0e129eefcacc8cf
diff --git a/src/lib/fuchsia-cxx/BUILD.gn b/src/lib/fuchsia-cxx/BUILD.gn
index 97198cc..862c8f9 100644
--- a/src/lib/fuchsia-cxx/BUILD.gn
+++ b/src/lib/fuchsia-cxx/BUILD.gn
@@ -14,7 +14,9 @@
   # It is recommended that you read https://docs.rs/cxx first to understand how cxx works, and to
   # understand what cxx does (and does not) do. All C++ code is still "unsafe" from the Rust
   # perspective; cxx just helps with building the Rust side of the FFI layer safely.
-  visibility = []
+  visibility = [
+    "//src/security/fcrypto-rust",
+  ]
 
   cxx_crate = "//third_party/rust_crates/vendor/cxx"
   sources = [
diff --git a/src/lib/fuchsia-cxx/rust_cxx_ffi_library.gni b/src/lib/fuchsia-cxx/rust_cxx_ffi_library.gni
index 01e486f4..b701f098 100644
--- a/src/lib/fuchsia-cxx/rust_cxx_ffi_library.gni
+++ b/src/lib/fuchsia-cxx/rust_cxx_ffi_library.gni
@@ -180,9 +180,10 @@
     deps = generation_targets + rust_deps
   }
 
+  crate_name = string_replace(package_name, "-", "_")
   generated_file(generated_rs_target) {
     outputs = [ "$source_gen_dir/staticlib_source_root.rs" ]
-    contents = "pub use " + package_name + "::*;"
+    contents = "pub use " + crate_name + "::*;"
   }
 
   # Create a staticlib from the rustc_library above. This uses a generated source_root that just
diff --git a/src/security/BUILD.gn b/src/security/BUILD.gn
index 99e1e52..83cb8d1 100644
--- a/src/security/BUILD.gn
+++ b/src/security/BUILD.gn
@@ -17,6 +17,7 @@
     "codelab:tests",
     "csa_helper:tests",
     "fcrypto:tests",
+    "fcrypto-rust:tests",
     "kms:tests",
     "scrutiny:tests",
     "tee_manager:tests",
diff --git a/src/security/fcrypto-rust/BUILD.gn b/src/security/fcrypto-rust/BUILD.gn
new file mode 100644
index 0000000..23cfee9
--- /dev/null
+++ b/src/security/fcrypto-rust/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2021 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/components.gni")
+import("//src/lib/fuchsia-cxx/rust_cxx_ffi_library.gni")
+
+rust_cxx_ffi_library("fcrypto-rust") {
+  name = "fcrypto_rust"
+  rust_source_root = "ffi.rs"
+  rust_sources = [ "ffi.rs" ]
+  bridge_sources = [ "ffi.rs" ]
+  rust_deps = [
+    "//src/lib/zircon/rust:fuchsia-zircon",
+    "//src/lib/zircon/rust:fuchsia-zircon-sys",
+    "//third_party/rust_crates:cxx",
+  ]
+  cpp_sources = [
+    "ffi.cc",
+    "ffi.h",
+  ]
+  public_deps = [ "//src/security/fcrypto" ]
+}
+
+rustc_test("fcrypto-rust-ffi-test") {
+  name = "fcrypto_rust_ffi_test"
+  edition = "2018"
+
+  source_root = "ffi_test.rs"
+  sources = [ "ffi_test.rs" ]
+
+  deps = [
+    ":fcrypto-rust",
+    "//src/lib/zircon/rust:fuchsia-zircon",
+    "//src/lib/zircon/rust:fuchsia-zircon-sys",
+    "//third_party/rust_crates:cxx",
+  ]
+}
+
+fuchsia_component("fcrypto-rust-test") {
+  testonly = true
+  manifest = "meta/fcrypto-rust-ffi-test.cml"
+  deps = [ ":fcrypto-rust-ffi-test" ]
+}
+
+fuchsia_test_package("fcrypto-rust-tests") {
+  test_components = [ ":fcrypto-rust-test" ]
+}
+
+group("tests") {
+  testonly = true
+  deps = [ ":fcrypto-rust-tests" ]
+}
diff --git a/src/security/fcrypto-rust/ffi.cc b/src/security/fcrypto-rust/ffi.cc
new file mode 100644
index 0000000..3692620
--- /dev/null
+++ b/src/security/fcrypto-rust/ffi.cc
@@ -0,0 +1,69 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/security/fcrypto-rust/ffi.h"
+
+#include <memory>
+
+#include "src/security/fcrypto/bytes.h"
+#include "src/security/fcrypto/cipher.h"
+#include "src/security/fcrypto/secret.h"
+
+namespace crypto {
+
+std::unique_ptr<Cipher> new_cipher() { return std::make_unique<Cipher>(); }
+
+static zx_status_t init_internal(Cipher& cipher, Cipher::Direction direction,
+                                 rust::Slice<const uint8_t>& secret, rust::Slice<const uint8_t>& iv,
+                                 uint64_t alignment) {
+  // Allocate Secret, Bytes
+  Secret crypto_secret;
+  zx_status_t rc;
+  uint8_t* secret_inner;
+  rc = crypto_secret.Allocate(secret.length(), &secret_inner);
+  if (rc != ZX_OK) {
+    return rc;
+  }
+
+  Bytes crypto_iv;
+  rc = crypto_iv.Resize(iv.length());
+  if (rc != ZX_OK) {
+    return rc;
+  }
+
+  // Populate the buffers.
+  memcpy(secret_inner, secret.data(), secret.length());
+  memcpy(crypto_iv.get(), iv.data(), iv.length());
+
+  return cipher.Init(crypto::Cipher::Algorithm::kAES256_XTS, direction, crypto_secret, crypto_iv,
+                     alignment);
+}
+
+zx_status_t init_for_encipher(Cipher& cipher, rust::Slice<const uint8_t> secret,
+                              rust::Slice<const uint8_t> iv, uint64_t alignment) {
+  return init_internal(cipher, Cipher::Direction::kEncrypt, secret, iv, alignment);
+}
+
+zx_status_t init_for_decipher(Cipher& cipher, rust::Slice<const uint8_t> secret,
+                              rust::Slice<const uint8_t> iv, uint64_t alignment) {
+  return init_internal(cipher, Cipher::Direction::kDecrypt, secret, iv, alignment);
+}
+
+zx_status_t encipher(Cipher& cipher, rust::Slice<const uint8_t> plaintext, uint64_t offset,
+                     rust::Slice<uint8_t> ciphertext) {
+  if (plaintext.length() != ciphertext.length()) {
+    return ZX_ERR_INVALID_ARGS;
+  }
+  return cipher.Encrypt(plaintext.data(), offset, plaintext.length(), ciphertext.data());
+}
+
+zx_status_t decipher(Cipher& cipher, rust::Slice<const uint8_t> ciphertext, uint64_t offset,
+                     rust::Slice<uint8_t> plaintext) {
+  if (plaintext.length() != ciphertext.length()) {
+    return ZX_ERR_INVALID_ARGS;
+  }
+  return cipher.Decrypt(ciphertext.data(), offset, ciphertext.length(), plaintext.data());
+}
+
+}  //  namespace crypto
diff --git a/src/security/fcrypto-rust/ffi.h b/src/security/fcrypto-rust/ffi.h
new file mode 100644
index 0000000..8cf3c83
--- /dev/null
+++ b/src/security/fcrypto-rust/ffi.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_SECURITY_FCRYPTO_RUST_FFI_H_
+#define SRC_SECURITY_FCRYPTO_RUST_FFI_H_
+
+#include <zircon/status.h>
+
+#include "src/security/fcrypto/cipher.h"
+
+// Header generated from Rust cxxbridge declarations.  Include dir added by build template.
+#include "src/security/fcrypto-rust/ffi.rs.h"
+
+namespace crypto {
+
+std::unique_ptr<Cipher> new_cipher();
+
+zx_status_t init_for_encipher(Cipher& cipher, rust::Slice<const uint8_t> secret,
+                              rust::Slice<const uint8_t> iv, uint64_t alignment);
+zx_status_t init_for_decipher(Cipher& cipher, rust::Slice<const uint8_t> secret,
+                              rust::Slice<const uint8_t> iv, uint64_t alignment);
+
+zx_status_t encipher(Cipher& cipher, rust::Slice<const uint8_t> plaintext, uint64_t offset,
+                     rust::Slice<uint8_t> ciphertext);
+zx_status_t decipher(Cipher& cipher, rust::Slice<const uint8_t> ciphertext, uint64_t offset,
+                     rust::Slice<uint8_t> plaintext);
+
+}  // namespace crypto
+
+#endif  // SRC_SECURITY_FCRYPTO_RUST_FFI_H_
diff --git a/src/security/fcrypto-rust/ffi.rs b/src/security/fcrypto-rust/ffi.rs
new file mode 100644
index 0000000..c9a0f99
--- /dev/null
+++ b/src/security/fcrypto-rust/ffi.rs
@@ -0,0 +1,136 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The `cxx` crate's expanded code appears to trigger this elided lifetimes in paths warning
+#![allow(elided_lifetimes_in_paths)]
+#![allow(dead_code)]
+
+use cxx::UniquePtr;
+use fuchsia_zircon_sys as sys;
+use std::pin::Pin;
+
+#[cxx::bridge(namespace = "crypto")]
+mod ffi {
+    // No shared structs
+
+    unsafe extern "C++" {
+        // C++ declarations
+        include!("src/security/fcrypto-rust/ffi.h");
+
+        type Cipher;
+
+        fn new_cipher() -> UniquePtr<Cipher>;
+
+        fn init_for_encipher(
+            cipher: Pin<&mut Cipher>,
+            secret: &[u8],
+            iv: &[u8],
+            alignment: u64,
+        ) -> i32;
+        fn init_for_decipher(
+            cipher: Pin<&mut Cipher>,
+            secret: &[u8],
+            iv: &[u8],
+            alignment: u64,
+        ) -> i32;
+
+        fn encipher(
+            cipher: Pin<&mut Cipher>,
+            plaintext: &[u8],
+            offset: u64,
+            ciphertext: &mut [u8],
+        ) -> i32;
+        fn decipher(
+            cipher: Pin<&mut Cipher>,
+            plaintext: &[u8],
+            offset: u64,
+            ciphertext: &mut [u8],
+        ) -> i32;
+    }
+}
+
+pub struct Aes256XtsCipher {
+    encipherer: Aes256XtsEncipherer,
+    decipherer: Aes256XtsDecipherer,
+}
+
+// TODO: make these failures return a better error type than i32
+impl Aes256XtsCipher {
+    pub fn new(secret: &[u8], iv: &[u8]) -> Result<Aes256XtsCipher, i32> {
+        // Allocate a pair of C++ Cipher objects.
+        let encipher_inner = ffi::new_cipher();
+        let decipher_inner = ffi::new_cipher();
+        let mut encipherer = Aes256XtsEncipherer { inner: encipher_inner };
+        let mut decipherer = Aes256XtsDecipherer { inner: decipher_inner };
+
+        encipherer.init(secret, iv)?;
+        decipherer.init(secret, iv)?;
+
+        Ok(Aes256XtsCipher { encipherer, decipherer })
+    }
+
+    pub fn encrypt(&mut self, offset: u64, plaintext: &[u8], ciphertext: &mut [u8]) -> Result<(), i32> {
+        self.encipherer.encipher(offset, plaintext, ciphertext)
+    }
+
+    pub fn decrypt(&mut self, offset: u64, ciphertext: &[u8], plaintext: &mut [u8]) -> Result<(), i32> {
+        self.decipherer.decipher(offset, ciphertext, plaintext)
+    }
+}
+
+pub struct Aes256XtsEncipherer {
+    inner: UniquePtr<ffi::Cipher>,
+}
+
+pub struct Aes256XtsDecipherer {
+    inner: UniquePtr<ffi::Cipher>,
+}
+
+const ALIGN: u64 = 128;
+
+impl Aes256XtsEncipherer {
+    fn init(&mut self, secret: &[u8], iv: &[u8]) -> Result<(), i32> {
+        let mut cref = self.inner.as_mut().ok_or(sys::ZX_ERR_INVALID_ARGS)?;
+        let res = ffi::init_for_encipher(Pin::as_mut(&mut cref), secret, iv, ALIGN);
+        if res == 0 {
+            Ok(())
+        } else {
+            Err(res)
+        }
+    }
+
+    fn encipher(&mut self, offset: u64, plaintext: &[u8], ciphertext: &mut [u8]) -> Result<(), i32> {
+        assert!(plaintext.len() == ciphertext.len());
+        let mut cref = self.inner.as_mut().ok_or(sys::ZX_ERR_INVALID_ARGS)?;
+        let res = ffi::encipher(Pin::as_mut(&mut cref), plaintext, offset, ciphertext);
+        if res == 0 {
+            Ok(())
+        } else {
+            Err(res)
+        }
+    }
+}
+
+impl Aes256XtsDecipherer {
+    fn init(&mut self, secret: &[u8], iv: &[u8]) -> Result<(), i32> {
+        let mut cref = self.inner.as_mut().ok_or(sys::ZX_ERR_INVALID_ARGS)?;
+        let res = ffi::init_for_decipher(Pin::as_mut(&mut cref), secret, iv, ALIGN);
+        if res == 0 {
+            Ok(())
+        } else {
+            Err(res)
+        }
+    }
+
+    fn decipher(&mut self, offset: u64, ciphertext: &[u8], plaintext: &mut [u8]) -> Result<(), i32> {
+        assert!(plaintext.len() == ciphertext.len());
+        let mut cref = self.inner.as_mut().ok_or(sys::ZX_ERR_INVALID_ARGS)?;
+        let res = ffi::decipher(Pin::as_mut(&mut cref), ciphertext, offset, plaintext);
+        if res == 0 {
+            Ok(())
+        } else {
+            Err(res)
+        }
+    }
+}
diff --git a/src/security/fcrypto-rust/ffi_test.rs b/src/security/fcrypto-rust/ffi_test.rs
new file mode 100644
index 0000000..eefb48e
--- /dev/null
+++ b/src/security/fcrypto-rust/ffi_test.rs
@@ -0,0 +1,17 @@
+// TODO: write a test that makes use of the AES-256-XTS bindings with some test vectors
+
+#![allow(elided_lifetimes_in_paths)]
+
+use fcrypto_rust::Aes256XtsCipher;
+
+#[test]
+fn test_cipher() {
+  // TODO: double-check these lengths
+  let key = [0u8; 32];
+  let iv = [0u8; 32];
+  let mut cipher = Aes256XtsCipher::new(&key, &iv).expect("create and init cipher");
+  let ctext = [0u8 ; 128];
+  let mut ptext = [0u8 ; 128];
+  cipher.encrypt(0, &ctext, &mut ptext).expect("encrypt");
+}
+
diff --git a/src/security/fcrypto-rust/meta/fcrypto-rust-ffi-test.cml b/src/security/fcrypto-rust/meta/fcrypto-rust-ffi-test.cml
new file mode 100644
index 0000000..4b32576
--- /dev/null
+++ b/src/security/fcrypto-rust/meta/fcrypto-rust-ffi-test.cml
@@ -0,0 +1,12 @@
+// Copyright 2021 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+{
+    include: [
+        "//src/sys/test_runners/rust/default.shard.cml",
+        "syslog/client.shard.cml",
+    ],
+    program: {
+        binary: "bin/fcrypto_rust_ffi_test",
+    },
+}