return Cipher from encrypt

This commit is contained in:
Vadim Anufriev 2024-06-09 14:16:12 +04:00
parent 4d81f6203e
commit 3c67b978a2
6 changed files with 131 additions and 25 deletions

View File

@ -77,6 +77,8 @@ state = "0.6"
chacha20poly1305 = "0.10.1"
hkdf = "0.12.4"
sha2 = "0.10.8"
base64 = "0.22.1"
hex = "0.4.3"
# tracing
tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"] }

View File

@ -138,4 +138,4 @@ mod secret_key;
pub use crate::shutdown::Sig;
#[cfg(feature = "secrets")]
pub use secret_key::SecretKey;
pub use secret_key::{SecretKey, Cipher};

View File

@ -7,12 +7,11 @@ use chacha20poly1305::{
use hkdf::Hkdf;
use sha2::Sha256;
use cookie::Key;
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use serde::{de, ser, Deserialize, Serialize};
use crate::request::{Outcome, Request, FromRequest};
const INFO_STRING: &[u8] = b"secret_key_data_encryption";
#[derive(Debug)]
pub enum Error {
KeyLengthError,
@ -20,6 +19,8 @@ pub enum Error {
EncryptionError,
DecryptionError,
EncryptedDataLengthError,
Base64DecodeError,
HexDecodeError,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -97,6 +98,38 @@ pub struct SecretKey {
provided: bool,
}
/// A struct representing encrypted data.
///
/// The `Cipher` struct encapsulates encrypted data and provides various
/// utility methods for encoding and decoding this data in different formats
/// such as bytes, hexadecimal, and base64.
///
/// # Examples
///
/// Creating a `Cipher` from bytes:
/// ```
/// let data = b"some encrypted data";
/// let cipher = Cipher::from_bytes(data);
/// ```
///
/// Converting a `Cipher` to a hexadecimal string:
/// ```
/// let hex = cipher.to_hex();
/// ```
///
/// Creating a `Cipher` from a base64 string:
/// ```
/// let base64_str = "c29tZSBlbmNyeXB0ZWQgZGF0YQ==";
/// let cipher = Cipher::from_base64(base64_str).unwrap();
/// ```
///
/// Converting a `Cipher` back to bytes:
/// ```
/// let bytes = cipher.as_bytes();
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cipher(Vec<u8>);
impl SecretKey {
/// Returns a secret key that is all zeroes.
pub(crate) fn zero() -> SecretKey {
@ -196,6 +229,13 @@ impl SecretKey {
ser.serialize_bytes(&[0; 32][..])
}
fn cipher(&self, nonce: &[u8]) -> Result<XChaCha20Poly1305, Error> {
let (mut prk, hk) = Hkdf::<Sha256>::extract(Some(nonce), self.key.encryption());
hk.expand(b"secret_key_data_encryption", &mut prk).map_err(|_| Error::KeyLengthError)?;
Ok(XChaCha20Poly1305::new(&prk))
}
/// Encrypts the given data.
/// Generates a random nonce for each encryption to ensure uniqueness.
/// Returns the Vec<u8> of the concatenated nonce and ciphertext.
@ -207,17 +247,14 @@ impl SecretKey {
/// let plaintext = "I like turtles".as_bytes();
/// let secret_key = SecretKey::generate().unwrap();
///
/// let encrypted = secret_key.encrypt(&plaintext).unwrap();
/// let decrypted = secret_key.decrypt(&encrypted).unwrap();
/// let cipher = secret_key.encrypt(&plaintext).unwrap();
/// let decrypted = secret_key.decrypt(&cipher).unwrap();
///
/// assert_eq!(decrypted, plaintext);
/// assert_eq!(plaintext, decrypted);
/// ```
pub fn encrypt<T: AsRef<[u8]>>(&self, value: T) -> Result<Vec<u8>, Error> {
pub fn encrypt<T: AsRef<[u8]>>(&self, value: T) -> Result<Cipher, Error> {
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
let (mut prk, hk) = Hkdf::<Sha256>::extract(Some(&nonce), self.key.encryption());
hk.expand(INFO_STRING, &mut prk).map_err(|_| Error::KeyLengthError)?;
let cipher = XChaCha20Poly1305::new(&prk);
let cipher = self.cipher(&nonce)?;
let ciphertext = cipher
.encrypt(&nonce, value.as_ref())
@ -228,7 +265,7 @@ impl SecretKey {
encrypted_data.extend_from_slice(nonce.as_slice());
encrypted_data.extend_from_slice(&ciphertext);
Ok(encrypted_data)
Ok(Cipher(encrypted_data))
}
/// Decrypts the given encrypted data.
@ -247,9 +284,7 @@ impl SecretKey {
let (nonce, ciphertext) = encrypted.split_at(nonce_len);
let nonce = XNonce::from_slice(nonce);
let (mut prk, hk) = Hkdf::<Sha256>::extract(Some(&nonce), self.key.encryption());
hk.expand(INFO_STRING, &mut prk).map_err(|_| Error::KeyLengthError)?;
let cipher = XChaCha20Poly1305::new(&prk);
let cipher = self.cipher(nonce)?;
// Decrypt the ciphertext using the nonce
let decrypted = cipher.decrypt(nonce, ciphertext)
@ -348,3 +383,59 @@ impl fmt::Debug for SecretKey {
<Self as fmt::Display>::fmt(self, f)
}
}
impl Cipher {
/// Create a `Cipher` from its raw bytes representation.
pub fn from_bytes(bytes: &[u8]) -> Self {
Cipher(bytes.to_vec())
}
/// Create a `Cipher` from a vector of bytes.
pub fn from_vec(vec: Vec<u8>) -> Self {
Cipher(vec)
}
/// Create a `Cipher` from a hex string.
pub fn from_hex(hex: &str) -> Result<Self, Error> {
let decoded = hex::decode(hex).map_err(|_| Error::HexDecodeError)?;
Ok(Cipher(decoded))
}
/// Create a `Cipher` from a base64 string.
pub fn from_base64(base64: &str) -> Result<Self, Error> {
let decoded = URL_SAFE.decode(base64).map_err(|_| Error::Base64DecodeError)?;
Ok(Cipher(decoded))
}
/// Returns the bytes contained in the `Cipher`.
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
/// Consumes the `Cipher` and returns the contained bytes as a vector.
pub fn into_vec(self) -> Vec<u8> {
self.0
}
/// Returns the hex representation of the bytes contained in the `Cipher`.
pub fn to_hex(&self) -> String {
hex::encode(&self.0)
}
/// Returns the base64 representation of the bytes contained in the `Cipher`.
pub fn to_base64(&self) -> String {
URL_SAFE.encode(&self.0)
}
}
impl fmt::Display for Cipher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_base64())
}
}
impl AsRef<[u8]> for Cipher {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}

View File

@ -3,7 +3,20 @@
#[cfg(test)]
mod cookies_private_tests {
use rocket::config::SecretKey;
use rocket::config::{SecretKey, Cipher};
#[test]
fn cipher_conversions() {
let secret_key = SecretKey::generate().unwrap();
let plaintext = "I like turtles";
let cipher = secret_key.encrypt(plaintext).unwrap();
assert_eq!(cipher, Cipher::from_bytes(&cipher.as_bytes()));
assert_eq!(cipher, Cipher::from_vec(cipher.clone().into_vec()));
assert_eq!(cipher, Cipher::from_hex(&cipher.to_hex()).unwrap());
assert_eq!(cipher, Cipher::from_base64(&cipher.to_base64()).unwrap());
}
#[test]
fn encrypt_decrypt() {

View File

@ -7,4 +7,3 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib", features = ["secrets"] }
base64 = "0.22.1"

View File

@ -1,11 +1,11 @@
#[macro_use]
extern crate rocket;
use rocket::config::Cipher;
use rocket::{Config, State};
use rocket::fairing::AdHoc;
use rocket::response::status;
use rocket::http::Status;
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
#[cfg(test)] mod tests;
@ -13,12 +13,13 @@ use base64::{engine::general_purpose::URL_SAFE, Engine as _};
fn encrypt_endpoint(msg: &str, config: &State<Config>) -> Result<String, status::Custom<String>> {
let secret_key = config.secret_key.clone();
let encrypted = secret_key.encrypt(msg).map_err(|_| {
let encrypted_msg = secret_key
.encrypt(msg)
.map(|cipher| cipher.to_base64())
.map_err(|_| {
status::Custom(Status::InternalServerError, "Failed to encrypt message".to_string())
})?;
let encrypted_msg = URL_SAFE.encode(&encrypted);
info!("received message for encrypt: '{}'", msg);
info!("encrypted msg: '{}'", encrypted_msg);
@ -29,11 +30,11 @@ fn encrypt_endpoint(msg: &str, config: &State<Config>) -> Result<String, status:
fn decrypt_endpoint(msg: &str, config: &State<Config>) -> Result<String, status::Custom<String>> {
let secret_key = config.secret_key.clone();
let decoded = URL_SAFE.decode(msg).map_err(|_| {
let cipher = Cipher::from_base64(msg).map_err(|_| {
status::Custom(Status::BadRequest, "Failed to decode base64".to_string())
})?;
let decrypted = secret_key.decrypt(&decoded).map_err(|_| {
let decrypted = secret_key.decrypt(&cipher).map_err(|_| {
status::Custom(Status::InternalServerError, "Failed to decrypt message".to_string())
})?;