/// The trait is used to represent a certificate compression data structure /// Used in order to enable Certificate Compression extension during TLS connection pubtrait CertificateCompressor { /// Certificate Compression identifier as in RFC8879 const ID: u16; /// Certification Compression name (used only for logging/debugging) const NAME: &CStr; /// Certificate Compression could be used to encode and decode a certificate /// though the encoding is not frequently used /// Enable decoding field is used to signal to the implementation /// to use the encoding as well const ENABLE_ENCODING: bool = false;
/// Certificate Compression encoding function /// /// This default implementation effectively does nothing. /// However, this is only run if `ENABLE_ENCODING` is `true`. /// Implementations that set `ENABLE_ENCODING` to `true` need to implement this function. /// /// # Errors /// Encoding was unsuccessful, for example, not enough memory
fn encode(input: &[u8], output: &mut [u8]) -> Res<usize> { let len = std::cmp::min(input.len(), output.len());
output[..len].copy_from_slice(&input[..len]);
Ok(len)
}
/// Certificate Compression decoding function. /// # Errors /// Decoding was unsuccessful. /// We require a decoder internally to check the length of the decoded buffer. /// If the decoded length is not equal to the length of the provided slice /// the decoder should return an error.
fn decode(input: &[u8], output: &mut [u8]) -> Res<()>;
}
/// The trait is responsible for calling `CertificateCompression` encoding and decoding /// functions using the NSS types impl<T: CertificateCompressor> UnsafeCertCompression for T { extern"C" fn decode_callback(
input: *const SECItem,
output: *mut ::std::os::raw::c_uchar,
output_len: usize,
used_len: *mut usize,
) -> SECStatus { let Some(input) = NonNull::new(input.cast_mut()) else { return ssl::SECFailure;
}; ifunsafe { input.as_ref().data.is_null() || input.as_ref().len == 0 } { return ssl::SECFailure;
}
let input_slice = unsafe { null_safe_slice(input.as_ref().data, input.as_ref().len) }; let output_slice = unsafe { slice::from_raw_parts_mut(output, output_len) };
if T::decode(input_slice, output_slice).is_err() { return ssl::SECFailure;
}
let (input_data, input_len) = unsafe { let input_ref = input.as_ref();
(input_ref.data, input_ref.len)
};
if input_data.is_null() || input_len == 0 { return ssl::SECFailure;
} let input_slice = unsafe { null_safe_slice(input_data, input_len) };
unsafe {
p11::SECITEM_AllocItem(
null_mut(), // p11::SECItem is the same as ssl::SECItem
output.cast::<SECItemStr>(), // Compression shouldn't make the thing *longer*, // but allocate one extra byte anyway to enable simple testing modes.
input_len + 1,
);
}
/// The maximum number of tickets to remember for a given connection. const MAX_TICKETS: usize = 4;
#[must_use] pub fn hex_snip_middle<A: AsRef<[u8]>>(buf: A) -> String { const SHOW_LEN: usize = 8; let buf = buf.as_ref(); if buf.len() <= SHOW_LEN * 2 {
hex_with_len(buf)
} else { letmut ret = String::with_capacity(SHOW_LEN * 2 + 16);
write!(&mut ret, "[{}]: ", buf.len()).expect("write OK");
for b in &buf[..SHOW_LEN] {
write!(&mut ret, "{b:02x}").expect("write OK");
}
ret.push_str("..");
for b in &buf[buf.len() - SHOW_LEN..] {
write!(&mut ret, "{b:02x}").expect("write OK");
}
ret
}
}
#[derive(Clone, Debug, PartialEq, Eq)] pubenum HandshakeState {
New,
InProgress,
AuthenticationPending, /// When encrypted client hello is enabled, the server might engage a fallback. /// This is the status that is returned. The included value is the public /// name of the server, which should be used to validate the certificate.
EchFallbackAuthenticationPending(String),
Authenticated(PRErrorCode),
Complete(SecretAgentInfo),
Failed(Error),
}
/// # Errors /// /// If `usize` is less than 32 bits and the value is too large. pub fn max_early_data(&self) -> Res<usize> {
Ok(usize::try_from(self.info.maxEarlyDataSize)?)
}
/// Get the ECH public name that was used. This will only be available /// (that is, not `None`) if `ech_accepted()` returns `false`. /// In this case, certificate validation needs to use this name rather /// than the original name to validate the certificate. If /// that validation passes (that is, `SecretAgent::authenticated` is called /// with `AuthenticationStatus::Ok`), then the handshake will still fail. /// After the failed handshake, the state will be `Error::EchRetry`, /// which contains a valid ECH configuration. /// /// # Errors /// /// When the public name is not valid UTF-8. (Note: names should be ASCII.) pub fn ech_public_name(&self) -> Res<Option<&str>> { ifself.info.valuesSet & ssl::ssl_preinfo_ech == 0 || self.info.echPublicName.is_null() {
Ok(None)
} else { let n = unsafe { CStr::from_ptr(self.info.echPublicName) };
Ok(Some(n.to_str()?))
}
}
/// `SecretAgent` holds the common parts of client and server. #[derive(Debug)] #[expect(clippy::module_name_repetitions, reason = "This is OK.")] pubstruct SecretAgent {
fd: *mut prio::PRFileDesc,
secrets: SecretHolder,
raw: Option<bool>,
io: Pin<Box<AgentIo>>,
state: HandshakeState,
/// Records whether authentication of certificates is required.
auth_required: Pin<Box<bool>>, /// Records any fatal alert that is sent by the stack.
alert: Pin<Box<Option<Alert>>>, /// The current time.
now: TimeHolder,
extension_handlers: Vec<ExtensionTracker>,
/// The encrypted client hello (ECH) configuration that is in use. /// Empty if ECH is not enabled.
ech_config: Vec<u8>,
}
// Create a new SSL file descriptor. // // Note that we create separate bindings for PRFileDesc as both // ssl::PRFileDesc and prio::PRFileDesc. This keeps the bindings // minimal, but it means that the two forms need casts to translate // between them. ssl::PRFileDesc is left as an opaque type, as the // ssl::SSL_* APIs only need an opaque type.
fn create_fd(io: &mut Pin<Box<AgentIo>>) -> Res<*mut prio::PRFileDesc> {
assert_initialized(); let label = CString::new("sslwrapper")?; let id = unsafe { prio::PR_GetUniqueIdentity(label.as_ptr()) };
let base_fd = unsafe { prio::PR_CreateIOLayerStub(id, METHODS) }; if base_fd.is_null() { return Err(Error::CreateSslSocket);
} let fd = unsafe {
(*base_fd).secret = as_c_void(io).cast();
ssl::SSL_ImportFD(null_mut(), base_fd.cast())
}; if fd.is_null() { unsafe {
prio::PR_Close(base_fd);
} return Err(Error::CreateSslSocket);
}
Ok(fd)
}
unsafeextern"C" fn auth_complete_hook(
arg: *mut c_void,
_fd: *mut prio::PRFileDesc,
_check_sig: PRBool,
_is_server: PRBool,
) -> SECStatus { let auth_required_ptr = arg.cast::<bool>(); unsafe {
*auth_required_ptr = true;
} // NSS insists on getting SECWouldBlock here rather than accepting // the usual combination of PR_WOULD_BLOCK_ERROR and SECFailure.
SECWouldBlock
}
/// Set the versions that are supported. /// /// # Errors /// /// If the range of versions isn't supported. pub fn set_version_range(&mutself, min: Version, max: Version) -> Res<()> { let range = ssl::SSLVersionRange { min, max };
secstatus_to_res(unsafe { ssl::SSL_VersionRangeSet(self.fd, &raw const range) })
}
/// Enable a set of ciphers. Note that the order of these is not respected. /// /// # Errors /// /// If NSS can't enable or disable ciphers. pub fn set_ciphers(&mutself, ciphers: &[Cipher]) -> Res<()> { ifself.state != HandshakeState::New {
warn!("[{self}] Cannot enable ciphers in state {:?}", self.state); return Err(Error::Internal);
}
let all_ciphers = unsafe { ssl::SSL_GetImplementedCiphers() }; let cipher_count = usize::from(unsafe { ssl::SSL_GetNumImplementedCiphers() });
for i in 0..cipher_count { let p = all_ciphers.wrapping_add(i);
secstatus_to_res(unsafe {
ssl::SSL_CipherPrefSet(self.fd, i32::from(*p), PRBool::from(false))
})?;
}
for c in ciphers {
secstatus_to_res(unsafe {
ssl::SSL_CipherPrefSet(self.fd, i32::from(*c), PRBool::from(true))
})?;
}
Ok(())
}
/// Set key exchange groups. /// /// # Errors /// /// If the underlying API fails (which shouldn't happen). pub fn set_groups(&mutself, groups: &[Group]) -> Res<()> { // SSLNamedGroup is a different size to Group, so copy one by one. let group_vec: Vec<_> = groups
.iter()
.map(|&g| ssl::SSLNamedGroup::Type::from(g))
.collect();
/// Set the number of additional key shares that will be sent in the client hello /// /// # Errors /// /// If the underlying API fails (which shouldn't happen). pub fn send_additional_key_shares(&mutself, count: usize) -> Res<()> {
secstatus_to_res(unsafe {
ssl::SSL_SendAdditionalKeyShares(self.fd, c_uint::try_from(count)?)
})
}
/// Set TLS options. /// /// # Errors /// /// Returns an error if the option or option value is invalid; i.e., never. pub fn set_option(&self, opt: ssl::Opt, value: bool) -> Res<()> {
opt.set(self.fd, value)
}
/// `set_alpn` sets a list of preferred protocols, starting with the most preferred. /// Though ALPN [RFC7301] permits octet sequences, this only allows for UTF-8-encoded /// strings. /// /// This asserts if no items are provided, or if any individual item is longer than /// 255 octets in length. /// /// # Errors /// /// If the list of protocols is empty, contains an empty value, or /// contains a value longer than 255 bytes. /// /// [RFC7301]: https://datatracker.ietf.org/doc/html/rfc7301 pub fn set_alpn<A: AsRef<[u8]>>(&mutself, protocols: &[A]) -> Res<()> { // Prepare to encode. let len = protocols.len() + protocols.iter().map(|p| p.as_ref().len()).sum::<usize>(); letmut encoded = Vec::with_capacity(len); letmut add = |v: &A| -> Res<()> { let v = v.as_ref();
u8::try_from(v.len()).map_or(Err(Error::InvalidAlpn), |s| { if s > 0 {
encoded.push(s);
encoded.extend_from_slice(v);
Ok(())
} else {
Err(Error::InvalidAlpn)
}
})
};
// NSS inherited an idiosyncratic API as a result of having implemented NPN // before ALPN. For that reason, we need to put the "best" option last. let (first, rest) = protocols.split_first().ok_or(Error::InvalidAlpn)?;
for v in rest {
add(v)?;
}
add(first)?;
debug_assert_eq!(len, encoded.len());
// Now give the result to NSS.
secstatus_to_res(unsafe {
ssl::SSL_SetNextProtoNego( self.fd,
encoded.as_slice().as_ptr(),
c_uint::try_from(encoded.len())?,
)
})
}
/// Install a certificate compression mechanism. /// /// # Errors /// If the compression mechanism with the same id is already registered /// If too many compression mechanisms are already registered /// /// This returns an error if the certificate compression could not be established /// /// [RFC8879]: https://datatracker.ietf.org/doc/rfc8879/ pub fn set_certificate_compression<T: CertificateCompressor>(&mutself) -> Res<()> { if T::ID == 0 { return Err(Error::InvalidCertificateCompressionID);
}
let compressor: ssl::SSLCertificateCompressionAlgorithm =
ssl::SSLCertificateCompressionAlgorithm {
id: T::ID,
name: T::NAME.as_ptr(),
encode: T::ENABLE_ENCODING.then_some(<T as UnsafeCertCompression>::encode_callback),
decode: Some(<T as UnsafeCertCompression>::decode_callback),
}; unsafe { ssl::SSL_SetCertificateCompressionAlgorithm(self.fd, compressor) }
}
/// Install an extension handler. /// /// This can be called multiple times with different values for `ext`. The handler is provided /// as `Rc<RefCell<dyn T>>` so that the caller is able to hold a reference to the handler /// and later access any state that it accumulates. /// /// # Errors /// /// When the extension handler can't be successfully installed. pub fn extension_handler(
&mutself,
ext: Extension,
handler: Rc<RefCell<dyn ExtensionHandler>>,
) -> Res<()> { let tracker = unsafe { ExtensionTracker::new(self.fd, ext, handler)? }; self.extension_handlers.push(tracker);
Ok(())
}
// This function tracks whether handshake() or handshake_raw() was used // and prevents the other from being used.
fn set_raw(&mutself, r: bool) -> Res<()> { iflet Some(raw) = self.raw { if raw == r {
Ok(())
} else {
Err(Error::MixedHandshakeMethod)
}
} else { self.secrets.register(self.fd)?; self.raw = Some(r);
Ok(())
}
}
/// Get information about the connection. /// This includes the version, ciphersuite, and ALPN. /// /// Calling this function returns None until the connection is complete. #[must_use] pubconst fn info(&self) -> Option<&SecretAgentInfo> { match &self.state {
HandshakeState::Complete(info) => Some(info),
_ => None,
}
}
/// Get any preliminary information about the status of the connection. /// /// This includes whether 0-RTT was accepted and any information related to that. /// Calling this function collects all the relevant information. /// /// # Errors /// /// When the underlying socket functions fail. pub fn preinfo(&self) -> Res<SecretAgentPreInfo> {
SecretAgentPreInfo::new(self.fd)
}
/// Get the peer's certificate chain. #[must_use] pub fn peer_certificate(&self) -> Option<CertificateInfo> {
CertificateInfo::new(self.fd)
}
/// Export keying material per RFC 8446 Section 7.5. /// /// This can only be called after the handshake is complete. /// In TLS 1.3, there is no distinction between no context and an empty /// context, so the caller passes `&[u8]` instead of `Option<&[u8]>`. /// /// # Errors /// /// Returns `Error::InvalidState` if the handshake is not complete, /// `Error::InvalidInput` if `out` is empty, or an NSS error if the /// export fails. pub fn export_keying_material(&self, label: &[u8], context: &[u8], out: &mut [u8]) -> Res<()> { if !self.state.is_connected() { return Err(Error::InvalidState);
}
if out.is_empty() { return Err(Error::InvalidInput);
}
/// Return any fatal alert that the TLS stack might have sent. #[must_use] pub fn alert(&self) -> Option<Alert> {
*self.alert
}
/// Call this function to mark the peer as authenticated. /// /// # Panics /// /// If the handshake doesn't need to be authenticated. pub fn authenticated(&mutself, status: AuthenticationStatus) {
assert!(self.state.authentication_needed());
*self.auth_required = false; self.state = HandshakeState::Authenticated(status.into());
}
fn capture_error<T>(&mutself, res: Res<T>) -> Res<T> { iflet Err(e) = res { let e = ech::convert_ech_error(self.fd, e);
warn!("[{self}] error: {e:?}"); self.state = HandshakeState::Failed(e.clone());
Err(e)
} else {
res
}
}
fn update_state(&mutself, res: Res<()>) -> Res<()> { self.state = if is_blocked(&res) { if *self.auth_required { self.preinfo()?.ech_public_name()?.map_or(
HandshakeState::AuthenticationPending,
|public_name| {
HandshakeState::EchFallbackAuthenticationPending(public_name.to_owned())
},
)
} else {
HandshakeState::InProgress
}
} else { self.capture_error(res)?; let info = self.capture_error(SecretAgentInfo::new(self.fd))?;
HandshakeState::Complete(info)
};
info!("[{self}] state -> {:?}", self.state);
Ok(())
}
/// Drive the TLS handshake, taking bytes from `input` and putting /// any bytes necessary into `output`. /// This takes the current time as `now`. /// On success a tuple of a `HandshakeState` and usize indicate whether the handshake /// is complete and how many bytes were written to `output`, respectively. /// If the state is `HandshakeState::AuthenticationPending`, then ONLY call this /// function if you want to proceed, because this will mark the certificate as OK. /// /// # Errors /// /// When the handshake fails this returns an error. pub fn handshake(&mutself, now: Instant, input: &[u8]) -> Res<Vec<u8>> { self.now.set(now)?; self.set_raw(false)?;
let rv = { // Within this scope, _h maintains a mutable reference to self.io. let _h = self.io.wrap(input); matchself.state {
HandshakeState::Authenticated(err) => unsafe {
ssl::SSL_AuthCertificateComplete(self.fd, err)
},
_ => unsafe { ssl::SSL_ForceHandshake(self.fd) },
}
}; // Take before updating state so that we leave the output buffer empty // even if there is an error. let output = self.io.take_output(); self.update_state(secstatus_to_res(rv))?;
Ok(output)
}
/// Setup to receive records for raw handshake functions.
fn setup_raw(&mutself) -> Res<Pin<Box<RecordList>>> { self.set_raw(true)?; self.capture_error(RecordList::setup(self.fd))
}
/// Drive the TLS handshake, but get the raw content of records, not /// protected records as bytes. This function is incompatible with /// `handshake()`; use either this or `handshake()` exclusively. /// /// Ideally, this only includes records from the current epoch. /// If you send data from multiple epochs, you might end up being sad. /// /// # Errors /// /// When the handshake fails this returns an error. pub fn handshake_raw(&mutself, now: Instant, input: Option<Record>) -> Res<RecordList> { self.now.set(now)?; let records = self.setup_raw()?;
// Fire off any authentication we might need to complete. iflet HandshakeState::Authenticated(err) = self.state { let result =
secstatus_to_res(unsafe { ssl::SSL_AuthCertificateComplete(self.fd, err) });
debug!("[{self}] SSL_AuthCertificateComplete: {result:?}"); // This should return SECSuccess, so don't use update_state(). self.capture_error(result)?;
}
// Feed in any records. iflet Some(rec) = input { self.capture_error(rec.write(self.fd))?;
}
// Drive the handshake once more. let rv = secstatus_to_res(unsafe { ssl::SSL_ForceHandshake(self.fd) }); self.update_state(rv)?;
Ok(*Pin::into_inner(records))
}
/// # Panics /// /// If setup fails. pub fn close(&mutself) { // It should be safe to close multiple times. ifself.fd.is_null() { return;
} #[expect(
clippy::branches_sharing_code,
reason = "The PR_Close calls cannot be run after dropping the returned values."
)] ifself.raw == Some(true) { // Need to hold the record list in scope until the close is done. let _records = self.setup_raw().expect("Can only close"); unsafe {
prio::PR_Close(self.fd.cast());
}
} else { // Need to hold the IO wrapper in scope until the close is done. let _io = self.io.wrap(&[]); unsafe {
prio::PR_Close(self.fd.cast());
}
} let _output = self.io.take_output(); self.fd = null_mut();
}
/// State returns the status of the handshake. #[must_use] pubconst fn state(&self) -> &HandshakeState {
&self.state
}
/// Check if the indicated secret is ready for installation. #[must_use] pub fn has_secret(&self, epoch: Epoch) -> bool { self.secrets.has(epoch)
}
/// Take a read secret. This will only return a non-`None` value once. #[must_use] pub fn read_secret(&mutself, epoch: Epoch) -> Option<p11::SymKey> { self.secrets.take_read(epoch)
}
/// Take a write secret. #[must_use] pub fn write_secret(&mutself, epoch: Epoch) -> Option<p11::SymKey> { self.secrets.take_write(epoch)
}
/// Get the active ECH configuration, which is empty if ECH is disabled. #[must_use] pub fn ech_config(&self) -> &[u8] {
&self.ech_config
}
}
impl Drop for SecretAgent {
fn drop(&mutself) { self.close();
}
}
/// A TLS Client. #[derive(Debug)] pubstruct Client {
agent: SecretAgent,
/// The name of the server we're attempting a connection to.
server_name: String, /// Records the resumption tokens we've received. #[expect(clippy::box_collection, reason = "We need the Box.")]
resumption: Pin<Box<Vec<ResumptionToken>>>,
}
impl Client { /// Create a new client agent. /// /// # Errors /// /// Errors returned if the socket can't be created or configured. pub fn new<I: Into<String>>(server_name: I, grease: bool) -> Res<Self> { let server_name = server_name.into(); letmut agent = SecretAgent::new()?; let url = CString::new(server_name.as_bytes())?;
secstatus_to_res(unsafe { ssl::SSL_SetURL(agent.fd, url.as_ptr()) })?;
agent.ready(false, grease)?; letmut client = Self {
agent,
server_name,
resumption: Box::pin(Vec::new()),
};
client.ready()?;
Ok(client)
}
/// Take a resumption token. #[must_use] pub fn resumption_token(&mutself) -> Option<ResumptionToken> {
(*self.resumption).pop()
}
/// Check if there are more resumption tokens. #[must_use] pub fn has_resumption_token(&self) -> bool {
!(*self.resumption).is_empty()
}
/// Enable resumption, using a token previously provided. /// /// # Errors /// /// Error returned when the resumption token is invalid or /// the socket is not able to use the value. pub fn enable_resumption<A: AsRef<[u8]>>(&mutself, token: A) -> Res<()> { unsafe {
ssl::SSL_SetResumptionToken( self.agent.fd,
token.as_ref().as_ptr(),
c_uint::try_from(token.as_ref().len())?,
)
}
}
/// Enable encrypted client hello (ECH), using the encoded `ECHConfigList`. /// /// When ECH is enabled, a client needs to look for `Error::EchRetry` as a /// failure code. If `Error::EchRetry` is received when connecting, the /// connection attempt should be retried and the included value provided /// to this function (instead of what is received from DNS). /// /// Calling this function with an empty value for `ech_config_list` enables /// ECH greasing. When that is done, there is no need to look for `EchRetry` /// /// # Errors /// /// Error returned when the configuration is invalid. pub fn enable_ech<A: AsRef<[u8]>>(&mutself, ech_config_list: A) -> Res<()> { let config = ech_config_list.as_ref();
debug!("[{self}] Enable ECH for a server: {}", hex_with_len(config)); self.ech_config = Vec::from(config); if config.is_empty() { unsafe { ech::SSL_EnableTls13GreaseEch(self.agent.fd, PRBool::from(true)) }
} else { unsafe {
ech::SSL_SetClientEchConfigs( self.agent.fd,
config.as_ptr(),
c_uint::try_from(config.len())?,
)?; // If the ECH configuration is valid, and only then, // allow writing of different transport parameters to the inner and outer // ClientHello. Avoid setting this otherwise, as the transport // parameter extension handler filters out essential values from the // outer ClientHello. Under normal operation, NSS reports to // extension writers that an ordinary, non-ECH ClientHello is an // outer ClientHello, resulting in unwanted filtering.
SSL_CallExtensionWriterOnEchInner(self.fd, PRBool::from(true))
}
}
}
}
impl Deref for Client { type Target = SecretAgent;
fn deref(&self) -> &SecretAgent {
&self.agent
}
}
/// `ZeroRttCheckResult` encapsulates the options for handling a `ClientHello`. #[derive(Clone, Debug, PartialEq, Eq)] pubenum ZeroRttCheckResult { /// Accept 0-RTT.
Accept, /// Reject 0-RTT, but continue the handshake normally.
Reject, /// Send `HelloRetryRequest` (probably not needed for QUIC).
HelloRetryRequest(Vec<u8>), /// Fail the handshake.
Fail,
}
/// A `ZeroRttChecker` is used by the agent to validate the application token (as provided by /// `send_ticket`) pubtrait ZeroRttChecker: Debug + Unpin {
fn check(&self, token: &[u8]) -> ZeroRttCheckResult;
}
/// Using `AllowZeroRtt` for the implementation of `ZeroRttChecker` means /// accepting 0-RTT always. This generally isn't a great idea, so this /// generates a strong warning when it is used. #[derive(Debug)] pubstruct AllowZeroRtt {} impl ZeroRttChecker for AllowZeroRtt {
fn check(&self, _token: &[u8]) -> ZeroRttCheckResult {
warn!("AllowZeroRtt accepting 0-RTT");
ZeroRttCheckResult::Accept
}
}
#[derive(Debug)] pubstruct Server {
agent: SecretAgent, /// This holds the HRR callback context.
zero_rtt_check: Option<Pin<Box<ZeroRttCheckState>>>,
}
fn load_cert_and_key(name: &str) -> Res<(p11::Certificate, PrivateKey)> { let c = CString::new(name)?; let cert = p11::Certificate::from_ptr(unsafe {
p11::PK11_FindCertFromNickname(c.as_ptr(), null_mut())
})
.map_err(|_| Error::CertificateLoading)?; let key = PrivateKey::from_ptr(unsafe { p11::PK11_FindKeyByAnyCert(*cert, null_mut()) })
.map_err(|_| Error::CertificateLoading)?;
Ok((cert, key))
}
impl Server { /// Create a new server agent. /// /// # Errors /// /// Errors returned when NSS fails. pub fn new<A: AsRef<str>>(certificates: &[A]) -> Res<Self> { letmut agent = SecretAgent::new()?;
for n in certificates { let (cert, key) = load_cert_and_key(n.as_ref())?;
secstatus_to_res(unsafe {
ssl::SSL_ConfigServerCert(agent.fd, (*cert).cast(), (*key).cast(), null(), 0)
})?;
}
agent.ready(true, true)?;
Ok(Self {
agent,
zero_rtt_check: None,
})
}
/// Create a server with OCSP responses and SCTs configured. /// Not suitable for multiple certificates, because it configures the same OCSP/SCT for all /// certificates. In other words, this is good for testing that the plumbing works, not for /// a real server. /// /// # Errors /// /// Errors returned when NSS fails. pub fn new_with_ocsp_and_scts<A: AsRef<str>>(
certificates: &[A],
ocsp_responses: &[&[u8]],
scts: &[u8],
) -> Res<Self> { letmut agent = SecretAgent::new()?;
for n in certificates { let (cert, key) = load_cert_and_key(n.as_ref())?; let ocsp_items: Vec<SECItemBorrowed> = ocsp_responses
.iter()
.map(|b| SECItemBorrowed::wrap(b))
.collect::<Res<_>>()?; let ocsp_array = SECItemArray {
items: ocsp_items.as_ptr().cast::<SECItem>().cast_mut(),
len: c_uint::try_from(ocsp_items.len())?,
}; let sct_item = SECItemBorrowed::wrap(scts)?; let extra = ssl::SSLExtraServerCertDataStr { // ssl_auth_null means "I don't care what sort of certificate this is".
authType: ssl::SSLAuthType::ssl_auth_null,
certChain: null(),
stapledOCSPResponses: &raw const ocsp_array,
signedCertTimestamps: std::ptr::from_ref(&sct_item).cast(),
delegCred: null(),
delegCredPrivKey: null(),
};
secstatus_to_res(unsafe {
ssl::SSL_ConfigServerCert(
agent.fd,
(*cert).cast(),
(*key).cast(),
&raw const extra,
c_uint::try_from(size_of::<ssl::SSLExtraServerCertDataStr>())?,
)
})?;
}
agent.ready(true, true)?;
Ok(Self {
agent,
zero_rtt_check: None,
})
}
unsafeextern"C" fn hello_retry_cb(
first_hello: PRBool,
client_token: *const u8,
client_token_len: c_uint,
retry_token: *mut u8,
retry_token_len: *mut c_uint,
retry_token_max: c_uint,
arg: *mut c_void,
) -> ssl::SSLHelloRetryRequestAction::Type { if first_hello == 0 { // On the second ClientHello after HelloRetryRequest, skip checks. return ssl::SSLHelloRetryRequestAction::ssl_hello_retry_accept;
}
let check_state = unsafe { arg.cast::<ZeroRttCheckState>().as_mut().unwrap() }; let token = unsafe { null_safe_slice(client_token, usize::try_from(client_token_len).unwrap()) }; match check_state.checker.check(token) {
ZeroRttCheckResult::Accept => ssl::SSLHelloRetryRequestAction::ssl_hello_retry_accept,
ZeroRttCheckResult::Fail => ssl::SSLHelloRetryRequestAction::ssl_hello_retry_fail,
ZeroRttCheckResult::Reject => {
ssl::SSLHelloRetryRequestAction::ssl_hello_retry_reject_0rtt
}
ZeroRttCheckResult::HelloRetryRequest(tok) => { // Don't bother propagating errors from this, because it should be caught in // testing.
assert!(tok.len() <= usize::try_from(retry_token_max).unwrap()); // and `retry_token_len` is a valid pointer provided by NSS. unsafe { let slc = slice::from_raw_parts_mut(retry_token, tok.len());
slc.copy_from_slice(&tok);
*retry_token_len = c_uint::try_from(tok.len()).unwrap();
}
ssl::SSLHelloRetryRequestAction::ssl_hello_retry_request
}
}
}
/// Enable 0-RTT. This shadows the function of the same name that can be accessed /// via the Deref implementation on Server. /// /// # Errors /// /// Returns an error if the underlying NSS functions fail. pub fn enable_0rtt(
&mutself,
anti_replay: &AntiReplay,
max_early_data: u32,
checker: Box<dyn ZeroRttChecker>,
) -> Res<()> { letmut check_state = Box::pin(ZeroRttCheckState::new(checker)); unsafe {
ssl::SSL_HelloRetryRequestCallback( self.agent.fd,
Some(Self::hello_retry_cb),
as_c_void(&mut check_state),
)
}?; unsafe { ssl::SSL_SetMaxEarlyDataSize(self.agent.fd, max_early_data) }?; self.zero_rtt_check = Some(check_state); self.agent.enable_0rtt()?;
anti_replay.config_socket(self.fd)?;
Ok(())
}
/// Send a session ticket to the client. /// This adds |extra| application-specific content into that ticket. /// The records that are sent are captured and returned. /// /// # Errors /// /// If NSS is unable to send a ticket, or if this agent is incorrectly configured. pub fn send_ticket(&mutself, now: Instant, extra: &[u8]) -> Res<RecordList> { self.agent.now.set(now)?; let records = self.setup_raw()?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.