Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add crypto module #175

Merged
merged 16 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ license = "MIT"
authors = ["PubNub <[email protected]>"]
description = "PubNub SDK for Rust"
repository = "https://github.com/pubnub/rust"
documentation = "https://docs.rs/pubnub/latest/pubnub"
documentation = "https://docs.rs/pubnub/latest/pubnub"
homepage = "https://www.pubnub.com"
categories = ["api-bindings", "asynchronous", "network-programming", "wasm"]
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", "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 All @@ -48,7 +48,7 @@ tokio = ["dep:tokio"]
blocking = ["reqwest?/blocking"]

## Enables std library
std = ["derive_builder/std", "log/std", "uuid/std", "base64/std", "spin/std", "snafu/std", "hmac/std", "sha2/std", "time/std", "bytes?/std", "getrandom/std", "rand/default", "serde?/std", "serde_json?/std", "ciborium?/std", "futures?/std", "futures?/async-await", "dep:async-channel"]
std = ["derive_builder/std", "log/std", "uuid/std", "base64/std", "spin/std", "snafu/std", "hmac/std", "sha2/std", "time/std", "bytes?/std", "getrandom/std", "rand/default", "serde?/std", "serde_json?/std", "ciborium?/std", "futures?/std", "futures?/async-await", "dep:async-channel"]

## Enables very specific implementations for different platforms.
##
Expand All @@ -60,13 +60,13 @@ std = ["derive_builder/std", "log/std", "uuid/std", "base64/std", "spin/std", "s
## https://docs.rs/portable_atomic
## and
## https://docs.rs/critical-section/latest/critical_section/
extra_platforms = ["spin/portable_atomic", "dep:portable-atomic"]
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"]
contract_test = ["parse_token", "publish", "access", "crypto"]
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 Expand Up @@ -194,4 +198,3 @@ required-features = ["default", "presence"]
[[example]]
name = "presence_state_blocking"
required-features = ["default", "blocking", "presence"]

75 changes: 75 additions & 0 deletions examples/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use pubnub::core::CryptoProvider;
use pubnub::providers::crypto::CryptoModule;
use pubnub::{Keyset, PubNubClientBuilder};
use std::env;

Xavrax marked this conversation as resolved.
Show resolved Hide resolved
/// This example demonstrates how data can be manually encrypted or
/// automatically as part of PubNubClient instance when publish / subscribe is
/// used.
///
/// The following example consists of two parts: a manual _encryption_ and
/// _decryption_ demonstration, and an automated _encryption_ and _decryption_
/// demonstration, as part of a configured `PubNubClientInstance`.

#[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";

// -----------------------------------------
// Manual encryption and decryption example.
//

// 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);

// --------------------------------------------
// Automated encryption and decryption example.
//

// 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 encrypted 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 {
Xavrax marked this conversation as resolved.
Show resolved Hide resolved
/// 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 {
Xavrax marked this conversation as resolved.
Show resolved Hide resolved
/// 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
15 changes: 9 additions & 6 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 Expand Up @@ -401,7 +401,10 @@ mod should {
use crate::{
core::TransportResponse,
dx::pubnub_client::{PubNubClientInstance, PubNubClientRef, PubNubConfig},
lib::alloc::{sync::Arc, vec},
lib::{
alloc::{sync::Arc, vec},
collections::HashMap,
},
transport::middleware::PubNubMiddleware,
Keyset, PubNubClientBuilder,
};
Expand Down Expand Up @@ -559,7 +562,7 @@ mod should {
fn test_send_map_when_get() {
let client = client();
let channel = String::from("ch");
let message = HashMap::from([("a", "b")]);
let message: HashMap<&str, &str> = HashMap::from([("a", "b")]);

let result = client
.publish_message(message)
Expand Down Expand Up @@ -605,7 +608,7 @@ mod should {
fn test_path_segments_get() {
let client = client();
let channel = String::from("channel_name");
let message = HashMap::from([("number", 7)]);
let message: HashMap<&str, u8> = HashMap::from([("number", 7)]);

let result = client
.publish_message(message)
Expand All @@ -630,7 +633,7 @@ mod should {
fn test_path_segments_post() {
let client = client();
let channel = String::from("channel_name");
let message = HashMap::from([("number", 7)]);
let message: HashMap<&str, u8> = HashMap::from([("number", 7)]);

let result = client
.publish_message(message)
Expand Down
Loading