Skip to content

Commit

Permalink
[PLATFORM-1854]: Fix encryption (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaeIsBad authored Jul 10, 2024
1 parent a93b186 commit f41bbb3
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 25 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ and this project adheres to

## [Unreleased]

### Security

- Switched to using XChaCha20Poly1305 for the redis token cache encryption.

This addresses a few medium severity security issues with the tokens

---

## [0.16.4] - 2024-07-04
Expand Down
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ rust-version = "1.72"
[features]
default = ["tracing_opentelemetry"]

auth0 = ["rand", "redis", "jsonwebtoken", "jwks_client_rs", "chrono", "aes", "cbc", "dashmap", "tracing"]
auth0 = ["rand", "redis", "jsonwebtoken", "jwks_client_rs", "chrono", "chacha20poly1305", "dashmap", "tracing"]
gzip = ["reqwest/gzip"]
redis-tls = ["redis/tls", "redis/tokio-native-tls-comp"]
tracing_opentelemetry = [ "tracing_opentelemetry_0_23" ]
Expand All @@ -26,10 +26,8 @@ tracing_opentelemetry_0_23 = ["_any_otel_version", "tracing", "tracing-opentelem
_any_otel_version = []

[dependencies]
aes = {version = "0.8", optional = true}
async-trait = "0.1"
bytes = "1.2"
cbc = {version = "0.1", features = ["std"], optional = true}
chrono = {version = "0.4", default-features = false, features = ["clock", "std", "serde"], optional = true}
dashmap = {version = "6.0", optional = true}
futures = "0.3"
Expand All @@ -45,6 +43,7 @@ thiserror = "1.0"
tokio = {version = "1.16", features = ["macros", "rt-multi-thread", "fs"]}
tracing = {version = "0.1", optional = true}
uuid = {version = ">=0.7.0, <2.0.0", features = ["serde", "v4"]}
chacha20poly1305 = { version = "0.10.1", features = ["std"], optional = true }

reqwest-middleware = { version = "0.3.0", features = ["json", "multipart"] }
http = "1.0.0"
Expand Down
38 changes: 20 additions & 18 deletions src/auth0/cache/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
use aes::{
cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit},
Aes256,
};
use cbc::{Decryptor, Encryptor};
use chacha20poly1305::{aead::Aead, AeadCore, KeyInit, XChaCha20Poly1305};
use rand::thread_rng;
use serde::{Deserialize, Serialize};

use crate::auth0::errors::Auth0Error;

type Aes256Enc = Encryptor<Aes256>;
type Aes256Dec = Decryptor<Aes256>;

const IV: &str = "301a9e39735f4646";
const NONCE_SIZE: usize = 24;

pub fn encrypt<T: Serialize>(value_ref: &T, token_encryption_key_str: &str) -> Result<Vec<u8>, Auth0Error> {
let json: String = serde_json::to_string(value_ref)?;

let ct = Aes256Enc::new(token_encryption_key_str.as_bytes().into(), IV.as_bytes().into())
.encrypt_padded_vec_mut::<Pkcs7>(json.as_bytes());
let enc = XChaCha20Poly1305::new_from_slice(token_encryption_key_str.as_bytes()).unwrap();
let nonce = XChaCha20Poly1305::generate_nonce(&mut thread_rng());

let mut ciphertext = enc.encrypt(&nonce, json.as_bytes())?;
ciphertext.extend(nonce);

Ok(ct.to_vec())
Ok(ciphertext)
}

pub fn decrypt<T>(token_encryption_key_str: &str, encrypted: &[u8]) -> Result<T, Auth0Error>
where
for<'de> T: Deserialize<'de>,
{
// `unwrap` here is fine because `IV` is set here and the only error returned is: `InvalidKeyIvLength`
// and this must never happen
let pt = Aes256Dec::new(token_encryption_key_str.as_bytes().into(), IV.as_bytes().into())
.decrypt_padded_vec_mut::<Pkcs7>(encrypted)
.unwrap();
let dec = XChaCha20Poly1305::new_from_slice(token_encryption_key_str.as_bytes()).unwrap();

let ciphertext = encrypted.get(..encrypted.len() - NONCE_SIZE);
let nonce = encrypted.get(encrypted.len() - NONCE_SIZE..);

let (Some(ciphertext), Some(nonce)) = (ciphertext, nonce) else {
return Err(Auth0Error::CryptoError(chacha20poly1305::Error));
};

Ok(serde_json::from_slice(&pt)?)
let nonce = chacha20poly1305::XNonce::from_slice(nonce);
let plaintext = dec.decrypt(nonce, ciphertext)?;
Ok(serde_json::from_slice(&plaintext)?)
}
2 changes: 1 addition & 1 deletion src/auth0/cache/redis_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl Cache for RedisCache {
let mut connection = self.client.get_async_connection().await?;
let encrypted_value: Vec<u8> = crypto::encrypt(value_ref, self.encryption_key.as_str())?;
let expiration: usize = value_ref.lifetime_in_seconds();
connection.set_ex(key, encrypted_value, expiration).await?;
let _: () = connection.set_ex(key, encrypted_value, expiration).await?;
Ok(())
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/auth0/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use aes::cipher::block_padding::UnpadError;
use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -18,5 +17,5 @@ pub enum Auth0Error {
#[error("redis error: {0}")]
RedisError(#[from] redis::RedisError),
#[error(transparent)]
CryptoError(#[from] UnpadError),
CryptoError(#[from] chacha20poly1305::Error),
}
2 changes: 1 addition & 1 deletion src/request/request_type/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<'a, Client: BridgeClient> GraphQLRequest<'a, Client> {
) -> PrimaBridgeResult<Self> {
// No content-type here because Form set it at `multipart/form-data` with extra params for
// disposition
let json_body = serde_json::to_value(&graphql_body.into())?;
let json_body = serde_json::to_value(graphql_body.into())?;
let body_with_injected_variables = match &multipart {
GraphQLMultipart::Single(single) => {
let path: VecDeque<&str> = single.path.split('.').collect();
Expand Down

0 comments on commit f41bbb3

Please sign in to comment.