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/aead/recprot.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::{
8
    fmt,
9
    os::raw::{c_char, c_int, c_uint},
10
    ptr::{null, null_mut},
11
};
12
13
use super::{COUNTER_LEN, NONCE_LEN, TAG_LEN, c_int_len, xor_nonce};
14
use crate::{
15
    Cipher, Error, Res, SECItemBorrowed, SymKey, Version,
16
    constants::{TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256},
17
    err::{sec::SEC_ERROR_BAD_DATA, secstatus_to_res},
18
    hp::SSL_HkdfExpandLabelWithMech,
19
    p11::{
20
        CK_ATTRIBUTE_TYPE, CK_GENERATOR_FUNCTION, CK_MECHANISM_TYPE, CKA_DECRYPT, CKA_ENCRYPT,
21
        CKA_NSS_MESSAGE, CKG_NO_GENERATE, CKM_AES_GCM, CKM_CHACHA20_POLY1305, CKM_HKDF_DATA,
22
        Context, PK11_AEADOp, PK11_CreateContextBySymKey, PK11SymKey,
23
    },
24
};
25
26
10.9k
fn cipher_mech_and_key_len(cipher: Cipher) -> Res<(CK_MECHANISM_TYPE, c_uint)> {
27
10.9k
    match cipher {
28
10.9k
        TLS_AES_128_GCM_SHA256 => Ok((CK_MECHANISM_TYPE::from(CKM_AES_GCM), 16)),
29
0
        TLS_AES_256_GCM_SHA384 => Ok((CK_MECHANISM_TYPE::from(CKM_AES_GCM), 32)),
30
0
        TLS_CHACHA20_POLY1305_SHA256 => Ok((CK_MECHANISM_TYPE::from(CKM_CHACHA20_POLY1305), 32)),
31
0
        _ => Err(Error::UnsupportedCipher),
32
    }
33
10.9k
}
34
35
21.8k
fn expand_label(
36
21.8k
    version: Version,
37
21.8k
    cipher: Cipher,
38
21.8k
    secret: &SymKey,
39
21.8k
    label: &str,
40
21.8k
    mech: CK_MECHANISM_TYPE,
41
21.8k
    key_len: c_uint,
42
21.8k
) -> Res<SymKey> {
43
21.8k
    let mut ptr: *mut PK11SymKey = null_mut();
44
    unsafe {
45
21.8k
        SSL_HkdfExpandLabelWithMech(
46
21.8k
            version,
47
21.8k
            cipher,
48
21.8k
            **secret,
49
21.8k
            null(),
50
            0,
51
21.8k
            label.as_ptr().cast::<c_char>(),
52
21.8k
            c_uint::try_from(label.len())?,
53
21.8k
            mech,
54
21.8k
            key_len,
55
21.8k
            &raw mut ptr,
56
        )
57
0
    }?;
58
21.8k
    SymKey::from_ptr(ptr)
59
21.8k
}
60
61
21.8k
fn make_ctx(
62
21.8k
    mech: CK_MECHANISM_TYPE,
63
21.8k
    op: CK_ATTRIBUTE_TYPE,
64
21.8k
    key: &SymKey,
65
21.8k
    nonce_base: &[u8; NONCE_LEN],
66
21.8k
) -> Res<Context> {
67
21.8k
    let ptr = unsafe {
68
21.8k
        PK11_CreateContextBySymKey(
69
21.8k
            mech,
70
21.8k
            op,
71
21.8k
            **key,
72
21.8k
            SECItemBorrowed::wrap(nonce_base.as_slice())?.as_ref(),
73
        )
74
    };
75
21.8k
    Context::from_ptr(ptr)
76
21.8k
}
77
78
/// Calls `PK11_AEADOp` with the fixed parameters for this module (`CKG_NO_GENERATE`, no
79
/// counter generation) and returns the number of output bytes written.
80
///
81
/// # Safety
82
///
83
/// `output`, `tag`, and `input` must be valid for `output_max`, `TAG_LEN`, and `input_len`
84
/// bytes respectively. `output` and `input` may fully overlap (in-place operation); `tag`
85
/// must not overlap with the `output` region.
86
#[expect(
87
    clippy::too_many_arguments,
88
    reason = "Thin wrapper over a 14-argument C function."
