Skip to content

Commit

Permalink
feat(crypto): add crypto module
Browse files Browse the repository at this point in the history
Update crypto module structure and add enhanced AES-CBC cryptor.
  • Loading branch information
parfeon committed Sep 21, 2023
1 parent d917aec commit 12511b0
Show file tree
Hide file tree
Showing 24 changed files with 1,582 additions and 185 deletions.
18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ build = "build.rs"
[features]

# Enables all non-conflicting features
full = ["publish", "subscribe", "presence", "access", "serde", "reqwest", "aescbc", "parse_token", "blocking", "std", "tokio"]
full = ["publish", "subscribe", "presence", "access", "serde", "reqwest", "crypto", "parse_token", "blocking", "std", "tokio"]

# Enables all default features
default = ["publish", "subscribe", "serde", "reqwest", "aescbc", "std", "blocking", "tokio"]
default = ["publish", "subscribe", "serde", "reqwest", "crypto", "std", "blocking", "tokio"]

# [PubNub features]

Expand All @@ -27,8 +27,8 @@ publish = []
## Enables access manager feature
access = []

## Enables AES-CBC encryption
aescbc = ["dep:aes", "dep:cbc", "getrandom"]
## Enables crypto module
crypto = ["dep:aes", "dep:cbc", "getrandom"]

## Enables token parsing
parse_token = ["dep:ciborium"]
Expand Down Expand Up @@ -64,9 +64,9 @@ extra_platforms = ["spin/portable_atomic", "dep:portable-atomic"]

# [Internal features] (not intended for use outside of the library)
contract_test = ["parse_token", "publish", "access"]
full_no_std = ["serde", "reqwest", "aescbc", "parse_token", "blocking", "publish", "access", "subscribe", "tokio", "presence"]
full_no_std_platform_independent = ["serde", "aescbc", "parse_token", "blocking", "publish", "access", "subscribe", "presence"]
pubnub_only = ["aescbc", "parse_token", "blocking", "publish", "access", "subscribe", "presence"]
full_no_std = ["serde", "reqwest", "crypto", "parse_token", "blocking", "publish", "access", "subscribe", "tokio", "presence"]
full_no_std_platform_independent = ["serde", "crypto", "parse_token", "blocking", "publish", "access", "subscribe", "presence"]
pubnub_only = ["crypto", "parse_token", "blocking", "publish", "access", "subscribe", "presence"]
mock_getrandom = ["getrandom/custom"]
# TODO: temporary treated as internal until we officially release it
subscribe = ["dep:futures"]
Expand Down Expand Up @@ -139,6 +139,10 @@ name = "contract_test"
harness = false
required-features = ["contract_test"]

[[example]]
name = "crypto"
required-features = ["default", "crypto"]

