nuts_container/
cipher.rs

1// MIT License
2//
3// Copyright (c) 2022-2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23#[cfg(test)]
24mod tests;
25
26use openssl::cipher as ossl_cipher;
27use openssl::cipher_ctx::CipherCtx;
28use openssl::error::ErrorStack;
29use std::str::FromStr;
30use std::{cmp, fmt};
31use thiserror::Error;
32
33use crate::buffer::{Buffer, BufferError, BufferMut};
34use crate::svec::SecureVec;
35
36/// [`Cipher`] related error codes.
37#[derive(Debug, Error)]
38pub enum CipherError {
39    /// The cipher key is invalid/too short.
40    #[error("invalid key")]
41    InvalidKey,
42
43    /// The cipher iv is invalid/too short.
44    #[error("invalid iv")]
45    InvalidIv,
46
47    /// The size of the block to be encrypted/decrypted is invalid and must be
48    /// aligned at the [block size](Cipher::block_size) of the cipher.
49    #[error("invalid block-size")]
50    InvalidBlockSize,
51
52    /// A cipher-text is not trustworthy.
53    ///
54    /// If an authenticated decryption is performed, and the tag mismatches,
55    /// this error is raised.
56    #[error("the plaintext is not trustworthy")]
57    NotTrustworthy,
58
59    /// An error in the OpenSSL library occured.
60    #[error(transparent)]
61    OpenSSL(#[from] ErrorStack),
62}
63
64/// Supported cipher algorithms.
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub enum Cipher {
67    /// No encryption.
68    None,
69
70    /// AES with a 128-bit key in CTR mode
71    Aes128Ctr,
72
73    /// AES with a 192-bit key in CTR mode
74    Aes192Ctr,
75
76    /// AES with a 256-bit key in CTR mode
77    Aes256Ctr,
78
79    /// AES with a 128-bit key in GCM mode
80    Aes128Gcm,
81
82    /// AES with a 192-bit key in GCM mode
83    Aes192Gcm,
84
85    /// AES with a 256-bit key in GCM mode
86    Aes256Gcm,
87}
88
89impl Cipher {
90    /// Returns the block size of the cipher.
91    pub fn block_size(&self) -> usize {
92        match self.to_openssl() {
93            None => 1,
94            Some(c) => c.block_size(),
95        }
96    }
97
98    /// Returns the key size of the cipher.
99    pub fn key_len(&self) -> usize {
100        match self.to_openssl() {
101            None => 0,
102            Some(c) => c.key_length(),
103        }
104    }
105
106    /// Returns the IV size of the cipher.
107    pub fn iv_len(&self) -> usize {
108        match self.to_openssl() {
109            None => 0,
110            Some(c) => c.iv_length(),
111        }
112    }
113
114    /// Returns the tag size of the cipher.
115    ///
116    /// An AE-cipher results into a
117    ///
118    /// 1. ciphertext
119    /// 2. tag
120    ///
121    /// Ciphertext and tag are both stored in a block of the container. Use
122    /// this method to get the size of the tag. For a non-AE-cipher the
123    /// tag-size is `0`.
124    pub fn tag_size(&self) -> u32 {
125        match self {
126            Cipher::None => 0,
127            Cipher::Aes128Ctr | Cipher::Aes192Ctr | Cipher::Aes256Ctr => 0,
128            Cipher::Aes128Gcm | Cipher::Aes192Gcm | Cipher::Aes256Gcm => 16,
129        }
130    }
131
132    pub(crate) fn get_from_buffer<T: Buffer>(buf: &mut T) -> Result<Cipher, BufferError> {
133        let b = buf.get_u32()?;
134
135        match b {
136            0 => Ok(Cipher::None),
137            1 => Ok(Cipher::Aes128Ctr),
138            2 => Ok(Cipher::Aes128Gcm),
139            3 => Ok(Cipher::Aes192Ctr),
140            4 => Ok(Cipher::Aes256Ctr),
141            5 => Ok(Cipher::Aes192Gcm),
142            6 => Ok(Cipher::Aes256Gcm),
143            _ => Err(BufferError::InvalidIndex("Cipher".to_string(), b)),
144        }
145    }
146
147    pub(crate) fn put_into_buffer<T: BufferMut>(&self, buf: &mut T) -> Result<(), BufferError> {
148        let b = match self {
149            Cipher::None => 0,
150            Cipher::Aes128Ctr => 1,
151            Cipher::Aes128Gcm => 2,
152            Cipher::Aes192Ctr => 3,
153            Cipher::Aes256Ctr => 4,
154            Cipher::Aes192Gcm => 5,
155            Cipher::Aes256Gcm => 6,
156        };
157
158        buf.put_u32(b)
159    }
160
161    fn to_openssl(self) -> Option<&'static ossl_cipher::CipherRef> {
162        match self {
163            Cipher::None => None,
164            Cipher::Aes128Ctr => Some(ossl_cipher::Cipher::aes_128_ctr()),
165            Cipher::Aes192Ctr => Some(ossl_cipher::Cipher::aes_192_ctr()),
166            Cipher::Aes256Ctr => Some(ossl_cipher::Cipher::aes_256_ctr()),
167            Cipher::Aes128Gcm => Some(ossl_cipher::Cipher::aes_128_gcm()),
168            Cipher::Aes192Gcm => Some(ossl_cipher::Cipher::aes_192_gcm()),
169            Cipher::Aes256Gcm => Some(ossl_cipher::Cipher::aes_256_gcm()),
170        }
171    }
172}
173
174impl fmt::Display for Cipher {
175    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
176        let s = match self {
177            Cipher::None => "none",
178            Cipher::Aes128Ctr => "aes128-ctr",
179            Cipher::Aes192Ctr => "aes192-ctr",
180            Cipher::Aes256Ctr => "aes256-ctr",
181            Cipher::Aes128Gcm => "aes128-gcm",
182            Cipher::Aes192Gcm => "aes192-gcm",
183            Cipher::Aes256Gcm => "aes256-gcm",
184        };
185
186        fmt.write_str(s)
187    }
188}
189
190impl FromStr for Cipher {
191    type Err = ();
192
193    fn from_str(str: &str) -> Result<Self, ()> {
194        match str {
195            "none" => Ok(Cipher::None),
196            "aes128-ctr" => Ok(Cipher::Aes128Ctr),
197            "aes192-ctr" => Ok(Cipher::Aes192Ctr),
198            "aes256-ctr" => Ok(Cipher::Aes256Ctr),
199            "aes128-gcm" => Ok(Cipher::Aes128Gcm),
200            "aes192-gcm" => Ok(Cipher::Aes192Gcm),
201            "aes256-gcm" => Ok(Cipher::Aes256Gcm),
202            _ => Err(()),
203        }
204    }
205}
206
207#[derive(Debug)]
208pub(super) struct CipherContext {
209    cipher: Cipher,
210    inp: SecureVec,
211    outp: SecureVec,
212}
213
214impl CipherContext {
215    pub(super) fn new(cipher: Cipher) -> CipherContext {
216        CipherContext {
217            cipher,
218            inp: vec![].into(),
219            outp: vec![].into(),
220        }
221    }
222
223    pub fn copy_from_slice(&mut self, buf_size: usize, buf: &[u8]) -> usize {
224        let len = cmp::min(buf_size, buf.len());
225
226        self.inp.resize(buf_size, 0);
227        self.inp[..len].copy_from_slice(&buf[..len]);
228        self.inp[len..].iter_mut().for_each(|n| *n = 0);
229
230        len
231    }
232
233    pub fn inp_mut(&mut self, buf_size: usize) -> &mut [u8] {
234        self.copy_from_slice(buf_size, &[]); // whiteout
235
236        &mut self.inp
237    }
238
239    pub fn encrypt(&mut self, key: &[u8], iv: &[u8]) -> Result<&[u8], CipherError> {
240        match self.cipher {
241            Cipher::None => self.make_none(),
242            _ => self.encrypt_aad(None, key, iv).map(|_| ())?,
243        };
244
245        Ok(self.outp.as_slice())
246    }
247
248    fn encrypt_aad(
249        &mut self,
250        aad: Option<&[u8]>,
251        key: &[u8],
252        iv: &[u8],
253    ) -> Result<usize, CipherError> {
254        let key = key
255            .get(..self.cipher.key_len())
256            .ok_or(CipherError::InvalidKey)?;
257        let iv = iv
258            .get(..self.cipher.iv_len())
259            .ok_or(CipherError::InvalidIv)?;
260
261        // number of plaintext bytes: equals to number of input bytes. There is
262        // no need to align at block size because blocksize is 1 for all
263        // ciphers.
264        let ptext_len = self.inp.len();
265
266        // number of ciphertext bytes: equals to plaintext bytes (for now) because
267        // blocksize is 1 for all ciphers.
268        let ctext_len = ptext_len;
269
270        if ptext_len == 0 {
271            return Ok(0);
272        }
273
274        if ptext_len % self.cipher.block_size() != 0 {
275            return Err(CipherError::InvalidBlockSize);
276        }
277
278        let mut ctx = CipherCtx::new()?;
279
280        ctx.encrypt_init(self.cipher.to_openssl(), Some(key), Some(iv))?;
281        ctx.set_padding(false);
282
283        if let Some(buf) = aad {
284            ctx.cipher_update(buf, None)?;
285        }
286
287        self.outp
288            .resize(ctext_len + self.cipher.tag_size() as usize, 0);
289        ctx.cipher_update(&self.inp[..ptext_len], Some(&mut self.outp[..ctext_len]))?;
290
291        if self.cipher.tag_size() > 0 {
292            ctx.cipher_final(&mut [])?;
293            ctx.tag(&mut self.outp[ctext_len..])?;
294        }
295
296        Ok(ctext_len)
297    }
298
299    pub fn decrypt(&mut self, key: &[u8], iv: &[u8]) -> Result<&[u8], CipherError> {
300        match self.cipher {
301            Cipher::None => self.make_none(),
302            _ => self.decrypt_aad(None, key, iv).map(|_| ())?,
303        }
304
305        Ok(self.outp.as_slice())
306    }
307
308    fn decrypt_aad(
309        &mut self,
310        aad: Option<&[u8]>,
311        key: &[u8],
312        iv: &[u8],
313    ) -> Result<usize, CipherError> {
314        let key = key
315            .get(..self.cipher.key_len())
316            .ok_or(CipherError::InvalidKey)?;
317        let iv = iv
318            .get(..self.cipher.iv_len())
319            .ok_or(CipherError::InvalidIv)?;
320
321        // number of ciphertext bytes: remove tag from the input.
322        let ctext_bytes = self
323            .inp
324            .len()
325            .saturating_sub(self.cipher.tag_size() as usize);
326
327        // number of plaintext bytes: equals to ciphertext bytes (for now) because
328        // blocksize is 1 for all ciphers.
329        let ptext_bytes = ctext_bytes;
330
331        if ctext_bytes == 0 {
332            return Ok(0);
333        }
334
335        if ctext_bytes % self.cipher.block_size() != 0 {
336            return Err(CipherError::InvalidBlockSize);
337        }
338
339        let mut ctx = CipherCtx::new()?;
340
341        ctx.decrypt_init(self.cipher.to_openssl(), Some(key), Some(iv))?;
342        ctx.set_padding(false);
343
344        if let Some(buf) = aad {
345            ctx.cipher_update(buf, None)?;
346        }
347
348        self.outp.resize(ptext_bytes, 0);
349        ctx.cipher_update(
350            &self.inp[..ctext_bytes],
351            Some(&mut self.outp[..ptext_bytes]),
352        )?;
353
354        if self.cipher.tag_size() > 0 {
355            ctx.set_tag(&self.inp[ctext_bytes..])?;
356            ctx.cipher_final(&mut [])
357                .map_err(|_| CipherError::NotTrustworthy)?;
358        }
359
360        Ok(ctext_bytes)
361    }
362
363    fn make_none(&mut self) {
364        self.outp.clear();
365        self.outp.extend_from_slice(&self.inp);
366    }
367}