89
)]
90
4.06k
unsafe fn aead_op(
91
4.06k
    ctx: &Context,
92
4.06k
    nonce_base: &[u8; NONCE_LEN],
93
4.06k
    count: u64,
94
4.06k
    aad: &[u8],
95
4.06k
    output: *mut u8,
96
4.06k
    output_max: usize,
97
4.06k
    tag: *mut u8,
98
4.06k
    input: *const u8,
99
4.06k
    input_len: usize,
100
4.06k
) -> Res<usize> {
101
4.06k
    let mut nonce = xor_nonce(nonce_base, count);
102
4.06k
    let mut out_len: c_int = 0;
103
4.06k
    secstatus_to_res(unsafe {
104
4.06k
        PK11_AEADOp(
105
4.06k
            **ctx,
106
4.06k
            CK_GENERATOR_FUNCTION::from(CKG_NO_GENERATE),
107
4.06k
            c_int_len(NONCE_LEN - COUNTER_LEN)?,
108
4.06k
            nonce.as_mut_ptr(),
109
4.06k
            c_int_len(NONCE_LEN)?,
110
4.06k
            aad.as_ptr(),
111
4.06k
            c_int_len(aad.len())?,
112
4.06k
            output,
113
4.06k
            &raw mut out_len,
114
4.06k
            c_int_len(output_max)?,
115
4.06k
            tag,
116
4.06k
            c_int_len(TAG_LEN)?,
117
4.06k
            input,
118
4.06k
            c_int_len(input_len)?,
119
        )
120
516
    })?;
121
3.54k
    Ok(usize::try_from(out_len)?)
122
4.06k
}
123
124
pub struct RecordProtection {
125
    ctx_encrypt: Context,
126
    ctx_decrypt: Context,
127
    nonce_base: [u8; NONCE_LEN],
128
}
129
130
impl RecordProtection {
131
    /// Create a new AEAD instance.
132
    ///
133
    /// # Errors
134
    ///
135
    /// Returns `Error` when the underlying crypto operations fail.
136
10.9k
    pub fn new(version: Version, cipher: Cipher, secret: &SymKey, prefix: &str) -> Res<Self> {
137
10.9k
        let (mech, key_len) = cipher_mech_and_key_len(cipher)?;
138
10.9k
        let key = expand_label(
139
10.9k
            version,
140
10.9k
            cipher,
141
10.9k
            secret,
142
10.9k
            &format!("{prefix}key"),
143
10.9k
            mech,
144
10.9k
            key_len,
145
0
        )?;
146
10.9k
        let iv_key = expand_label(
147
10.9k
            version,
148
10.9k
            cipher,
149
10.9k
            secret,
150
10.9k
            &format!("{prefix}iv"),
151
10.9k
            CK_MECHANISM_TYPE::from(CKM_HKDF_DATA),
152
10.9k
            c_uint::try_from(NONCE_LEN)?,
153
0
        )?;
154
10.9k
        let nonce_base: [u8; NONCE_LEN] =
155
10.9k
            iv_key.key_data()?.try_into().map_err(|_| Error::Internal)?;
156
10.9k
        let ctx_encrypt = make_ctx(
157
10.9k
            mech,
158
10.9k
            CK_ATTRIBUTE_TYPE::from(CKA_NSS_MESSAGE | CKA_ENCRYPT),
159
10.9k
            &key,
160
10.9k
            &nonce_base,
161
0
        )?;
162
10.9k
        let ctx_decrypt = make_ctx(
163
10.9k
            mech,
164
10.9k
            CK_ATTRIBUTE_TYPE::from(CKA_NSS_MESSAGE | CKA_DECRYPT),
165
10.9k
            &key,
166
10.9k
            &nonce_base,
167
0
        )?;
168
10.9k
        Ok(Self {
169
10.9k
            ctx_encrypt,
170
10.9k
            ctx_decrypt,
171
10.9k
            nonce_base,
172
10.9k
        })
173
10.9k
    }
174
175
    /// Get the expansion size (authentication tag length) for this AEAD.
176
    #[must_use]
177
    #[expect(clippy::missing_const_for_fn, clippy::unused_self)]
178
18.0k
    pub fn expansion(&self) -> usize {
179
18.0k
        TAG_LEN
180
18.0k
    }
181
182
    /// Encrypt plaintext with associated data.
183
    ///
184
    /// # Errors
185
    ///
186
    /// Returns `Error` when encryption fails.
187
805
    pub fn encrypt<'a>(
188
805
        &self,
189
805
        count: u64,
190
805
        aad: &[u8],
191
805
        input: &[u8],
192
805
        output: &'a mut [u8],
193
805
    ) -> Res<&'a [u8]> {
194
805
        if output.len()
195
805
            < input
196
805
                .len()
197
805
                .checked_add(TAG_LEN)
198
805
                .ok_or(Error::IntegerOverflow)?
199
        {
200
0
            return Err(Error::from(SEC_ERROR_BAD_DATA));
201
805
        }
