tink_aead/subtle/
encrypt_then_authenticate.rs

1// Copyright 2020 The Tink-Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17//! General AEAD implementation combining an `IndCpaCipher` with a `tink_core::Mac`
18
19use super::IndCpaCipher;
20use tink_core::{utils::wrap_err, TinkError};
21
22/// `EncryptThenAuthenticate` performs an encrypt-then-MAC operation on plaintext
23/// and additional authenticated data (aad).
24///
25/// The MAC is computed over (aad || ciphertext || size of aad). This implementation is based on
26/// <http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05>.
27pub struct EncryptThenAuthenticate {
28    ind_cpa_cipher: Box<dyn IndCpaCipher>,
29    mac: Box<dyn tink_core::Mac>,
30    tag_size: usize,
31}
32
33/// Manual implementation of [`Clone`] relying on the trait bounds for
34/// primitives to provide `.box_clone()` methods.
35impl Clone for EncryptThenAuthenticate {
36    fn clone(&self) -> Self {
37        Self {
38            ind_cpa_cipher: self.ind_cpa_cipher.box_clone(),
39            mac: self.mac.box_clone(),
40            tag_size: self.tag_size,
41        }
42    }
43}
44
45const MIN_TAG_SIZE_IN_BYTES: usize = 10;
46
47impl EncryptThenAuthenticate {
48    /// Return a new instance of EncryptThenAuthenticate.
49    pub fn new(
50        ind_cpa_cipher: Box<dyn IndCpaCipher>,
51        mac: Box<dyn tink_core::Mac>,
52        tag_size: usize,
53    ) -> Result<EncryptThenAuthenticate, TinkError> {
54        if tag_size < MIN_TAG_SIZE_IN_BYTES {
55            return Err("EncryptThenAuthenticate: tag size too small".into());
56        }
57        Ok(EncryptThenAuthenticate {
58            ind_cpa_cipher,
59            mac,
60            tag_size,
61        })
62    }
63}
64
65impl tink_core::Aead for EncryptThenAuthenticate {
66    /// Encrypt `plaintext` with `additional_data` as additional authenticated
67    /// data. The resulting ciphertext allows for checking authenticity and
68    /// integrity of additional data, but does not guarantee its secrecy.
69    ///
70    /// The plaintext is encrypted with an [`IndCpaCipher`], then MAC is computed over
71    /// (additional_data || ciphertext || n) where n is additional_data's length
72    /// in bits represented as a 64-bit bigendian unsigned integer. The final
73    /// ciphertext format is (IND-CPA ciphertext || mac).
74    fn encrypt(&self, plaintext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, TinkError> {
75        let mut ciphertext = self
76            .ind_cpa_cipher
77            .encrypt(plaintext)
78            .map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
79
80        // Authenticate the following data:
81        // additional_data || payload || aad_size_in_bits
82        let mut to_auth_data = Vec::with_capacity(additional_data.len() + ciphertext.len() + 8);
83        to_auth_data.extend_from_slice(additional_data);
84        to_auth_data.extend_from_slice(&ciphertext);
85        let aad_size_in_bits: u64 = (additional_data.len() as u64)
86            .checked_mul(8)
87            .ok_or_else(|| TinkError::new("EncryptThenAuthenticate: additional data too long"))?;
88        to_auth_data.extend_from_slice(&aad_size_in_bits.to_be_bytes());
89
90        let tag = self
91            .mac
92            .compute_mac(&to_auth_data)
93            .map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
94        if tag.len() != self.tag_size {
95            return Err("EncryptThenAuthenticate: invalid tag size".into());
96        }
97
98        // Put the tag at the end of the ciphertext.
99        ciphertext.extend_from_slice(&tag);
100        Ok(ciphertext)
101    }
102
103    /// Decrypt `ciphertext` with `additional_data` as additional authenticated
104    /// data.
105    fn decrypt(&self, ciphertext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, TinkError> {
106        if ciphertext.len() < self.tag_size {
107            return Err("EncryptThenAuthenticate: ciphertext too short".into());
108        }
109
110        // payload contains everything except the tag at the end.
111        let payload = &ciphertext[..(ciphertext.len() - self.tag_size)];
112
113        // Authenticate the following data:
114        // additional_data || payload || aad_size_in_bits
115        let mut to_auth_data = Vec::with_capacity(additional_data.len() + payload.len() + 8);
116        to_auth_data.extend_from_slice(additional_data);
117        to_auth_data.extend_from_slice(payload);
118        let aad_size_in_bits: u64 = (additional_data.len() as u64)
119            .checked_mul(8)
120            .ok_or_else(|| TinkError::new("EncryptThenAuthenticate: additional data too long"))?;
121        to_auth_data.extend_from_slice(&aad_size_in_bits.to_be_bytes());
122
123        // Verify against the tag at the end of the ciphertext.
124        self.mac
125            .verify_mac(
126                &ciphertext[(ciphertext.len() - self.tag_size)..],
127                &to_auth_data,
128            )
129            .map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
130
131        let plaintext = self
132            .ind_cpa_cipher
133            .decrypt(payload)
134            .map_err(|e| wrap_err("EncryptThenAuthenticate", e))?;
135
136        Ok(plaintext)
137    }
138}