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/lib.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
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
8
9
pub mod agent;
10
mod agentio;
11
mod auth;
12
mod cert;
13
pub mod constants;
14
mod ech;
15
#[macro_use]
16
mod util;
17
#[macro_use]
18
mod err;
19
#[macro_use]
20
mod exp;
21
pub mod ext;
22
pub mod hkdf;
23
pub mod hp;
24
25
pub mod aead;
26
pub mod der;
27
pub mod ec;
28
pub mod hash;
29
pub mod hmac;
30
pub mod p11;
31
pub mod pbkdf2;
32
pub mod pk11_utils;
33
mod prio;
34
mod replay;
35
mod secrets;
36
pub mod selfencrypt;
37
mod ssl;
38
pub mod time;
39
40
use std::{
41
    env,
42
    ffi::CString,
43
    path::{Path, PathBuf},
44
    ptr::null,
45
};
46
47
use log::error;
48
use once_cell::sync::OnceCell;
49
50
#[cfg(windows)]
51
#[expect(unused_imports, reason = "Force Advapi32 linkage")]
52
mod _link_anchor {
53
    use windows::Win32::Security::Authentication::Identity::RtlGenRandom as _;
54
}
55
56
pub use self::{
57
    aead::RecordProtection,
58
    agent::{
59
        Agent, AllowZeroRtt, Client, HandshakeState, Record, RecordList, ResumptionToken,
60
        SecretAgent, SecretAgentInfo, SecretAgentPreInfo, Server, ZeroRttCheckResult,
61
        ZeroRttChecker,
62
    },
63
    auth::AuthenticationStatus,
64
    constants::*,
65
    ech::{
66
        AeadId, KdfId, KemId, SymmetricSuite, encode_config as encode_ech_config,
67
        generate_keys as generate_ech_keys,
68
    },
69
    err::{Error, IntoResult, PRErrorCode, Res, secstatus_to_res},
70
    ext::{ExtensionHandler, ExtensionHandlerResult, ExtensionWriterResult},
71
    p11::{PrivateKey, PublicKey, SymKey, random, randomize},
72
    replay::AntiReplay,
73
    secrets::SecretDirection,
74
    ssl::Opt,
75
    util::*,
76
};
77
78
mod min_version;
79
use min_version::MINIMUM_NSS_VERSION;
80
81
pub mod nss_prelude {
82
    #![allow(
83
        non_snake_case,
84
        non_upper_case_globals,
85
        clippy::restriction,
86
        reason = "For included bindgen code."
87
    )]
88
    pub use _SECStatus::*;
89
90
    include!(concat!(env!("OUT_DIR"), "/nss_prelude.rs"));
