mirror of https://github.com/rwf2/Rocket.git
return Cipher from encrypt
This commit is contained in:
parent
4d81f6203e
commit
3c67b978a2
|
@ -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"] }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -7,4 +7,3 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||
base64 = "0.22.1"
|
||||
|
|
|
@ -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,11 +13,12 @@ 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(|_| {
|
||||
status::Custom(Status::InternalServerError, "Failed to encrypt message".to_string())
|
||||
})?;
|
||||
|
||||
let encrypted_msg = URL_SAFE.encode(&encrypted);
|
||||
let encrypted_msg = secret_key
|
||||
.encrypt(msg)
|
||||
.map(|cipher| cipher.to_base64())
|
||||
.map_err(|_| {
|
||||
status::Custom(Status::InternalServerError, "Failed to encrypt message".to_string())
|
||||
})?;
|
||||
|
||||
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())
|
||||
})?;
|
||||
|
||||
|
|
Loading…
Reference in New Issue