Skip to content

Commit

Permalink
Add open_in_place_separate_tag API to LessSafeKey
Browse files Browse the repository at this point in the history
I agree to license my contributions to each file under the terms given
at the top of each file I changed.
  • Loading branch information
complexspaces authored and briansmith committed Sep 16, 2021
1 parent d8378ac commit 2896a01
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 20 deletions.
24 changes: 22 additions & 2 deletions src/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
//! [`crypto.cipher.AEAD`]: https://golang.org/pkg/crypto/cipher/#AEAD

use crate::{cpu, error, hkdf, polyfill};
use core::ops::RangeFrom;
use core::{
convert::{TryFrom, TryInto},
ops::RangeFrom,
};

pub use self::{
aes_gcm::{AES_128_GCM, AES_256_GCM},
Expand Down Expand Up @@ -208,9 +211,10 @@ impl PartialEq for Algorithm {

impl Eq for Algorithm {}

/// An authentication tag.
/// A possibly valid authentication tag.
#[must_use]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Tag([u8; TAG_LEN]);

impl AsRef<[u8]> for Tag {
Expand All @@ -219,6 +223,22 @@ impl AsRef<[u8]> for Tag {
}
}

impl TryFrom<&[u8]> for Tag {
type Error = error::Unspecified;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let raw_tag: [u8; TAG_LEN] = value.try_into().map_err(|_| error::Unspecified)?;
Ok(Self::from(raw_tag))
}
}

impl From<[u8; TAG_LEN]> for Tag {
#[inline]
fn from(value: [u8; TAG_LEN]) -> Self {
Self(value)
}
}

const MAX_KEY_LEN: usize = 32;

// All the AEADs we support use 128-bit tags.
Expand Down
55 changes: 38 additions & 17 deletions src/aead/less_safe_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

use super::{Aad, Algorithm, KeyInner, Nonce, Tag, UnboundKey, TAG_LEN};
use crate::{constant_time, cpu, error, polyfill};
use core::ops::RangeFrom;
use core::{convert::TryInto, ops::RangeFrom};

/// Immutable keys for use in situations where `OpeningKey`/`SealingKey` and
/// `NonceSequence` cannot reasonably be used.
Expand Down Expand Up @@ -44,6 +44,24 @@ impl LessSafeKey {
})
}

/// Like [open_in_place](Self::open_in_place), except the authentication tag is
/// passed separately.
#[inline]
pub fn open_in_place_separate_tag<'in_out, A>(
&self,
nonce: Nonce,
aad: Aad<A>,
tag: Tag,
in_out: &'in_out mut [u8],
ciphertext: RangeFrom<usize>,
) -> Result<&'in_out mut [u8], error::Unspecified>
where
A: AsRef<[u8]>,
{
let aad = Aad::from(aad.as_ref());
open_within_(self, nonce, aad, tag, in_out, ciphertext)
}

/// Like [`OpeningKey::open_in_place()`], except it accepts an arbitrary nonce.
///
/// `nonce` must be unique for every use of the key to open data.
Expand Down Expand Up @@ -74,13 +92,17 @@ impl LessSafeKey {
where
A: AsRef<[u8]>,
{
open_within_(
self,
nonce,
Aad::from(aad.as_ref()),
in_out,
ciphertext_and_tag,
)
let tag_offset = in_out
.len()
.checked_sub(TAG_LEN)
.ok_or(error::Unspecified)?;

// Split the tag off the end of `in_out`.
let (in_out, received_tag) = in_out.split_at_mut(tag_offset);
let received_tag = (*received_tag).try_into()?;
let ciphertext = ciphertext_and_tag;

self.open_in_place_separate_tag(nonce, aad, received_tag, in_out, ciphertext)
}

/// Like [`SealingKey::seal_in_place_append_tag()`], except it accepts an
Expand Down Expand Up @@ -140,20 +162,18 @@ fn open_within_<'in_out>(
key: &LessSafeKey,
nonce: Nonce,
aad: Aad<&[u8]>,
received_tag: Tag,
in_out: &'in_out mut [u8],
src: RangeFrom<usize>,
) -> Result<&'in_out mut [u8], error::Unspecified> {
let ciphertext_and_tag_len = in_out
.len()
.checked_sub(src.start)
.ok_or(error::Unspecified)?;
let ciphertext_len = ciphertext_and_tag_len
.checked_sub(TAG_LEN)
.ok_or(error::Unspecified)?;
let ciphertext_len = in_out.get(src.clone()).ok_or(error::Unspecified)?.len();
check_per_nonce_max_bytes(key.algorithm, ciphertext_len)?;
let (in_out, received_tag) = in_out.split_at_mut(src.start + ciphertext_len);