[[example]]
name = "publish"
required-features = ["default"]
Expand Down
59 changes: 59 additions & 0 deletions examples/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use pubnub::core::CryptoProvider;
use pubnub::providers::crypto::CryptoModule;
use pubnub::{Keyset, PubNubClientBuilder};
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn snafu::Error>> {
let source_data: Vec<u8> = "Hello world!".into();
let use_random_iv = true;
let cipher = "enigma";

// Crypto module with legacy AES-CBC cryptor (with enhanced AES-CBC decrypt
// support).
let legacy_crypto_module = CryptoModule::new_legacy_module(cipher, use_random_iv)?;
let legacy_encrypt_result = legacy_crypto_module.encrypt(source_data.clone());

println!("encrypt with legacy AES-CBC result: {legacy_encrypt_result:?}");

// Crypto module with enhanced AES-CBC cryptor (with legacy AES-CBC decrypt
// support).
let crypto_module = CryptoModule::new_aes_cbc_module(cipher, use_random_iv)?;
let encrypt_result = crypto_module.encrypt(source_data.clone());

println!("encrypt with enhanced AES-CBC result: {encrypt_result:?}");

// Decrypt data created with legacy AES-CBC crypto module.
let legacy_decrypt_result = crypto_module.decrypt(legacy_encrypt_result.ok().unwrap())?;
assert_eq!(legacy_decrypt_result, source_data);

// Decrypt data created with enhanced AES-CBC crypto module.
let decrypt_result = legacy_crypto_module.decrypt(encrypt_result.ok().unwrap())?;
assert_eq!(decrypt_result, source_data);

// Setup client with crypto module
let publish_key = env::var("SDK_PUB_KEY")?;
let subscribe_key = env::var("SDK_SUB_KEY")?;

let client = PubNubClientBuilder::with_reqwest_transport()
.with_keyset(Keyset {
subscribe_key,
publish_key: Some(publish_key),
secret_key: None,
})
.with_user_id("user_id")
.with_cryptor(crypto_module)
.build()?;

// publish simple string
let result = client
.publish_message("hello world!")
.channel("my_channel")
.r#type("text-message")
.execute()
.await?;

println!("publish result: {:?}", result);

Ok(())
}
27 changes: 27 additions & 0 deletions src/core/crypto_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! # Crypto provider module
//!
//! This module contains the [`CryptoProvider`] trait, which is used to
//! implement a module that can be used to configure [`PubNubClientInstance`] or
//! for manual data encryption and decryption.
use crate::{
core::PubNubError,
lib::{alloc::vec::Vec, core::fmt::Debug},
};

/// Crypto provider trait.
pub trait CryptoProvider: Debug + Send + Sync {
/// Encrypt provided data.
///
/// # Errors
/// Should return an [`PubNubError::Encryption`] if provided data can't be
/// _encrypted_ or underlying cryptor misconfigured.
fn encrypt(&self, data: Vec<u8>) -> Result<Vec<u8>, PubNubError>;

/// Decrypt provided data.
///
/// # Errors
/// Should return an [`PubNubError::Decryption`] if provided data can't be
/// _decrypted_ or underlying cryptor misconfigured.
fn decrypt(&self, data: Vec<u8>) -> Result<Vec<u8>, PubNubError>;
}
93 changes: 66 additions & 27 deletions src/core/cryptor.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,98 @@
//! Cryptor module
//! # Cryptor module
//!
//! This module contains the [`Cryptor`] trait which is used to implement
//! encryption and decryption of published data.
//! This module contains the [`Cryptor`] trait, which is used to implement
//! crypto algorithms that should be used with [`CryptorProvider`]
//! implementation for data _encryption_ and _decryption_.
use crate::core::error::PubNubError;
use crate::lib::{alloc::vec::Vec, core::fmt::Debug};
use crate::{
core::PubNubError,
lib::{alloc::vec::Vec, core::fmt::Debug},
};

/// This trait is used to encrypt and decrypt messages sent to the
/// [`PubNub API`].
/// Encrypted data representation object.
///
/// It is used by the [`dx`] modules to encrypt messages sent to PubNub and
/// returned by the [`PubNub API`].
/// Objects contain both encrypted data and additional data created by cryptor
/// that will be required to decrypt the data.
#[derive(Debug)]
pub struct EncryptedData {
/// Cryptor-defined information.
///
/// Cryptor may provide here any information which will be useful when data
/// should be decrypted.
///
/// For example `metadata` may contain:
/// * initialization vector
/// * cipher key identifier
/// * encrypted `data` length.
pub metadata: Option<Vec<u8>>,

/// Encrypted data.
pub data: Vec<u8>,
}

