1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! Extra constant-time decryption API.

use crate::{AesGcm, Tag, A_MAX, C_MAX};
use aead::AeadInPlace;
use cipher::{
    consts::U16,
    generic_array::{ArrayLength, GenericArray},
    Block, BlockCipher, BlockEncrypt, StreamCipher,
};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use zeroize::Zeroize;

#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::vec::Vec;

use aes::NewBlockCipher;

/// API for Aead in-place decryption which is constant-time with respect to
/// the mac check failing
///
/// This is meant to extend the AeadInPlace trait and be implemented by those
/// AEAD's which have a constant-time decrypt operation.
pub trait CtAeadDecrypt: AeadInPlace {
    /// Decrypt a buffer using given aead nonce, validating associated data
    /// under the mac (tag).
    ///
    /// This API promises to be branchless and constant time, particularly,
    /// not branching on whether or not the mac check succeeded.
    ///
    /// Returns:
    /// Choice::from(true): The mac check succeeded and the buffer contains the
    /// plaintext Choice::from(false): Decryption failed, and the buffer
    /// contains failed decryption.        The caller SHOULD zeroize buffer
    /// before it is discarded.
    fn ct_decrypt_in_place_detached(
        &self,
        nonce: &GenericArray<u8, Self::NonceSize>,
        associated_data: &[u8],
        buffer: &mut [u8],
        tag: &GenericArray<u8, Self::TagSize>,
    ) -> CtDecryptResult;
}

/// A new-type wrapper around choice with the #[must_use] annotation that Result
/// has. This wraps the value Choice::from(true) when decryption succeeded, and
/// Choice::from(false) otherwise.
#[must_use = "The result of constant time decryption should not be discarded"]
#[derive(Copy, Clone, Debug)]
pub struct CtDecryptResult(pub Choice);

impl AsRef<Choice> for CtDecryptResult {
    fn as_ref(&self) -> &Choice {
        &self.0
    }
}

impl From<Choice> for CtDecryptResult {
    fn from(src: Choice) -> Self {
        CtDecryptResult(src)
    }
}

impl From<CtDecryptResult> for Choice {
    fn from(src: CtDecryptResult) -> Choice {
        src.0
    }
}

impl From<CtDecryptResult> for bool {
    fn from(src: CtDecryptResult) -> bool {
        bool::from(Choice::from(src))
    }
}

impl<Aes, NonceSize> CtAeadDecrypt for AesGcm<Aes, NonceSize>
where
    Aes: BlockCipher<BlockSize = U16> + BlockEncrypt + NewBlockCipher,
    Aes::ParBlocks: ArrayLength<Block<Aes>>,
    NonceSize: ArrayLength<u8>,
{
    /// A constant time version of the original
    /// https://docs.rs/aes-gcm/0.6.0/src/aes_gcm/lib.rs.html#251
    fn ct_decrypt_in_place_detached(
        &self,
        nonce: &GenericArray<u8, NonceSize>,
        associated_data: &[u8],
        buffer: &mut [u8],
        tag: &Tag,
    ) -> CtDecryptResult {
        let len = buffer.len();

        if len as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
            return CtDecryptResult(Choice::from(0));
        }

        // TODO(tarcieri): interleave encryption with GHASH
        // See: <https://github.com/RustCrypto/AEADs/issues/74>
        let mut expected_tag = self.compute_tag(associated_data, buffer);
        let mut ctr = self.init_ctr(nonce);
        let mut ciphertext = Vec::with_capacity(len);
        ciphertext.extend_from_slice(buffer);

        ctr.apply_keystream(expected_tag.as_mut_slice());
        ctr.apply_keystream(&mut ciphertext);

        let result = expected_tag.ct_eq(&tag);

        // Conditionally copy the actual plaintext _only_ if the tag verified
        // correctly, in order to increase misuse resistance and reduce attack
        // surface for chosen ciphertext attacks.
        for i in 0..len {
            buffer[i] = u8::conditional_select(&buffer[i], &ciphertext[i], result);
        }
        // Unconditionally zeroize the decryption result to refrain from keeping
        // a CCA oracle in memory.
        ciphertext.zeroize();

        CtDecryptResult(result)
    }
}