/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/selfencrypt.rs
Line | Count | Source |
1 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
2 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
3 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
4 | | // option. This file may not be copied, modified, or distributed |
5 | | // except according to those terms. |
6 | | |
7 | | use std::{fmt::Write as _, io::Write as _, mem}; |
8 | | |
9 | | use log::{info, trace}; |
10 | | |
11 | | use crate::{ |
12 | | RecordProtection, |
13 | | constants::{Cipher, Version}, |
14 | | err::{Error, Res}, |
15 | | hkdf, |
16 | | p11::{SymKey, random}, |
17 | | }; |
18 | | |
19 | | #[must_use] |
20 | 0 | pub fn hex<A: AsRef<[u8]>>(buf: A) -> String { |
21 | 0 | let mut ret = String::with_capacity(buf.as_ref().len() * 2); |
22 | 0 | for b in buf.as_ref() { |
23 | 0 | write!(&mut ret, "{b:02x}").expect("write OK"); |
24 | 0 | } |
25 | 0 | ret |
26 | 0 | } Unexecuted instantiation: nss_rs::selfencrypt::hex::<&alloc::vec::Vec<u8>> Unexecuted instantiation: nss_rs::selfencrypt::hex::<&[u8]> |
27 | | |
28 | | #[derive(Debug)] |
29 | | pub struct SelfEncrypt { |
30 | | version: Version, |
31 | | cipher: Cipher, |
32 | | key_id: u8, |
33 | | key: SymKey, |
34 | | old_key: Option<SymKey>, |
35 | | } |
36 | | |
37 | | impl SelfEncrypt { |
38 | | const VERSION: u8 = 1; |
39 | | const SALT_LENGTH: usize = 16; |
40 | | |
41 | | /// # Errors |
42 | | /// |
43 | | /// Failure to generate a new HKDF key using NSS results in an error. |
44 | 598 | pub fn new(version: Version, cipher: Cipher) -> Res<Self> { |
45 | 598 | let key = hkdf::generate_key(version, cipher)?; |
46 | 598 | Ok(Self { |
47 | 598 | version, |
48 | 598 | cipher, |
49 | 598 | key_id: 0, |
50 | 598 | key, |
51 | 598 | old_key: None, |
52 | 598 | }) |
53 | 598 | } |
54 | | |
55 | 301 | fn make_aead(&self, k: &SymKey, salt: &[u8]) -> Res<RecordProtection> { |
56 | 301 | debug_assert_eq!(salt.len(), Self::SALT_LENGTH); |
57 | 301 | let salt = hkdf::import_key(self.version, salt)?; |
58 | 301 | let secret = hkdf::extract(self.version, self.cipher, Some(&salt), k)?; |
59 | 301 | RecordProtection::new(self.version, self.cipher, &secret, "neqo self") |
60 | 301 | } |
61 | | |
62 | | /// Rotate keys. This causes any previous key that is being held to be replaced by the current |
63 | | /// key. |
64 | | /// |
65 | | /// # Errors |
66 | | /// |
67 | | /// Failure to generate a new HKDF key using NSS results in an error. |
68 | 0 | pub fn rotate(&mut self) -> Res<()> { |
69 | 0 | let new_key = hkdf::generate_key(self.version, self.cipher)?; |
70 | 0 | self.old_key = Some(mem::replace(&mut self.key, new_key)); |
71 | 0 | let (kid, _) = self.key_id.overflowing_add(1); |
72 | 0 | self.key_id = kid; |
73 | 0 | info!("[SelfEncrypt] Rotated keys to {}", self.key_id); |
74 | 0 | Ok(()) |
75 | 0 | } |
76 | | |
77 | | /// Seal an item using the underlying key. This produces a single buffer that contains |
78 | | /// the encrypted `plaintext`, plus a version number and salt. |
79 | | /// `aad` is only used as input to the AEAD, it is not included in the output; the |
80 | | /// caller is responsible for carrying the AAD as appropriate. |
81 | | /// |
82 | | /// # Errors |
83 | | /// |
84 | | /// Failure to protect using NSS AEAD APIs produces an error. |
85 | 0 | pub fn seal(&self, aad: &[u8], plaintext: &[u8]) -> Res<Vec<u8>> { |
86 | | // Format is: |
87 | | // struct { |
88 | | // uint8 version; |
89 | | // uint8 key_id; |
90 | | // uint8 salt[16]; |
91 | | // opaque aead_encrypted(plaintext)[length as expanded]; |
92 | | // }; |
93 | | // AAD covers the entire header, plus the value of the AAD parameter that is provided. |
94 | 0 | let salt = random::<{ Self::SALT_LENGTH }>(); |
95 | 0 | let cipher = self.make_aead(&self.key, &salt)?; |
96 | 0 | let encoded_len = 2 + salt.len() + plaintext.len() + cipher.expansion(); |
97 | | |
98 | 0 | let mut enc = Vec::<u8>::with_capacity(encoded_len); |
99 | 0 | enc.write_all(&[Self::VERSION]) |
100 | 0 | .unwrap_or_else(|_| unreachable!("Buffer has enough capacity.")); |
101 | 0 | enc.write_all(&[self.key_id]) |
102 | 0 | .unwrap_or_else(|_| unreachable!("Buffer has enough capacity.")); |
103 | 0 | enc.write_all(&salt) |
104 | 0 | .unwrap_or_else(|_| unreachable!("Buffer has enough capacity.")); |
105 | | |
106 | 0 | let mut extended_aad = enc.clone(); |
107 | 0 | extended_aad |
108 | 0 | .write_all(aad) |
109 | 0 | .unwrap_or_else(|_| unreachable!("Buffer has enough capacity.")); |
110 | | |
111 | 0 | let offset = enc.len(); |
112 | 0 | let mut output: Vec<u8> = enc; |
113 | 0 | output.resize(encoded_len, 0); |
114 | 0 | cipher.encrypt(0, extended_aad.as_ref(), plaintext, &mut output[offset..])?; |
115 | 0 | trace!( |
116 | | "[SelfEncrypt] seal {} {} -> {}", |
117 | 0 | hex(aad), |
118 | 0 | hex(plaintext), |
119 | 0 | hex(&output) |
120 | | ); |
121 | 0 | Ok(output) |
122 | 0 | } |
123 | | |
124 | 338 | const fn select_key(&self, kid: u8) -> Option<&SymKey> { |
125 | 338 | if kid == self.key_id { |
126 | 314 | Some(&self.key) |
127 | | } else { |
128 | 24 | let (prev_key_id, _) = self.key_id.overflowing_sub(1); |
129 | 24 | if kid == prev_key_id { |
130 | 3 | self.old_key.as_ref() |
131 | | } else { |
132 | 21 | None |
133 | | } |
134 | | } |
135 | 338 | } |
136 | | |
137 | | /// Open the protected `ciphertext`. |
138 | | /// |
139 | | /// # Errors |
140 | | /// |
141 | | /// Returns an error when the self-encrypted object is invalid; |
142 | | /// when the keys have been rotated; or when NSS fails. |
143 | | #[expect(clippy::similar_names, reason = "aad is similar to aead.")] |
144 | 494 | pub fn open(&self, aad: &[u8], ciphertext: &[u8]) -> Res<Vec<u8>> { |
145 | | const OFFSET: usize = 2 + SelfEncrypt::SALT_LENGTH; |
146 | 494 | if *ciphertext.first().ok_or(Error::SelfEncrypt)? != Self::VERSION { |
147 | 143 | return Err(Error::SelfEncrypt); |
148 | 351 | } |
149 | 351 | let Some(key) = self.select_key(*ciphertext.get(1).ok_or(Error::SelfEncrypt)?) else { |
150 | 24 | return Err(Error::SelfEncrypt); |
151 | | }; |
152 | 314 | let salt = ciphertext.get(2..OFFSET).ok_or(Error::SelfEncrypt)?; |
153 | | |
154 | 301 | let mut extended_aad = Vec::<u8>::with_capacity(OFFSET + aad.len()); |
155 | 301 | extended_aad |
156 | 301 | .write_all(&ciphertext[..OFFSET]) |
157 | 301 | .unwrap_or_else(|_| unreachable!("Buffer has enough capacity.")); |
158 | 301 | extended_aad |
159 | 301 | .write_all(aad) |
160 | 301 | .unwrap_or_else(|_| unreachable!("Buffer has enough capacity.")); |
161 | | |
162 | 301 | let aead = self.make_aead(key, salt)?; |
163 | | // NSS insists on having extra space available for decryption. |
164 | 301 | let padded_len = ciphertext.len() - OFFSET; |
165 | 301 | let mut output = vec![0; padded_len]; |
166 | 0 | let decrypted = |
167 | 301 | aead.decrypt(0, extended_aad.as_ref(), &ciphertext[OFFSET..], &mut output)?; |
168 | 0 | let final_len = decrypted.len(); |
169 | 0 | output.truncate(final_len); |
170 | 0 | trace!( |
171 | | "[SelfEncrypt] open {} {} -> {}", |
172 | 0 | hex(aad), |
173 | 0 | hex(ciphertext), |
174 | 0 | hex(&output) |
175 | | ); |
176 | 0 | Ok(output) |
177 | 494 | } |
178 | | } |