Coverage Report

Created: 2026-05-18 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}