From f41bbb3eec3173bf2745d80e76a01916e6ddeaa0 Mon Sep 17 00:00:00 2001 From: MaeIsBad <26093674+MaeIsBad@users.noreply.github.com> Date: Wed, 10 Jul 2024 09:43:58 +0200 Subject: [PATCH] [PLATFORM-1854]: Fix encryption (#164) --- CHANGELOG.md | 6 +++++ Cargo.toml | 5 ++-- src/auth0/cache/crypto.rs | 38 +++++++++++++++-------------- src/auth0/cache/redis_impl.rs | 2 +- src/auth0/errors.rs | 3 +-- src/request/request_type/graphql.rs | 2 +- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e43f2..c1ba319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index b77e158..486b479 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" ] @@ -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" @@ -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" diff --git a/src/auth0/cache/crypto.rs b/src/auth0/cache/crypto.rs index 796b4e3..343c4de 100644 --- a/src/auth0/cache/crypto.rs +++ b/src/auth0/cache/crypto.rs @@ -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; -type Aes256Dec = Decryptor; - -const IV: &str = "301a9e39735f4646"; +const NONCE_SIZE: usize = 24; pub fn encrypt(value_ref: &T, token_encryption_key_str: &str) -> Result, 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::(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(token_encryption_key_str: &str, encrypted: &[u8]) -> Result 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::(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)?) } diff --git a/src/auth0/cache/redis_impl.rs b/src/auth0/cache/redis_impl.rs index 3b13001..48640c1 100644 --- a/src/auth0/cache/redis_impl.rs +++ b/src/auth0/cache/redis_impl.rs @@ -53,7 +53,7 @@ impl Cache for RedisCache { let mut connection = self.client.get_async_connection().await?; let encrypted_value: Vec = 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(()) } } diff --git a/src/auth0/errors.rs b/src/auth0/errors.rs index 246f39c..7512734 100644 --- a/src/auth0/errors.rs +++ b/src/auth0/errors.rs @@ -1,4 +1,3 @@ -use aes::cipher::block_padding::UnpadError; use thiserror::Error; #[derive(Debug, Error)] @@ -18,5 +17,5 @@ pub enum Auth0Error { #[error("redis error: {0}")] RedisError(#[from] redis::RedisError), #[error(transparent)] - CryptoError(#[from] UnpadError), + CryptoError(#[from] chacha20poly1305::Error), } diff --git a/src/request/request_type/graphql.rs b/src/request/request_type/graphql.rs index 1ec6ce9..0652a5d 100644 --- a/src/request/request_type/graphql.rs +++ b/src/request/request_type/graphql.rs @@ -60,7 +60,7 @@ impl<'a, Client: BridgeClient> GraphQLRequest<'a, Client> { ) -> PrimaBridgeResult { // 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();