202
805
        let out_ptr = output.as_mut_ptr();
203
805
        let out_len = unsafe {
204
805
            aead_op(
205
805
                &self.ctx_encrypt,
206
805
                &self.nonce_base,
207
805
                count,
208
805
                aad,
209
805
                out_ptr,
210
805
                input.len(),
211
805
                out_ptr.add(input.len()),
212
805
                input.as_ptr(),
213
805
                input.len(),
214
            )
215
0
        }?;
216
805
        if out_len != input.len() {
217
0
            return Err(Error::Internal);
218
805
        }
219
805
        Ok(&output[..out_len + TAG_LEN])
220
805
    }
221
222
    /// Encrypt plaintext in place with associated data.
223
    ///
224
    /// # Errors
225
    ///
226
    /// Returns `Error` when encryption fails.
227
2.25k
    pub fn encrypt_in_place(&self, count: u64, aad: &[u8], data: &mut [u8]) -> Res<usize> {
228
2.25k
        if data.len() < self.expansion() {
229
0
            return Err(Error::from(SEC_ERROR_BAD_DATA));
230
2.25k
        }
231
2.25k
        let pt_len = data.len() - self.expansion();
232
2.25k
        let data_ptr = data.as_mut_ptr();
233
2.25k
        let out_len = unsafe {
234
2.25k
            aead_op(
235
2.25k
                &self.ctx_encrypt,
236
2.25k
                &self.nonce_base,
237
2.25k
                count,
238
2.25k
                aad,
239
2.25k
                data_ptr,
240
2.25k
                pt_len,
241
2.25k
                data_ptr.add(pt_len),
242
2.25k
                data_ptr.cast_const(),
243
2.25k
                pt_len,
244
            )
245
0
        }?;
246
2.25k
        if out_len != pt_len {
247
0
            return Err(Error::Internal);
248
2.25k
        }
249
2.25k
        Ok(data.len())
250
2.25k
    }
251
252
    /// Decrypt ciphertext with associated data.
253
    ///
254
    /// # Errors
255
    ///
256
    /// Returns `Error` when decryption or authentication fails.
257
301
    pub fn decrypt<'a>(
258
301
        &self,
259
301
        count: u64,
260
301
        aad: &[u8],
261
301
        input: &[u8],
262
301
        output: &'a mut [u8],
263
301
    ) -> Res<&'a [u8]> {
264
301
        let ct_len = input
265
301
            .len()
266
301
            .checked_sub(TAG_LEN)
267
301
            .ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?;
268
164
        if output.len() < ct_len {
269
0
            return Err(Error::from(SEC_ERROR_BAD_DATA));
270
164
        }
271
164
        let mut tag = [0u8; TAG_LEN];
272
164
        tag.copy_from_slice(&input[ct_len..]);
273
0
        let out_len = unsafe {
274
164
            aead_op(
275
164
                &self.ctx_decrypt,
276
164
                &self.nonce_base,
277
164
                count,
278
164
                aad,
279
164
                output.as_mut_ptr(),
280
164
                output.len(),
281
164
                tag.as_mut_ptr(),
282
164
                input.as_ptr(),
283
164
                ct_len,
284
            )
285
164
        }?;
286
0
        Ok(&output[..out_len])
287
301
    }
288
289
    /// Decrypt ciphertext in place with associated data.
290
    ///
291
    /// # Errors
292
    ///
293
    /// Returns `Error` when decryption or authentication fails.
294
836
    pub fn decrypt_in_place(&self, count: u64, aad: &[u8], data: &mut [u8]) -> Res<usize> {
295
836
        let ct_len = data
296
836
            .len()
297
836
            .checked_sub(TAG_LEN)
298
836
            .ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?;
299
836
        let mut tag = [0u8; TAG_LEN];
300
836
        tag.copy_from_slice(&data[ct_len..]);
301
836
        let data_ptr = data.as_mut_ptr();
302
484
        let out_len = unsafe {
303
836
            aead_op(
304
836
                &self.ctx_decrypt,
305
836
                &self.nonce_base,
306
836
                count,
307
836
                aad,
308
836
                data_ptr,
309
836
                data.len(),
310
836
                tag.as_mut_ptr(),
311
836
                data_ptr.cast_const(),
312
836
                ct_len,
313
            )
314
352
        }?;
315
484
        if out_len != ct_len {
316
0
            return Err(Error::Internal);
317
484
        }
318
484
        Ok(ct_len)
319
836
    }
320
}
321
322
impl fmt::Debug for RecordProtection {
323
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
324
0
        write!(f, "[AEAD Context]")
325
0
    }
326
}