diff --git a/Cargo.lock b/Cargo.lock index 4d2fa786c82..f1fba42fcad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4786,7 +4786,6 @@ dependencies = [ "treeline", "url", "uuid", - "zeroize", ] [[package]] diff --git a/implementations/rust/ockam/ockam_api/Cargo.toml b/implementations/rust/ockam/ockam_api/Cargo.toml index efa7871963f..f078ac14a9c 100644 --- a/implementations/rust/ockam/ockam_api/Cargo.toml +++ b/implementations/rust/ockam/ockam_api/Cargo.toml @@ -112,7 +112,6 @@ tracing-error = "0.2.0" tracing-opentelemetry = "0.27.0" tracing-subscriber = { version = "0.3", features = ["json"] } url = "2.5.2" -zeroize = { version = "1.8.1", features = ["zeroize_derive"] } ockam_multiaddr = { path = "../ockam_multiaddr", version = "0.66.0", features = ["cbor", "serde"] } ockam_transport_core = { path = "../ockam_transport_core", version = "^0.99.0" } diff --git a/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/controller.rs b/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/controller.rs index 7de12088288..6784daffe11 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/controller.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/controller.rs @@ -266,7 +266,6 @@ mod test { pub fn rekey_rotation() -> ockam_core::Result<()> { let runtime = Arc::new(Runtime::new().unwrap()); let runtime_cloned = runtime.clone(); - std::env::set_var("OCKAM_LOGGING", "false"); runtime_cloned.block_on(async move { let test_body = async move { @@ -297,24 +296,34 @@ mod test { .get_flow_control_with_spawner(&DefaultAddress::SECURE_CHANNEL_LISTENER.into()) .unwrap(); + let test_clock = TestClock::new(0); + KafkaKeyExchangeListener::create( + test_clock.clone(), &consumer_node.context, consumer_node .node_manager .secure_channels .vault() .encryption_at_rest_vault, - Duration::from_secs(60), - Duration::from_secs(60), - Duration::from_secs(60), + consumer_node + .node_manager + .secure_channels + .vault() + .secure_channel_vault, + consumer_node + .node_manager + .secure_channels + .secure_channel_registry(), + Duration::from_secs(5 * 60), //rotation + Duration::from_secs(10 * 60), //validity + Duration::from_secs(60), //rekey &consumer_secure_channel_listener_flow_control_id, AllowAll, AllowAll, ) .await?; - let test_clock = TestClock::new(0); - let destination = consumer_node.listen_address().await.multi_addr().unwrap(); let producer_secure_channel_controller = create_secure_channel_controller( test_clock.clone(), @@ -355,7 +364,10 @@ mod test { .await?; assert_eq!(third_key.rekey_counter, 1); - assert_eq!(first_key.secret_key_handle, third_key.secret_key_handle); + assert_eq!( + first_key.key_identifier_for_consumer, + third_key.key_identifier_for_consumer + ); // 04:00 - yet another rekey should happen, but no rotation test_clock.add_seconds(60 * 3); @@ -365,7 +377,10 @@ mod test { .await?; assert_eq!(fourth_key.rekey_counter, 2); - assert_eq!(first_key.secret_key_handle, fourth_key.secret_key_handle); + assert_eq!( + first_key.key_identifier_for_consumer, + fourth_key.key_identifier_for_consumer + ); // 05:00 - the default duration of the key is 10 minutes, // but the rotation should happen after 5 minutes @@ -375,7 +390,10 @@ mod test { .get_or_exchange_key(&mut producer_node.context, "topic_name") .await?; - assert_ne!(third_key.secret_key_handle, fifth_key.secret_key_handle); + assert_ne!( + third_key.key_identifier_for_consumer, + fifth_key.key_identifier_for_consumer + ); assert_eq!(fifth_key.rekey_counter, 0); // Now let's simulate a failure to rekey by shutting down the consumer @@ -389,7 +407,10 @@ mod test { .await?; assert_eq!(sixth_key.rekey_counter, 1); - assert_eq!(fifth_key.secret_key_handle, sixth_key.secret_key_handle); + assert_eq!( + fifth_key.key_identifier_for_consumer, + sixth_key.key_identifier_for_consumer + ); // 10:00 - Rotation fails, but the existing key is still valid // and needs to be rekeyed @@ -400,7 +421,10 @@ mod test { .await?; assert_eq!(seventh_key.rekey_counter, 2); - assert_eq!(fifth_key.secret_key_handle, seventh_key.secret_key_handle); + assert_eq!( + fifth_key.key_identifier_for_consumer, + seventh_key.key_identifier_for_consumer + ); // 15:00 - Rotation fails, and the existing key is no longer valid test_clock.add_seconds(60 * 5); diff --git a/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/listener.rs b/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/listener.rs index 5fefb017d78..d75569c07ce 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/listener.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/listener.rs @@ -1,37 +1,42 @@ use crate::DefaultAddress; use minicbor::{CborLen, Decode, Encode}; -use ockam::identity::TimestampInSeconds; +use ockam::identity::{ + SecureChannelApiRequest, SecureChannelApiResponse, SecureChannelRegistry, TimestampInSeconds, +}; +use ockam_core::compat::clock::Clock; use ockam_core::flow_control::FlowControlId; use ockam_core::{ - async_trait, Address, Decodable, Encodable, Encoded, IncomingAccessControl, Message, + async_trait, route, Address, Decodable, Encodable, Encoded, IncomingAccessControl, Message, OutgoingAccessControl, Routed, Worker, }; use ockam_node::{Context, WorkerBuilder}; -use ockam_vault::VaultForEncryptionAtRest; -use rand::Rng; +use ockam_vault::{VaultForEncryptionAtRest, VaultForSecureChannels}; use std::sync::Arc; use std::time::Duration; pub(crate) struct KafkaKeyExchangeListener { encryption_at_rest: Arc, + secure_channel_vault: Arc, + secure_channel_registry: SecureChannelRegistry, rekey_period: Duration, key_validity: Duration, key_rotation: Duration, + clock: Box, } #[derive(Debug, CborLen, Encode, Decode)] #[rustfmt::skip] pub(crate) struct KeyExchangeRequest { + #[n(1)] pub local_decryptor_address: Address, } #[derive(Debug, CborLen, Encode, Decode)] #[rustfmt::skip] pub(crate) struct KeyExchangeResponse { #[n(0)] pub key_identifier_for_consumer: Vec, - #[n(1)] pub secret_key: [u8; 32], - #[n(2)] pub valid_until: TimestampInSeconds, - #[n(3)] pub rotate_after: TimestampInSeconds, - #[n(4)] pub rekey_period: Duration, + #[n(1)] pub valid_until: TimestampInSeconds, + #[n(2)] pub rotate_after: TimestampInSeconds, + #[n(3)] pub rekey_period: Duration, } impl Encodable for KeyExchangeRequest { @@ -70,14 +75,43 @@ impl Worker for KafkaKeyExchangeListener { context: &mut Self::Context, message: Routed, ) -> ockam_core::Result<()> { - let mut secret_key = [0u8; 32]; - rand::thread_rng().fill(&mut secret_key[..]); - let handle = self - .encryption_at_rest - .import_aead_key(secret_key.to_vec()) - .await?; + let request: KeyExchangeRequest = minicbor::decode(message.payload())?; + let local_decryptor = Address::from_string(request.local_decryptor_address); + + let entry = self + .secure_channel_registry + .get_channel_by_decryptor_address(&local_decryptor); + let handle = match entry { + None => { + warn!("No secure channel found for local decryptor {local_decryptor}",); + return Ok(()); + } + Some(entry) => { + let response: SecureChannelApiResponse = context + .send_and_receive( + route![entry.decryptor_api_address().clone()], + SecureChannelApiRequest::ExtractKey, + ) + .await?; + + let key_identifier = match response { + SecureChannelApiResponse::Ok(key_identifier) => key_identifier, + SecureChannelApiResponse::Err(error) => { + error!("Error extracting key: {error}"); + return Ok(()); + } + }; + + let secret = self + .secure_channel_vault + .export_rekey(&key_identifier) + .await?; + + self.encryption_at_rest.import_aead_key(secret).await? + } + }; - let now = TimestampInSeconds(ockam_core::compat::time::now()?); + let now = TimestampInSeconds(self.clock.now()?); let valid_until = now + self.key_validity; let rotate_after = now + self.key_rotation; @@ -86,7 +120,6 @@ impl Worker for KafkaKeyExchangeListener { message.return_route().clone(), KeyExchangeResponse { key_identifier_for_consumer: handle.into_vec(), - secret_key, valid_until, rotate_after, rekey_period: self.rekey_period, @@ -101,8 +134,11 @@ impl Worker for KafkaKeyExchangeListener { impl KafkaKeyExchangeListener { #[allow(clippy::too_many_arguments)] pub async fn create( + clock: impl Clock, context: &Context, encryption_at_rest: Arc, + secure_channel_vault: Arc, + secure_channel_registry: SecureChannelRegistry, key_rotation: Duration, key_validity: Duration, rekey_period: Duration, @@ -117,9 +153,12 @@ impl KafkaKeyExchangeListener { WorkerBuilder::new(KafkaKeyExchangeListener { encryption_at_rest, + secure_channel_vault, key_rotation, key_validity, rekey_period, + secure_channel_registry, + clock: Box::new(clock), }) .with_address(address) .with_incoming_access_control(incoming_access_control) diff --git a/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/secure_channels.rs b/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/secure_channels.rs index f970bbfe641..fe295376b45 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/secure_channels.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/key_exchange/secure_channels.rs @@ -5,9 +5,9 @@ use crate::kafka::key_exchange::controller::{ use crate::kafka::key_exchange::listener::{KeyExchangeRequest, KeyExchangeResponse}; use crate::kafka::ConsumerResolution; use crate::DefaultAddress; -use ockam::identity::TimestampInSeconds; +use ockam::identity::{SecureChannelApiRequest, SecureChannelApiResponse, TimestampInSeconds}; use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; +use ockam_core::{route, Error, Result}; use ockam_multiaddr::proto::{Secure, Service}; use ockam_multiaddr::MultiAddr; use ockam_node::{Context, MessageSendReceiveOptions}; @@ -35,6 +35,72 @@ impl KafkaKeyExchangeControllerImpl { destination.push_back(Secure::new(DefaultAddress::SECURE_CHANNEL_LISTENER))?; destination.push_back(Service::new(DefaultAddress::KAFKA_CUSTODIAN))?; if let Some(node_manager) = inner.node_manager.upgrade() { + // create a second secure channel to be used for key exchange + let (aead_secret_key_handle, their_decryptor_address) = { + let key_exchange_connection = node_manager + .make_connection(context, &destination, node_manager.identifier(), None, None) + .await?; + + let encryptor_address = key_exchange_connection + .secure_channel_encryptors + .first() + .expect("encryptor should be present"); + + let entry = node_manager + .secure_channels + .secure_channel_registry() + .get_channel_by_encryptor_address(encryptor_address) + .expect("channel should be present"); + + if !inner + .consumer_policy_access_control + .is_identity_authorized(entry.their_id()) + .await? + { + key_exchange_connection + .close(context, &node_manager) + .await?; + return Err(Error::new( + Origin::Channel, + Kind::Invalid, + "Consumer is not authorized to use the secure channel", + )); + } + + let response: SecureChannelApiResponse = context + .send_and_receive( + route![entry.encryptor_api_address().clone()], + SecureChannelApiRequest::ExtractKey, + ) + .await?; + + match response { + SecureChannelApiResponse::Ok(secret_handle) => { + let secret = node_manager + .secure_channels + .vault() + .secure_channel_vault + .export_rekey(&secret_handle) + .await?; + + key_exchange_connection + .close(context, &node_manager) + .await?; + + ( + self.encryption_at_rest.import_aead_key(secret).await?, + entry.their_decryptor_address(), + ) + } + SecureChannelApiResponse::Err(error) => { + key_exchange_connection + .close(context, &node_manager) + .await?; + return Err(error); + } + } + }; + let connection = node_manager .make_connection(context, &destination, node_manager.identifier(), None, None) .await?; @@ -52,14 +118,17 @@ impl KafkaKeyExchangeControllerImpl { let route = connection.route()?; let response: KeyExchangeResponse = context - .send_and_receive_extended(route, KeyExchangeRequest {}, send_and_receive_options) + .send_and_receive_extended( + route, + KeyExchangeRequest { + local_decryptor_address: their_decryptor_address, + }, + send_and_receive_options, + ) .await? .into_body()?; - let aead_secret_key_handle = self - .encryption_at_rest - .import_aead_key(response.secret_key.to_vec()) - .await?; + connection.close(context, &node_manager).await?; Ok(ExchangedKey { secret_key_handler: aead_secret_key_handle, diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs index 837227b17c2..ac4a1466a99 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs @@ -23,6 +23,7 @@ use ockam::{Address, Context, Result}; use ockam_abac::PolicyExpression; use ockam_abac::{Action, Resource, ResourceType}; use ockam_core::api::{Error, Response}; +use ockam_core::compat::clock::ProductionClock; use ockam_core::compat::rand::random_string; use ockam_core::flow_control::FlowControls; use ockam_core::route; @@ -197,8 +198,11 @@ impl InMemoryNode { // TODO: remove key exchange from inlets KafkaKeyExchangeListener::create( + ProductionClock, context, vault.encryption_at_rest_vault, + self.secure_channels.vault().secure_channel_vault, + self.secure_channels.secure_channel_registry(), std::time::Duration::from_secs(60 * 60 * 24), std::time::Duration::from_secs(60 * 60 * 30), std::time::Duration::from_secs(60 * 60), @@ -395,8 +399,11 @@ impl InMemoryNode { .await?; KafkaKeyExchangeListener::create( + ProductionClock, context, vault.encryption_at_rest_vault, + self.secure_channels.vault().secure_channel_vault, + self.secure_channels.secure_channel_registry(), request.key_rotation, request.key_validity, request.rekey_period, diff --git a/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs b/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs index 09316951c21..f39ffd9ab30 100644 --- a/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs +++ b/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs @@ -485,8 +485,8 @@ pub mod vault_for_secure_channels { use minicbor::{CborLen, Decode, Encode}; use ockam_core::{async_trait, cbor_encode_preallocate, MaybeZeroizeOnDrop}; use ockam_vault::{ - AeadSecretKeyHandle, HKDFNumberOfOutputs, HashOutput, HkdfOutput, SecretBufferHandle, - VaultForSecureChannels, X25519PublicKey, X25519SecretKeyHandle, + AeadSecretKeyHandle, HKDFNumberOfOutputs, HashOutput, HkdfOutput, SecretBuffer, + SecretBufferHandle, VaultForSecureChannels, X25519PublicKey, X25519SecretKeyHandle, }; pub(super) async fn handle_request( @@ -617,7 +617,7 @@ pub mod vault_for_secure_channels { } Request::DeleteAeadSecretKey { secret_key_handle } => { trace!("delete_aead_secret_key request for {secret_key_handle:?}"); - let result = vault.delete_aead_secret_key(secret_key_handle).await; + let result = vault.delete_aead_secret_key(&secret_key_handle).await; Response::DeleteAeadSecretKey(result.map_err(Into::into)) } Request::Rekey { @@ -628,6 +628,11 @@ pub mod vault_for_secure_channels { let result = vault.rekey(&secret_key_handle, n).await; Response::Rekey(result.map_err(Into::into)) } + Request::ExportRekey { secret_key_handle } => { + trace!("export_rekey request for {secret_key_handle:?}"); + let result = vault.export_rekey(&secret_key_handle).await; + Response::ExportRekey(result.map_err(Into::into)) + } }; cbor_encode_preallocate(response) } @@ -695,6 +700,9 @@ pub mod vault_for_secure_channels { #[n(0)] secret_key_handle: AeadSecretKeyHandle, #[n(1)] n: u16, }, + #[n(18)] ExportRekey { + #[n(0)] secret_key_handle: AeadSecretKeyHandle + }, } #[derive(Encode, Decode, CborLen)] @@ -718,6 +726,7 @@ pub mod vault_for_secure_channels { #[n(15)] ConvertSecretBufferToAeadKey(#[n(0)] Result), #[n(16)] DeleteAeadSecretKey(#[n(0)] Result), #[n(17)] Rekey(#[n(0)] Result), + #[n(18)] ExportRekey(#[n(0)] Result), } #[async_trait] @@ -866,6 +875,25 @@ pub mod vault_for_secure_channels { Ok(result) } + async fn export_rekey( + &self, + secret_key_handle: &AeadSecretKeyHandle, + ) -> ockam_core::Result { + trace!("sending export_rekey request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::ExportRekey { + secret_key_handle: secret_key_handle.clone(), + }) + .await?; + + let result = match response { + Response::ExportRekey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + async fn persist_aead_key( &self, secret_key_handle: &AeadSecretKeyHandle, @@ -1067,11 +1095,13 @@ pub mod vault_for_secure_channels { async fn delete_aead_secret_key( &self, - secret_key_handle: AeadSecretKeyHandle, + secret_key_handle: &AeadSecretKeyHandle, ) -> ockam_core::Result { trace!("sending delete_aead_secret_key request for {secret_key_handle:?}"); let response: Response = self - .send_and_receive(Request::DeleteAeadSecretKey { secret_key_handle }) + .send_and_receive(Request::DeleteAeadSecretKey { + secret_key_handle: secret_key_handle.clone(), + }) .await?; let result = match response { @@ -1183,7 +1213,7 @@ pub mod vault_for_encryption_at_rest { use crate::proxy_vault::protocol::{ProxyError, SpecificClient}; use minicbor::{CborLen, Decode, Encode}; use ockam_core::{async_trait, cbor_encode_preallocate, MaybeZeroizeOnDrop}; - use ockam_vault::{AeadSecretKeyHandle, VaultForEncryptionAtRest}; + use ockam_vault::{AeadSecretKeyHandle, SecretBuffer, VaultForEncryptionAtRest}; pub(super) async fn handle_request( vault: &dyn VaultForEncryptionAtRest, @@ -1246,7 +1276,7 @@ pub mod vault_for_encryption_at_rest { #[n(0)] secret_key_handle: AeadSecretKeyHandle, }, #[n(3)] ImportAeadKey { - #[n(0)] secret: Vec, + #[n(0)] secret: SecretBuffer, }, } @@ -1343,7 +1373,7 @@ pub mod vault_for_encryption_at_rest { async fn import_aead_key( &self, - secret: Vec, + secret: SecretBuffer, ) -> ockam_core::Result { trace!("sending import_aead_key request"); let response: Response = self diff --git a/implementations/rust/ockam/ockam_core/src/zeroize.rs b/implementations/rust/ockam/ockam_core/src/zeroize.rs index 2ad7d844942..cda70e4c524 100644 --- a/implementations/rust/ockam/ockam_core/src/zeroize.rs +++ b/implementations/rust/ockam/ockam_core/src/zeroize.rs @@ -1,9 +1,9 @@ use core::fmt::{Debug, Display, Formatter}; use core::hash::{Hash, Hasher}; use core::ops::Deref; +use core::ops::DerefMut; use minicbor::encode::{Error, Write}; use minicbor::{CborLen, Decode, Encode, Encoder}; -use std::ops::DerefMut; use zeroize::Zeroize; /// OnDrop is an enum to specify whether to zeroize the inner value when dropped. @@ -43,7 +43,7 @@ impl MaybeZeroizeOnDrop { /// Return the inner value regardless of the zeroize_on_drop flag. /// The caller has the responsibility to ensure that the inner value is zeroized when necessary. pub fn discard_zeroize(mut self) -> T { - std::mem::take(&mut self.target) + core::mem::take(&mut self.target) } } @@ -85,7 +85,7 @@ impl Default for MaybeZeroizeOnDrop { } impl Debug for MaybeZeroizeOnDrop { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("MaybeZeroizeOnDrop") .field("target", &self.target) .field("on_drop", &self.on_drop) @@ -94,7 +94,7 @@ impl Debug for MaybeZeroizeOnDrop { } impl Display for MaybeZeroizeOnDrop { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { self.target.fmt(f) } } @@ -117,13 +117,13 @@ impl PartialEq for MaybeZeroizeOnDrop { impl Eq for MaybeZeroizeOnDrop {} impl PartialOrd for MaybeZeroizeOnDrop { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { self.target.partial_cmp(&other.target) } } impl Ord for MaybeZeroizeOnDrop { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.target.cmp(&other.target) } } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/api.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/api.rs index a0fc95b3125..42ff40972a9 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/api.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/api.rs @@ -1,20 +1,19 @@ -use minicbor::{CborLen, Decode, Encode}; +use ockam_core::{Error, Message}; use ockam_vault::AeadSecretKeyHandle; +use serde::{Deserialize, Serialize}; /// Request type for `SecureChannel` API Address -#[derive(Encode, Decode, CborLen)] -#[rustfmt::skip] +#[derive(Serialize, Deserialize, Message)] pub enum SecureChannelApiRequest { /// Derive a new key from current key and shutdown the worker - #[n(0)] ExtractKey, + ExtractKey, } /// Response type for `SecureChannel` API Address -#[derive(Encode, Decode, CborLen)] -#[rustfmt::skip] +#[derive(Serialize, Deserialize, Message)] pub enum SecureChannelApiResponse { /// Success - #[n(0)] Ok(#[n(0)] AeadSecretKeyHandle), + Ok(AeadSecretKeyHandle), /// Error - #[n(1)] Err(#[n(0)] String), + Err(Error), } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs index 9971eb9c5ad..ec707458a8d 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs @@ -1,7 +1,7 @@ use core::sync::atomic::Ordering; use ockam_core::compat::sync::Arc; -use ockam_core::{cbor_encode_preallocate, LocalMessage, NeutralMessage}; use ockam_core::{route, Any, OnDrop, Result, Route, Routed, SecureChannelLocalInfo}; +use ockam_core::{Decodable, LocalMessage}; use ockam_node::Context; use crate::models::Identifier; @@ -68,19 +68,23 @@ impl DecryptorHandler { let return_route = msg.return_route; // Decode raw payload binary - let request = minicbor::decode(msg.payload.as_slice())?; + let request = SecureChannelApiRequest::decode(&msg.payload)?; let response = match request { SecureChannelApiRequest::ExtractKey => { let handle = self.decryptor.derive_new_key().await?; SecureChannelApiResponse::Ok(handle) } }; - let response = NeutralMessage::from(cbor_encode_preallocate(&response)?); // Send reply to the caller ctx.send_from_address(return_route, response, self.addresses.decryptor_api.clone()) .await?; + // Avoid sending a Close message, to the other party can extract the key as wel + self.shared_state + .should_send_close + .store(false, Ordering::Relaxed); + // Once we have extracted the key, we can't use it anymore ctx.stop_worker(self.addresses.encryptor.clone()).await?; @@ -273,7 +277,7 @@ impl Decryptor { Ok(result) => { self.nonce_tracker = nonce_tracker; if let Some(key_to_delete) = self.key_tracker.update_key(&key.clone())? { - self.vault.delete_aead_secret_key(key_to_delete).await?; + self.vault.delete_aead_secret_key(&key_to_delete).await?; } Ok((result, nonce)) @@ -291,13 +295,11 @@ impl Decryptor { #[instrument(skip_all)] pub(crate) async fn shutdown(&self) -> Result<()> { self.vault - .delete_aead_secret_key(self.key_tracker.current_key.clone()) + .delete_aead_secret_key(&self.key_tracker.current_key) .await?; if let Some(previous_key) = &self.key_tracker.previous_key { - self.vault - .delete_aead_secret_key(previous_key.clone()) - .await?; + self.vault.delete_aead_secret_key(previous_key).await?; }; Ok(()) diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs index 8723cb896d3..de7dce64d44 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs @@ -27,7 +27,7 @@ impl Encryptor { if current_nonce.value() > 0 && current_nonce.value() % KEY_RENEWAL_INTERVAL == 0 { let new_key = self.vault.rekey(&self.key, 1).await?; let old_key = core::mem::replace(&mut self.key, new_key); - self.vault.delete_aead_secret_key(old_key).await?; + self.vault.delete_aead_secret_key(&old_key).await?; } payload[..NOISE_NONCE_LEN].copy_from_slice(¤t_nonce.to_noise_nonce()); @@ -59,7 +59,7 @@ impl Encryptor { #[instrument(skip_all)] pub(crate) async fn shutdown(&self) -> Result<()> { - if !self.vault.delete_aead_secret_key(self.key.clone()).await? { + if !self.vault.delete_aead_secret_key(&self.key).await? { Err(Error::new( Origin::Ockam, Kind::Internal, diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs index e7e65659417..5de490081de 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs @@ -4,7 +4,7 @@ use ockam_core::compat::sync::{Arc, RwLock}; use ockam_core::compat::vec::Vec; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{ - async_trait, cbor_encode_preallocate, route, CowBytes, Error, LocalMessage, MaybeZeroizeOnDrop, + async_trait, route, CowBytes, Decodable, Error, LocalMessage, MaybeZeroizeOnDrop, NeutralMessage, OnDrop, Route, }; use ockam_core::{Any, Result, Routed, Worker}; @@ -135,7 +135,7 @@ impl EncryptorWorker { let return_route = msg.return_route; // Decode raw payload binary - let request = minicbor::decode(msg.payload.as_slice())?; + let request = SecureChannelApiRequest::decode(&msg.payload)?; // If encryption fails, that means we have some internal error, // and we may be in an invalid state, it's better to stop the Worker @@ -145,12 +145,16 @@ impl EncryptorWorker { SecureChannelApiResponse::Ok(handle) } }; - let response = NeutralMessage::from(cbor_encode_preallocate(&response)?); // Send the reply to the caller ctx.send_from_address(return_route, response, self.addresses.encryptor_api.clone()) .await?; + // Avoid sending a Close message, to the other party can extract the key as wel + self.shared_state + .should_send_close + .store(false, Ordering::Relaxed); + // Once we have extracted the key, we can't use it anymore ctx.stop_worker(self.addresses.encryptor.clone()).await?; diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs index 1fd2a7757c9..80cf2f59e99 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs @@ -307,7 +307,7 @@ impl Handshake { let old_k = state.k.replace(new_k); if let Some(old_k) = old_k { - self.vault.delete_aead_secret_key(old_k).await?; + self.vault.delete_aead_secret_key(&old_k).await?; } state.n = 0; @@ -334,7 +334,7 @@ impl Handshake { let k2 = self.vault.convert_secret_buffer_to_aead_key(k2).await?; self.vault.delete_secret_buffer(state.take_ck()?).await?; - self.vault.delete_aead_secret_key(state.take_k()?).await?; + self.vault.delete_aead_secret_key(&state.take_k()?).await?; Ok((k1, k2)) } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channels/storage/secure_channel_repository.rs b/implementations/rust/ockam/ockam_identity/src/secure_channels/storage/secure_channel_repository.rs index ff8e6fbfa60..07928aa6a23 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channels/storage/secure_channel_repository.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channels/storage/secure_channel_repository.rs @@ -18,6 +18,8 @@ pub struct PersistedSecureChannel { } impl PersistedSecureChannel { + // TODO: remove when persistent secure channels are no longer required + #[allow(dead_code)] pub(crate) fn new( role: Role, my_identifier: Identifier, diff --git a/implementations/rust/ockam/ockam_identity/src/vault.rs b/implementations/rust/ockam/ockam_identity/src/vault.rs index c534dd5fc96..0392494a5a1 100644 --- a/implementations/rust/ockam/ockam_identity/src/vault.rs +++ b/implementations/rust/ockam/ockam_identity/src/vault.rs @@ -87,6 +87,7 @@ impl Vault { } /// Create [`SoftwareVaultForAtRestEncryption`] with an in-memory storage + #[cfg(feature = "storage")] pub async fn create_encryption_at_rest_vault() -> Result> { Ok(Arc::new(SoftwareVaultForAtRestEncryption::new(Arc::new( SecretsSqlxDatabase::create().await?, diff --git a/implementations/rust/ockam/ockam_node/src/context/send_message.rs b/implementations/rust/ockam/ockam_node/src/context/send_message.rs index f8f66733e5a..4457b17679e 100644 --- a/implementations/rust/ockam/ockam_node/src/context/send_message.rs +++ b/implementations/rust/ockam/ockam_node/src/context/send_message.rs @@ -159,7 +159,7 @@ impl Context { child_ctx.set_tracing_context(self.tracing_context()); child_ctx - .send_from_address_impl(route, msg, self.address(), vec![], options.on_drop) + .send_from_address_impl(route, msg, child_ctx.address(), vec![], options.on_drop) .await?; child_ctx .receive_extended::( diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/common.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/common.rs index 4c585c53d01..bed83547ac6 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/common.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/common.rs @@ -1,4 +1,5 @@ use crate::{AeadSecretKeyHandle, HandleToSecret, SecretBufferHandle}; +use alloc::vec; use ockam_core::compat::rand::{thread_rng, RngCore}; pub(super) fn generate_random_handle() -> HandleToSecret { diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/types.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/types.rs index 942640fc688..c40bb6e4f6d 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/types.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/types.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; +use minicbor::{CborLen, Decode, Encode}; use zeroize::{Zeroize, ZeroizeOnDrop}; use ockam_core::compat::vec::Vec; @@ -22,10 +23,10 @@ impl X25519SecretKey { } /// Buffer with sensitive data, like HKDF output. -#[derive(Eq, PartialEq, Clone, Zeroize, ZeroizeOnDrop)] -pub struct BufferSecret(Vec); +#[derive(Eq, PartialEq, Clone, Encode, Decode, CborLen, Zeroize, ZeroizeOnDrop)] +pub struct SecretBuffer(#[n(1)] Vec); -impl BufferSecret { +impl SecretBuffer { /// Constructor. pub fn new(data: Vec) -> Self { Self(data) diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_encryption_at_rest.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_encryption_at_rest.rs index 48046ed0b47..9ba9bb34b09 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_encryption_at_rest.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_encryption_at_rest.rs @@ -1,15 +1,18 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; use ockam_core::compat::collections::BTreeMap; use ockam_core::compat::rand::{thread_rng, RngCore}; use ockam_core::compat::sync::{Arc, RwLock}; -use ockam_core::compat::vec::Vec; use ockam_core::{async_trait, Result}; use super::make_aes; -use crate::storage::{SecretsRepository, SecretsSqlxDatabase}; +use crate::storage::SecretsRepository; +#[cfg(feature = "storage")] +use crate::storage::SecretsSqlxDatabase; use crate::software::vault_for_secure_channels::common::generate_aead_handle; use crate::{ - AeadSecret, AeadSecretKeyHandle, BufferSecret, VaultError, VaultForEncryptionAtRest, + AeadSecret, AeadSecretKeyHandle, SecretBuffer, VaultError, VaultForEncryptionAtRest, AEAD_SECRET_LENGTH, AES_GCM_TAGSIZE, AES_NONCE_LENGTH, }; @@ -18,6 +21,7 @@ pub struct SoftwareVaultForAtRestEncryption { ephemeral_aead_secrets: Arc>>, secrets_repository: Arc, } + #[async_trait] impl VaultForEncryptionAtRest for SoftwareVaultForAtRestEncryption { async fn aead_encrypt( @@ -63,7 +67,9 @@ impl VaultForEncryptionAtRest for SoftwareVaultForAtRestEncryption { ) -> Result { let secret = self.get_aead_secret(secret_key_handle).await?; let new_key_secret = self.rekey(secret, 1).await?; - let new_key_handle = self.import_aead_key(new_key_secret.0.to_vec()).await?; + let new_key_handle = self + .import_aead_key(SecretBuffer::new(new_key_secret.0.to_vec())) + .await?; self.secrets_repository .delete_aead_secret(secret_key_handle) .await?; @@ -74,9 +80,7 @@ impl VaultForEncryptionAtRest for SoftwareVaultForAtRestEncryption { Ok(new_key_handle) } - async fn import_aead_key(&self, secret: Vec) -> Result { - let secret = BufferSecret::new(secret); - + async fn import_aead_key(&self, secret: SecretBuffer) -> Result { if secret.data().len() != AEAD_SECRET_LENGTH { return Err(VaultError::InvalidSecretLength)?; } diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs index aca08877eba..f5aec44f2cc 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs @@ -1,3 +1,4 @@ +use alloc::boxed::Box; use sha2::{Digest, Sha256}; use tracing::instrument; @@ -18,15 +19,15 @@ use crate::software::vault_for_secure_channels::common::{ }; use crate::software::vault_for_secure_channels::types::AES_GCM_TAGSIZE; use crate::{ - AeadSecret, AeadSecretKeyHandle, BufferSecret, HKDFNumberOfOutputs, HandleToSecret, HashOutput, - HkdfOutput, SecretBufferHandle, SoftwareVaultForVerifyingSignatures, VaultError, + AeadSecret, AeadSecretKeyHandle, HKDFNumberOfOutputs, HandleToSecret, HashOutput, HkdfOutput, + SecretBuffer, SecretBufferHandle, SoftwareVaultForVerifyingSignatures, VaultError, VaultForSecureChannels, X25519PublicKey, X25519SecretKey, X25519SecretKeyHandle, - AEAD_SECRET_LENGTH, + AEAD_SECRET_LENGTH, AES_NONCE_LENGTH, }; /// [`SecureChannelVault`] implementation using software pub struct SoftwareVaultForSecureChannels { - ephemeral_buffer_secrets: Arc>>, + ephemeral_buffer_secrets: Arc>>, ephemeral_aead_secrets: Arc>>, ephemeral_x25519_secrets: Arc>>, secrets_repository: Arc, @@ -139,11 +140,11 @@ impl SoftwareVaultForSecureChannels { fn ecdh_internal( secret: X25519SecretKey, peer_public_key: X25519PublicKey, - ) -> Result { + ) -> Result { let peer_public_key = Self::import_x25519_public_key(peer_public_key); let secret_key = Self::import_x25519_secret_key(secret); let dh = secret_key.diffie_hellman(&peer_public_key); - Ok(BufferSecret::new(dh.as_bytes().to_vec())) + Ok(SecretBuffer::new(dh.as_bytes().to_vec())) } fn generate_x25519_secret() -> X25519SecretKey { @@ -152,7 +153,7 @@ impl SoftwareVaultForSecureChannels { X25519SecretKey::new(secret.to_bytes()) } - fn import_buffer_secret_impl(&self, secret: BufferSecret) -> SecretBufferHandle { + fn import_buffer_secret_impl(&self, secret: SecretBuffer) -> SecretBufferHandle { let handle = generate_buffer_handle(); self.ephemeral_buffer_secrets @@ -177,7 +178,7 @@ impl SoftwareVaultForSecureChannels { .ok_or_else(|| VaultError::KeyNotFound)?) } - async fn get_buffer_secret(&self, handle: &SecretBufferHandle) -> Result { + async fn get_buffer_secret(&self, handle: &SecretBufferHandle) -> Result { match self.ephemeral_buffer_secrets.read().unwrap().get(handle) { Some(secret) => Ok(secret.clone()), None => Err(VaultError::KeyNotFound)?, @@ -190,6 +191,31 @@ impl SoftwareVaultForSecureChannels { None => Err(VaultError::KeyNotFound)?, } } + + async fn raw_rekey(&self, secret_key_handle: &AeadSecretKeyHandle, n: u16) -> Result> { + if n == 0 { + return Err(VaultError::InvalidRekeyCount)?; + } + + const MAX_NONCE: [u8; AES_NONCE_LENGTH] = [ + 0x00, 0x00, 0x00, 0x00, // we only use 8 bytes of nonce + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // u64::MAX + ]; + + let mut new_key_buffer = vec![0u8; AEAD_SECRET_LENGTH + AES_GCM_TAGSIZE]; + let mut counter = n; + + while counter > 0 { + let secret = self.get_aead_secret(secret_key_handle).await?; + let aes = make_aes(&secret); + aes.encrypt_message(&mut new_key_buffer, &MAX_NONCE, &[])?; + + counter -= 1; + } + + new_key_buffer.truncate(AEAD_SECRET_LENGTH); + Ok(new_key_buffer) + } } #[async_trait] @@ -221,7 +247,7 @@ impl VaultForSecureChannels for SoftwareVaultForSecureChannels { let ikm = match input_key_material { Some(ikm) => self.get_buffer_secret(ikm).await?, - None => BufferSecret::new(vec![]), + None => SecretBuffer::new(vec![]), }; let salt = self.get_buffer_secret(salt).await?; @@ -252,7 +278,7 @@ impl VaultForSecureChannels for SoftwareVaultForSecureChannels { let output = chunks .into_iter() - .map(|chunk| self.import_buffer_secret_impl(BufferSecret::new(chunk.to_vec()))) + .map(|chunk| self.import_buffer_secret_impl(SecretBuffer::new(chunk.to_vec()))) .collect::>(); use crate::Sha256HkdfOutput; @@ -294,30 +320,18 @@ impl VaultForSecureChannels for SoftwareVaultForSecureChannels { secret_key_handle: &AeadSecretKeyHandle, n: u16, ) -> Result { - if n == 0 { - return Err(VaultError::InvalidRekeyCount)?; - } - - const MAX_NONCE: [u8; 12] = [ - 0x00, 0x00, 0x00, 0x00, // we only use 8 bytes of nonce - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // u64::MAX - ]; - - let mut new_key_buffer = vec![0u8; 32 + AES_GCM_TAGSIZE]; - let mut counter = n; - - while counter > 0 { - let secret = self.get_aead_secret(secret_key_handle).await?; - let aes = make_aes(&secret); - aes.encrypt_message(&mut new_key_buffer, &MAX_NONCE, &[])?; - - counter -= 1; - } - + let new_key_buffer = self.raw_rekey(secret_key_handle, n).await?; let buffer = self.import_secret_buffer(new_key_buffer).await?; self.convert_secret_buffer_to_aead_key(buffer).await } + #[instrument(skip_all)] + async fn export_rekey(&self, secret_key_handle: &AeadSecretKeyHandle) -> Result { + let buffer = SecretBuffer::new(self.raw_rekey(secret_key_handle, 1).await?); + self.delete_aead_secret_key(secret_key_handle).await?; + Ok(buffer) + } + #[instrument(skip_all)] async fn persist_aead_key(&self, secret_key_handle: &AeadSecretKeyHandle) -> Result<()> { let secret = self.get_aead_secret(secret_key_handle).await?; @@ -394,7 +408,7 @@ impl VaultForSecureChannels for SoftwareVaultForSecureChannels { } async fn import_secret_buffer(&self, buffer: Vec) -> Result { - Ok(self.import_buffer_secret_impl(BufferSecret::new(buffer))) + Ok(self.import_buffer_secret_impl(SecretBuffer::new(buffer))) } async fn delete_secret_buffer(&self, secret_buffer_handle: SecretBufferHandle) -> Result { @@ -440,12 +454,15 @@ impl VaultForSecureChannels for SoftwareVaultForSecureChannels { } #[instrument(skip_all)] - async fn delete_aead_secret_key(&self, secret_key_handle: AeadSecretKeyHandle) -> Result { + async fn delete_aead_secret_key( + &self, + secret_key_handle: &AeadSecretKeyHandle, + ) -> Result { Ok(self .ephemeral_aead_secrets .write() .unwrap() - .remove(&secret_key_handle) + .remove(secret_key_handle) .is_some()) } } diff --git a/implementations/rust/ockam/ockam_vault/src/traits/vault_for_at_rest_encryption.rs b/implementations/rust/ockam/ockam_vault/src/traits/vault_for_at_rest_encryption.rs index 3097dd2fbc6..5e1dee0f23f 100644 --- a/implementations/rust/ockam/ockam_vault/src/traits/vault_for_at_rest_encryption.rs +++ b/implementations/rust/ockam/ockam_vault/src/traits/vault_for_at_rest_encryption.rs @@ -1,5 +1,5 @@ -use crate::AeadSecretKeyHandle; - +use crate::{AeadSecretKeyHandle, SecretBuffer}; +use alloc::boxed::Box; use ockam_core::{async_trait, Result}; /// Vault for verifying signatures and computing SHA-256. @@ -34,5 +34,5 @@ pub trait VaultForEncryptionAtRest: Send + Sync + 'static { ) -> Result; /// Import an AES-GCM key and return a handle to it - async fn import_aead_key(&self, secret: Vec) -> Result; + async fn import_aead_key(&self, secret: SecretBuffer) -> Result; } diff --git a/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs b/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs index 76236478fc0..cfb026cb3ae 100644 --- a/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs +++ b/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs @@ -1,5 +1,5 @@ use crate::{ - AeadSecretKeyHandle, HashOutput, HkdfOutput, SecretBufferHandle, X25519PublicKey, + AeadSecretKeyHandle, HashOutput, HkdfOutput, SecretBuffer, SecretBufferHandle, X25519PublicKey, X25519SecretKeyHandle, }; use minicbor::{CborLen, Decode, Encode}; @@ -69,6 +69,9 @@ pub trait VaultForSecureChannels: Send + Sync + 'static { n: u16, ) -> Result; + /// Perform rekey, export the new key, and delete the previous key. + async fn export_rekey(&self, secret_key_handle: &AeadSecretKeyHandle) -> Result; + /// Persist an existing AEAD key. async fn persist_aead_key(&self, secret_key_handle: &AeadSecretKeyHandle) -> Result<()>; @@ -118,5 +121,6 @@ pub trait VaultForSecureChannels: Send + Sync + 'static { ) -> Result; /// Delete AEAD Key. - async fn delete_aead_secret_key(&self, secret_key_handle: AeadSecretKeyHandle) -> Result; + async fn delete_aead_secret_key(&self, secret_key_handle: &AeadSecretKeyHandle) + -> Result; } diff --git a/implementations/rust/ockam/ockam_vault/src/types/hashes.rs b/implementations/rust/ockam/ockam_vault/src/types/hashes.rs index fa83ffb9178..6503ed7a6e3 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/hashes.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/hashes.rs @@ -2,6 +2,7 @@ use crate::{HandleToSecret, SecretBufferHandle}; use cfg_if::cfg_if; use minicbor::{CborLen, Decode, Encode}; use ockam_core::compat::vec::Vec; +use serde::{Deserialize, Serialize}; /// SHA256 digest length pub const SHA256_LENGTH: usize = 32; @@ -11,7 +12,9 @@ pub const SHA256_LENGTH: usize = 32; pub struct Sha256Output(#[cbor(n(0), with = "minicbor::bytes")] pub [u8; SHA256_LENGTH]); /// Handle to an AES-256 Secret Key. -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +#[derive( + Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize, Encode, Decode, CborLen, +)] pub struct AeadSecretKeyHandle(#[n(0)] pub AeadSecretKeyHandleType); impl AeadSecretKeyHandle { @@ -41,7 +44,7 @@ cfg_if! { pub struct HkdfOutput(#[n(0)] pub Sha256HkdfOutput); /// Handle to an AES-256 Secret Key. - #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] + #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize, Encode, Decode, CborLen)] pub struct Aes256GcmSecretKeyHandle(#[n(0)] pub HandleToSecret); impl Aes256GcmSecretKeyHandle { diff --git a/implementations/rust/ockam/ockam_vault/src/types/secrets.rs b/implementations/rust/ockam/ockam_vault/src/types/secrets.rs index 492dd42eef7..3bbc4b5f427 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/secrets.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/secrets.rs @@ -1,10 +1,11 @@ use core::fmt::Debug; use minicbor::{CborLen, Decode, Encode}; use ockam_core::compat::vec::Vec; +use serde::{Deserialize, Serialize}; /// Implementation-specific arbitrary vector of bytes that allows a concrete Vault implementation /// to address a specific secret that it stores. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize, Encode, Decode, CborLen)] #[rustfmt::skip] pub struct HandleToSecret(#[cbor(n(0), with = "minicbor::bytes")] Vec);