Page MenuHomePhabricator

D9034.diff
No OneTemporary

D9034.diff

diff --git a/services/comm-services-lib/Cargo.lock b/services/comm-services-lib/Cargo.lock
--- a/services/comm-services-lib/Cargo.lock
+++ b/services/comm-services-lib/Cargo.lock
@@ -266,6 +266,42 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "bytes",
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
[[package]]
name = "ahash"
version = "0.7.6"
@@ -786,6 +822,16 @@
"winapi",
]
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@@ -804,6 +850,8 @@
"actix-multipart",
"actix-web",
"actix-web-httpauth",
+ "aead",
+ "aes-gcm",
"anyhow",
"aws-config",
"aws-sdk-dynamodb",
@@ -882,9 +930,19 @@
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
+ "rand_core",
"typenum",
]
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "cxx"
version = "1.0.91"
@@ -1155,6 +1213,16 @@
"wasi 0.11.0+wasi-snapshot-preview1",
]
+[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
[[package]]
name = "gimli"
version = "0.28.0"
@@ -1343,6 +1411,15 @@
"hashbrown",
]
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -1568,6 +1645,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
[[package]]
name = "openssl"
version = "0.10.56"
@@ -1697,6 +1780,18 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+[[package]]
+name = "polyval"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -2365,6 +2460,16 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
[[package]]
name = "untrusted"
version = "0.7.1"
diff --git a/services/comm-services-lib/Cargo.toml b/services/comm-services-lib/Cargo.toml
--- a/services/comm-services-lib/Cargo.toml
+++ b/services/comm-services-lib/Cargo.toml
@@ -21,6 +21,7 @@
"dep:tokio-stream",
"dep:actix-web-httpauth",
]
+crypto = ["dep:aead", "dep:aes-gcm", "dep:bytes"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
@@ -51,3 +52,6 @@
actix-web-httpauth = { version = "0.8.0", optional = true }
actix-multipart = { version = "0.6", optional = true }
tokio-stream = { version = "0.1.14", optional = true }
+# crypto dependencies
+aes-gcm = { version = "0.10", optional = true }
+aead = { version = "0.5", features = ["bytes"], optional = true }
diff --git a/services/comm-services-lib/src/crypto/aes256.rs b/services/comm-services-lib/src/crypto/aes256.rs
new file mode 100644
--- /dev/null
+++ b/services/comm-services-lib/src/crypto/aes256.rs
@@ -0,0 +1,122 @@
+use aead::{
+ generic_array::GenericArray, Aead, AeadCore, AeadInPlace, KeyInit, OsRng,
+};
+use aes_gcm::Aes256Gcm;
+use aws_sdk_dynamodb::types::AttributeValue;
+use bytes::BytesMut;
+
+use crate::database::{DBItemError, TryFromAttribute};
+
+pub use aes_gcm::Error as AES256Error;
+
+const TAG_LEN: usize = 16;
+const NONCE_LEN: usize = 12;
+
+#[derive(Clone, Debug, derive_more::From, derive_more::AsRef)]
+pub struct EncryptionKey(aes_gcm::Key<Aes256Gcm>);
+
+impl EncryptionKey {
+ /// Generates a new AES256 key.
+ pub fn new() -> EncryptionKey {
+ Aes256Gcm::generate_key(&mut OsRng).into()
+ }
+}
+
+impl Default for EncryptionKey {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+// database conversions
+impl From<EncryptionKey> for AttributeValue {
+ fn from(key: EncryptionKey) -> Self {
+ use aws_sdk_dynamodb::primitives::Blob;
+ AttributeValue::B(Blob::new(key.0.to_vec()))
+ }
+}
+impl TryFromAttribute for EncryptionKey {
+ fn try_from_attr(
+ attribute_name: impl Into<String>,
+ attribute: Option<AttributeValue>,
+ ) -> Result<Self, DBItemError> {
+ let bytes = Vec::<u8>::try_from_attr(attribute_name, attribute)?;
+ let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&bytes);
+ Ok(Self(*key))
+ }
+}
+
+/// Encrypts a plaintext with the given key. Returns the sealed data
+/// in the following format: nonce || ciphertext || tag
+pub fn encrypt(
+ plaintext: &[u8],
+ key: &EncryptionKey,
+) -> Result<Vec<u8>, AES256Error> {
+ let cipher = Aes256Gcm::new(key.as_ref());
+ let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
+
+ // Construct an output buffer: nonce || ciphertext || tag
+ // Then split it into nonce and ciphertext || tag,
+ // encrypt and concatenate again.
+ // This is done on contiguous memory to avoid extra allocations.
+
+ let mut output =
+ BytesMut::with_capacity(nonce.len() + plaintext.len() + TAG_LEN);
+ output.extend_from_slice(&nonce);
+ output.extend_from_slice(plaintext);
+
+ let mut buffer = output.split_off(nonce.len());
+ cipher.encrypt_in_place(&nonce, b"", &mut buffer)?;
+ output.unsplit(buffer);
+
+ Ok(output.into())
+}
+
+/// Decrypts a ciphertext with the given key. Expects the sealed data
+/// in the following format: nonce || ciphertext || tag
+pub fn decrypt(
+ ciphertext: &[u8],
+ key: &EncryptionKey,
+) -> Result<Vec<u8>, AES256Error> {
+ if ciphertext.len() < NONCE_LEN + TAG_LEN {
+ return Err(AES256Error);
+ }
+ let cipher = Aes256Gcm::new(key.as_ref());
+ let nonce = GenericArray::from_slice(&ciphertext[..NONCE_LEN]);
+ cipher.decrypt(nonce, &ciphertext[NONCE_LEN..])
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_aes256() {
+ let key = EncryptionKey::new();
+ let plaintext = b"hello world";
+ let ciphertext = encrypt(plaintext, &key).expect("Encrypt failed");
+ let decrypted = decrypt(&ciphertext, &key).expect("Decrypt failed");
+ assert_eq!(plaintext, &decrypted[..]);
+ }
+
+ #[test]
+ fn test_aes256_invalid_key() {
+ let key = EncryptionKey::new();
+ let plaintext = b"hello world";
+ let ciphertext = encrypt(plaintext, &key).expect("Encrypt failed");
+
+ let other_key = EncryptionKey::new();
+ decrypt(&ciphertext, &other_key).expect_err("Decrypt should fail");
+ }
+
+ #[test]
+ fn test_aes256_malfolmed() {
+ let key = EncryptionKey::new();
+ let plaintext = b"hello world";
+ let mut ciphertext = encrypt(plaintext, &key).expect("Encrypt failed");
+
+ ciphertext[0] ^= 0xaa; // do the flip
+
+ decrypt(&ciphertext, &key).expect_err("Decrypt should fail");
+ }
+}
diff --git a/services/comm-services-lib/src/crypto/mod.rs b/services/comm-services-lib/src/crypto/mod.rs
new file mode 100644
--- /dev/null
+++ b/services/comm-services-lib/src/crypto/mod.rs
@@ -0,0 +1,2 @@
+/// AES 256 GCM encryption and decryption.
+pub mod aes256;
diff --git a/services/comm-services-lib/src/lib.rs b/services/comm-services-lib/src/lib.rs
--- a/services/comm-services-lib/src/lib.rs
+++ b/services/comm-services-lib/src/lib.rs
@@ -2,6 +2,8 @@
pub mod backup;
pub mod blob;
pub mod constants;
+#[cfg(feature = "crypto")]
+pub mod crypto;
pub mod database;
#[cfg(feature = "http")]
pub mod http;

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 23, 4:08 AM (18 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2568074
Default Alt Text
D9034.diff (9 KB)

Event Timeline