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/ech.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
    convert::TryFrom as _,
9
    ffi::CString,
10
    os::raw::{c_char, c_uint},
11
    ptr::{addr_of_mut, null_mut},
12
};
13
14
use log::trace;
15
use pkcs11_bindings::{CKF_DERIVE, CKM_EC_KEY_PAIR_GEN};
16
17
use crate::{
18
    SECItem, SECItemBorrowed, SECItemMut,
19
    err::{Error, Res, ssl::SSL_ERROR_ECH_RETRY_WITH_ECH},
20
    experimental_api, null_safe_slice,
21
    p11::{self, PrivateKey, PublicKey, SECKEYPrivateKey, SECKEYPublicKey, Slot},
22
    prio::PRFileDesc,
23
    ssl::PRBool,
24
};
25
pub use crate::{
26
    p11::{HpkeAeadId as AeadId, HpkeKdfId as KdfId, HpkeKemId as KemId},
27
    ssl::HpkeSymmetricSuite as SymmetricSuite,
28
};
29
30
experimental_api!(SSL_EnableTls13GreaseEch(
31
    fd: *mut PRFileDesc,
32
    enabled: PRBool,
33
));
34
35
experimental_api!(SSL_GetEchRetryConfigs(
36
    fd: *mut PRFileDesc,
37
    config: *mut SECItem,
38
));
39
40
experimental_api!(SSL_SetClientEchConfigs(
41
    fd: *mut PRFileDesc,
42
    config_list: *const u8,
43
    config_list_len: c_uint,
44
));
45
46
experimental_api!(SSL_SetServerEchConfigs(
47
    fd: *mut PRFileDesc,
48
    pk: *const SECKEYPublicKey,
49
    sk: *const SECKEYPrivateKey,
50
    record: *const u8,
51
    record_len: c_uint,
52
));
53
54
experimental_api!(SSL_EncodeEchConfigId(
55
    config_id: u8,
56
    public_name: *const c_char,
57
    max_name_len: c_uint,
58
    kem_id: KemId::Type,
59
    pk: *const SECKEYPublicKey,
60
    hpke_suites: *const SymmetricSuite,
61
    hpke_suite_count: c_uint,
62
    out: *mut u8,
63
    out_len: *mut c_uint,
64
    max_len: c_uint,
65
));
66
67
/// Convert any result that contains an ECH error into a result with an `EchRetry`.
68
0
pub fn convert_ech_error(fd: *mut PRFileDesc, err: Error) -> Error {
69
    if let Error::Nss {
70
        code: SSL_ERROR_ECH_RETRY_WITH_ECH,
71
        ..
72
0
    } = &err
73
    {
74
0
        let mut item = SECItemMut::make_empty();
75
0
        if unsafe { SSL_GetEchRetryConfigs(fd, item.as_mut()).is_err() } {
76
0
            return Error::Internal;
77
0
        }
78
0
        let buf = unsafe {
79
0
            let slc = null_safe_slice(item.as_ref().data, item.as_ref().len as usize);
80
0
            Vec::from(slc)
81
        };
82
0
        Error::EchRetry(buf)
83
    } else {
84
0
        err
85
    }
86
0
}
87
88
/// Generate a key pair for encrypted client hello (ECH).
89
///
90
/// # Errors
91
///
92
/// When NSS fails to generate a key pair or when the KEM is not supported.
93
///
94
/// # Panics
95
///
96
/// When underlying types aren't large enough to hold keys.  So never.
97
0
pub fn generate_keys() -> Res<(PrivateKey, PublicKey)> {
98
0
    let slot = Slot::internal()?;
99
100
0
    let oid_data = unsafe { p11::SECOID_FindOIDByTag(p11::SECOidTag::SEC_OID_CURVE25519) };
101
0
    let oid = unsafe { oid_data.as_ref() }.ok_or(Error::Internal)?;
102
0
    let oid_slc = unsafe { null_safe_slice(oid.oid.data, oid.oid.len) };
103
0
    let mut params: Vec<u8> = Vec::with_capacity(oid_slc.len() + 2);
104
0
    params.push(u8::try_from(p11::SEC_ASN1_OBJECT_ID)?);
105
0
    params.push(u8::try_from(oid.oid.len)?);
106
0
    params.extend_from_slice(oid_slc);
107
108
0
    let mut public_ptr: *mut SECKEYPublicKey = null_mut();
109
0
    let mut param_item = SECItemBorrowed::wrap(&params)?;
110
111
    // If we have tracing on, try to ensure that key data can be read.
112
0
    let insensitive_secret_ptr = if log::log_enabled!(log::Level::Trace) {
113
        unsafe {
114
0
            p11::PK11_GenerateKeyPairWithOpFlags(
115
0
                *slot,
116
0
                p11::CK_MECHANISM_TYPE::from(CKM_EC_KEY_PAIR_GEN),
117
0
                addr_of_mut!(param_item).cast(),
118
0
                &raw mut public_ptr,
119
0
                p11::PK11_ATTR_SESSION | p11::PK11_ATTR_INSENSITIVE | p11::PK11_ATTR_PUBLIC,
120
0
                p11::CK_FLAGS::from(CKF_DERIVE),
121
0
                p11::CK_FLAGS::from(CKF_DERIVE),
122
0
                null_mut(),
123
            )
124
        }
125
    } else {
126
0
        null_mut()
127
    };
128
0
    assert_eq!(insensitive_secret_ptr.is_null(), public_ptr.is_null());
129
0
    let secret_ptr = if insensitive_secret_ptr.is_null() {
130
        unsafe {
131
0
            p11::PK11_GenerateKeyPairWithOpFlags(
132
0
                *slot,
133
0
                p11::CK_MECHANISM_TYPE::from(CKM_EC_KEY_PAIR_GEN),
134
0
                addr_of_mut!(param_item).cast(),
135
0
                &raw mut public_ptr,
136
0
                p11::PK11_ATTR_SESSION | p11::PK11_ATTR_SENSITIVE | p11::PK11_ATTR_PRIVATE,
137
0
                p11::CK_FLAGS::from(CKF_DERIVE),
138
0
                p11::CK_FLAGS::from(CKF_DERIVE),
139
0
                null_mut(),
140
            )
141
        }
142
    } else {
143
0
        insensitive_secret_ptr
144
    };
145
0
    assert_eq!(secret_ptr.is_null(), public_ptr.is_null());
146
0
    let sk = PrivateKey::from_ptr(secret_ptr)?;
147
0
    let pk = PublicKey::from_ptr(public_ptr)?;
148
0
    trace!("Generated key pair: sk={sk:?} pk={pk:?}");
149
0
    Ok((sk, pk))
150
0
}
151
152
/// Encode a configuration for encrypted client hello (ECH).
153
///
154
/// # Errors
155
///
156
/// When NSS fails to generate a valid configuration encoding (i.e., unlikely).
157
0
pub fn encode_config(config: u8, public_name: &str, pk: &PublicKey) -> Res<Vec<u8>> {
158
    // A sensible fixed value for the maximum length of a name.
159
    const MAX_NAME_LEN: c_uint = 64;
160
    // Enable a selection of suites.
161
    // NSS supports SHA-512 as well, which could be added here.
162
    const SUITES: &[SymmetricSuite] = &[
163
        SymmetricSuite {
164
            kdfId: KdfId::HpkeKdfHkdfSha256,
165
            aeadId: AeadId::HpkeAeadAes128Gcm,
166
        },
167
        SymmetricSuite {
168
            kdfId: KdfId::HpkeKdfHkdfSha256,
169
            aeadId: AeadId::HpkeAeadChaCha20Poly1305,
170
        },
171
        SymmetricSuite {
172
            kdfId: KdfId::HpkeKdfHkdfSha384,
173
            aeadId: AeadId::HpkeAeadAes128Gcm,
174
        },
175
        SymmetricSuite {
176
            kdfId: KdfId::HpkeKdfHkdfSha384,
177
            aeadId: AeadId::HpkeAeadChaCha20Poly1305,
178
        },
179
    ];
180
181
0
    let name = CString::new(public_name)?;
182
0
    let mut encoded = [0; 1024];
183
0
    let mut encoded_len = 0;
184
    unsafe {
185
0
        SSL_EncodeEchConfigId(
186
0
            config,
187
0
            name.as_ptr(),
188
            MAX_NAME_LEN,
189
            KemId::HpkeDhKemX25519Sha256,
190
0
            **pk,
191
0
            SUITES.as_ptr(),
192
0
            c_uint::try_from(SUITES.len())?,
193
0
            encoded.as_mut_ptr(),
194
0
            &raw mut encoded_len,
195
0
            c_uint::try_from(encoded.len())?,
196
0
        )?;
197
    }
198
0
    Ok(Vec::from(&encoded[..usize::try_from(encoded_len)?]))
199
0
}