- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 67 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
Source code
Revision control
Copy as Markdown
Other Tools
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{os::raw::c_char, str::Utf8Error};
use crate::ssl::{SECStatus, SECSuccess};
include!(concat!(env!("OUT_DIR"), "/nspr_error.rs"));
#[expect(non_snake_case, dead_code, reason = "Code is bindgen-generated.")]
mod codes {
include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs"));
}
pub use codes::{SECErrorCodes as sec, SSLErrorCodes as ssl};
use thiserror::Error;
#[expect(dead_code, reason = "Code is bindgen-generated.")]
pub mod nspr {
include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
}
#[expect(dead_code, reason = "Some constants are not used.")]
pub mod mozpkix {
// These are manually extracted from the many bindings generated
// by bindgen when provided with the simple header:
// #include "mozpkix/pkixnss.h"
#[expect(non_camel_case_types, reason = "Code is bindgen-generated.")]
pub type mozilla_pkix_ErrorCode = ::std::os::raw::c_int;
pub const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE: mozilla_pkix_ErrorCode = -16384;
pub const MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY: mozilla_pkix_ErrorCode = -16383;
pub const MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE: mozilla_pkix_ErrorCode = -16382;
pub const MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA: mozilla_pkix_ErrorCode = -16381;
pub const MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH: mozilla_pkix_ErrorCode = -16380;
pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE: mozilla_pkix_ErrorCode = -16379;
pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: mozilla_pkix_ErrorCode = -16378;
pub const MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH: mozilla_pkix_ErrorCode = -16377;
pub const MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING: mozilla_pkix_ErrorCode = -16376;
pub const MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG: mozilla_pkix_ErrorCode = -16375;
pub const MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING: mozilla_pkix_ErrorCode = -16374;
pub const MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING: mozilla_pkix_ErrorCode = -16373;
pub const MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME: mozilla_pkix_ErrorCode = -16372;
pub const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED: mozilla_pkix_ErrorCode =
-16371;
pub const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: mozilla_pkix_ErrorCode = -16370;
pub const MOZILLA_PKIX_ERROR_MITM_DETECTED: mozilla_pkix_ErrorCode = -16369;
pub const END_OF_LIST: mozilla_pkix_ErrorCode = -16368;
}
pub type Res<T> = Result<T, Error>;
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Error)]
pub enum Error {
#[error("Certificate decoding error")]
CertificateDecoding,
#[error("Certificate encoding error")]
CertificateEncoding,
#[error("Certificate loading error")]
CertificateLoading,
#[error("Cipher initialization error")]
CipherInit,
#[error("Failed to create SSL socket")]
CreateSslSocket,
#[error("ECH error, retry needed")]
EchRetry(Vec<u8>),
#[error("HKDF error")]
Hkdf,
#[error("Internal error")]
Internal,
#[error("Integer overflow")]
IntegerOverflow,
#[error("Invalid ALPN")]
InvalidAlpn,
#[error("Invalid epoch")]
InvalidEpoch,
#[error("Invalid certificate compression ID")]
InvalidCertificateCompressionID,
#[error("Mixed handshake method")]
MixedHandshakeMethod,
#[error("No data available")]
NoDataAvailable,
#[error("NSS error: {name} ({code}): {desc}")]
Nss {
name: String,
code: PRErrorCode,
desc: String,
},
#[error("Self encryption error")]
SelfEncrypt,
#[error("String conversion error")]
String,
#[error("Time travel detected")]
TimeTravel,
#[error("Unsupported cipher")]
UnsupportedCipher,
#[error("Unsupported version")]
UnsupportedVersion,
}
impl Error {
pub(crate) fn last_nss_error() -> Self {
Self::from(unsafe { PR_GetError() })
}
}
impl From<std::num::TryFromIntError> for Error {
fn from(_: std::num::TryFromIntError) -> Self {
Self::IntegerOverflow
}
}
impl From<std::ffi::NulError> for Error {
fn from(_: std::ffi::NulError) -> Self {
Self::Internal
}
}
impl From<Utf8Error> for Error {
fn from(_: Utf8Error) -> Self {
Self::String
}
}
impl From<PRErrorCode> for Error {
fn from(code: PRErrorCode) -> Self {
let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR");
let desc = wrap_str_fn(
|| unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) },
"...",
);
Self::Nss { name, code, desc }
}
}
use std::ffi::CStr;
fn wrap_str_fn<F>(f: F, dflt: &str) -> String
where
F: FnOnce() -> *const c_char,
{
unsafe {
let p = f();
if p.is_null() {
return dflt.to_string();
}
CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
pub fn secstatus_to_res(rv: SECStatus) -> Res<()> {
if rv == SECSuccess {
Ok(())
} else {
Err(Error::last_nss_error())
}
}
pub const fn is_blocked(result: &Res<()>) -> bool {
match result {
Err(Error::Nss { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR,
_ => false,
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use test_fixture::fixture_init;
use crate::{
err::{self, Error, PR_SetError, PRErrorCode, is_blocked, secstatus_to_res},
ssl::{SECFailure, SECSuccess},
};
fn set_error_code(code: PRErrorCode) {
// This code doesn't work without initializing NSS first.
fixture_init();
unsafe {
PR_SetError(code, 0);
}
}
#[test]
fn error_code() {
fixture_init();
assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ);
assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL);
assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR);
}
#[test]
fn is_ok() {
assert!(secstatus_to_res(SECSuccess).is_ok());
}
#[test]
fn is_err() {
set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ);
let r = secstatus_to_res(SECFailure);
assert!(r.is_err());
match r.unwrap_err() {
Error::Nss { name, code, desc } => {
assert_eq!(name, "SSL_ERROR_BAD_MAC_READ");
assert_eq!(code, -12273);
assert_eq!(
desc,
"SSL received a record with an incorrect Message Authentication Code."
);
}
_ => unreachable!(),
}
}
#[test]
fn is_err_zero_code() {
set_error_code(0);
let r = secstatus_to_res(SECFailure);
assert!(r.is_err());
match r.unwrap_err() {
Error::Nss { name, code, .. } => {
assert_eq!(name, "UNKNOWN_ERROR");
assert_eq!(code, 0);
// Note that we don't test |desc| here because that comes from
// strerror(0), which is platform-dependent.
}
_ => unreachable!(),
}
}
#[test]
fn blocked() {
set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR);
let r = secstatus_to_res(SECFailure);
assert!(r.is_err());
assert!(is_blocked(&r));
match r.unwrap_err() {
Error::Nss { name, code, desc } => {
assert_eq!(name, "PR_WOULD_BLOCK_ERROR");
assert_eq!(code, -5998);
assert_eq!(desc, "The operation would have blocked");
}
_ => panic!("bad error type"),
}
}
#[test]
#[expect(invalid_from_utf8, reason = "Testing error conversion.")]
fn error_from_std_errors() {
use std::ffi::CString;
assert_eq!(
Error::from(CString::new("a\0b").unwrap_err()),
Error::Internal
);
assert_eq!(
Error::from(std::str::from_utf8(&[0xff]).unwrap_err()),
Error::String
);
}
}