/// Cryptor trait.
///
/// Types that implement this trait can be used to configure [`CryptoProvider`]
/// implementations for standalone usage or as part of [`PubNubClientInstance`]
/// for automated data _encryption_ and _decryption_.
///
/// To implement this trait, you must provide `encrypt` and `decrypt` methods
/// that takes a `&[u8]` and returns a `Result<Vec<u8>, PubNubError>`.
/// that takes a `Vec<u8>` and returns a `Result<EncryptedData, PubNubError>`.
///
/// You can implement this trait for your own types, or use one of the provided
/// features to use a crypto library.
/// When you use this trait to make your own crypto, make sure that other SDKs
/// use the same encryption and decryption algorithms.
/// features to use a `crypto` library.
///
/// You can implement this trait for your cryptor types, or use one of the
/// implementations provided by `crypto` feature.
/// When you implement your cryptor for custom encryption and use multiple
/// platforms, make sure that the same logic is implemented for other SDKs.
///
/// # Examples
/// ```
/// use pubnub::core::{Cryptor, error::PubNubError};
/// use pubnub::core::{Cryptor, EncryptedData, error::PubNubError};
///
/// #[derive(Debug)]
/// struct MyCryptor;
///
/// impl Cryptor for MyCryptor {
/// fn encrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError> {
/// fn identifier(&self) -> [u8; 4] {
/// *b"MCID"
/// }
///
/// fn encrypt(&self, source: Vec<u8>) -> Result<EncryptedData, PubNubError> {
/// // Encrypt provided data here
/// Ok(vec![])
/// Ok(EncryptedData {
/// metadata: None,
/// data: vec![]
/// })
/// }
///
/// fn decrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError> {
/// fn decrypt(&self, source: EncryptedData) -> Result<Vec<u8>, PubNubError> {
/// // Decrypt provided data here
/// Ok(vec![])
/// }
/// }
/// ```
///
/// [`dx`]: ../dx/index.html
/// [`PubNub API`]: https://www.pubnub.com/docs
pub trait Cryptor: Debug + Send + Sync {
/// Decrypt provided data.
/// Unique cryptor identifier.
///
/// Identifier will be encoded into cryptor data header and passed along
/// with encrypted data.
///
/// The identifier **must** be 4 bytes long.
fn identifier(&self) -> [u8; 4];

/// Encrypt provided data.
///
/// # Errors
/// Should return an [`PubNubError::Encryption`] if provided data can't
/// be encrypted or underlying cryptor misconfigured.
fn encrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError>;
/// Should return an [`PubNubError::Encryption`] if provided data can't be
/// _encrypted_ or underlying cryptor misconfigured.
fn encrypt(&self, data: Vec<u8>) -> Result<EncryptedData, PubNubError>;

/// Decrypt provided data.
///
/// # Errors
/// Should return an [`PubNubError::Decryption`] if provided data can't
/// be decrypted or underlying cryptor misconfigured.
fn decrypt(&self, source: Vec<u8>) -> Result<Vec<u8>, PubNubError>;
/// Should return an [`PubNubError::Decryption`] if provided data can't be
/// _decrypted_ or underlying cryptor misconfigured.
fn decrypt(&self, data: EncryptedData) -> Result<Vec<u8>, PubNubError>;
}
9 changes: 8 additions & 1 deletion src/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub enum PubNubError {
},

