Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33177134
D9035.1768528975.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
D9035.1768528975.diff
View Options
diff --git a/services/reports/Cargo.lock b/services/reports/Cargo.lock
--- a/services/reports/Cargo.lock
+++ b/services/reports/Cargo.lock
@@ -264,6 +264,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.8.3"
@@ -846,6 +882,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 = "clap"
version = "4.4.0"
@@ -901,6 +947,8 @@
"actix-multipart",
"actix-web",
"actix-web-httpauth",
+ "aead",
+ "aes-gcm",
"anyhow",
"aws-config",
"aws-sdk-dynamodb",
@@ -979,9 +1027,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 = "darling"
version = "0.20.3"
@@ -1220,6 +1278,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"
@@ -1413,6 +1481,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"
@@ -1667,6 +1744,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+[[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"
@@ -1802,6 +1885,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 = "postmark"
version = "0.8.1"
@@ -2665,6 +2760,16 @@
"tinyvec",
]
+[[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/reports/Cargo.toml b/services/reports/Cargo.toml
--- a/services/reports/Cargo.toml
+++ b/services/reports/Cargo.toml
@@ -16,6 +16,7 @@
comm-services-lib = { path = "../comm-services-lib", features = [
"blob-client",
"http",
+ "crypto",
] }
derive_more = "0.99"
hex = "0.4"
diff --git a/services/reports/src/config.rs b/services/reports/src/config.rs
--- a/services/reports/src/config.rs
+++ b/services/reports/src/config.rs
@@ -1,5 +1,5 @@
use anyhow::Result;
-use clap::Parser;
+use clap::{ArgAction, Parser};
use comm_services_lib::blob::client::Url;
use once_cell::sync::Lazy;
use tracing::{info, warn};
@@ -26,9 +26,16 @@
/// HTTP server listening port
#[arg(long, default_value_t = 50056)]
pub http_port: u16,
+
#[arg(env = ENV_BLOB_SERVICE_URL)]
#[arg(long, default_value = "http://localhost:50053")]
pub blob_service_url: Url,
+
+ /// Should reports be encrypted? Note that this flag disables encryption
+ /// which is enabled by default.
+ #[arg(long = "no-encrypt", action = ArgAction::SetFalse)]
+ pub encrypt_reports: bool,
+
/// AWS Localstack service URL
#[arg(env = ENV_LOCALSTACK_ENDPOINT)]
#[arg(long)]
@@ -72,6 +79,10 @@
}
}
+ if !cfg.encrypt_reports {
+ warn!("Encryption disabled. Reports will be stored in plaintext!");
+ }
+
Ok(cfg)
}
diff --git a/services/reports/src/database/item.rs b/services/reports/src/database/item.rs
--- a/services/reports/src/database/item.rs
+++ b/services/reports/src/database/item.rs
@@ -7,6 +7,7 @@
},
bytes::Bytes,
constants::DDB_ITEM_SIZE_LIMIT,
+ crypto::aes256::EncryptionKey,
database::{
self, AttributeExtractor, AttributeMap, DBItemError, TryFromAttribute,
},
@@ -35,7 +36,7 @@
#[serde(skip_serializing)]
pub content: ReportContent,
#[serde(skip_serializing)]
- pub encryption_key: Option<String>,
+ pub encryption_key: Option<EncryptionKey>,
}
/// contains some redundancy as not all keys are always present
@@ -73,7 +74,7 @@
attrs.insert(content_attr_name, content_attr);
if let Some(key) = self.encryption_key {
- attrs.insert(ATTR_ENCRYPTION_KEY.to_string(), AttributeValue::S(key));
+ attrs.insert(ATTR_ENCRYPTION_KEY.to_string(), key.into());
}
attrs
}
@@ -111,7 +112,7 @@
}
};
if let Some(key) = self.encryption_key.as_ref() {
- size += key.as_bytes().len();
+ size += key.as_ref().len();
}
size
}
@@ -130,7 +131,7 @@
let content = ReportContent::parse_from_attrs(&mut row)?;
let encryption_key = row
.remove(ATTR_ENCRYPTION_KEY)
- .map(|attr| String::try_from_attr(ATTR_ENCRYPTION_KEY, Some(attr)))
+ .map(|attr| EncryptionKey::try_from_attr(ATTR_ENCRYPTION_KEY, Some(attr)))
.transpose()?;
Ok(ReportItem {
diff --git a/services/reports/src/http/mod.rs b/services/reports/src/http/mod.rs
--- a/services/reports/src/http/mod.rs
+++ b/services/reports/src/http/mod.rs
@@ -86,7 +86,6 @@
ErrorInternalServerError("internal error")
}
},
- #[allow(unreachable_patterns)]
err => {
error!("Received an unexpected error: {0:?} - {0}", err);
ErrorInternalServerError("server error")
diff --git a/services/reports/src/service.rs b/services/reports/src/service.rs
--- a/services/reports/src/service.rs
+++ b/services/reports/src/service.rs
@@ -3,6 +3,7 @@
use comm_services_lib::{
auth::UserIdentity,
blob::client::{BlobServiceClient, BlobServiceError},
+ crypto::aes256,
database,
};
use derive_more::{Display, Error, From};
@@ -11,9 +12,10 @@
future::{ready, Ready},
sync::Arc,
};
-use tracing::error;
+use tracing::{error, trace};
use crate::{
+ config::CONFIG,
database::{
client::{DatabaseClient, ReportsPage},
item::{ReportContent, ReportItem},
@@ -38,6 +40,9 @@
/// Returned when trying to perform an operation on an incompatible report type
/// e.g. create a Redux Devtools import from a media mission report
UnsupportedReportType,
+ /// Error during encryption or decryption
+ #[display(fmt = "Encryption error")]
+ EncryptionError,
/// Unexpected error
Unexpected,
}
@@ -88,8 +93,7 @@
let blob_client = self.blob_client.clone();
let user_id = self.requesting_user_id.clone();
tasks.spawn(async move {
- let mut report = process_report(input, user_id)
- .map_err(ReportsServiceError::SerdeError)?;
+ let mut report = process_report(input, user_id)?;
report.db_item.ensure_size_constraints(&blob_client).await?;
Ok(report)
});
@@ -126,6 +130,7 @@
&self,
report_id: ReportID,
) -> ServiceResult<Option<ReportOutput>> {
+ use ReportsServiceError::{EncryptionError, SerdeError};
let Some(report_item) = self.db.get_report(&report_id).await? else {
return Ok(None);
};
@@ -135,12 +140,21 @@
platform,
creation_time,
content,
+ encryption_key,
..
} = report_item;
- let report_data = content.fetch_bytes(&self.blob_client).await?;
- let report_json = serde_json::from_slice(report_data.as_slice())
- .map_err(ReportsServiceError::SerdeError)?;
+ let mut report_data = content.fetch_bytes(&self.blob_client).await?;
+ if let Some(key) = encryption_key {
+ trace!("Encryption key present. Decrypting report data");
+ report_data = aes256::decrypt(&report_data, &key).map_err(|_| {
+ error!("Failed to decrypt report");
+ EncryptionError
+ })?;
+ }
+
+ let report_json =
+ serde_json::from_slice(report_data.as_slice()).map_err(SerdeError)?;
let output = ReportOutput {
id: report_id,
@@ -222,7 +236,9 @@
fn process_report(
input: ReportInput,
user_id: Option<String>,
-) -> Result<ProcessedReport, serde_json::Error> {
+) -> Result<ProcessedReport, ReportsServiceError> {
+ use ReportsServiceError::*;
+
let id = ReportID::default();
let email = crate::email::prepare_email(&input, &id, user_id.as_deref());
@@ -235,11 +251,26 @@
// Add "platformDetails" back to report content.
// It was deserialized into a separate field.
- let platform_details_value = serde_json::to_value(&platform_details)?;
+ let platform_details_value =
+ serde_json::to_value(&platform_details).map_err(SerdeError)?;
report_content.insert("platformDetails".to_string(), platform_details_value);
// serialize report JSON to bytes
- let content_bytes = serde_json::to_vec(&report_content)?;
+ let content_bytes =
+ serde_json::to_vec(&report_content).map_err(SerdeError)?;
+
+ // possibly encrypt report
+ let (content, encryption_key) = if CONFIG.encrypt_reports {
+ trace!(?id, "Encrypting report");
+ let key = aes256::EncryptionKey::new();
+ let data = aes256::encrypt(&content_bytes, &key).map_err(|_| {
+ error!("Failed to encrypt report");
+ EncryptionError
+ })?;
+ (data, Some(key))
+ } else {
+ (content_bytes, None)
+ };
let db_item = ReportItem {
id: id.clone(),
@@ -247,8 +278,8 @@
platform: platform_details.platform.clone(),
report_type,
creation_time: time.unwrap_or_else(Utc::now),
- encryption_key: None,
- content: ReportContent::Database(content_bytes),
+ encryption_key,
+ content: ReportContent::Database(content),
};
Ok(ProcessedReport { id, db_item, email })
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Jan 16, 2:02 AM (6 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5941345
Default Alt Text
D9035.1768528975.diff (11 KB)
Attached To
Mode
D9035: [reports-service] Encrypt reports
Attached
Detach File
Event Timeline
Log In to Comment