use crate::{Aad, Cipher, Decipher, Decrypt, Encrypt, IntoAad};
fn fold_tag_aad<'a, Tag, A>(tag: Tag, extra_aad: A) -> (A, Aad<'a>)
where
Tag: IntoAad<'static>,
{
let tag_aad: Aad<'static> = tag.into_aad();
let tag_aad: Aad<'a> = tag_aad;
(extra_aad, tag_aad)
}
pub struct ContextTag<Tag, T> {
inner: T,
tag: Tag,
}
impl<Tag, T> ContextTag<Tag, T> {
pub fn new(inner: T, tag: Tag) -> Self {
ContextTag { inner, tag }
}
pub fn refine<B>(self, tag: B) -> ContextTag<(Tag, B), T> {
ContextTag {
inner: self.inner,
tag: (self.tag, tag),
}
}
pub fn into_parts(self) -> (T, Tag) {
(self.inner, self.tag)
}
}
impl<Tag> ContextTag<Tag, ()> {
pub fn aad(tag: Tag) -> (Aad<'static>, Tag) {
(Aad::empty(), tag)
}
pub fn aad_with<A>(extra_aad: A, tag: Tag) -> (A, Tag) {
(extra_aad, tag)
}
pub fn context(tag: Tag) -> Self {
ContextTag { inner: (), tag }
}
}
impl<Tag> ContextTag<Tag, ()>
where
Tag: IntoAad<'static>,
{
pub fn decrypt<'c, T, D>(self, decipher: D) -> D::Ok<T>
where
D: Decipher<'c>,
T: Decrypt<'c> + 'c,
{
self.decrypt_with_aad(decipher, Aad::empty())
}
pub fn decrypt_with_aad<'c, 'a, T, D, A>(self, decipher: D, extra_aad: A) -> D::Ok<T>
where
D: Decipher<'c>,
T: Decrypt<'c> + 'c,
A: IntoAad<'a>,
{
T::decrypt_with_aad(decipher, fold_tag_aad(self.tag, extra_aad))
}
}
impl<Tag, T> Encrypt for ContextTag<Tag, T>
where
T: Encrypt,
Tag: IntoAad<'static>,
{
fn encrypt_with_aad<'a, C, A>(self, cipher: C, extra_aad: A) -> Result<C::Ok, C::Error>
where
C: Cipher,
A: IntoAad<'a>,
{
let ContextTag { inner, tag } = self;
inner.encrypt_with_aad(cipher, fold_tag_aad(tag, extra_aad))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::MockCipher;
use crate::DecipherVisitor;
use std::cell::RefCell;
use std::rc::Rc;
#[test]
fn encrypts_inner_value_and_binds_tag() {
let plaintext = "hello world";
let cipher = MockCipher::new();
let ciphertext = ContextTag::new(plaintext, "tag_aad")
.encrypt_with_aad(&cipher, "extra")
.expect("encryption should succeed");
assert_eq!(ciphertext, plaintext.as_bytes());
let expected = Aad::pae(&[b"extra", b"tag_aad"]);
assert_eq!(cipher.captured_aad(), expected.as_bytes());
}
#[test]
fn encrypt_with_no_extra_aad_still_binds_tag() {
let cipher = MockCipher::new();
ContextTag::new("secret", "user:42")
.encrypt(&cipher)
.expect("encryption should succeed");
let expected = ((), "user:42").into_aad();
assert_eq!(cipher.captured_aad(), expected.as_bytes());
}
#[test]
fn unit_tag_still_binds_nonempty_aad() {
let cipher = MockCipher::new();
ContextTag::new("secret", ())
.encrypt(&cipher)
.expect("encryption should succeed");
assert_eq!(cipher.captured_aad(), ((), ()).into_aad().as_bytes());
assert_ne!(cipher.captured_aad(), Aad::empty().as_bytes());
}
#[test]
fn refine_nests_the_tag() {
let cipher = MockCipher::new();
ContextTag::new("secret", "table:users")
.refine("column:email")
.encrypt_with_aad(&cipher, "extra")
.expect("encryption should succeed");
let inner = Aad::pae(&[b"table:users", b"column:email"]);
let expected = Aad::pae(&[b"extra", inner.as_bytes()]);
assert_eq!(cipher.captured_aad(), expected.as_bytes());
}
#[test]
fn chained_refine_builds_left_nested_tuple() {
let cipher = MockCipher::new();
ContextTag::new("data", "a")
.refine("b")
.refine("c")
.encrypt(&cipher)
.expect("encryption should succeed");
let expected = ((), (("a", "b"), "c")).into_aad();
assert_eq!(cipher.captured_aad(), expected.as_bytes());
}
#[test]
fn nested_context_tag_folds_aad_twice() {
let cipher = MockCipher::new();
ContextTag::new(ContextTag::new("secret", "b"), "a")
.encrypt(&cipher)
.expect("encryption should succeed");
assert_eq!(
cipher.captured_aad(),
(((), "a"), "b").into_aad().as_bytes()
);
assert_ne!(
cipher.captured_aad(),
((), ("a", "b")).into_aad().as_bytes()
);
}
#[test]
fn owned_string_tag_is_accepted() {
let cipher = MockCipher::new();
ContextTag::new("secret", String::from("owned-tag"))
.encrypt(&cipher)
.expect("encryption should succeed");
let expected = ((), "owned-tag").into_aad();
assert_eq!(cipher.captured_aad(), expected.as_bytes());
}
#[test]
fn vec_u8_tag_is_accepted() {
let cipher = MockCipher::new();
ContextTag::new("secret", vec![0xde_u8, 0xad, 0xbe, 0xef])
.encrypt(&cipher)
.expect("encryption should succeed");
let expected = ((), vec![0xde_u8, 0xad, 0xbe, 0xef]).into_aad();
assert_eq!(cipher.captured_aad(), expected.as_bytes());
}
#[test]
fn aad_helper_matches_encrypt_binding() {
let cipher = MockCipher::new();
ContextTag::new("secret", "user:42")
.encrypt(&cipher)
.expect("encryption should succeed");
let decrypt_aad = ContextTag::aad("user:42").into_aad();
assert_eq!(cipher.captured_aad(), decrypt_aad.as_bytes());
}
#[test]
fn aad_with_helper_matches_encrypt_binding() {
let cipher = MockCipher::new();
ContextTag::new("secret", "table:users")
.encrypt_with_aad(&cipher, "row:99")
.expect("encryption should succeed");
let decrypt_aad = ContextTag::aad_with("row:99", "table:users").into_aad();
assert_eq!(cipher.captured_aad(), decrypt_aad.as_bytes());
}
#[test]
fn different_tags_produce_different_aad() {
let cipher_a = MockCipher::new();
let cipher_b = MockCipher::new();
ContextTag::new("secret", "user:42")
.encrypt(&cipher_a)
.expect("encryption should succeed");
ContextTag::new("secret", "user:99")
.encrypt(&cipher_b)
.expect("encryption should succeed");
assert_ne!(cipher_a.captured_aad(), cipher_b.captured_aad());
}
#[test]
fn into_parts_round_trips() {
let tagged = ContextTag::new("secret", "ctx");
let (inner, tag) = tagged.into_parts();
assert_eq!(inner, "secret");
assert_eq!(tag, "ctx");
}
struct CapturingDecipher {
captured_aad: Rc<RefCell<Vec<u8>>>,
}
impl<'c> Decipher<'c> for CapturingDecipher {
type Ok<T>
= Option<T>
where
T: Send + 'c;
type Passthrough = ();
fn map_ok<T, U, F>(ok: Self::Ok<T>, f: F) -> Self::Ok<U>
where
T: Send + 'c,
U: Send + 'c,
F: FnOnce(T) -> U,
{
ok.map(f)
}
fn decrypt_bytes<'a, V, A>(self, _visitor: V, aad: A) -> Self::Ok<V::Value>
where
V: DecipherVisitor<'c> + Send + 'c,
A: IntoAad<'a>,
{
*self.captured_aad.borrow_mut() = aad.into_aad().as_bytes().to_vec();
None
}
fn decrypt_seq<'a, V, A>(self, _visitor: V, _aad: A) -> Self::Ok<V::Value>
where
V: DecipherVisitor<'c> + Send + 'c,
A: IntoAad<'a>,
{
None
}
fn decrypt_map<'a, V, A>(self, _visitor: V, _aad: A) -> Self::Ok<V::Value>
where
V: DecipherVisitor<'c> + Send + 'c,
A: IntoAad<'a>,
{
None
}
fn decrypt_any<'a, V, A>(self, _visitor: V, _aad: A) -> Self::Ok<V::Value>
where
V: DecipherVisitor<'c> + Send + 'c,
A: IntoAad<'a>,
{
None
}
fn decrypt_passthrough(self) -> Self::Ok<Self::Passthrough> {
None
}
fn decrypt_option<'a, T, A>(self, _aad: A) -> Self::Ok<Option<T>>
where
T: Decrypt<'c> + 'c,
A: IntoAad<'a>,
{
None
}
}
fn captured_helper_aad<F>(build: F) -> Vec<u8>
where
F: FnOnce(CapturingDecipher) -> Option<String>,
{
let captured = Rc::new(RefCell::new(Vec::new()));
let _ = build(CapturingDecipher {
captured_aad: Rc::clone(&captured),
});
let bytes = captured.borrow().clone();
bytes
}
#[test]
fn decrypt_helper_folds_empty_extra_plus_tag() {
let captured =
captured_helper_aad(|d| ContextTag::context("user:42").decrypt::<String, _>(d));
assert_eq!(captured, ((), "user:42").into_aad().as_bytes());
}
#[test]
fn decrypt_helper_folds_extra_then_tag() {
let captured = captured_helper_aad(|d| {
ContextTag::context("table:users").decrypt_with_aad::<String, _, _>(d, "row:99")
});
assert_eq!(captured, ("row:99", "table:users").into_aad().as_bytes());
}
#[test]
fn decrypt_helper_refine_folds_nested_tag() {
let captured = captured_helper_aad(|d| {
ContextTag::context("table:users")
.refine("column:email")
.decrypt::<String, _>(d)
});
assert_eq!(
captured,
((), ("table:users", "column:email")).into_aad().as_bytes()
);
}
#[test]
fn decrypt_helper_matches_encrypt_binding() {
let cipher = MockCipher::new();
ContextTag::new("secret", "table:users")
.encrypt_with_aad(&cipher, "row:99")
.expect("encryption should succeed");
let captured = captured_helper_aad(|d| {
ContextTag::context("table:users").decrypt_with_aad::<String, _, _>(d, "row:99")
});
assert_eq!(captured, cipher.captured_aad());
}
}