91
}
92
pub use nss_prelude::{SECItem, SECItemArray, SECItemType, SECStatus};
93
94
#[expect(non_upper_case_globals, reason = "Code is bindgen-generated.")]
95
mod nss {
96
    use crate::nss_prelude::*;
97
    include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
98
}
99
100
enum NssLoaded {
101
    External,
102
    NoDb,
103
    #[expect(dead_code)]
104
    Db(Box<Path>),
105
}
106
107
impl Drop for NssLoaded {
108
0
    fn drop(&mut self) {
109
0
        if !matches!(self, Self::External) {
110
0
            unsafe {
111
0
                secstatus_to_res(nss::NSS_Shutdown()).expect("NSS Shutdown failed");
112
0
            }
113
0
        }
114
0
    }
115
}
116
117
static INITIALIZED: OnceCell<Res<NssLoaded>> = OnceCell::new();
118
119
6
fn version_check() -> Res<()> {
120
6
    let min_ver = CString::new(MINIMUM_NSS_VERSION)?;
121
6
    if unsafe { nss::NSS_VersionCheck(min_ver.as_ptr()) } == 0 {
122
0
        error!("Minimum NSS version of {MINIMUM_NSS_VERSION} not supported");
123
0
        return Err(Error::UnsupportedVersion);
124
6
    }
125
6
    Ok(())
126
6
}
127
128
/// This enables SSLTRACE by calling a simple, harmless function to trigger its
129
/// side effects.  SSLTRACE is not enabled in NSS until a socket is made or
130
/// global options are accessed.  Reading an option is the least impact approach.
131
/// This allows us to use SSLTRACE in all of our unit tests and programs.
132
#[cfg(debug_assertions)]
133
6
fn enable_ssl_trace() -> Res<()> {
134
6
    let opt = Opt::Locking.as_int();
135
6
    let mut v: ::std::os::raw::c_int = 0;
136
6
    secstatus_to_res(unsafe { ssl::SSL_OptionGetDefault(opt, &raw mut v) })
137
6
}
138
139
6
fn init_once(db: Option<PathBuf>) -> Res<NssLoaded> {
140
    // Set time zero.
141
6
    time::init();
142
6
    version_check()?;
143
6
    if unsafe { nss::NSS_IsInitialized() != 0 } {
144
0
        return Ok(NssLoaded::External);
145
6
    }
146
147
6
    let state = if let Some(path) = db {
148
5
        if !path.is_dir() {
149
0
            return Err(Error::Internal);
150
5
        }
151
5
        let pathstr = path.to_str().ok_or(Error::Internal)?;
152
5
        let dircstr = CString::new(pathstr)?;
153
5
        let empty = CString::new("")?;
154
5
        secstatus_to_res(unsafe {
155
5
            nss::NSS_Initialize(
156
5
                dircstr.as_ptr(),
157
5
                empty.as_ptr(),
158
5
                empty.as_ptr(),
159
5
                nss::SECMOD_DB.as_ptr().cast(),
160
                nss::NSS_INIT_READONLY,
161
            )
162
0
        })?;
163
164
5
        secstatus_to_res(unsafe {
165
5
            ssl::SSL_ConfigServerSessionIDCache(1024, 0, 0, dircstr.as_ptr())
166
0
        })?;
167
5
        NssLoaded::Db(path.into_boxed_path())
168
    } else {
169
1
        secstatus_to_res(unsafe { nss::NSS_NoDB_Init(null()) })?;
170
1
        NssLoaded::NoDb
171
    };
172
173
6
    secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() })?;
174
175
    #[cfg(debug_assertions)]
176
6
    enable_ssl_trace()?;
177
178
6
    Ok(state)