/// this error is returned when the initialization of the cryptor fails
#[snafu(display("Cryptor initialization error: {details}"))]
#[snafu(display("Crypto initialization error: {details}"))]
CryptoInitialization {
///docs
details: String,
Expand All @@ -107,6 +107,13 @@ pub enum PubNubError {
details: String,
},

/// this error returned when suitable cryptor not found for data decryption.
#[snafu(display("Unknown cryptor error: {details}"))]
UnknownCryptor {
/// docs
details: String,
},

/// this error is returned when the event engine effect is canceled
#[snafu(display("Event engine effect has been canceled"))]
EffectCanceled,
Expand Down
6 changes: 5 additions & 1 deletion src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ pub use serialize::Serialize;
pub mod serialize;

#[doc(inline)]
pub use cryptor::Cryptor;
pub use crypto_provider::CryptoProvider;
pub mod crypto_provider;

#[doc(inline)]
pub use cryptor::{Cryptor, EncryptedData};
pub mod cryptor;

#[cfg(all(feature = "std", feature = "subscribe"))]
Expand Down
4 changes: 2 additions & 2 deletions src/dx/publish/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{
encoding::{url_encode, url_encode_extended, UrlEncodeExtension},
headers::{APPLICATION_JSON, CONTENT_TYPE},
},
Cryptor, Deserializer, PubNubError, Serialize, Transport, TransportMethod,
CryptoProvider, Deserializer, PubNubError, Serialize, Transport, TransportMethod,
TransportRequest,
},
dx::pubnub_client::{PubNubClientInstance, PubNubConfig},
Expand Down Expand Up @@ -266,7 +266,7 @@ where
fn create_transport_request(
self,
config: &PubNubConfig,
cryptor: &Option<Arc<dyn Cryptor + Send + Sync>>,
cryptor: &Option<Arc<dyn CryptoProvider + Send + Sync>>,
) -> Result<TransportRequest, PubNubError> {
let query_params = self.prepare_publish_query_params();

Expand Down
17 changes: 12 additions & 5 deletions src/dx/pubnub_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::transport::TransportReqwest;
use crate::core::RequestRetryPolicy;

use crate::{
core::{Cryptor, PubNubError},
core::{CryptoProvider, PubNubError},
lib::{
alloc::{
string::{String, ToString},
Expand Down Expand Up @@ -277,7 +277,7 @@ pub struct PubNubClientRef<T, D> {
field(vis = "pub(crate)"),
default = "None"
)]
pub(crate) cryptor: Option<Arc<dyn Cryptor + Send + Sync>>,
pub(crate) cryptor: Option<Arc<dyn CryptoProvider + Send + Sync>>,

/// Instance ID
#[builder(
Expand Down Expand Up @@ -450,15 +450,15 @@ impl<T, D> PubNubClientConfigBuilder<T, D> {

/// Data encryption / decryption
///
/// Cryptor used by client when publish messages / signals and receive them
/// as real-time updates from subscription module.
/// Crypto module used by client when publish messages / signals and receive
/// them as real-time updates from subscription module.
///
/// It returns [`PubNubClientConfigBuilder`] that you can use to set the
/// configuration for the client. This is a part of the
/// [`PubNubClientConfigBuilder`].
pub fn with_cryptor<C>(mut self, cryptor: C) -> Self
where
C: Cryptor + Send + Sync + 'static,
C: CryptoProvider + Send + Sync + 'static,
{
self.cryptor = Some(Some(Arc::new(cryptor)));

Expand Down Expand Up @@ -1157,6 +1157,7 @@ impl<T> PubNubClientDeserializerBuilder<T> {
transport: self.transport,
deserializer: DeserializerSerde,
keyset,

#[cfg(all(any(feature = "subscribe", feature = "presence"), feature = "std"))]
runtime: self.runtime,
}
Expand Down Expand Up @@ -1196,6 +1197,7 @@ where
transport: T,
deserializer: D,
keyset: Keyset<S>,

#[cfg(all(any(feature = "subscribe", feature = "presence"), feature = "std"))]
runtime: RuntimeSupport,
}
Expand Down Expand Up @@ -1225,15 +1227,20 @@ where
secret_key,
user_id: Arc::new(user_id.into()),
auth_key: None,

#[cfg(feature = "std")]
retry_policy: Default::default(),

#[cfg(any(feature = "subscribe", feature = "presence"))]
heartbeat_value: 300,

#[cfg(any(feature = "subscribe", feature = "presence"))]
heartbeat_interval: None,
}),

#[cfg(all(any(feature = "subscribe", feature = "presence"), feature = "std"))]
runtime: Some(self.runtime),

deserializer: Some(Arc::new(self.deserializer)),
..Default::default()
}
Expand Down
Loading

0 comments on commit 12511b0

Please sign in to comment.