let Tag(calculated_tag) = (key.algorithm.open)(&key.inner, nonce, aad, in_out, src);
if constant_time::verify_slices_are_equal(calculated_tag.as_ref(), received_tag).is_err() {

if constant_time::verify_slices_are_equal(calculated_tag.as_ref(), received_tag.as_ref())
.is_err()
{
// Zero out the plaintext so that it isn't accidentally leaked or used
// after verification fails. It would be safest if we could check the
// tag before decrypting, but some `open` implementations interleave
Expand All @@ -163,6 +183,7 @@ fn open_within_<'in_out>(
}
return Err(error::Unspecified);
}

// `ciphertext_len` is also the plaintext length.
Ok(&mut in_out[..ciphertext_len])
}
Expand Down
45 changes: 44 additions & 1 deletion tests/aead_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015-2016 Brian Smith.
// Copyright 2015-2021 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
Expand Down Expand Up @@ -68,6 +68,7 @@ macro_rules! test_aead {
opening_key_open_within,
sealing_key_seal_in_place_append_tag,
sealing_key_seal_in_place_separate_tag,
test_open_in_place_seperate_tag,
]);

#[test]
Expand Down Expand Up @@ -190,6 +191,41 @@ where
Ok(())
}

fn test_open_in_place_seperate_tag(
alg: &'static aead::Algorithm,
tc: KnownAnswerTestCase,
) -> Result<(), error::Unspecified> {
let key = make_less_safe_key(alg, tc.key);

let mut in_out = Vec::from(tc.ciphertext);
let tag = tc.tag.try_into().unwrap();

// Test the simplest behavior.
{
let nonce = aead::Nonce::assume_unique_for_key(tc.nonce);
let actual_plaintext =
key.open_in_place_separate_tag(nonce, tc.aad, tag, &mut in_out, 0..)?;

assert_eq!(actual_plaintext, tc.plaintext);
assert_eq!(&in_out[..tc.plaintext.len()], tc.plaintext);
}

// Test that ciphertext range shifing works as expected.
{
let range = in_out.len()..;
in_out.extend_from_slice(tc.ciphertext);

let nonce = aead::Nonce::assume_unique_for_key(tc.nonce);
let actual_plaintext =
key.open_in_place_separate_tag(nonce, tc.aad, tag, &mut in_out, range)?;

assert_eq!(actual_plaintext, tc.plaintext);
assert_eq!(&in_out[..tc.plaintext.len()], tc.plaintext);
}

Ok(())
}

fn test_open_within<OpenWithin>(
tc: &KnownAnswerTestCase<'_>,
open_within: OpenWithin,
Expand Down Expand Up @@ -473,6 +509,13 @@ fn aead_test_aad_traits() {
fn test_tag_traits() {
test::compile_time_assert_send::<aead::Tag>();
test::compile_time_assert_sync::<aead::Tag>();

test::compile_time_assert_copy::<aead::Tag>();
test::compile_time_assert_clone::<aead::Tag>();

let tag = aead::Tag::from([4u8; 16]);
let _tag_2 = tag; // Cover `Copy`
assert_eq!(tag.as_ref(), tag.clone().as_ref()); // Cover `Clone`
}

#[test]
Expand Down

0 comments on commit 2896a01

Please sign in to comment.