179
6
}
180
181
/// Initialize NSS.  This only executes the initialization routines once, so if there is any chance
182
/// that this is invoked twice, that's OK.
183
///
184
/// # Errors
185
///
186
/// When NSS initialization fails.
187
1.55k
pub fn init() -> Res<()> {
188
1.55k
    let res = INITIALIZED.get_or_init(|| init_once(None));
189
1.55k
    res.as_ref().map(|_| ()).map_err(Clone::clone)
190
1.55k
}
191
192
/// Initialize with a database.
193
///
194
/// # Errors
195
///
196
/// If NSS cannot be initialized.
197
14.1k
pub fn init_db<P: Into<PathBuf>>(dir: P) -> Res<()> {
198
    // Allow overriding the NSS database path with an environment variable.
199
14.1k
    let dir =
200
14.1k
        env::var("NSS_DB_PATH").unwrap_or(dir.into().to_str().ok_or(Error::Internal)?.to_string());
201
14.1k
    let res = INITIALIZED.get_or_init(|| init_once(Some(dir.into())));
nss_rs::init_db::<&str>::{closure#0}
Line
Count
Source
201
5
    let res = INITIALIZED.get_or_init(|| init_once(Some(dir.into())));
Unexecuted instantiation: nss_rs::init_db::<_>::{closure#0}
202
14.1k
    res.as_ref().map(|_| ()).map_err(Clone::clone)
203
14.1k
}
nss_rs::init_db::<&str>
Line
Count
Source
197
14.1k
pub fn init_db<P: Into<PathBuf>>(dir: P) -> Res<()> {
198
    // Allow overriding the NSS database path with an environment variable.
199
14.1k
    let dir =
200
14.1k
        env::var("NSS_DB_PATH").unwrap_or(dir.into().to_str().ok_or(Error::Internal)?.to_string());
201
14.1k
    let res = INITIALIZED.get_or_init(|| init_once(Some(dir.into())));
202
14.1k
    res.as_ref().map(|_| ()).map_err(Clone::clone)
203
14.1k
}
Unexecuted instantiation: nss_rs::init_db::<_>
204
205
/// # Panics
206
///
207
/// If NSS isn't initialized.
208
2.58k
pub fn assert_initialized() {
209
2.58k
    INITIALIZED
210
2.58k
        .get()
211
2.58k
        .expect("NSS not initialized with init or init_db");
212
2.58k
}
213
214
/// NSS tends to return empty "slices" with a null pointer, which will cause
215
/// `std::slice::from_raw_parts` to panic if passed directly.  This wrapper avoids
216
/// that issue.  It also performs conversion for lengths, as a convenience.
217
///
218
/// # Panics
219
/// If the provided length doesn't fit into a `usize`.
220
///
221
/// # Safety
222
/// The caller must adhere to the safety constraints of `std::slice::from_raw_parts`,
223
/// except that this will accept a null value for `data`.
224
14.1k
unsafe fn null_safe_slice<'a, T, L>(data: *const T, len: L) -> &'a [T]
225
14.1k
where
226
14.1k
    usize: TryFrom<L>,
227
{
228
14.1k
    let len = usize::try_from(len).unwrap_or_else(|_| panic!("null_safe_slice: size overflow"));
Unexecuted instantiation: nss_rs::null_safe_slice::<nss_rs::nss_prelude::SECItemStr, u32>::{closure#0}
Unexecuted instantiation: nss_rs::null_safe_slice::<u8, usize>::{closure#0}
Unexecuted instantiation: nss_rs::null_safe_slice::<u8, u32>::{closure#0}
229
14.1k
    if data.is_null() || len == 0 {
230
484
        &[]
231
    } else {
232
        unsafe {
233
            #[expect(clippy::disallowed_methods, reason = "This is non-null.")]
234
13.6k
            std::slice::from_raw_parts(data, len)
235
        }
236
    }
237
14.1k
}
Unexecuted instantiation: nss_rs::null_safe_slice::<nss_rs::nss_prelude::SECItemStr, u32>
nss_rs::null_safe_slice::<u8, usize>
Line
Count
Source
224
484
unsafe fn null_safe_slice<'a, T, L>(data: *const T, len: L) -> &'a [T]
225
484
where
226
484
    usize: TryFrom<L>,
227
{
228
484
    let len = usize::try_from(len).unwrap_or_else(|_| panic!("null_safe_slice: size overflow"));
229
484
    if data.is_null() || len == 0 {
230
484
        &[]
231
    } else {
232
        unsafe {
233
            #[expect(clippy::disallowed_methods, reason = "This is non-null.")]
234
0
            std::slice::from_raw_parts(data, len)
235
        }
236
    }
237
484
}
nss_rs::null_safe_slice::<u8, u32>
Line
Count
Source
224
13.6k
unsafe fn null_safe_slice<'a, T, L>(data: *const T, len: L) -> &'a [T]
225
13.6k
where
226
13.6k
    usize: TryFrom<L>,
227
{
228
13.6k
    let len = usize::try_from(len).unwrap_or_else(|_| panic!("null_safe_slice: size overflow"));
229
13.6k
    if data.is_null() || len == 0 {
230
0
        &[]
231
    } else {
232
        unsafe {
233
            #[expect(clippy::disallowed_methods, reason = "This is non-null.")]
234
13.6k
            std::slice::from_raw_parts(data, len)
235
        }
236
    }
237
13.6k
}