Skip to content

Commit 343ab5d

Browse files
authored
Merge branch 'main' into tore/fix/network-concurrency
2 parents a2a77a5 + f9b4ac7 commit 343ab5d

File tree

16 files changed

+252
-53
lines changed

16 files changed

+252
-53
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ reqwest = { version = "=0.12.22", default-features = false, features = [
7272
"rustls-tls",
7373
] }
7474
schemars = "=0.8.22"
75-
secp256k1 = "=0.31.1"
7675
serde = { version = "=1.0.219", features = ["derive", "rc"] }
7776
serde_json = "=1.0.140"
7877
serial_test = "=3.2.0"
@@ -100,6 +99,7 @@ tracing-opentelemetry = "=0.30.0"
10099
tracing-subscriber = { version = "=0.3.19", features = ["fmt", "std"] }
101100
trait-variant = "0.1.2"
102101
validator = { version = "=0.20.0", features = ["derive"] }
102+
zeroize = { version = "=1.8.1", features = ["zeroize_derive"] }
103103
x509-parser = { version = "=0.17.0", features = ["verify"] }
104104

105105
alloy-dyn-abi = "=1.1.2"

ci/slab.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ image_id = "ami-0a56cf46caf2fd41c"
44
instance_type = "m6i.4xlarge"
55

66
[backend.aws.big-instance-service]
7-
region = "eu-west-3"
8-
image_id = "ami-0a56cf46caf2fd41c"
7+
region = "eu-west-1"
8+
image_id = "ami-04f62ae93462b459a"
99
instance_type = "c7i.8xlarge"
1010

1111
[backend.aws.docker-big-instance]

core/grpc/proto/kms-service.v1.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "kms.v1.proto";
66
service CoreServiceEndpoint {
77
// Perform the threshold KMS initialization.
88
// This call returns an error on the centralized KMS.
9+
// This is a synchronous call and an successful response suggests the initialization completed.
910
rpc Init(kms.v1.InitRequest) returns (kms.v1.Empty);
1011

1112
// Start generating preprocessing materials for key generation asynchronously.

core/service/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ tracing = { workspace = true, features = ["log"] }
109109
trait-variant.workspace = true
110110
url = { version = "=2.5.4", features = ["serde"] }
111111
validator.workspace = true
112+
zeroize.workspace = true
112113
ordermap = "=0.5.7"
113114
x509-parser = { workspace = true, optional = true }
114115

core/service/src/cryptography/backup_pke.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
66
use tfhe::named::Named;
77
use tfhe::Versionize;
88
use tfhe_versionable::VersionsDispatch;
9+
use zeroize::Zeroize;
910

1011
use crate::consts::SAFE_SER_SIZE_LIMIT;
1112
use crate::cryptography::hybrid_ml_kem::{self};
@@ -21,12 +22,40 @@ struct InnerBackupPrivateKey {
2122
decapsulation_key: <ml_kem::kem::Kem<MlKemParams> as ml_kem::KemCore>::DecapsulationKey,
2223
}
2324

25+
impl Drop for InnerBackupPrivateKey {
26+
fn drop(&mut self) {
27+
// Directly zeroize the underlying key bytes without creating copies
28+
// This is more secure as it avoids temporary allocations of sensitive data
29+
let key_bytes_ptr = self.decapsulation_key.as_bytes().as_ptr() as *mut u8;
30+
let key_len = self.decapsulation_key.as_bytes().len();
31+
32+
// SAFETY: We're zeroizing the memory that belongs to this struct
33+
// The pointer is valid and the length is correct from as_bytes()
34+
unsafe {
35+
std::ptr::write_bytes(key_bytes_ptr, 0, key_len);
36+
}
37+
}
38+
}
39+
2440
#[derive(Debug, Clone, Serialize, Deserialize, VersionsDispatch)]
2541
pub enum BackupPrivateKeyVersioned {
2642
V0(BackupPrivateKey),
2743
}
2844

29-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Versionize)]
45+
// NOTE:
46+
// The `Versionize` derive macro cannot be combined with a custom `Drop` implementation
47+
// or `ZeroizeOnDrop` derivation without conflicts.
48+
//
49+
// Security-wise, the current design is solid:
50+
// - Actual cryptographic key material resides in `InnerBackupPrivateKey`, not in the
51+
// serialized `Vec<u8>` representation.
52+
// - Most `BackupPrivateKey` instances are short-lived and immediately used for decryption;
53+
// `decrypt()` converts them into `InnerBackupPrivateKey`.
54+
// - `InnerBackupPrivateKey` implements a custom `Drop` with explicit memory zeroization.
55+
// - `BackupPrivateKey` implements `Zeroize` for manual wiping if needed.
56+
// - `try_from()` ensures that the intermediate `decaps_key_buf` (which *does* contain
57+
// sensitive data) is securely zeroized after use.
58+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Versionize, Zeroize)]
3059
#[versionize(BackupPrivateKeyVersioned)]
3160
pub struct BackupPrivateKey {
3261
decapsulation_key: Vec<u8>,
@@ -59,6 +88,10 @@ impl TryFrom<&BackupPrivateKey> for InnerBackupPrivateKey {
5988

6089
let decapsulation_key =
6190
<MlKemType as ml_kem::KemCore>::DecapsulationKey::from_bytes(&decaps_key_buf);
91+
92+
// Zeroize the key buffer to prevent memory disclosure attacks
93+
decaps_key_buf.zeroize();
94+
6295
Ok(Self { decapsulation_key })
6396
}
6497
}

