/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/agent.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 | | #![expect( |
8 | | clippy::unwrap_used, |
9 | | reason = "Let's assume the use of `unwrap` was checked when the use of `unsafe` was reviewed." |
10 | | )] |
11 | | |
12 | | use std::{ |
13 | | cell::RefCell, |
14 | | convert::{TryFrom as _, TryInto as _}, |
15 | | ffi::{CStr, CString}, |
16 | | fmt::{self, Debug, Display, Formatter, Write as _}, |
17 | | mem::MaybeUninit, |
18 | | ops::{Deref, DerefMut}, |
19 | | os::raw::{c_uint, c_void}, |
20 | | pin::Pin, |
21 | | ptr::{NonNull, null, null_mut}, |
22 | | rc::Rc, |
23 | | slice, |
24 | | time::Instant, |
25 | | }; |
26 | | |
27 | | use log::{debug, info, trace, warn}; |
28 | | |
29 | | use crate::{ |
30 | | SECItem, SECItemArray, SECItemBorrowed, SECStatus, |
31 | | agentio::{AgentIo, METHODS}, |
32 | | assert_initialized, |
33 | | auth::AuthenticationStatus, |
34 | | constants::{ |
35 | | Alert, Cipher, Epoch, Extension, Group, SignatureScheme, TLS_VERSION_1_3, Version, |
36 | | }, |
37 | | ech, |
38 | | err::{Error, PRErrorCode, Res, is_blocked, secstatus_to_res}, |
39 | | ext::{ExtensionHandler, ExtensionTracker, SSL_CallExtensionWriterOnEchInner}, |
40 | | nss_prelude::{SECItemStr, SECWouldBlock}, |
41 | | null_safe_slice, |
42 | | p11::{self, PrivateKey, PublicKey, hex_with_len}, |
43 | | prio, |
44 | | replay::AntiReplay, |
45 | | secrets::SecretHolder, |
46 | | ssl::{self, PRBool}, |
47 | | time::{Time, TimeHolder}, |
48 | | }; |
49 | | pub use crate::{ |
50 | | agentio::{Record, RecordList, as_c_void}, |
51 | | cert::CertificateInfo, |
52 | | }; |
53 | | |
54 | | /// Private trait for Certificate Compression implementation |
55 | | /// Use `SafeCertCompression` to implement an encoder/decoder instead. |
56 | | trait UnsafeCertCompression { |
57 | | extern "C" fn decode_callback( |
58 | | input: *const SECItem, |
59 | | output: *mut ::std::os::raw::c_uchar, |
60 | | output_len: usize, |
61 | | used_len: *mut usize, |
62 | | ) -> SECStatus; |
63 | | |
64 | | extern "C" fn encode_callback(input: *const SECItem, output: *mut SECItem) -> SECStatus; |
65 | | } |
66 | | |
67 | | /// The trait is used to represent a certificate compression data structure |
68 | | /// Used in order to enable Certificate Compression extension during TLS connection |
69 | | pub trait CertificateCompressor { |
70 | | /// Certificate Compression identifier as in RFC8879 |
71 | | const ID: u16; |
72 | | /// Certification Compression name (used only for logging/debugging) |
73 | | const NAME: &CStr; |
74 | | /// Certificate Compression could be used to encode and decode a certificate |
75 | | /// though the encoding is not frequently used |
76 | | /// Enable decoding field is used to signal to the implementation |
77 | | /// to use the encoding as well |
78 | | const ENABLE_ENCODING: bool = false; |
79 | | |
80 | | /// Certificate Compression encoding function |
81 | | /// |
82 | | /// This default implementation effectively does nothing. |
83 | | /// However, this is only run if `ENABLE_ENCODING` is `true`. |
84 | | /// Implementations that set `ENABLE_ENCODING` to `true` need to implement this function. |
85 | | /// |
86 | | /// # Errors |
87 | | /// Encoding was unsuccessful, for example, not enough memory |
88 | 0 | fn encode(input: &[u8], output: &mut [u8]) -> Res<usize> { |
89 | 0 | let len = std::cmp::min(input.len(), output.len()); |
90 | 0 | output[..len].copy_from_slice(&input[..len]); |
91 | 0 | Ok(len) |
92 | 0 | } |
93 | | |
94 | | /// Certificate Compression decoding function. |
95 | | /// # Errors |
96 | | /// Decoding was unsuccessful. |
97 | | /// We require a decoder internally to check the length of the decoded buffer. |
98 | | /// If the decoded length is not equal to the length of the provided slice |
99 | | /// the decoder should return an error. |
100 | | fn decode(input: &[u8], output: &mut [u8]) -> Res<()>; |
101 | | } |
102 | | |
103 | | /// The trait is responsible for calling `CertificateCompression` encoding and decoding |
104 | | /// functions using the NSS types |
105 | | impl<T: CertificateCompressor> UnsafeCertCompression for T { |
106 | 0 | extern "C" fn decode_callback( |
107 | 0 | input: *const SECItem, |
108 | 0 | output: *mut ::std::os::raw::c_uchar, |
109 | 0 | output_len: usize, |
110 | 0 | used_len: *mut usize, |
111 | 0 | ) -> SECStatus { |
112 | 0 | let Some(input) = NonNull::new(input.cast_mut()) else { |
113 | 0 | return ssl::SECFailure; |
114 | | }; |
115 | 0 | if unsafe { input.as_ref().data.is_null() || input.as_ref().len == 0 } { |
116 | 0 | return ssl::SECFailure; |
117 | 0 | } |
118 | | |
119 | 0 | let input_slice = unsafe { null_safe_slice(input.as_ref().data, input.as_ref().len) }; |
120 | 0 | let output_slice = unsafe { slice::from_raw_parts_mut(output, output_len) }; |
121 | | |
122 | 0 | if T::decode(input_slice, output_slice).is_err() { |
123 | 0 | return ssl::SECFailure; |
124 | 0 | } |
125 | | |
126 | 0 | unsafe { |
127 | 0 | *used_len = output_len; |
128 | 0 | } |
129 | 0 | ssl::SECSuccess |
130 | 0 | } |
131 | | |
132 | 0 | extern "C" fn encode_callback(input: *const SECItem, output: *mut SECItem) -> SECStatus { |
133 | 0 | let Some(input) = NonNull::new(input.cast_mut()) else { |
134 | 0 | return ssl::SECFailure; |
135 | | }; |
136 | | |
137 | 0 | let (input_data, input_len) = unsafe { |
138 | 0 | let input_ref = input.as_ref(); |
139 | 0 | (input_ref.data, input_ref.len) |
140 | 0 | }; |
141 | | |
142 | 0 | if input_data.is_null() || input_len == 0 { |
143 | 0 | return ssl::SECFailure; |
144 | 0 | } |
145 | 0 | let input_slice = unsafe { null_safe_slice(input_data, input_len) }; |
146 | | |
147 | 0 | unsafe { |
148 | 0 | p11::SECITEM_AllocItem( |
149 | 0 | null_mut(), |
150 | 0 | // p11::SECItem is the same as ssl::SECItem |
151 | 0 | output.cast::<SECItemStr>(), |
152 | 0 | // Compression shouldn't make the thing *longer*, |
153 | 0 | // but allocate one extra byte anyway to enable simple testing modes. |
154 | 0 | input_len + 1, |
155 | 0 | ); |
156 | 0 | } |
157 | | |
158 | 0 | if unsafe { (*output).data.is_null() } { |
159 | 0 | return ssl::SECFailure; |
160 | 0 | } |
161 | | |
162 | 0 | let Ok(output_len) = (unsafe { (*output).len.try_into() }) else { |
163 | 0 | return ssl::SECFailure; |
164 | | }; |
165 | | |
166 | 0 | let output_slice = unsafe { slice::from_raw_parts_mut((*output).data, output_len) }; |
167 | | |
168 | 0 | let Ok(encoded_len) = T::encode(input_slice, output_slice) else { |
169 | 0 | return ssl::SECFailure; |
170 | | }; |
171 | | |
172 | 0 | if encoded_len == 0 || encoded_len > output_len { |
173 | 0 | return ssl::SECFailure; |
174 | 0 | } |
175 | | |
176 | 0 | let Ok(encoded_len) = encoded_len.try_into() else { |
177 | 0 | return ssl::SECFailure; |
178 | | }; |
179 | | |
180 | 0 | unsafe { |
181 | 0 | (*output).len = encoded_len; |
182 | 0 | } |
183 | 0 | ssl::SECSuccess |
184 | 0 | } |
185 | | } |
186 | | |
187 | | /// The maximum number of tickets to remember for a given connection. |
188 | | const MAX_TICKETS: usize = 4; |
189 | | |
190 | | #[must_use] |
191 | 0 | pub fn hex_snip_middle<A: AsRef<[u8]>>(buf: A) -> String { |
192 | | const SHOW_LEN: usize = 8; |
193 | 0 | let buf = buf.as_ref(); |
194 | 0 | if buf.len() <= SHOW_LEN * 2 { |
195 | 0 | hex_with_len(buf) |
196 | | } else { |
197 | 0 | let mut ret = String::with_capacity(SHOW_LEN * 2 + 16); |
198 | 0 | write!(&mut ret, "[{}]: ", buf.len()).expect("write OK"); |
199 | 0 | for b in &buf[..SHOW_LEN] { |
200 | 0 | write!(&mut ret, "{b:02x}").expect("write OK"); |
201 | 0 | } |
202 | 0 | ret.push_str(".."); |
203 | 0 | for b in &buf[buf.len() - SHOW_LEN..] { |
204 | 0 | write!(&mut ret, "{b:02x}").expect("write OK"); |
205 | 0 | } |
206 | 0 | ret |
207 | | } |
208 | 0 | } |
209 | | |
210 | | #[derive(Clone, Debug, PartialEq, Eq)] |
211 | | pub enum HandshakeState { |
212 | | New, |
213 | | InProgress, |
214 | | AuthenticationPending, |
215 | | /// When encrypted client hello is enabled, the server might engage a fallback. |
216 | | /// This is the status that is returned. The included value is the public |
217 | | /// name of the server, which should be used to validate the certificate. |
218 | | EchFallbackAuthenticationPending(String), |
219 | | Authenticated(PRErrorCode), |
220 | | Complete(SecretAgentInfo), |
221 | | Failed(Error), |
222 | | } |
223 | | |
224 | | impl HandshakeState { |
225 | | #[must_use] |
226 | 0 | pub const fn is_connected(&self) -> bool { |
227 | 0 | matches!(self, Self::Complete(_)) |
228 | 0 | } |
229 | | |
230 | | #[must_use] |
231 | 484 | pub const fn is_final(&self) -> bool { |
232 | 484 | matches!(self, Self::Complete(_) | Self::Failed(_)) |
233 | 484 | } |
234 | | |
235 | | #[must_use] |
236 | 0 | pub const fn authentication_needed(&self) -> bool { |
237 | 0 | matches!( |
238 | 0 | self, |
239 | | Self::AuthenticationPending | Self::EchFallbackAuthenticationPending(_) |
240 | | ) |
241 | 0 | } |
242 | | } |
243 | | |
244 | 2.25k | fn get_alpn(fd: *mut prio::PRFileDesc, pre: bool) -> Res<Option<String>> { |
245 | 2.25k | let mut alpn_state = ssl::SSLNextProtoState::SSL_NEXT_PROTO_NO_SUPPORT; |
246 | 2.25k | let mut chosen = vec![0_u8; 255]; |
247 | 2.25k | let mut chosen_len: c_uint = 0; |
248 | 2.25k | secstatus_to_res(unsafe { |
249 | 2.25k | ssl::SSL_GetNextProto( |
250 | 2.25k | fd, |
251 | 2.25k | &raw mut alpn_state, |
252 | 2.25k | chosen.as_mut_ptr(), |
253 | 2.25k | &raw mut chosen_len, |
254 | 2.25k | c_uint::try_from(chosen.len())?, |
255 | | ) |
256 | 0 | })?; |
257 | | |
258 | 2.25k | let alpn = match (pre, alpn_state) { |
259 | | (true, ssl::SSLNextProtoState::SSL_NEXT_PROTO_EARLY_VALUE) |
260 | | | ( |
261 | | false, |
262 | | ssl::SSLNextProtoState::SSL_NEXT_PROTO_NEGOTIATED |
263 | | | ssl::SSLNextProtoState::SSL_NEXT_PROTO_SELECTED, |
264 | | ) => { |
265 | 0 | chosen.truncate(usize::try_from(chosen_len)?); |
266 | 0 | Some(match String::from_utf8(chosen) { |
267 | 0 | Ok(a) => a, |
268 | 0 | Err(_) => return Err(Error::Internal), |
269 | | }) |
270 | | } |
271 | 2.25k | _ => None, |
272 | | }; |
273 | 2.25k | trace!("[{fd:p}] got ALPN {alpn:?}"); |
274 | 2.25k | Ok(alpn) |
275 | 2.25k | } |
276 | | |
277 | | pub struct SecretAgentPreInfo { |
278 | | info: ssl::SSLPreliminaryChannelInfo, |
279 | | alpn: Option<String>, |
280 | | } |
281 | | |
282 | | macro_rules! preinfo_arg { |
283 | | ($v:ident, $m:ident, $f:ident: $t:ty $(,)?) => { |
284 | | #[must_use] |
285 | 2.25k | pub fn $v(&self) -> Option<$t> { |
286 | 2.25k | match self.info.valuesSet & ssl::$m { |
287 | 1.77k | 0 => None, |
288 | | _ => Some( |
289 | 484 | <$t>::try_from(self.info.$f) |
290 | 484 | .inspect_err(|e| debug!("Invalid value in preinfo: {e:?}"))Unexecuted instantiation: <nss_rs::agent::SecretAgentPreInfo>::cipher_suite::{closure#0}Unexecuted instantiation: <nss_rs::agent::SecretAgentPreInfo>::early_data_cipher::{closure#0}Unexecuted instantiation: <nss_rs::agent::SecretAgentPreInfo>::version::{closure#0} |
291 | 484 | .ok()?, |
292 | | ), |
293 | | } |
294 | 2.25k | } <nss_rs::agent::SecretAgentPreInfo>::cipher_suite Line | Count | Source | 285 | 484 | pub fn $v(&self) -> Option<$t> { | 286 | 484 | match self.info.valuesSet & ssl::$m { | 287 | 0 | 0 => None, | 288 | | _ => Some( | 289 | 484 | <$t>::try_from(self.info.$f) | 290 | 484 | .inspect_err(|e| debug!("Invalid value in preinfo: {e:?}")) | 291 | 484 | .ok()?, | 292 | | ), | 293 | | } | 294 | 484 | } |
<nss_rs::agent::SecretAgentPreInfo>::early_data_cipher Line | Count | Source | 285 | 1.77k | pub fn $v(&self) -> Option<$t> { | 286 | 1.77k | match self.info.valuesSet & ssl::$m { | 287 | 1.77k | 0 => None, | 288 | | _ => Some( | 289 | 0 | <$t>::try_from(self.info.$f) | 290 | 0 | .inspect_err(|e| debug!("Invalid value in preinfo: {e:?}")) | 291 | 0 | .ok()?, | 292 | | ), | 293 | | } | 294 | 1.77k | } |
Unexecuted instantiation: <nss_rs::agent::SecretAgentPreInfo>::version |
295 | | }; |
296 | | } |
297 | | |
298 | | impl SecretAgentPreInfo { |
299 | 2.25k | fn new(fd: *mut prio::PRFileDesc) -> Res<Self> { |
300 | 2.25k | let mut info: MaybeUninit<ssl::SSLPreliminaryChannelInfo> = MaybeUninit::uninit(); |
301 | 2.25k | secstatus_to_res(unsafe { |
302 | 2.25k | ssl::SSL_GetPreliminaryChannelInfo( |
303 | 2.25k | fd, |
304 | 2.25k | info.as_mut_ptr(), |
305 | 2.25k | c_uint::try_from(size_of::<ssl::SSLPreliminaryChannelInfo>())?, |
306 | | ) |
307 | 0 | })?; |
308 | | |
309 | | Ok(Self { |
310 | 2.25k | info: unsafe { info.assume_init() }, |
311 | 2.25k | alpn: get_alpn(fd, true)?, |
312 | | }) |
313 | 2.25k | } |
314 | | |
315 | | preinfo_arg!(version, ssl_preinfo_version, protocolVersion: Version); |
316 | | preinfo_arg!(cipher_suite, ssl_preinfo_cipher_suite, cipherSuite: Cipher); |
317 | | preinfo_arg!( |
318 | | early_data_cipher, |
319 | | ssl_preinfo_0rtt_cipher_suite, |
320 | | zeroRttCipherSuite: Cipher, |
321 | | ); |
322 | | |
323 | | #[must_use] |
324 | 0 | pub const fn early_data(&self) -> bool { |
325 | 0 | self.info.canSendEarlyData != 0 |
326 | 0 | } |
327 | | |
328 | | /// # Errors |
329 | | /// |
330 | | /// If `usize` is less than 32 bits and the value is too large. |
331 | 0 | pub fn max_early_data(&self) -> Res<usize> { |
332 | 0 | Ok(usize::try_from(self.info.maxEarlyDataSize)?) |
333 | 0 | } |
334 | | |
335 | | /// Was ECH accepted. |
336 | | #[must_use] |
337 | 0 | pub const fn ech_accepted(&self) -> Option<bool> { |
338 | 0 | if self.info.valuesSet & ssl::ssl_preinfo_ech == 0 { |
339 | 0 | None |
340 | | } else { |
341 | 0 | Some(self.info.echAccepted != 0) |
342 | | } |
343 | 0 | } |
344 | | |
345 | | /// Get the ECH public name that was used. This will only be available |
346 | | /// (that is, not `None`) if `ech_accepted()` returns `false`. |
347 | | /// In this case, certificate validation needs to use this name rather |
348 | | /// than the original name to validate the certificate. If |
349 | | /// that validation passes (that is, `SecretAgent::authenticated` is called |
350 | | /// with `AuthenticationStatus::Ok`), then the handshake will still fail. |
351 | | /// After the failed handshake, the state will be `Error::EchRetry`, |
352 | | /// which contains a valid ECH configuration. |
353 | | /// |
354 | | /// # Errors |
355 | | /// |
356 | | /// When the public name is not valid UTF-8. (Note: names should be ASCII.) |
357 | 0 | pub fn ech_public_name(&self) -> Res<Option<&str>> { |
358 | 0 | if self.info.valuesSet & ssl::ssl_preinfo_ech == 0 || self.info.echPublicName.is_null() { |
359 | 0 | Ok(None) |
360 | | } else { |
361 | 0 | let n = unsafe { CStr::from_ptr(self.info.echPublicName) }; |
362 | 0 | Ok(Some(n.to_str()?)) |
363 | | } |
364 | 0 | } |
365 | | |
366 | | #[must_use] |
367 | 0 | pub fn alpn(&self) -> Option<&str> { |
368 | 0 | self.alpn.as_deref() |
369 | 0 | } |
370 | | } |
371 | | |
372 | | #[derive(Clone, Debug, Default, PartialEq, Eq)] |
373 | | pub struct SecretAgentInfo { |
374 | | version: Version, |
375 | | cipher: Cipher, |
376 | | group: Group, |
377 | | resumed: bool, |
378 | | early_data: bool, |
379 | | ech_accepted: bool, |
380 | | alpn: Option<String>, |
381 | | signature_scheme: SignatureScheme, |
382 | | } |
383 | | |
384 | | impl SecretAgentInfo { |
385 | 0 | fn new(fd: *mut prio::PRFileDesc) -> Res<Self> { |
386 | 0 | let mut info: MaybeUninit<ssl::SSLChannelInfo> = MaybeUninit::uninit(); |
387 | 0 | secstatus_to_res(unsafe { |
388 | 0 | ssl::SSL_GetChannelInfo( |
389 | 0 | fd, |
390 | 0 | info.as_mut_ptr(), |
391 | 0 | c_uint::try_from(size_of::<ssl::SSLChannelInfo>())?, |
392 | | ) |
393 | 0 | })?; |
394 | 0 | let info = unsafe { info.assume_init() }; |
395 | | Ok(Self { |
396 | 0 | version: info.protocolVersion, |
397 | 0 | cipher: info.cipherSuite, |
398 | 0 | group: Group::try_from(info.keaGroup)?, |
399 | 0 | resumed: info.resumed != 0, |
400 | 0 | early_data: info.earlyDataAccepted != 0, |
401 | 0 | ech_accepted: info.echAccepted != 0, |
402 | 0 | alpn: get_alpn(fd, false)?, |
403 | 0 | signature_scheme: SignatureScheme::try_from(info.signatureScheme)?, |
404 | | }) |
405 | 0 | } |
406 | | #[must_use] |
407 | 0 | pub const fn version(&self) -> Version { |
408 | 0 | self.version |
409 | 0 | } |
410 | | #[must_use] |
411 | 0 | pub const fn cipher_suite(&self) -> Cipher { |
412 | 0 | self.cipher |
413 | 0 | } |
414 | | #[must_use] |
415 | 0 | pub const fn key_exchange(&self) -> Group { |
416 | 0 | self.group |
417 | 0 | } |
418 | | #[must_use] |
419 | 0 | pub const fn resumed(&self) -> bool { |
420 | 0 | self.resumed |
421 | 0 | } |
422 | | #[must_use] |
423 | 0 | pub const fn early_data_accepted(&self) -> bool { |
424 | 0 | self.early_data |
425 | 0 | } |
426 | | #[must_use] |
427 | 0 | pub const fn ech_accepted(&self) -> bool { |
428 | 0 | self.ech_accepted |
429 | 0 | } |
430 | | #[must_use] |
431 | 0 | pub fn alpn(&self) -> Option<&str> { |
432 | 0 | self.alpn.as_deref() |
433 | 0 | } |
434 | | #[must_use] |
435 | 0 | pub const fn signature_scheme(&self) -> SignatureScheme { |
436 | 0 | self.signature_scheme |
437 | 0 | } |
438 | | } |
439 | | |
440 | | /// `SecretAgent` holds the common parts of client and server. |
441 | | #[derive(Debug)] |
442 | | #[expect(clippy::module_name_repetitions, reason = "This is OK.")] |
443 | | pub struct SecretAgent { |
444 | | fd: *mut prio::PRFileDesc, |
445 | | secrets: SecretHolder, |
446 | | raw: Option<bool>, |
447 | | io: Pin<Box<AgentIo>>, |
448 | | state: HandshakeState, |
449 | | |
450 | | /// Records whether authentication of certificates is required. |
451 | | auth_required: Pin<Box<bool>>, |
452 | | /// Records any fatal alert that is sent by the stack. |
453 | | alert: Pin<Box<Option<Alert>>>, |
454 | | /// The current time. |
455 | | now: TimeHolder, |
456 | | |
457 | | extension_handlers: Vec<ExtensionTracker>, |
458 | | |
459 | | /// The encrypted client hello (ECH) configuration that is in use. |
460 | | /// Empty if ECH is not enabled. |
461 | | ech_config: Vec<u8>, |
462 | | } |
463 | | |
464 | | impl SecretAgent { |
465 | 2.57k | fn new() -> Res<Self> { |
466 | 2.57k | let mut io = Box::pin(AgentIo::default()); |
467 | 2.57k | let fd = Self::create_fd(&mut io)?; |
468 | 2.57k | Ok(Self { |
469 | 2.57k | fd, |
470 | 2.57k | secrets: SecretHolder::default(), |
471 | 2.57k | raw: None, |
472 | 2.57k | io, |
473 | 2.57k | state: HandshakeState::New, |
474 | 2.57k | |
475 | 2.57k | auth_required: Box::pin(false), |
476 | 2.57k | alert: Box::pin(None), |
477 | 2.57k | now: TimeHolder::default(), |
478 | 2.57k | |
479 | 2.57k | extension_handlers: Vec::new(), |
480 | 2.57k | |
481 | 2.57k | ech_config: Vec::new(), |
482 | 2.57k | }) |
483 | 2.57k | } |
484 | | |
485 | | // Create a new SSL file descriptor. |
486 | | // |
487 | | // Note that we create separate bindings for PRFileDesc as both |
488 | | // ssl::PRFileDesc and prio::PRFileDesc. This keeps the bindings |
489 | | // minimal, but it means that the two forms need casts to translate |
490 | | // between them. ssl::PRFileDesc is left as an opaque type, as the |
491 | | // ssl::SSL_* APIs only need an opaque type. |
492 | 2.57k | fn create_fd(io: &mut Pin<Box<AgentIo>>) -> Res<*mut prio::PRFileDesc> { |
493 | 2.57k | assert_initialized(); |
494 | 2.57k | let label = CString::new("sslwrapper")?; |
495 | 2.57k | let id = unsafe { prio::PR_GetUniqueIdentity(label.as_ptr()) }; |
496 | | |
497 | 2.57k | let base_fd = unsafe { prio::PR_CreateIOLayerStub(id, METHODS) }; |
498 | 2.57k | if base_fd.is_null() { |
499 | 0 | return Err(Error::CreateSslSocket); |
500 | 2.57k | } |
501 | 2.57k | let fd = unsafe { |
502 | 2.57k | (*base_fd).secret = as_c_void(io).cast(); |
503 | 2.57k | ssl::SSL_ImportFD(null_mut(), base_fd.cast()) |
504 | | }; |
505 | 2.57k | if fd.is_null() { |
506 | 0 | unsafe { |
507 | 0 | prio::PR_Close(base_fd); |
508 | 0 | } |
509 | 0 | return Err(Error::CreateSslSocket); |
510 | 2.57k | } |
511 | 2.57k | Ok(fd) |
512 | 2.57k | } |
513 | | |
514 | 0 | unsafe extern "C" fn auth_complete_hook( |
515 | 0 | arg: *mut c_void, |
516 | 0 | _fd: *mut prio::PRFileDesc, |
517 | 0 | _check_sig: PRBool, |
518 | 0 | _is_server: PRBool, |
519 | 0 | ) -> SECStatus { |
520 | 0 | let auth_required_ptr = arg.cast::<bool>(); |
521 | 0 | unsafe { |
522 | 0 | *auth_required_ptr = true; |
523 | 0 | } |
524 | | // NSS insists on getting SECWouldBlock here rather than accepting |
525 | | // the usual combination of PR_WOULD_BLOCK_ERROR and SECFailure. |
526 | 0 | SECWouldBlock |
527 | 0 | } |
528 | | |
529 | 0 | unsafe extern "C" fn alert_sent_cb( |
530 | 0 | fd: *const prio::PRFileDesc, |
531 | 0 | arg: *mut c_void, |
532 | 0 | alert: *const ssl::SSLAlert, |
533 | 0 | ) { |
534 | 0 | let alert = unsafe { alert.as_ref().unwrap() }; |
535 | 0 | if alert.level == 2 { |
536 | | // Fatal alerts demand attention. |
537 | 0 | let st = unsafe { arg.cast::<Option<Alert>>().as_mut().unwrap() }; |
538 | 0 | if st.is_none() { |
539 | 0 | *st = Some(alert.description); |
540 | 0 | } else { |
541 | 0 | warn!("[{fd:p}] duplicate alert {}", alert.description); |
542 | | } |
543 | 0 | } |
544 | 0 | } |
545 | | |
546 | | // Ready this for connecting. |
547 | 2.57k | fn ready(&mut self, is_server: bool, grease: bool) -> Res<()> { |
548 | 2.57k | secstatus_to_res(unsafe { |
549 | 2.57k | ssl::SSL_AuthCertificateHook( |
550 | 2.57k | self.fd, |
551 | 2.57k | Some(Self::auth_complete_hook), |
552 | 2.57k | as_c_void(&mut self.auth_required), |
553 | | ) |
554 | 0 | })?; |
555 | | |
556 | 2.57k | secstatus_to_res(unsafe { |
557 | 2.57k | ssl::SSL_AlertSentCallback( |
558 | 2.57k | self.fd, |
559 | 2.57k | Some(Self::alert_sent_cb), |
560 | 2.57k | as_c_void(&mut self.alert), |
561 | | ) |
562 | 0 | })?; |
563 | | |
564 | 2.57k | self.now.bind(self.fd)?; |
565 | 2.57k | self.configure(grease)?; |
566 | 2.57k | secstatus_to_res(unsafe { ssl::SSL_ResetHandshake(self.fd, PRBool::from(is_server)) }) |
567 | 2.57k | } |
568 | | |
569 | | /// Default configuration. |
570 | | /// |
571 | | /// # Errors |
572 | | /// |
573 | | /// If `set_version_range` fails. |
574 | 2.57k | fn configure(&mut self, grease: bool) -> Res<()> { |
575 | 2.57k | self.set_version_range(TLS_VERSION_1_3, TLS_VERSION_1_3)?; |
576 | 2.57k | self.set_option(ssl::Opt::Locking, false)?; |
577 | 2.57k | self.set_option(ssl::Opt::Tickets, false)?; |
578 | 2.57k | self.set_option(ssl::Opt::OcspStapling, true)?; |
579 | 2.57k | self.set_option( |
580 | 2.57k | ssl::Opt::Grease, |
581 | 2.57k | cfg!(not(feature = "disable-random")) && grease, |
582 | 0 | )?; |
583 | 2.57k | self.set_option( |
584 | 2.57k | ssl::Opt::EnableChExtensionPermutation, |
585 | | cfg!(not(feature = "disable-random")), |
586 | 0 | )?; |
587 | 2.57k | Ok(()) |
588 | 2.57k | } |
589 | | |
590 | | /// Set the versions that are supported. |
591 | | /// |
592 | | /// # Errors |
593 | | /// |
594 | | /// If the range of versions isn't supported. |
595 | 5.15k | pub fn set_version_range(&mut self, min: Version, max: Version) -> Res<()> { |
596 | 5.15k | let range = ssl::SSLVersionRange { min, max }; |
597 | 5.15k | secstatus_to_res(unsafe { ssl::SSL_VersionRangeSet(self.fd, &raw const range) }) |
598 | 5.15k | } |
599 | | |
600 | | /// Enable a set of ciphers. Note that the order of these is not respected. |
601 | | /// |
602 | | /// # Errors |
603 | | /// |
604 | | /// If NSS can't enable or disable ciphers. |
605 | 2.57k | pub fn set_ciphers(&mut self, ciphers: &[Cipher]) -> Res<()> { |
606 | 2.57k | if self.state != HandshakeState::New { |
607 | 0 | warn!("[{self}] Cannot enable ciphers in state {:?}", self.state); |
608 | 0 | return Err(Error::Internal); |
609 | 2.57k | } |
610 | | |
611 | 2.57k | let all_ciphers = unsafe { ssl::SSL_GetImplementedCiphers() }; |
612 | 2.57k | let cipher_count = usize::from(unsafe { ssl::SSL_GetNumImplementedCiphers() }); |
613 | 183k | for i in 0..cipher_count { |
614 | 183k | let p = all_ciphers.wrapping_add(i); |
615 | 183k | secstatus_to_res(unsafe { |
616 | 183k | ssl::SSL_CipherPrefSet(self.fd, i32::from(*p), PRBool::from(false)) |
617 | 0 | })?; |
618 | | } |
619 | | |
620 | 7.73k | for c in ciphers { |
621 | 7.73k | secstatus_to_res(unsafe { |
622 | 7.73k | ssl::SSL_CipherPrefSet(self.fd, i32::from(*c), PRBool::from(true)) |
623 | 0 | })?; |
624 | | } |
625 | 2.57k | Ok(()) |
626 | 2.57k | } |
627 | | |
628 | | /// Set key exchange groups. |
629 | | /// |
630 | | /// # Errors |
631 | | /// |
632 | | /// If the underlying API fails (which shouldn't happen). |
633 | 2.57k | pub fn set_groups(&mut self, groups: &[Group]) -> Res<()> { |
634 | | // SSLNamedGroup is a different size to Group, so copy one by one. |
635 | 2.57k | let group_vec: Vec<_> = groups |
636 | 2.57k | .iter() |
637 | 10.3k | .map(|&g| ssl::SSLNamedGroup::Type::from(g)) |
638 | 2.57k | .collect(); |
639 | | |
640 | 2.57k | let ptr = group_vec.as_slice().as_ptr(); |
641 | 2.57k | secstatus_to_res(unsafe { |
642 | 2.57k | ssl::SSL_NamedGroupConfig(self.fd, ptr, c_uint::try_from(group_vec.len())?) |
643 | | }) |
644 | 2.57k | } |
645 | | |
646 | | /// Set the number of additional key shares that will be sent in the client hello |
647 | | /// |
648 | | /// # Errors |
649 | | /// |
650 | | /// If the underlying API fails (which shouldn't happen). |
651 | 1.28k | pub fn send_additional_key_shares(&mut self, count: usize) -> Res<()> { |
652 | 1.28k | secstatus_to_res(unsafe { |
653 | 1.28k | ssl::SSL_SendAdditionalKeyShares(self.fd, c_uint::try_from(count)?) |
654 | | }) |
655 | 1.28k | } |
656 | | |
657 | | /// Set TLS options. |
658 | | /// |
659 | | /// # Errors |
660 | | /// |
661 | | /// Returns an error if the option or option value is invalid; i.e., never. |
662 | 18.0k | pub fn set_option(&self, opt: ssl::Opt, value: bool) -> Res<()> { |
663 | 18.0k | opt.set(self.fd, value) |
664 | 18.0k | } |
665 | | |
666 | | /// Enable 0-RTT. |
667 | | /// |
668 | | /// # Errors |
669 | | /// |
670 | | /// See `set_option`. |
671 | 2.57k | pub fn enable_0rtt(&self) -> Res<()> { |
672 | 2.57k | self.set_option(ssl::Opt::EarlyData, true) |
673 | 2.57k | } |
674 | | |
675 | | /// Disable the `EndOfEarlyData` message. |
676 | | /// |
677 | | /// # Errors |
678 | | /// |
679 | | /// See `set_option`. |
680 | 2.57k | pub fn disable_end_of_early_data(&self) -> Res<()> { |
681 | 2.57k | self.set_option(ssl::Opt::SuppressEndOfEarlyData, true) |
682 | 2.57k | } |
683 | | |
684 | | /// `set_alpn` sets a list of preferred protocols, starting with the most preferred. |
685 | | /// Though ALPN [RFC7301] permits octet sequences, this only allows for UTF-8-encoded |
686 | | /// strings. |
687 | | /// |
688 | | /// This asserts if no items are provided, or if any individual item is longer than |
689 | | /// 255 octets in length. |
690 | | /// |
691 | | /// # Errors |
692 | | /// |
693 | | /// If the list of protocols is empty, contains an empty value, or |
694 | | /// contains a value longer than 255 bytes. |
695 | | /// |
696 | | /// [RFC7301]: https://datatracker.ietf.org/doc/html/rfc7301 |
697 | 2.57k | pub fn set_alpn<A: AsRef<[u8]>>(&mut self, protocols: &[A]) -> Res<()> { |
698 | | // Prepare to encode. |
699 | 2.57k | let len = protocols.len() + protocols.iter().map(|p| p.as_ref().len()).sum::<usize>(); <nss_rs::agent::SecretAgent>::set_alpn::<alloc::string::String>::{closure#0}Line | Count | Source | 699 | 2.57k | let len = protocols.len() + protocols.iter().map(|p| p.as_ref().len()).sum::<usize>(); |
Unexecuted instantiation: <nss_rs::agent::SecretAgent>::set_alpn::<_>::{closure#0} |
700 | 2.57k | let mut encoded = Vec::with_capacity(len); |
701 | 2.57k | let mut add = |v: &A| -> Res<()> { |
702 | 2.57k | let v = v.as_ref(); |
703 | 2.57k | u8::try_from(v.len()).map_or(Err(Error::InvalidAlpn), |s| { |
704 | 2.57k | if s > 0 { |
705 | 2.57k | encoded.push(s); |
706 | 2.57k | encoded.extend_from_slice(v); |
707 | 2.57k | Ok(()) |
708 | | } else { |
709 | 0 | Err(Error::InvalidAlpn) |
710 | | } |
711 | 2.57k | }) <nss_rs::agent::SecretAgent>::set_alpn::<alloc::string::String>::{closure#1}::{closure#0}Line | Count | Source | 703 | 2.57k | u8::try_from(v.len()).map_or(Err(Error::InvalidAlpn), |s| { | 704 | 2.57k | if s > 0 { | 705 | 2.57k | encoded.push(s); | 706 | 2.57k | encoded.extend_from_slice(v); | 707 | 2.57k | Ok(()) | 708 | | } else { | 709 | 0 | Err(Error::InvalidAlpn) | 710 | | } | 711 | 2.57k | }) |
Unexecuted instantiation: <nss_rs::agent::SecretAgent>::set_alpn::<_>::{closure#1}::{closure#0} |
712 | 2.57k | }; <nss_rs::agent::SecretAgent>::set_alpn::<alloc::string::String>::{closure#1}Line | Count | Source | 701 | 2.57k | let mut add = |v: &A| -> Res<()> { | 702 | 2.57k | let v = v.as_ref(); | 703 | 2.57k | u8::try_from(v.len()).map_or(Err(Error::InvalidAlpn), |s| { | 704 | | if s > 0 { | 705 | | encoded.push(s); | 706 | | encoded.extend_from_slice(v); | 707 | | Ok(()) | 708 | | } else { | 709 | | Err(Error::InvalidAlpn) | 710 | | } | 711 | | }) | 712 | 2.57k | }; |
Unexecuted instantiation: <nss_rs::agent::SecretAgent>::set_alpn::<_>::{closure#1} |
713 | | |
714 | | // NSS inherited an idiosyncratic API as a result of having implemented NPN |
715 | | // before ALPN. For that reason, we need to put the "best" option last. |
716 | 2.57k | let (first, rest) = protocols.split_first().ok_or(Error::InvalidAlpn)?; |
717 | 2.57k | for v in rest { |
718 | 0 | add(v)?; |
719 | | } |
720 | 2.57k | add(first)?; |
721 | 2.57k | debug_assert_eq!(len, encoded.len()); |
722 | | |
723 | | // Now give the result to NSS. |
724 | 2.57k | secstatus_to_res(unsafe { |
725 | 2.57k | ssl::SSL_SetNextProtoNego( |
726 | 2.57k | self.fd, |
727 | 2.57k | encoded.as_slice().as_ptr(), |
728 | 2.57k | c_uint::try_from(encoded.len())?, |
729 | | ) |
730 | | }) |
731 | 2.57k | } <nss_rs::agent::SecretAgent>::set_alpn::<alloc::string::String> Line | Count | Source | 697 | 2.57k | pub fn set_alpn<A: AsRef<[u8]>>(&mut self, protocols: &[A]) -> Res<()> { | 698 | | // Prepare to encode. | 699 | 2.57k | let len = protocols.len() + protocols.iter().map(|p| p.as_ref().len()).sum::<usize>(); | 700 | 2.57k | let mut encoded = Vec::with_capacity(len); | 701 | 2.57k | let mut add = |v: &A| -> Res<()> { | 702 | | let v = v.as_ref(); | 703 | | u8::try_from(v.len()).map_or(Err(Error::InvalidAlpn), |s| { | 704 | | if s > 0 { | 705 | | encoded.push(s); | 706 | | encoded.extend_from_slice(v); | 707 | | Ok(()) | 708 | | } else { | 709 | | Err(Error::InvalidAlpn) | 710 | | } | 711 | | }) | 712 | | }; | 713 | | | 714 | | // NSS inherited an idiosyncratic API as a result of having implemented NPN | 715 | | // before ALPN. For that reason, we need to put the "best" option last. | 716 | 2.57k | let (first, rest) = protocols.split_first().ok_or(Error::InvalidAlpn)?; | 717 | 2.57k | for v in rest { | 718 | 0 | add(v)?; | 719 | | } | 720 | 2.57k | add(first)?; | 721 | 2.57k | debug_assert_eq!(len, encoded.len()); | 722 | | | 723 | | // Now give the result to NSS. | 724 | 2.57k | secstatus_to_res(unsafe { | 725 | 2.57k | ssl::SSL_SetNextProtoNego( | 726 | 2.57k | self.fd, | 727 | 2.57k | encoded.as_slice().as_ptr(), | 728 | 2.57k | c_uint::try_from(encoded.len())?, | 729 | | ) | 730 | | }) | 731 | 2.57k | } |
Unexecuted instantiation: <nss_rs::agent::SecretAgent>::set_alpn::<_> |
732 | | |
733 | | /// Install a certificate compression mechanism. |
734 | | /// |
735 | | /// # Errors |
736 | | /// If the compression mechanism with the same id is already registered |
737 | | /// If too many compression mechanisms are already registered |
738 | | /// |
739 | | /// This returns an error if the certificate compression could not be established |
740 | | /// |
741 | | /// [RFC8879]: https://datatracker.ietf.org/doc/rfc8879/ |
742 | 0 | pub fn set_certificate_compression<T: CertificateCompressor>(&mut self) -> Res<()> { |
743 | 0 | if T::ID == 0 { |
744 | 0 | return Err(Error::InvalidCertificateCompressionID); |
745 | 0 | } |
746 | | |
747 | 0 | let compressor: ssl::SSLCertificateCompressionAlgorithm = |
748 | 0 | ssl::SSLCertificateCompressionAlgorithm { |
749 | 0 | id: T::ID, |
750 | 0 | name: T::NAME.as_ptr(), |
751 | 0 | encode: T::ENABLE_ENCODING.then_some(<T as UnsafeCertCompression>::encode_callback), |
752 | 0 | decode: Some(<T as UnsafeCertCompression>::decode_callback), |
753 | 0 | }; |
754 | 0 | unsafe { ssl::SSL_SetCertificateCompressionAlgorithm(self.fd, compressor) } |
755 | 0 | } |
756 | | |
757 | | /// Install an extension handler. |
758 | | /// |
759 | | /// This can be called multiple times with different values for `ext`. The handler is provided |
760 | | /// as `Rc<RefCell<dyn T>>` so that the caller is able to hold a reference to the handler |
761 | | /// and later access any state that it accumulates. |
762 | | /// |
763 | | /// # Errors |
764 | | /// |
765 | | /// When the extension handler can't be successfully installed. |
766 | 2.57k | pub fn extension_handler( |
767 | 2.57k | &mut self, |
768 | 2.57k | ext: Extension, |
769 | 2.57k | handler: Rc<RefCell<dyn ExtensionHandler>>, |
770 | 2.57k | ) -> Res<()> { |
771 | 2.57k | let tracker = unsafe { ExtensionTracker::new(self.fd, ext, handler)? }; |
772 | 2.57k | self.extension_handlers.push(tracker); |
773 | 2.57k | Ok(()) |
774 | 2.57k | } |
775 | | |
776 | | // This function tracks whether handshake() or handshake_raw() was used |
777 | | // and prevents the other from being used. |
778 | 3.54k | fn set_raw(&mut self, r: bool) -> Res<()> { |
779 | 3.54k | if let Some(raw) = self.raw { |
780 | 1.77k | if raw == r { |
781 | 1.77k | Ok(()) |
782 | | } else { |
783 | 0 | Err(Error::MixedHandshakeMethod) |
784 | | } |
785 | | } else { |
786 | 1.77k | self.secrets.register(self.fd)?; |
787 | 1.77k | self.raw = Some(r); |
788 | 1.77k | Ok(()) |
789 | | } |
790 | 3.54k | } |
791 | | |
792 | | /// Get information about the connection. |
793 | | /// This includes the version, ciphersuite, and ALPN. |
794 | | /// |
795 | | /// Calling this function returns None until the connection is complete. |
796 | | #[must_use] |
797 | 484 | pub const fn info(&self) -> Option<&SecretAgentInfo> { |
798 | 484 | match &self.state { |
799 | 0 | HandshakeState::Complete(info) => Some(info), |
800 | 484 | _ => None, |
801 | | } |
802 | 484 | } |
803 | | |
804 | | /// Get any preliminary information about the status of the connection. |
805 | | /// |
806 | | /// This includes whether 0-RTT was accepted and any information related to that. |
807 | | /// Calling this function collects all the relevant information. |
808 | | /// |
809 | | /// # Errors |
810 | | /// |
811 | | /// When the underlying socket functions fail. |
812 | 2.25k | pub fn preinfo(&self) -> Res<SecretAgentPreInfo> { |
813 | 2.25k | SecretAgentPreInfo::new(self.fd) |
814 | 2.25k | } |
815 | | |
816 | | /// Get the peer's certificate chain. |
817 | | #[must_use] |
818 | 0 | pub fn peer_certificate(&self) -> Option<CertificateInfo> { |
819 | 0 | CertificateInfo::new(self.fd) |
820 | 0 | } |
821 | | |
822 | | /// Export keying material per RFC 8446 Section 7.5. |
823 | | /// |
824 | | /// This can only be called after the handshake is complete. |
825 | | /// In TLS 1.3, there is no distinction between no context and an empty |
826 | | /// context, so the caller passes `&[u8]` instead of `Option<&[u8]>`. |
827 | | /// |
828 | | /// # Errors |
829 | | /// |
830 | | /// Returns `Error::InvalidState` if the handshake is not complete, |
831 | | /// `Error::InvalidInput` if `out` is empty, or an NSS error if the |
832 | | /// export fails. |
833 | 0 | pub fn export_keying_material(&self, label: &[u8], context: &[u8], out: &mut [u8]) -> Res<()> { |
834 | 0 | if !self.state.is_connected() { |
835 | 0 | return Err(Error::InvalidState); |
836 | 0 | } |
837 | | |
838 | 0 | if out.is_empty() { |
839 | 0 | return Err(Error::InvalidInput); |
840 | 0 | } |
841 | | |
842 | 0 | secstatus_to_res(unsafe { |
843 | 0 | ssl::SSL_ExportKeyingMaterial( |
844 | 0 | self.fd, |
845 | 0 | label.as_ptr().cast(), |
846 | 0 | c_uint::try_from(label.len())?, |
847 | 0 | PRBool::from(!context.is_empty()), |
848 | 0 | context.as_ptr(), |
849 | 0 | c_uint::try_from(context.len())?, |
850 | 0 | out.as_mut_ptr(), |
851 | 0 | c_uint::try_from(out.len())?, |
852 | | ) |
853 | | }) |
854 | 0 | } |
855 | | |
856 | | /// Return any fatal alert that the TLS stack might have sent. |
857 | | #[must_use] |
858 | 0 | pub fn alert(&self) -> Option<Alert> { |
859 | 0 | *self.alert |
860 | 0 | } |
861 | | |
862 | | /// Call this function to mark the peer as authenticated. |
863 | | /// |
864 | | /// # Panics |
865 | | /// |
866 | | /// If the handshake doesn't need to be authenticated. |
867 | 0 | pub fn authenticated(&mut self, status: AuthenticationStatus) { |
868 | 0 | assert!(self.state.authentication_needed()); |
869 | 0 | *self.auth_required = false; |
870 | 0 | self.state = HandshakeState::Authenticated(status.into()); |
871 | 0 | } |
872 | | |
873 | 4.03k | fn capture_error<T>(&mut self, res: Res<T>) -> Res<T> { |
874 | 4.03k | if let Err(e) = res { |
875 | 0 | let e = ech::convert_ech_error(self.fd, e); |
876 | 0 | warn!("[{self}] error: {e:?}"); |
877 | 0 | self.state = HandshakeState::Failed(e.clone()); |
878 | 0 | Err(e) |
879 | | } else { |
880 | 4.03k | res |
881 | | } |
882 | 4.03k | } <nss_rs::agent::SecretAgent>::capture_error::<core::pin::Pin<alloc::boxed::Box<nss_rs::agentio::RecordList>>> Line | Count | Source | 873 | 3.54k | fn capture_error<T>(&mut self, res: Res<T>) -> Res<T> { | 874 | 3.54k | if let Err(e) = res { | 875 | 0 | let e = ech::convert_ech_error(self.fd, e); | 876 | 0 | warn!("[{self}] error: {e:?}"); | 877 | 0 | self.state = HandshakeState::Failed(e.clone()); | 878 | 0 | Err(e) | 879 | | } else { | 880 | 3.54k | res | 881 | | } | 882 | 3.54k | } |
Unexecuted instantiation: <nss_rs::agent::SecretAgent>::capture_error::<nss_rs::agent::SecretAgentInfo> <nss_rs::agent::SecretAgent>::capture_error::<()> Line | Count | Source | 873 | 484 | fn capture_error<T>(&mut self, res: Res<T>) -> Res<T> { | 874 | 484 | if let Err(e) = res { | 875 | 0 | let e = ech::convert_ech_error(self.fd, e); | 876 | 0 | warn!("[{self}] error: {e:?}"); | 877 | 0 | self.state = HandshakeState::Failed(e.clone()); | 878 | 0 | Err(e) | 879 | | } else { | 880 | 484 | res | 881 | | } | 882 | 484 | } |
|
883 | | |
884 | 1.77k | fn update_state(&mut self, res: Res<()>) -> Res<()> { |
885 | 1.77k | self.state = if is_blocked(&res) { |
886 | 1.77k | if *self.auth_required { |
887 | 0 | self.preinfo()?.ech_public_name()?.map_or( |
888 | 0 | HandshakeState::AuthenticationPending, |
889 | 0 | |public_name| { |
890 | 0 | HandshakeState::EchFallbackAuthenticationPending(public_name.to_owned()) |
891 | 0 | }, |
892 | | ) |
893 | | } else { |
894 | 1.77k | HandshakeState::InProgress |
895 | | } |
896 | | } else { |
897 | 0 | self.capture_error(res)?; |
898 | 0 | let info = self.capture_error(SecretAgentInfo::new(self.fd))?; |
899 | 0 | HandshakeState::Complete(info) |
900 | | }; |
901 | 1.77k | info!("[{self}] state -> {:?}", self.state); |
902 | 1.77k | Ok(()) |
903 | 1.77k | } |
904 | | |
905 | | /// Drive the TLS handshake, taking bytes from `input` and putting |
906 | | /// any bytes necessary into `output`. |
907 | | /// This takes the current time as `now`. |
908 | | /// On success a tuple of a `HandshakeState` and usize indicate whether the handshake |
909 | | /// is complete and how many bytes were written to `output`, respectively. |
910 | | /// If the state is `HandshakeState::AuthenticationPending`, then ONLY call this |
911 | | /// function if you want to proceed, because this will mark the certificate as OK. |
912 | | /// |
913 | | /// # Errors |
914 | | /// |
915 | | /// When the handshake fails this returns an error. |
916 | 0 | pub fn handshake(&mut self, now: Instant, input: &[u8]) -> Res<Vec<u8>> { |
917 | 0 | self.now.set(now)?; |
918 | 0 | self.set_raw(false)?; |
919 | | |
920 | 0 | let rv = { |
921 | | // Within this scope, _h maintains a mutable reference to self.io. |
922 | 0 | let _h = self.io.wrap(input); |
923 | 0 | match self.state { |
924 | 0 | HandshakeState::Authenticated(err) => unsafe { |
925 | 0 | ssl::SSL_AuthCertificateComplete(self.fd, err) |
926 | | }, |
927 | 0 | _ => unsafe { ssl::SSL_ForceHandshake(self.fd) }, |
928 | | } |
929 | | }; |
930 | | // Take before updating state so that we leave the output buffer empty |
931 | | // even if there is an error. |
932 | 0 | let output = self.io.take_output(); |
933 | 0 | self.update_state(secstatus_to_res(rv))?; |
934 | 0 | Ok(output) |
935 | 0 | } |
936 | | |
937 | | /// Setup to receive records for raw handshake functions. |
938 | 3.54k | fn setup_raw(&mut self) -> Res<Pin<Box<RecordList>>> { |
939 | 3.54k | self.set_raw(true)?; |
940 | 3.54k | self.capture_error(RecordList::setup(self.fd)) |
941 | 3.54k | } |
942 | | |
943 | | /// Drive the TLS handshake, but get the raw content of records, not |
944 | | /// protected records as bytes. This function is incompatible with |
945 | | /// `handshake()`; use either this or `handshake()` exclusively. |
946 | | /// |
947 | | /// Ideally, this only includes records from the current epoch. |
948 | | /// If you send data from multiple epochs, you might end up being sad. |
949 | | /// |
950 | | /// # Errors |
951 | | /// |
952 | | /// When the handshake fails this returns an error. |
953 | 1.77k | pub fn handshake_raw(&mut self, now: Instant, input: Option<Record>) -> Res<RecordList> { |
954 | 1.77k | self.now.set(now)?; |
955 | 1.77k | let records = self.setup_raw()?; |
956 | | |
957 | | // Fire off any authentication we might need to complete. |
958 | 1.77k | if let HandshakeState::Authenticated(err) = self.state { |
959 | 0 | let result = |
960 | 0 | secstatus_to_res(unsafe { ssl::SSL_AuthCertificateComplete(self.fd, err) }); |
961 | 0 | debug!("[{self}] SSL_AuthCertificateComplete: {result:?}"); |
962 | | // This should return SECSuccess, so don't use update_state(). |
963 | 0 | self.capture_error(result)?; |
964 | 1.77k | } |
965 | | |
966 | | // Feed in any records. |
967 | 1.77k | if let Some(rec) = input { |
968 | 484 | self.capture_error(rec.write(self.fd))?; |
969 | 1.28k | } |
970 | | |
971 | | // Drive the handshake once more. |
972 | 1.77k | let rv = secstatus_to_res(unsafe { ssl::SSL_ForceHandshake(self.fd) }); |
973 | 1.77k | self.update_state(rv)?; |
974 | | |
975 | 1.77k | Ok(*Pin::into_inner(records)) |
976 | 1.77k | } |
977 | | |
978 | | /// # Panics |
979 | | /// |
980 | | /// If setup fails. |
981 | 2.57k | pub fn close(&mut self) { |
982 | | // It should be safe to close multiple times. |
983 | 2.57k | if self.fd.is_null() { |
984 | 0 | return; |
985 | 2.57k | } |
986 | | #[expect( |
987 | | clippy::branches_sharing_code, |
988 | | reason = "The PR_Close calls cannot be run after dropping the returned values." |
989 | | )] |
990 | 2.57k | if self.raw == Some(true) { |
991 | | // Need to hold the record list in scope until the close is done. |
992 | 1.77k | let _records = self.setup_raw().expect("Can only close"); |
993 | 1.77k | unsafe { |
994 | 1.77k | prio::PR_Close(self.fd.cast()); |
995 | 1.77k | } |
996 | | } else { |
997 | | // Need to hold the IO wrapper in scope until the close is done. |
998 | 805 | let _io = self.io.wrap(&[]); |
999 | 805 | unsafe { |
1000 | 805 | prio::PR_Close(self.fd.cast()); |
1001 | 805 | } |
1002 | | } |
1003 | 2.57k | let _output = self.io.take_output(); |
1004 | 2.57k | self.fd = null_mut(); |
1005 | 2.57k | } |
1006 | | |
1007 | | /// State returns the status of the handshake. |
1008 | | #[must_use] |
1009 | 4.03k | pub const fn state(&self) -> &HandshakeState { |
1010 | 4.03k | &self.state |
1011 | 4.03k | } |
1012 | | |
1013 | | /// Check if the indicated secret is ready for installation. |
1014 | | #[must_use] |
1015 | 484 | pub fn has_secret(&self, epoch: Epoch) -> bool { |
1016 | 484 | self.secrets.has(epoch) |
1017 | 484 | } |
1018 | | |
1019 | | /// Take a read secret. This will only return a non-`None` value once. |
1020 | | #[must_use] |
1021 | 484 | pub fn read_secret(&mut self, epoch: Epoch) -> Option<p11::SymKey> { |
1022 | 484 | self.secrets.take_read(epoch) |
1023 | 484 | } |
1024 | | |
1025 | | /// Take a write secret. |
1026 | | #[must_use] |
1027 | 968 | pub fn write_secret(&mut self, epoch: Epoch) -> Option<p11::SymKey> { |
1028 | 968 | self.secrets.take_write(epoch) |
1029 | 968 | } |
1030 | | |
1031 | | /// Get the active ECH configuration, which is empty if ECH is disabled. |
1032 | | #[must_use] |
1033 | 0 | pub fn ech_config(&self) -> &[u8] { |
1034 | 0 | &self.ech_config |
1035 | 0 | } |
1036 | | } |
1037 | | |
1038 | | impl Drop for SecretAgent { |
1039 | 2.57k | fn drop(&mut self) { |
1040 | 2.57k | self.close(); |
1041 | 2.57k | } |
1042 | | } |
1043 | | |
1044 | | impl Display for SecretAgent { |
1045 | 0 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { |
1046 | 0 | write!(f, "Agent {:p}", self.fd) |
1047 | 0 | } |
1048 | | } |
1049 | | |
1050 | | #[derive(PartialOrd, Ord, PartialEq, Eq, Clone)] |
1051 | | pub struct ResumptionToken { |
1052 | | token: Vec<u8>, |
1053 | | expiration_time: Instant, |
1054 | | } |
1055 | | |
1056 | | impl Debug for ResumptionToken { |
1057 | 0 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
1058 | 0 | f.debug_struct("ResumptionToken") |
1059 | 0 | .field("token", &hex_snip_middle(&self.token)) |
1060 | 0 | .field("expiration_time", &self.expiration_time) |
1061 | 0 | .finish() |
1062 | 0 | } |
1063 | | } |
1064 | | |
1065 | | impl AsRef<[u8]> for ResumptionToken { |
1066 | 0 | fn as_ref(&self) -> &[u8] { |
1067 | 0 | &self.token |
1068 | 0 | } |
1069 | | } |
1070 | | |
1071 | | impl ResumptionToken { |
1072 | | #[must_use] |
1073 | 0 | pub const fn new(token: Vec<u8>, expiration_time: Instant) -> Self { |
1074 | 0 | Self { |
1075 | 0 | token, |
1076 | 0 | expiration_time, |
1077 | 0 | } |
1078 | 0 | } |
1079 | | |
1080 | | #[must_use] |
1081 | 0 | pub const fn expiration_time(&self) -> Instant { |
1082 | 0 | self.expiration_time |
1083 | 0 | } |
1084 | | } |
1085 | | |
1086 | | /// A TLS Client. |
1087 | | #[derive(Debug)] |
1088 | | pub struct Client { |
1089 | | agent: SecretAgent, |
1090 | | |
1091 | | /// The name of the server we're attempting a connection to. |
1092 | | server_name: String, |
1093 | | /// Records the resumption tokens we've received. |
1094 | | #[expect(clippy::box_collection, reason = "We need the Box.")] |
1095 | | resumption: Pin<Box<Vec<ResumptionToken>>>, |
1096 | | } |
1097 | | |
1098 | | impl Client { |
1099 | | /// Create a new client agent. |
1100 | | /// |
1101 | | /// # Errors |
1102 | | /// |
1103 | | /// Errors returned if the socket can't be created or configured. |
1104 | 1.28k | pub fn new<I: Into<String>>(server_name: I, grease: bool) -> Res<Self> { |
1105 | 1.28k | let server_name = server_name.into(); |
1106 | 1.28k | let mut agent = SecretAgent::new()?; |
1107 | 1.28k | let url = CString::new(server_name.as_bytes())?; |
1108 | 1.28k | secstatus_to_res(unsafe { ssl::SSL_SetURL(agent.fd, url.as_ptr()) })?; |
1109 | 1.28k | agent.ready(false, grease)?; |
1110 | 1.28k | let mut client = Self { |
1111 | 1.28k | agent, |
1112 | 1.28k | server_name, |
1113 | 1.28k | resumption: Box::pin(Vec::new()), |
1114 | 1.28k | }; |
1115 | 1.28k | client.ready()?; |
1116 | 1.28k | Ok(client) |
1117 | 1.28k | } Unexecuted instantiation: <nss_rs::agent::Client>::new::<alloc::string::String> Unexecuted instantiation: <nss_rs::agent::Client>::new::<alloc::string::String> Unexecuted instantiation: <nss_rs::agent::Client>::new::<_> <nss_rs::agent::Client>::new::<alloc::string::String> Line | Count | Source | 1104 | 484 | pub fn new<I: Into<String>>(server_name: I, grease: bool) -> Res<Self> { | 1105 | 484 | let server_name = server_name.into(); | 1106 | 484 | let mut agent = SecretAgent::new()?; | 1107 | 484 | let url = CString::new(server_name.as_bytes())?; | 1108 | 484 | secstatus_to_res(unsafe { ssl::SSL_SetURL(agent.fd, url.as_ptr()) })?; | 1109 | 484 | agent.ready(false, grease)?; | 1110 | 484 | let mut client = Self { | 1111 | 484 | agent, | 1112 | 484 | server_name, | 1113 | 484 | resumption: Box::pin(Vec::new()), | 1114 | 484 | }; | 1115 | 484 | client.ready()?; | 1116 | 484 | Ok(client) | 1117 | 484 | } |
<nss_rs::agent::Client>::new::<alloc::string::String> Line | Count | Source | 1104 | 805 | pub fn new<I: Into<String>>(server_name: I, grease: bool) -> Res<Self> { | 1105 | 805 | let server_name = server_name.into(); | 1106 | 805 | let mut agent = SecretAgent::new()?; | 1107 | 805 | let url = CString::new(server_name.as_bytes())?; | 1108 | 805 | secstatus_to_res(unsafe { ssl::SSL_SetURL(agent.fd, url.as_ptr()) })?; | 1109 | 805 | agent.ready(false, grease)?; | 1110 | 805 | let mut client = Self { | 1111 | 805 | agent, | 1112 | 805 | server_name, | 1113 | 805 | resumption: Box::pin(Vec::new()), | 1114 | 805 | }; | 1115 | 805 | client.ready()?; | 1116 | 805 | Ok(client) | 1117 | 805 | } |
|
1118 | | |
1119 | 0 | unsafe extern "C" fn resumption_token_cb( |
1120 | 0 | fd: *mut prio::PRFileDesc, |
1121 | 0 | token: *const u8, |
1122 | 0 | len: c_uint, |
1123 | 0 | arg: *mut c_void, |
1124 | 0 | ) -> SECStatus { |
1125 | 0 | let mut info: MaybeUninit<ssl::SSLResumptionTokenInfo> = MaybeUninit::uninit(); |
1126 | 0 | let Ok(info_len) = c_uint::try_from(size_of::<ssl::SSLResumptionTokenInfo>()) else { |
1127 | 0 | return ssl::SECFailure; |
1128 | | }; |
1129 | 0 | let info_res = |
1130 | 0 | unsafe { ssl::SSL_GetResumptionTokenInfo(token, len, info.as_mut_ptr(), info_len) }; |
1131 | 0 | if info_res.is_err() { |
1132 | | // Ignore the token. |
1133 | 0 | return ssl::SECSuccess; |
1134 | 0 | } |
1135 | 0 | let expiration_time = unsafe { info.assume_init_ref() }.expirationTime; |
1136 | 0 | if unsafe { ssl::SSL_DestroyResumptionTokenInfo(info.as_mut_ptr()) }.is_err() { |
1137 | | // Ignore the token. |
1138 | 0 | return ssl::SECSuccess; |
1139 | 0 | } |
1140 | 0 | let Some(resumption) = (unsafe { arg.cast::<Vec<ResumptionToken>>().as_mut() }) else { |
1141 | 0 | return ssl::SECFailure; |
1142 | | }; |
1143 | 0 | let Ok(len) = usize::try_from(len) else { |
1144 | 0 | return ssl::SECFailure; |
1145 | | }; |
1146 | 0 | let mut v = Vec::with_capacity(len); |
1147 | 0 | v.extend_from_slice(unsafe { null_safe_slice(token, len) }); |
1148 | 0 | debug!("[{fd:p}] Got resumption token {}", hex_snip_middle(&v)); |
1149 | | |
1150 | 0 | if resumption.len() >= MAX_TICKETS { |
1151 | 0 | resumption.remove(0); |
1152 | 0 | } |
1153 | 0 | if let Ok(t) = Time::try_from(expiration_time) { |
1154 | 0 | resumption.push(ResumptionToken::new(v, *t)); |
1155 | 0 | } |
1156 | 0 | ssl::SECSuccess |
1157 | 0 | } |
1158 | | |
1159 | | #[must_use] |
1160 | 0 | pub fn server_name(&self) -> &str { |
1161 | 0 | &self.server_name |
1162 | 0 | } |
1163 | | |
1164 | 1.28k | fn ready(&mut self) -> Res<()> { |
1165 | 1.28k | let fd = self.fd; |
1166 | | unsafe { |
1167 | 1.28k | ssl::SSL_SetResumptionTokenCallback( |
1168 | 1.28k | fd, |
1169 | 1.28k | Some(Self::resumption_token_cb), |
1170 | 1.28k | as_c_void(&mut self.resumption), |
1171 | | ) |
1172 | | } |
1173 | 1.28k | } |
1174 | | |
1175 | | /// Take a resumption token. |
1176 | | #[must_use] |
1177 | 0 | pub fn resumption_token(&mut self) -> Option<ResumptionToken> { |
1178 | 0 | (*self.resumption).pop() |
1179 | 0 | } |
1180 | | |
1181 | | /// Check if there are more resumption tokens. |
1182 | | #[must_use] |
1183 | 0 | pub fn has_resumption_token(&self) -> bool { |
1184 | 0 | !(*self.resumption).is_empty() |
1185 | 0 | } |
1186 | | |
1187 | | /// Enable resumption, using a token previously provided. |
1188 | | /// |
1189 | | /// # Errors |
1190 | | /// |
1191 | | /// Error returned when the resumption token is invalid or |
1192 | | /// the socket is not able to use the value. |
1193 | 0 | pub fn enable_resumption<A: AsRef<[u8]>>(&mut self, token: A) -> Res<()> { |
1194 | | unsafe { |
1195 | 0 | ssl::SSL_SetResumptionToken( |
1196 | 0 | self.agent.fd, |
1197 | 0 | token.as_ref().as_ptr(), |
1198 | 0 | c_uint::try_from(token.as_ref().len())?, |
1199 | | ) |
1200 | | } |
1201 | 0 | } |
1202 | | |
1203 | | /// Enable encrypted client hello (ECH), using the encoded `ECHConfigList`. |
1204 | | /// |
1205 | | /// When ECH is enabled, a client needs to look for `Error::EchRetry` as a |
1206 | | /// failure code. If `Error::EchRetry` is received when connecting, the |
1207 | | /// connection attempt should be retried and the included value provided |
1208 | | /// to this function (instead of what is received from DNS). |
1209 | | /// |
1210 | | /// Calling this function with an empty value for `ech_config_list` enables |
1211 | | /// ECH greasing. When that is done, there is no need to look for `EchRetry` |
1212 | | /// |
1213 | | /// # Errors |
1214 | | /// |
1215 | | /// Error returned when the configuration is invalid. |
1216 | 0 | pub fn enable_ech<A: AsRef<[u8]>>(&mut self, ech_config_list: A) -> Res<()> { |
1217 | 0 | let config = ech_config_list.as_ref(); |
1218 | 0 | debug!("[{self}] Enable ECH for a server: {}", hex_with_len(config)); |
1219 | 0 | self.ech_config = Vec::from(config); |
1220 | 0 | if config.is_empty() { |
1221 | 0 | unsafe { ech::SSL_EnableTls13GreaseEch(self.agent.fd, PRBool::from(true)) } |
1222 | | } else { |
1223 | | unsafe { |
1224 | 0 | ech::SSL_SetClientEchConfigs( |
1225 | 0 | self.agent.fd, |
1226 | 0 | config.as_ptr(), |
1227 | 0 | c_uint::try_from(config.len())?, |
1228 | 0 | )?; |
1229 | | // If the ECH configuration is valid, and only then, |
1230 | | // allow writing of different transport parameters to the inner and outer |
1231 | | // ClientHello. Avoid setting this otherwise, as the transport |
1232 | | // parameter extension handler filters out essential values from the |
1233 | | // outer ClientHello. Under normal operation, NSS reports to |
1234 | | // extension writers that an ordinary, non-ECH ClientHello is an |
1235 | | // outer ClientHello, resulting in unwanted filtering. |
1236 | 0 | SSL_CallExtensionWriterOnEchInner(self.fd, PRBool::from(true)) |
1237 | | } |
1238 | | } |
1239 | 0 | } |
1240 | | } |
1241 | | |
1242 | | impl Deref for Client { |
1243 | | type Target = SecretAgent; |
1244 | 7.73k | fn deref(&self) -> &SecretAgent { |
1245 | 7.73k | &self.agent |
1246 | 7.73k | } |
1247 | | } |
1248 | | |
1249 | | impl DerefMut for Client { |
1250 | 9.02k | fn deref_mut(&mut self) -> &mut SecretAgent { |
1251 | 9.02k | &mut self.agent |
1252 | 9.02k | } |
1253 | | } |
1254 | | |
1255 | | impl Display for Client { |
1256 | 0 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { |
1257 | 0 | write!(f, "Client {:p}", self.agent.fd) |
1258 | 0 | } |
1259 | | } |
1260 | | |
1261 | | /// `ZeroRttCheckResult` encapsulates the options for handling a `ClientHello`. |
1262 | | #[derive(Clone, Debug, PartialEq, Eq)] |
1263 | | pub enum ZeroRttCheckResult { |
1264 | | /// Accept 0-RTT. |
1265 | | Accept, |
1266 | | /// Reject 0-RTT, but continue the handshake normally. |
1267 | | Reject, |
1268 | | /// Send `HelloRetryRequest` (probably not needed for QUIC). |
1269 | | HelloRetryRequest(Vec<u8>), |
1270 | | /// Fail the handshake. |
1271 | | Fail, |
1272 | | } |
1273 | | |
1274 | | /// A `ZeroRttChecker` is used by the agent to validate the application token (as provided by |
1275 | | /// `send_ticket`) |
1276 | | pub trait ZeroRttChecker: Debug + Unpin { |
1277 | | fn check(&self, token: &[u8]) -> ZeroRttCheckResult; |
1278 | | } |
1279 | | |
1280 | | /// Using `AllowZeroRtt` for the implementation of `ZeroRttChecker` means |
1281 | | /// accepting 0-RTT always. This generally isn't a great idea, so this |
1282 | | /// generates a strong warning when it is used. |
1283 | | #[derive(Debug)] |
1284 | | pub struct AllowZeroRtt {} |
1285 | | impl ZeroRttChecker for AllowZeroRtt { |
1286 | 0 | fn check(&self, _token: &[u8]) -> ZeroRttCheckResult { |
1287 | 0 | warn!("AllowZeroRtt accepting 0-RTT"); |
1288 | 0 | ZeroRttCheckResult::Accept |
1289 | 0 | } |
1290 | | } |
1291 | | |
1292 | | #[derive(Debug)] |
1293 | | struct ZeroRttCheckState { |
1294 | | checker: Pin<Box<dyn ZeroRttChecker>>, |
1295 | | } |
1296 | | |
1297 | | impl ZeroRttCheckState { |
1298 | 1.28k | pub fn new(checker: Box<dyn ZeroRttChecker>) -> Self { |
1299 | 1.28k | Self { |
1300 | 1.28k | checker: Pin::new(checker), |
1301 | 1.28k | } |
1302 | 1.28k | } |
1303 | | } |
1304 | | |
1305 | | #[derive(Debug)] |
1306 | | pub struct Server { |
1307 | | agent: SecretAgent, |
1308 | | /// This holds the HRR callback context. |
1309 | | zero_rtt_check: Option<Pin<Box<ZeroRttCheckState>>>, |
1310 | | } |
1311 | | |
1312 | 1.28k | fn load_cert_and_key(name: &str) -> Res<(p11::Certificate, PrivateKey)> { |
1313 | 1.28k | let c = CString::new(name)?; |
1314 | 1.28k | let cert = p11::Certificate::from_ptr(unsafe { |
1315 | 1.28k | p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut()) |
1316 | | }) |
1317 | 1.28k | .map_err(|_| Error::CertificateLoading)?; |
1318 | 1.28k | let key = PrivateKey::from_ptr(unsafe { p11::PK11_FindKeyByAnyCert(*cert, null_mut()) }) |
1319 | 1.28k | .map_err(|_| Error::CertificateLoading)?; |
1320 | 1.28k | Ok((cert, key)) |
1321 | 1.28k | } |
1322 | | |
1323 | | impl Server { |
1324 | | /// Create a new server agent. |
1325 | | /// |
1326 | | /// # Errors |
1327 | | /// |
1328 | | /// Errors returned when NSS fails. |
1329 | 1.28k | pub fn new<A: AsRef<str>>(certificates: &[A]) -> Res<Self> { |
1330 | 1.28k | let mut agent = SecretAgent::new()?; |
1331 | 1.28k | for n in certificates { |
1332 | 1.28k | let (cert, key) = load_cert_and_key(n.as_ref())?; |
1333 | 1.28k | secstatus_to_res(unsafe { |
1334 | 1.28k | ssl::SSL_ConfigServerCert(agent.fd, (*cert).cast(), (*key).cast(), null(), 0) |
1335 | 0 | })?; |
1336 | | } |
1337 | 1.28k | agent.ready(true, true)?; |
1338 | 1.28k | Ok(Self { |
1339 | 1.28k | agent, |
1340 | 1.28k | zero_rtt_check: None, |
1341 | 1.28k | }) |
1342 | 1.28k | } Unexecuted instantiation: <nss_rs::agent::Server>::new::<alloc::string::String> Unexecuted instantiation: <nss_rs::agent::Server>::new::<&str> Unexecuted instantiation: <nss_rs::agent::Server>::new::<alloc::string::String> Unexecuted instantiation: <nss_rs::agent::Server>::new::<alloc::string::String> Unexecuted instantiation: <nss_rs::agent::Server>::new::<_> <nss_rs::agent::Server>::new::<&str> Line | Count | Source | 1329 | 484 | pub fn new<A: AsRef<str>>(certificates: &[A]) -> Res<Self> { | 1330 | 484 | let mut agent = SecretAgent::new()?; | 1331 | 484 | for n in certificates { | 1332 | 484 | let (cert, key) = load_cert_and_key(n.as_ref())?; | 1333 | 484 | secstatus_to_res(unsafe { | 1334 | 484 | ssl::SSL_ConfigServerCert(agent.fd, (*cert).cast(), (*key).cast(), null(), 0) | 1335 | 0 | })?; | 1336 | | } | 1337 | 484 | agent.ready(true, true)?; | 1338 | 484 | Ok(Self { | 1339 | 484 | agent, | 1340 | 484 | zero_rtt_check: None, | 1341 | 484 | }) | 1342 | 484 | } |
<nss_rs::agent::Server>::new::<&str> Line | Count | Source | 1329 | 805 | pub fn new<A: AsRef<str>>(certificates: &[A]) -> Res<Self> { | 1330 | 805 | let mut agent = SecretAgent::new()?; | 1331 | 805 | for n in certificates { | 1332 | 805 | let (cert, key) = load_cert_and_key(n.as_ref())?; | 1333 | 805 | secstatus_to_res(unsafe { | 1334 | 805 | ssl::SSL_ConfigServerCert(agent.fd, (*cert).cast(), (*key).cast(), null(), 0) | 1335 | 0 | })?; | 1336 | | } | 1337 | 805 | agent.ready(true, true)?; | 1338 | 805 | Ok(Self { | 1339 | 805 | agent, | 1340 | 805 | zero_rtt_check: None, | 1341 | 805 | }) | 1342 | 805 | } |
|
1343 | | |
1344 | | /// Create a server with OCSP responses and SCTs configured. |
1345 | | /// Not suitable for multiple certificates, because it configures the same OCSP/SCT for all |
1346 | | /// certificates. In other words, this is good for testing that the plumbing works, not for |
1347 | | /// a real server. |
1348 | | /// |
1349 | | /// # Errors |
1350 | | /// |
1351 | | /// Errors returned when NSS fails. |
1352 | 0 | pub fn new_with_ocsp_and_scts<A: AsRef<str>>( |
1353 | 0 | certificates: &[A], |
1354 | 0 | ocsp_responses: &[&[u8]], |
1355 | 0 | scts: &[u8], |
1356 | 0 | ) -> Res<Self> { |
1357 | 0 | let mut agent = SecretAgent::new()?; |
1358 | 0 | for n in certificates { |
1359 | 0 | let (cert, key) = load_cert_and_key(n.as_ref())?; |
1360 | 0 | let ocsp_items: Vec<SECItemBorrowed> = ocsp_responses |
1361 | 0 | .iter() |
1362 | 0 | .map(|b| SECItemBorrowed::wrap(b)) |
1363 | 0 | .collect::<Res<_>>()?; |
1364 | 0 | let ocsp_array = SECItemArray { |
1365 | 0 | items: ocsp_items.as_ptr().cast::<SECItem>().cast_mut(), |
1366 | 0 | len: c_uint::try_from(ocsp_items.len())?, |
1367 | | }; |
1368 | 0 | let sct_item = SECItemBorrowed::wrap(scts)?; |
1369 | 0 | let extra = ssl::SSLExtraServerCertDataStr { |
1370 | 0 | // ssl_auth_null means "I don't care what sort of certificate this is". |
1371 | 0 | authType: ssl::SSLAuthType::ssl_auth_null, |
1372 | 0 | certChain: null(), |
1373 | 0 | stapledOCSPResponses: &raw const ocsp_array, |
1374 | 0 | signedCertTimestamps: std::ptr::from_ref(&sct_item).cast(), |
1375 | 0 | delegCred: null(), |
1376 | 0 | delegCredPrivKey: null(), |
1377 | 0 | }; |
1378 | 0 | secstatus_to_res(unsafe { |
1379 | 0 | ssl::SSL_ConfigServerCert( |
1380 | 0 | agent.fd, |
1381 | 0 | (*cert).cast(), |
1382 | 0 | (*key).cast(), |
1383 | 0 | &raw const extra, |
1384 | 0 | c_uint::try_from(size_of::<ssl::SSLExtraServerCertDataStr>())?, |
1385 | | ) |
1386 | 0 | })?; |
1387 | | } |
1388 | 0 | agent.ready(true, true)?; |
1389 | 0 | Ok(Self { |
1390 | 0 | agent, |
1391 | 0 | zero_rtt_check: None, |
1392 | 0 | }) |
1393 | 0 | } |
1394 | | |
1395 | 484 | unsafe extern "C" fn hello_retry_cb( |
1396 | 484 | first_hello: PRBool, |
1397 | 484 | client_token: *const u8, |
1398 | 484 | client_token_len: c_uint, |
1399 | 484 | retry_token: *mut u8, |
1400 | 484 | retry_token_len: *mut c_uint, |
1401 | 484 | retry_token_max: c_uint, |
1402 | 484 | arg: *mut c_void, |
1403 | 484 | ) -> ssl::SSLHelloRetryRequestAction::Type { |
1404 | 484 | if first_hello == 0 { |
1405 | | // On the second ClientHello after HelloRetryRequest, skip checks. |
1406 | 0 | return ssl::SSLHelloRetryRequestAction::ssl_hello_retry_accept; |
1407 | 484 | } |
1408 | | |
1409 | 484 | let check_state = unsafe { arg.cast::<ZeroRttCheckState>().as_mut().unwrap() }; |
1410 | 484 | let token = |
1411 | 484 | unsafe { null_safe_slice(client_token, usize::try_from(client_token_len).unwrap()) }; |
1412 | 484 | match check_state.checker.check(token) { |
1413 | 0 | ZeroRttCheckResult::Accept => ssl::SSLHelloRetryRequestAction::ssl_hello_retry_accept, |
1414 | 0 | ZeroRttCheckResult::Fail => ssl::SSLHelloRetryRequestAction::ssl_hello_retry_fail, |
1415 | | ZeroRttCheckResult::Reject => { |
1416 | 484 | ssl::SSLHelloRetryRequestAction::ssl_hello_retry_reject_0rtt |
1417 | | } |
1418 | 0 | ZeroRttCheckResult::HelloRetryRequest(tok) => { |
1419 | | // Don't bother propagating errors from this, because it should be caught in |
1420 | | // testing. |
1421 | 0 | assert!(tok.len() <= usize::try_from(retry_token_max).unwrap()); |
1422 | | // and `retry_token_len` is a valid pointer provided by NSS. |
1423 | 0 | unsafe { |
1424 | 0 | let slc = slice::from_raw_parts_mut(retry_token, tok.len()); |
1425 | 0 | slc.copy_from_slice(&tok); |
1426 | 0 | *retry_token_len = c_uint::try_from(tok.len()).unwrap(); |
1427 | 0 | } |
1428 | 0 | ssl::SSLHelloRetryRequestAction::ssl_hello_retry_request |
1429 | | } |
1430 | | } |
1431 | 484 | } |
1432 | | |
1433 | | /// Enable 0-RTT. This shadows the function of the same name that can be accessed |
1434 | | /// via the Deref implementation on Server. |
1435 | | /// |
1436 | | /// # Errors |
1437 | | /// |
1438 | | /// Returns an error if the underlying NSS functions fail. |
1439 | 1.28k | pub fn enable_0rtt( |
1440 | 1.28k | &mut self, |
1441 | 1.28k | anti_replay: &AntiReplay, |
1442 | 1.28k | max_early_data: u32, |
1443 | 1.28k | checker: Box<dyn ZeroRttChecker>, |
1444 | 1.28k | ) -> Res<()> { |
1445 | 1.28k | let mut check_state = Box::pin(ZeroRttCheckState::new(checker)); |
1446 | | unsafe { |
1447 | 1.28k | ssl::SSL_HelloRetryRequestCallback( |
1448 | 1.28k | self.agent.fd, |
1449 | 1.28k | Some(Self::hello_retry_cb), |
1450 | 1.28k | as_c_void(&mut check_state), |
1451 | | ) |
1452 | 0 | }?; |
1453 | 1.28k | unsafe { ssl::SSL_SetMaxEarlyDataSize(self.agent.fd, max_early_data) }?; |
1454 | 1.28k | self.zero_rtt_check = Some(check_state); |
1455 | 1.28k | self.agent.enable_0rtt()?; |
1456 | 1.28k | anti_replay.config_socket(self.fd)?; |
1457 | 1.28k | Ok(()) |
1458 | 1.28k | } |
1459 | | |
1460 | | /// Send a session ticket to the client. |
1461 | | /// This adds |extra| application-specific content into that ticket. |
1462 | | /// The records that are sent are captured and returned. |
1463 | | /// |
1464 | | /// # Errors |
1465 | | /// |
1466 | | /// If NSS is unable to send a ticket, or if this agent is incorrectly configured. |
1467 | 0 | pub fn send_ticket(&mut self, now: Instant, extra: &[u8]) -> Res<RecordList> { |
1468 | 0 | self.agent.now.set(now)?; |
1469 | 0 | let records = self.setup_raw()?; |
1470 | | |
1471 | | unsafe { |
1472 | 0 | ssl::SSL_SendSessionTicket(self.fd, extra.as_ptr(), c_uint::try_from(extra.len())?) |
1473 | 0 | }?; |
1474 | | |
1475 | 0 | Ok(*Pin::into_inner(records)) |
1476 | 0 | } |
1477 | | |
1478 | | /// Enable encrypted client hello (ECH). |
1479 | | /// |
1480 | | /// # Errors |
1481 | | /// |
1482 | | /// Fails when NSS cannot create a key pair. |
1483 | 0 | pub fn enable_ech( |
1484 | 0 | &mut self, |
1485 | 0 | config: u8, |
1486 | 0 | public_name: &str, |
1487 | 0 | sk: &PrivateKey, |
1488 | 0 | pk: &PublicKey, |
1489 | 0 | ) -> Res<()> { |
1490 | 0 | let cfg = ech::encode_config(config, public_name, pk)?; |
1491 | 0 | debug!("[{self}] Enable ECH for a server: {}", hex_with_len(&cfg)); |
1492 | | unsafe { |
1493 | 0 | ech::SSL_SetServerEchConfigs( |
1494 | 0 | self.agent.fd, |
1495 | 0 | **pk, |
1496 | 0 | **sk, |
1497 | 0 | cfg.as_ptr(), |
1498 | 0 | c_uint::try_from(cfg.len())?, |
1499 | 0 | )?; |
1500 | | }; |
1501 | 0 | self.ech_config = cfg; |
1502 | 0 | Ok(()) |
1503 | 0 | } |
1504 | | } |
1505 | | |
1506 | | impl Deref for Server { |
1507 | | type Target = SecretAgent; |
1508 | 5.96k | fn deref(&self) -> &SecretAgent { |
1509 | 5.96k | &self.agent |
1510 | 5.96k | } |
1511 | | } |
1512 | | |
1513 | | impl DerefMut for Server { |
1514 | 8.38k | fn deref_mut(&mut self) -> &mut SecretAgent { |
1515 | 8.38k | &mut self.agent |
1516 | 8.38k | } |
1517 | | } |
1518 | | |
1519 | | impl Display for Server { |
1520 | 0 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { |
1521 | 0 | write!(f, "Server {:p}", self.agent.fd) |
1522 | 0 | } |
1523 | | } |
1524 | | |
1525 | | /// A generic container for Client or Server. |
1526 | | #[derive(Debug)] |
1527 | | pub enum Agent { |
1528 | | Client(Client), |
1529 | | Server(Server), |
1530 | | } |
1531 | | |
1532 | | impl Deref for Agent { |
1533 | | type Target = SecretAgent; |
1534 | 9.83k | fn deref(&self) -> &SecretAgent { |
1535 | 9.83k | match self { |
1536 | 5.15k | Self::Client(c) => c, |
1537 | 4.67k | Self::Server(s) => s, |
1538 | | } |
1539 | 9.83k | } |
1540 | | } |
1541 | | |
1542 | | impl DerefMut for Agent { |
1543 | 16.1k | fn deref_mut(&mut self) -> &mut SecretAgent { |
1544 | 16.1k | match self { |
1545 | 7.73k | Self::Client(c) => c, |
1546 | 8.38k | Self::Server(s) => s, |
1547 | | } |
1548 | 16.1k | } |
1549 | | } |
1550 | | |
1551 | | impl From<Client> for Agent { |
1552 | 1.28k | fn from(c: Client) -> Self { |
1553 | 1.28k | Self::Client(c) |
1554 | 1.28k | } |
1555 | | } |
1556 | | |
1557 | | impl From<Server> for Agent { |
1558 | 1.28k | fn from(s: Server) -> Self { |
1559 | 1.28k | Self::Server(s) |
1560 | 1.28k | } |
1561 | | } |
1562 | | |
1563 | | #[cfg(test)] |
1564 | | #[cfg_attr(coverage_nightly, coverage(off))] |
1565 | | mod tests { |
1566 | | use crate::ResumptionToken; |
1567 | | |
1568 | | #[test] |
1569 | | fn resumption_token_debug_impl() { |
1570 | | let now = test_fixture::now(); |
1571 | | let token = [ |
1572 | | 2, 0, 6, 60, 37, 21, 238, 165, 182, 0, 6, 60, 77, 81, 157, 101, 182, 0, 6, 60, 37, 21, |
1573 | | 238, 165, 182, 0, 2, 163, 0, 0, 0, 0, 1, 72, 146, 254, 127, 255, 255, 255, 255, 0, 1, |
1574 | | 73, 48, 130, 1, 69, 48, 129, 236, 160, 3, 2, 1, 2, 2, 5, 0, 176, 13, 245, 143, 48, 10, |
1575 | | 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 48, 15, 49, 13, 48, 11, 6, 3, 85, 4, 3, 19, 4, |
1576 | | 116, 101, 115, 116, 48, 30, 23, 13, 49, 57, 48, 49, 50, 55, 49, 50, 50, 54, 52, 55, 90, |
1577 | | 23, 13, 49, 57, 48, 52, 50, 55, 49, 50, 50, 54, 52, 55, 90, 48, 15, 49, 13, 48, 11, 6, |
1578 | | 3, 85, 4, 3, 19, 4, 116, 101, 115, 116, 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, |
1579 | | 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 42, 240, 199, 17, 152, 64, 127, |
1580 | | 175, 32, 106, 156, 147, 147, 171, 185, 13, 157, 177, 225, 249, 112, 141, 249, 175, 72, |
1581 | | 224, 44, 119, 74, 249, 38, 109, 45, 217, 239, 119, 190, 34, 246, 151, 97, 85, 39, 175, |
1582 | | 182, 174, 5, 16, 183, 139, 81, 228, 52, 245, 172, 45, 183, 68, 82, 214, 10, 3, 114, 4, |
1583 | | 163, 53, 48, 51, 48, 25, 6, 3, 85, 29, 17, 4, 18, 48, 16, 130, 14, 115, 101, 114, 118, |
1584 | | 101, 114, 46, 101, 120, 97, 109, 112, 108, 101, 48, 9, 6, 3, 85, 29, 19, 4, 2, 48, 0, |
1585 | | 48, 11, 6, 3, 85, 29, 15, 4, 4, 3, 2, 7, 128, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, |
1586 | | 2, 3, 72, 0, 48, 69, 2, 32, 118, 227, 238, 3, 17, 159, 222, 78, 215, 173, 203, 63, 51, |
1587 | | 101, 41, 145, 17, 156, 179, 18, 64, 146, 197, 54, 64, 212, 255, 201, 133, 92, 244, 58, |
1588 | | 2, 33, 0, 148, 138, 31, 164, 15, 1, 107, 82, 152, 245, 77, 127, 251, 227, 229, 183, 33, |
1589 | | 162, 111, 169, 222, 240, 171, 167, 99, 81, 10, 183, 76, 80, 130, 65, 0, 0, 0, 5, 91, |
1590 | | 58, 58, 49, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 3, |
1591 | | 4, 0, 6, 60, 37, 21, 238, 165, 182, 0, 4, 0, 0, 1, 0, 0, 8, 0, 0, 0, 255, 0, 17, 236, |
1592 | | 0, 4, 3, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
1593 | | 0, 0, 0, 0, 0, 0, 0, 0, 19, 1, 1, 48, 245, 92, 231, 183, 9, 67, 178, 200, 227, 203, 91, |
1594 | | 5, 146, 135, 61, 159, 135, 68, 96, 200, 86, 35, 189, 174, 81, 95, 157, 75, 177, 235, |
1595 | | 124, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 1, |
1596 | | 50, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 2, |
1597 | | 104, 51, 1, 34, 56, 7, 64, 215, 199, 178, 229, 41, 63, 246, 119, 142, 0, 0, 0, 0, 86, |
1598 | | 68, 109, 48, 102, 65, 96, 24, 170, 163, 83, 214, 9, 243, 208, 236, 0, 224, 110, 111, |
1599 | | 243, 244, 243, 4, 172, 188, 247, 135, 255, 109, 241, 240, 77, 178, 162, 32, 255, 45, |
1600 | | 207, 25, 89, 74, 86, 140, 114, 11, 192, 20, 144, 139, 49, 228, 250, 252, 228, 107, 5, |
1601 | | 62, 203, 139, 146, 248, 73, 124, 72, 94, 138, 216, 190, 223, 150, 181, 55, 62, 32, 13, |
1602 | | 231, 230, 104, 142, 27, 182, 15, 13, 116, 26, 234, 154, 228, 241, 134, 131, 25, 214, |
1603 | | 229, 187, 181, 219, 209, 217, 53, 162, 177, 203, 100, 80, 175, 64, 226, 159, 115, 202, |
1604 | | 43, 68, 72, 160, 34, 214, 158, 4, 242, 7, 13, 132, 20, 128, 160, 237, 168, 122, 66, 37, |
1605 | | 54, 11, 116, 148, 94, 173, 80, 228, 7, 89, 223, 156, 249, 22, 50, 62, 33, 22, 157, 115, |
1606 | | 28, 195, 4, 220, 57, 62, 204, 222, 188, 211, 82, 34, 121, 54, 106, 197, 183, 98, 155, |
1607 | | 36, 249, 164, 205, 114, 61, 39, 144, 54, 43, 180, 25, 28, 130, 80, 22, 109, 133, 27, |
1608 | | 87, 184, 66, 54, 117, 151, 113, 36, 172, 98, 31, 60, 42, 85, 190, 148, 160, 65, 222, |
1609 | | 186, 200, 101, 183, 103, 23, 95, 97, 7, 248, 159, 114, 229, 74, 252, 55, 68, 254, 63, |
1610 | | 244, 195, 194, 99, 118, 60, 231, 118, 219, 241, 146, 94, 249, 94, 40, 231, 103, 238, |
1611 | | 215, 120, 60, 213, 142, 121, 11, 15, 251, 195, 120, 39, 17, 117, 183, 1, 90, 178, 149, |
1612 | | 74, 184, 51, 9, 38, 114, 144, 66, 153, |
1613 | | ]; |
1614 | | let resumption_token = ResumptionToken::new(token.to_vec(), now); |
1615 | | let expected = format!( |
1616 | | "ResumptionToken {{ token: \"[848]: 0200063c2515eea5..b833092672904299\", expiration_time: {now:?} }}" |
1617 | | ); |
1618 | | assert_eq!(format!("{resumption_token:?}"), expected); |
1619 | | } |
1620 | | } |