mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-24 00:21:08 +01:00
Warn when an unsealed private cookie isn't valid UTF-8 (#746)
This commit is contained in:
parent
7596d0b7cb
commit
ddf5089bff
@ -1,3 +1,6 @@
|
|||||||
|
use std::str;
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM};
|
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM};
|
||||||
use ring::aead::{OpeningKey, SealingKey};
|
use ring::aead::{OpeningKey, SealingKey};
|
||||||
use ring::rand::{SecureRandom, SystemRandom};
|
use ring::rand::{SecureRandom, SystemRandom};
|
||||||
@ -57,9 +60,14 @@ impl<'a> PrivateJar<'a> {
|
|||||||
let unsealed = open_in_place(&key, nonce, ad, 0, sealed)
|
let unsealed = open_in_place(&key, nonce, ad, 0, sealed)
|
||||||
.map_err(|_| "invalid key/nonce/value: bad seal")?;
|
.map_err(|_| "invalid key/nonce/value: bad seal")?;
|
||||||
|
|
||||||
::std::str::from_utf8(unsealed)
|
if let Ok(unsealed_utf8) = str::from_utf8(unsealed) {
|
||||||
.map(|s| s.to_string())
|
Ok(unsealed_utf8.to_string())
|
||||||
.map_err(|_| "bad unsealed utf8")
|
} else {
|
||||||
|
warn!("Private cookie does not have utf8 content!
|
||||||
|
It is likely the secret key used to encrypt them has been leaked.
|
||||||
|
Please change it as soon as possible.");
|
||||||
|
Err("bad unsealed utf8")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the `Cookie` inside this jar with the name `name`
|
/// Returns a reference to the `Cookie` inside this jar with the name `name`
|
||||||
@ -147,34 +155,12 @@ impl<'a> PrivateJar<'a> {
|
|||||||
/// Encrypts the cookie's value with
|
/// Encrypts the cookie's value with
|
||||||
/// authenticated encryption assuring confidentiality, integrity, and authenticity.
|
/// authenticated encryption assuring confidentiality, integrity, and authenticity.
|
||||||
fn encrypt_cookie(&self, cookie: &mut Cookie) {
|
fn encrypt_cookie(&self, cookie: &mut Cookie) {
|
||||||
let mut data;
|
let name = cookie.name().as_bytes();
|
||||||
let output_len = {
|
let value = cookie.value().as_bytes();
|
||||||
// Create the `SealingKey` structure.
|
let data = encrypt_name_value(name, value, &self.key);
|
||||||
let key = SealingKey::new(ALGO, &self.key).expect("sealing key creation");
|
|
||||||
|
|
||||||
// Create a vec to hold the [nonce | cookie value | overhead].
|
|
||||||
let overhead = ALGO.tag_len();
|
|
||||||
let cookie_val = cookie.value().as_bytes();
|
|
||||||
data = vec![0; NONCE_LEN + cookie_val.len() + overhead];
|
|
||||||
|
|
||||||
// Randomly generate the nonce, then copy the cookie value as input.
|
|
||||||
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
|
|
||||||
SystemRandom::new()
|
|
||||||
.fill(nonce)
|
|
||||||
.expect("couldn't random fill nonce");
|
|
||||||
in_out[..cookie_val.len()].copy_from_slice(cookie_val);
|
|
||||||
let nonce = Nonce::try_assume_unique_for_key(nonce)
|
|
||||||
.expect("invalid length of `nonce`");
|
|
||||||
|
|
||||||
// Use cookie's name as associated data to prevent value swapping.
|
|
||||||
let ad = Aad::from(cookie.name().as_bytes());
|
|
||||||
|
|
||||||
// Perform the actual sealing operation and get the output length.
|
|
||||||
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Base64 encode the nonce and encrypted value.
|
// Base64 encode the nonce and encrypted value.
|
||||||
let sealed_value = base64::encode(&data[..(NONCE_LEN + output_len)]);
|
let sealed_value = base64::encode(&data);
|
||||||
cookie.set_value(sealed_value);
|
cookie.set_value(sealed_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,9 +192,38 @@ impl<'a> PrivateJar<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec<u8> {
|
||||||
|
// Create the `SealingKey` structure.
|
||||||
|
let key = SealingKey::new(ALGO, key).expect("sealing key creation");
|
||||||
|
|
||||||
|
// Create a vec to hold the [nonce | cookie value | overhead].
|
||||||
|
let overhead = ALGO.tag_len();
|
||||||
|
let mut data = vec![0; NONCE_LEN + value.len() + overhead];
|
||||||
|
|
||||||
|
// Randomly generate the nonce, then copy the cookie value as input.
|
||||||
|
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
|
||||||
|
SystemRandom::new()
|
||||||
|
.fill(nonce)
|
||||||
|
.expect("couldn't random fill nonce");
|
||||||
|
in_out[..value.len()].copy_from_slice(value);
|
||||||
|
let nonce = Nonce::try_assume_unique_for_key(nonce)
|
||||||
|
.expect("invalid length of `nonce`");
|
||||||
|
|
||||||
|
// Use cookie's name as associated data to prevent value swapping.
|
||||||
|
let ad = Aad::from(name);
|
||||||
|
|
||||||
|
// Perform the actual sealing operation and get the output length.
|
||||||
|
let output_len = seal_in_place(&key, nonce, ad, in_out, overhead)
|
||||||
|
.expect("in-place seal");
|
||||||
|
|
||||||
|
// Remove the overhead and return the sealed content.
|
||||||
|
data.truncate(NONCE_LEN + output_len);
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{Cookie, CookieJar, Key};
|
use super::{Cookie, CookieJar, Key, encrypt_name_value};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple() {
|
fn simple() {
|
||||||
@ -223,4 +238,27 @@ mod test {
|
|||||||
let mut jar = CookieJar::new();
|
let mut jar = CookieJar::new();
|
||||||
assert_secure_behaviour!(jar, jar.private(&key));
|
assert_secure_behaviour!(jar, jar.private(&key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_utf8() {
|
||||||
|
let key = Key::generate();
|
||||||
|
let mut jar = CookieJar::new();
|
||||||
|
|
||||||
|
let name = "malicious";
|
||||||
|
let mut assert_non_utf8 = |value: &[u8]| {
|
||||||
|
let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption());
|
||||||
|
let encoded = base64::encode(&sealed);
|
||||||
|
assert_eq!(jar.private(&key).unseal(name, &encoded), Err("bad unsealed utf8"));
|
||||||
|
jar.add(Cookie::new(name, encoded));
|
||||||
|
assert_eq!(jar.private(&key).get(name), None);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1
|
||||||
|
|
||||||
|
let mut malicious = String::from(r#"{"id":"abc123??%X","admin":true}"#)
|
||||||
|
.into_bytes();
|
||||||
|
malicious[8] |= 0b1100_0000;
|
||||||
|
malicious[9] |= 0b1100_0000;
|
||||||
|
assert_non_utf8(&malicious);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user