core/service/src/engine/threshold/service/crs_generator.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ use observability::{
1515
use threshold_fhe::{
1616
algebra::base_ring::Z64,
1717
execution::{
18-
runtime::session::{BaseSession, ParameterHandles, ToBaseSession},
18+
runtime::session::{BaseSession, ParameterHandles},
1919
tfhe_internals::parameters::DKGParams,
2020
zk::ceremony::{compute_witness_dim, Ceremony},
2121
},
22+
networking::NetworkMode,
2223
};
2324
use tokio::sync::{Mutex, OwnedSemaphorePermit, RwLock};
2425
use tokio_util::{sync::CancellationToken, task::TaskTracker};
@@ -182,11 +183,11 @@ impl<
182183
}
183184

184185
let session_id = req_id.derive_session_id()?;
186+
// CRS ceremony requires a sync network
185187
let session = self
186188
.session_preparer
187-
.prepare_ddec_data_from_sessionid_z128(session_id)
188-
.await?
189-
.to_base_session();
189+
.make_base_session(session_id, NetworkMode::Sync)
190+
.await?;
190191

191192
let meta_store = Arc::clone(&self.crs_meta_store);
192193
let meta_store_cancelled = Arc::clone(&self.crs_meta_store);
@@ -664,6 +665,19 @@ mod tests {
664665
};
665666

666667
let request = Request::new(req);
668+
let _ = crs_gen.crs_gen(request).await.unwrap();
669+
670+
// Send a the request again, it should return an error
671+
// because the session has already been used.
672+
let req = CrsGenRequest {
673+
params: 0,
674+
max_num_bits: None,
675+
request_id: Some(req_id.into()),
676+
domain: None,
677+
};
678+
679+
let request = Request::new(req);
680+
667681
assert_eq!(
668682
crs_gen.crs_gen(request).await.unwrap_err().code(),
669683
tonic::Code::Aborted

core/service/src/engine/threshold/service/initiator.rs

Lines changed: 127 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// === Standard Library ===
2-
use std::sync::Arc;
2+
use std::{marker::PhantomData, sync::Arc};
33

44
// === External Crates ===
55
use kms_grpc::{
@@ -12,7 +12,7 @@ use threshold_fhe::{
1212
algebra::galois_rings::degree_4::{ResiduePolyF4Z128, ResiduePolyF4Z64},
1313
execution::{
1414
runtime::session::ParameterHandles,
15-
small_execution::prss::{PRSSInit, PRSSSetup, RobustSecurePrssInit},
15+
small_execution::prss::{PRSSInit, PRSSSetup},
1616
},
1717
networking::NetworkMode,
1818
};
@@ -33,16 +33,26 @@ use crate::{
3333
// === Current Module Imports ===
3434
use super::{session::SessionPreparer, RealThresholdKms};
3535

36-
pub struct RealInitiator<PrivS: Storage + Send + Sync + 'static> {
36+
pub struct RealInitiator<
37+
PrivS: Storage + Send + Sync + 'static,
38+
Init: PRSSInit<ResiduePolyF4Z64> + PRSSInit<ResiduePolyF4Z128>,
39+
> {
3740
// TODO eventually add mode to allow for nlarge as well.
3841
pub prss_setup_z128: Arc<RwLock<Option<PRSSSetup<ResiduePolyF4Z128>>>>,
3942
pub prss_setup_z64: Arc<RwLock<Option<PRSSSetup<ResiduePolyF4Z64>>>>,
4043
pub private_storage: Arc<Mutex<PrivS>>,
4144
pub session_preparer: SessionPreparer,
42-
pub health_reporter: Arc<RwLock<HealthReporter>>,
45+
pub health_reporter: HealthReporter,
46+
pub(crate) _init: PhantomData<Init>,
4347
}
4448

45-
impl<PrivS: Storage + Send + Sync + 'static> RealInitiator<PrivS> {
49+
impl<
50+
PrivS: Storage + Send + Sync + 'static,
51+
Init: PRSSInit<ResiduePolyF4Z64, OutputType = PRSSSetup<ResiduePolyF4Z64>>
52+
+ PRSSInit<ResiduePolyF4Z128, OutputType = PRSSSetup<ResiduePolyF4Z128>>
53+
+ Default,
54+
> RealInitiator<PrivS, Init>
55+
{
4656
pub async fn init_prss_from_disk(&self, req_id: &RequestId) -> anyhow::Result<()> {
4757
let prss_setup_z128_from_file = {
4858
let guarded_private_storage = self.private_storage.lock().await;
@@ -109,8 +119,6 @@ impl<PrivS: Storage + Send + Sync + 'static> RealInitiator<PrivS> {
109119
{
110120
// Notice that this is a hack to get the health reporter to report serving. The type `PrivS` has no influence on the service name.
111121
self.health_reporter
112-
.write()
113-
.await
114122
.set_serving::<CoreServiceEndpointServer<RealThresholdKms<PrivS, PrivS>>>()
115123
.await;
116124
}
@@ -141,13 +149,14 @@ impl<PrivS: Storage + Send + Sync + 'static> RealInitiator<PrivS> {
141149
"Role assignments: {:?}",
142150
base_session.parameters.role_assignments()
143151
);
144-
let prss_setup_obj_z128: PRSSSetup<ResiduePolyF4Z128> = RobustSecurePrssInit::default()
145-
.init(&mut base_session)
146-
.await?;
147152

148-
let prss_setup_obj_z64: PRSSSetup<ResiduePolyF4Z64> = RobustSecurePrssInit::default()
149-
.init(&mut base_session)
150-
.await?;
153+
// It seems we cannot do something like
154+
// `Init::default().init(&mut base_session).await?;`
155+
// as the type inference gets confused even when using the correct return type.
156+
let prss_setup_obj_z128: PRSSSetup<ResiduePolyF4Z128> =
157+
PRSSInit::<ResiduePolyF4Z128>::init(&Init::default(), &mut base_session).await?;
158+
let prss_setup_obj_z64: PRSSSetup<ResiduePolyF4Z64> =
159+
PRSSInit::<ResiduePolyF4Z64>::init(&Init::default(), &mut base_session).await?;
151160

152161
let mut guarded_prss_setup = self.prss_setup_z128.write().await;
153162
*guarded_prss_setup = Some(prss_setup_obj_z128.clone());
@@ -186,8 +195,6 @@ impl<PrivS: Storage + Send + Sync + 'static> RealInitiator<PrivS> {
186195
{
187196
// Notice that this is a hack to get the health reporter to report serving. The type `PrivS` has no influence on the service name.
188197
self.health_reporter
189-
.write()
190-
.await
191198
.set_serving::<CoreServiceEndpointServer<RealThresholdKms<PrivS, PrivS>>>()
192199
.await;
193200
}
@@ -197,7 +204,13 @@ impl<PrivS: Storage + Send + Sync + 'static> RealInitiator<PrivS> {
197204
}
198205

199206
#[tonic::async_trait]
200-
impl<PrivS: Storage + Send + Sync + 'static> Initiator for RealInitiator<PrivS> {
207+
impl<
208+
PrivS: Storage + Send + Sync + 'static,
209+
Init: PRSSInit<ResiduePolyF4Z64, OutputType = PRSSSetup<ResiduePolyF4Z64>>
210+
+ PRSSInit<ResiduePolyF4Z128, OutputType = PRSSSetup<ResiduePolyF4Z128>>
211+
+ Default,
212+
> Initiator for RealInitiator<PrivS, Init>
213+
{
201214
async fn init(&self, request: Request<v1::InitRequest>) -> Result<Response<Empty>, Status> {
202215
let inner = request.into_inner();
203216
let request_id = tonic_some_or_err(
@@ -219,14 +232,38 @@ impl<PrivS: Storage + Send + Sync + 'static> Initiator for RealInitiator<PrivS>
219232

220233
#[cfg(test)]
221234
mod tests {
235+
use super::*;
236+
222237
use crate::{
223238
client::test_tools::{self},
224239
consts::PRSS_INIT_REQ_ID,
225-
engine::base::derive_request_id,
226240
util::key_setup::test_tools::purge,
227-
vault::storage::{file::FileStorage, StorageType},
241+
vault::storage::{file::FileStorage, ram, StorageType},
242+
};
243+
use aes_prng::AesRng;
244+
use kms_grpc::kms::v1::InitRequest;
245+
use rand::SeedableRng;
246+
use threshold_fhe::{
247+
execution::runtime::party::Role,
248+
malicious_execution::small_execution::malicious_prss::{EmptyPrss, FailingPrss},
228249
};
229-
use threshold_fhe::execution::runtime::party::Role;
250+
251+
impl<
252+
Init: PRSSInit<ResiduePolyF4Z64, OutputType = PRSSSetup<ResiduePolyF4Z64>>
253+
+ PRSSInit<ResiduePolyF4Z128, OutputType = PRSSSetup<ResiduePolyF4Z128>>,
254+
> RealInitiator<ram::RamStorage, Init>
255+
{
256+
fn init_test(session_preparer: SessionPreparer) -> Self {
257+
Self {
258+
prss_setup_z128: Arc::new(RwLock::new(None)),
259+
prss_setup_z64: Arc::new(RwLock::new(None)),
260+
private_storage: Arc::new(Mutex::new(ram::RamStorage::new())),
261+
session_preparer,
262+
health_reporter: HealthReporter::new(),
263+
_init: PhantomData,
264+
}
265+
}
266+
}
230267

231268
#[tokio::test]
232269
#[serial_test::serial]
@@ -316,4 +353,75 @@ mod tests {
316353
"Initializing threshold KMS server with PRSS Setup Z64 from disk"
317354
));
318355
}
356+
357+
#[tokio::test]
358+
async fn sunshine() {
359+
let session_preparer = SessionPreparer::new_test_session(false);
360+
let initiator = RealInitiator::<ram::RamStorage, EmptyPrss>::init_test(session_preparer);
361+
362+
let mut rng = AesRng::seed_from_u64(42);
363+
let req_id = RequestId::new_random(&mut rng);
364+
initiator
365+
.init(tonic::Request::new(InitRequest {
366+
request_id: Some(req_id.into()),
367+
}))
368+
.await
369+
.unwrap();
370+
}
371+
#[tokio::test]
372+
async fn invalid_argument() {
373+
let session_preparer = SessionPreparer::new_test_session(false);
374+
let initiator = RealInitiator::<ram::RamStorage, EmptyPrss>::init_test(session_preparer);
375+
376+
let bad_req_id = kms_grpc::kms::v1::RequestId {
377+
request_id: "bad req id".to_string(),
378+
};
379+
assert_eq!(
380+
initiator
381+
.init(tonic::Request::new(InitRequest {
382+
request_id: Some(bad_req_id)
383+
}))
384+
.await
385+
.unwrap_err()
386+
.code(),
387+
tonic::Code::InvalidArgument
388+
);
389+
}
390+
391+
#[tokio::test]
392+
async fn aborted() {
393+
let session_preparer = SessionPreparer::new_test_session(false);
394+
let initiator = RealInitiator::<ram::RamStorage, EmptyPrss>::init_test(session_preparer);
395+
396+
assert_eq!(
397+
initiator
398+
.init(tonic::Request::new(InitRequest {
399+
// this is set to none
400+
request_id: None
401+
}))
402+
.await
403+
.unwrap_err()
404+
.code(),
405+
tonic::Code::Aborted
406+
);
407+
}
408+
409+
#[tokio::test]
410+
async fn internal() {
411+
let session_preparer = SessionPreparer::new_test_session(false);
412+
let initiator = RealInitiator::<ram::RamStorage, FailingPrss>::init_test(session_preparer);
413+
414+
let mut rng = AesRng::seed_from_u64(42);
415+
let req_id = RequestId::new_random(&mut rng);
416+
assert_eq!(
417+
initiator
418+
.init(tonic::Request::new(InitRequest {
419+
request_id: Some(req_id.into())
420+
}))
421+
.await
422+
.unwrap_err()
423+
.code(),
424+
tonic::Code::Internal
425+
);
426+
}
319427
}

0 commit comments

Comments
 (0)