/rust/git/checkouts/nss-rs-71e20fe79ef91440/9b94ca3/src/err.rs
Line | Count | Source |
1 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
2 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
3 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
4 | | // option. This file may not be copied, modified, or distributed |
5 | | // except according to those terms. |
6 | | |
7 | | use std::{os::raw::c_char, str::Utf8Error}; |
8 | | |
9 | | use crate::nss_prelude::*; |
10 | | |
11 | | include!(concat!(env!("OUT_DIR"), "/nspr_error.rs")); |
12 | | #[expect(non_snake_case, dead_code, reason = "Code is bindgen-generated.")] |
13 | | mod codes { |
14 | | include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs")); |
15 | | include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs")); |
16 | | } |
17 | | pub use codes::{SECErrorCodes as sec, SSLErrorCodes as ssl}; |
18 | | use thiserror::Error; |
19 | | |
20 | | #[expect(dead_code, reason = "Code is bindgen-generated.")] |
21 | | pub mod nspr { |
22 | | include!(concat!(env!("OUT_DIR"), "/nspr_err.rs")); |
23 | | } |
24 | | |
25 | | #[cfg_attr(not(test), expect(dead_code, reason = "Some constants are not used."))] |
26 | | pub mod mozpkix { |
27 | | // These are manually extracted from the many bindings generated |
28 | | // by bindgen when provided with the simple header: |
29 | | // #include "mozpkix/pkixnss.h" |
30 | | |
31 | | #[expect(non_camel_case_types, reason = "Code is bindgen-generated.")] |
32 | | pub type mozilla_pkix_ErrorCode = ::std::os::raw::c_int; |
33 | | pub const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE: mozilla_pkix_ErrorCode = -16384; |
34 | | pub const MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY: mozilla_pkix_ErrorCode = -16383; |
35 | | pub const MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE: mozilla_pkix_ErrorCode = -16382; |
36 | | pub const MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA: mozilla_pkix_ErrorCode = -16381; |
37 | | pub const MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH: mozilla_pkix_ErrorCode = -16380; |
38 | | pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE: mozilla_pkix_ErrorCode = -16379; |
39 | | pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: mozilla_pkix_ErrorCode = -16378; |
40 | | pub const MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH: mozilla_pkix_ErrorCode = -16377; |
41 | | pub const MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING: mozilla_pkix_ErrorCode = -16376; |
42 | | pub const MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG: mozilla_pkix_ErrorCode = -16375; |
43 | | pub const MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING: mozilla_pkix_ErrorCode = -16374; |
44 | | pub const MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING: mozilla_pkix_ErrorCode = -16373; |
45 | | pub const MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME: mozilla_pkix_ErrorCode = -16372; |
46 | | pub const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED: mozilla_pkix_ErrorCode = |
47 | | -16371; |
48 | | pub const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: mozilla_pkix_ErrorCode = -16370; |
49 | | pub const MOZILLA_PKIX_ERROR_MITM_DETECTED: mozilla_pkix_ErrorCode = -16369; |
50 | | pub const END_OF_LIST: mozilla_pkix_ErrorCode = -16368; |
51 | | } |
52 | | |
53 | | pub type Res<T> = Result<T, Error>; |
54 | | |
55 | | #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Error)] |
56 | | pub enum Error { |
57 | | #[error("AEAD error")] |
58 | | Aead, |
59 | | #[error("Aead truncated")] |
60 | | AeadTruncated, |
61 | | #[error("Certificate decoding error")] |
62 | | CertificateDecoding, |
63 | | #[error("Certificate encoding error")] |
64 | | CertificateEncoding, |
65 | | #[error("Certificate loading error")] |
66 | | CertificateLoading, |
67 | | #[error("Cipher initialization error")] |
68 | | CipherInit, |
69 | | #[error("Failed to create SSL socket")] |
70 | | CreateSslSocket, |
71 | | #[error("ECH error, retry needed")] |
72 | | EchRetry(Vec<u8>), |
73 | | #[error("HKDF error")] |
74 | | Hkdf, |
75 | | #[error("Internal error")] |
76 | | Internal, |
77 | | #[error("Integer overflow")] |
78 | | IntegerOverflow, |
79 | | #[error("Invalid ALPN")] |
80 | | InvalidAlpn, |
81 | | #[error("Invalid epoch")] |
82 | | InvalidEpoch, |
83 | | #[error("Invalid certificate compression ID")] |
84 | | InvalidCertificateCompressionID, |
85 | | #[error("Invalid input")] |
86 | | InvalidInput, |
87 | | #[error("Invalid state for this operation")] |
88 | | InvalidState, |
89 | | #[error("Mixed handshake method")] |
90 | | MixedHandshakeMethod, |
91 | | #[error("No data available")] |
92 | | NoDataAvailable, |
93 | | #[error("NSS error: {name} ({code}): {desc}")] |
94 | | Nss { |
95 | | name: String, |
96 | | code: PRErrorCode, |
97 | | desc: String, |
98 | | }, |
99 | | #[error("Self encryption error")] |
100 | | SelfEncrypt, |
101 | | #[error("String conversion error")] |
102 | | String, |
103 | | #[error("Time travel detected")] |
104 | | TimeTravel, |
105 | | #[error("Unsupported cipher")] |
106 | | UnsupportedCipher, |
107 | | #[error("Unsupported curve")] |
108 | | UnsupportedCurve, |
109 | | #[error("Unsupported hash")] |
110 | | UnsupportedHash, |
111 | | #[error("Unsupported version")] |
112 | | UnsupportedVersion, |
113 | | } |
114 | | |
115 | | impl Error { |
116 | 2.28k | pub(crate) fn last_nss_error() -> Self { |
117 | 2.28k | Self::from(unsafe { PR_GetError() }) |
118 | 2.28k | } |
119 | | } |
120 | | |
121 | | impl From<std::num::TryFromIntError> for Error { |
122 | 0 | fn from(_: std::num::TryFromIntError) -> Self { |
123 | 0 | Self::IntegerOverflow |
124 | 0 | } |
125 | | } |
126 | | impl From<std::ffi::NulError> for Error { |
127 | 0 | fn from(_: std::ffi::NulError) -> Self { |
128 | 0 | Self::Internal |
129 | 0 | } |
130 | | } |
131 | | impl From<Utf8Error> for Error { |
132 | 0 | fn from(_: Utf8Error) -> Self { |
133 | 0 | Self::String |
134 | 0 | } |
135 | | } |
136 | | impl From<PRErrorCode> for Error { |
137 | 2.42k | fn from(code: PRErrorCode) -> Self { |
138 | 2.42k | let name = wrap_str_fn(|| unsafe { PR_ErrorToName(code) }, "UNKNOWN_ERROR"); |
139 | 2.42k | let desc = wrap_str_fn( |
140 | 2.42k | || unsafe { PR_ErrorToString(code, PR_LANGUAGE_I_DEFAULT) }, |
141 | 2.42k | "...", |
142 | | ); |
143 | 2.42k | Self::Nss { name, code, desc } |
144 | 2.42k | } |
145 | | } |
146 | | |
147 | | use std::ffi::CStr; |
148 | | |
149 | 4.85k | fn wrap_str_fn<F>(f: F, dflt: &str) -> String |
150 | 4.85k | where |
151 | 4.85k | F: FnOnce() -> *const c_char, |
152 | | { |
153 | | unsafe { |
154 | 4.85k | let p = f(); |
155 | 4.85k | if p.is_null() { |
156 | 0 | return dflt.to_string(); |
157 | 4.85k | } |
158 | 4.85k | CStr::from_ptr(p).to_string_lossy().into_owned() |
159 | | } |
160 | 4.85k | } nss_rs::err::wrap_str_fn::<<nss_rs::err::Error as core::convert::From<i32>>::from::{closure#0}>Line | Count | Source | 149 | 2.42k | fn wrap_str_fn<F>(f: F, dflt: &str) -> String | 150 | 2.42k | where | 151 | 2.42k | F: FnOnce() -> *const c_char, | 152 | | { | 153 | | unsafe { | 154 | 2.42k | let p = f(); | 155 | 2.42k | if p.is_null() { | 156 | 0 | return dflt.to_string(); | 157 | 2.42k | } | 158 | 2.42k | CStr::from_ptr(p).to_string_lossy().into_owned() | 159 | | } | 160 | 2.42k | } |
nss_rs::err::wrap_str_fn::<<nss_rs::err::Error as core::convert::From<i32>>::from::{closure#1}>Line | Count | Source | 149 | 2.42k | fn wrap_str_fn<F>(f: F, dflt: &str) -> String | 150 | 2.42k | where | 151 | 2.42k | F: FnOnce() -> *const c_char, | 152 | | { | 153 | | unsafe { | 154 | 2.42k | let p = f(); | 155 | 2.42k | if p.is_null() { | 156 | 0 | return dflt.to_string(); | 157 | 2.42k | } | 158 | 2.42k | CStr::from_ptr(p).to_string_lossy().into_owned() | 159 | | } | 160 | 2.42k | } |
|
161 | | |
162 | 1.77k | pub const fn is_blocked(result: &Res<()>) -> bool { |
163 | 1.77k | match result { |
164 | 1.77k | Err(Error::Nss { code, .. }) => *code == nspr::PR_WOULD_BLOCK_ERROR, |
165 | 0 | _ => false, |
166 | | } |
167 | 1.77k | } |
168 | | |
169 | | pub trait IntoResult { |
170 | | /// The `Ok` type for the result. |
171 | | type Ok; |
172 | | |
173 | | /// Unsafe in our implementors because they take a pointer and have no way |
174 | | /// to ensure that the pointer is valid. An invalid pointer could cause UB |
175 | | /// in `impl Drop for Scoped`. |
176 | | fn into_result(self) -> Result<Self::Ok, Error>; |
177 | | } |
178 | | |
179 | 128k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { |
180 | 128k | if ptr.is_null() { |
181 | 0 | Err(Error::last_nss_error()) |
182 | | } else { |
183 | 128k | Ok(ptr) |
184 | | } |
185 | 128k | } Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::nss_prelude::SECItemStr> Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::nss_prelude::SECItemArrayStr> nss_rs::err::into_result::<nss_rs::replay::SSLAntiReplayContext> Line | Count | Source | 179 | 1.28k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { | 180 | 1.28k | if ptr.is_null() { | 181 | 0 | Err(Error::last_nss_error()) | 182 | | } else { | 183 | 1.28k | Ok(ptr) | 184 | | } | 185 | 1.28k | } |
nss_rs::err::into_result::<nss_rs::p11::nss_p11::PK11SymKeyStr> Line | Count | Source | 179 | 72.7k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { | 180 | 72.7k | if ptr.is_null() { | 181 | 0 | Err(Error::last_nss_error()) | 182 | | } else { | 183 | 72.7k | Ok(ptr) | 184 | | } | 185 | 72.7k | } |
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::SECOidDataStr> nss_rs::err::into_result::<nss_rs::p11::nss_p11::PK11ContextStr> Line | Count | Source | 179 | 32.4k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { | 180 | 32.4k | if ptr.is_null() { | 181 | 0 | Err(Error::last_nss_error()) | 182 | | } else { | 183 | 32.4k | Ok(ptr) | 184 | | } | 185 | 32.4k | } |
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::CERTCertListStr> nss_rs::err::into_result::<nss_rs::p11::nss_p11::PK11SlotInfoStr> Line | Count | Source | 179 | 19.2k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { | 180 | 19.2k | if ptr.is_null() { | 181 | 0 | Err(Error::last_nss_error()) | 182 | | } else { | 183 | 19.2k | Ok(ptr) | 184 | | } | 185 | 19.2k | } |
nss_rs::err::into_result::<nss_rs::p11::nss_p11::CERTCertificateStr> Line | Count | Source | 179 | 1.28k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { | 180 | 1.28k | if ptr.is_null() { | 181 | 0 | Err(Error::last_nss_error()) | 182 | | } else { | 183 | 1.28k | Ok(ptr) | 184 | | } | 185 | 1.28k | } |
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::SECKEYPublicKeyStr> nss_rs::err::into_result::<nss_rs::p11::nss_p11::SECKEYPrivateKeyStr> Line | Count | Source | 179 | 1.28k | pub fn into_result<P>(ptr: *mut P) -> Result<*mut P, Error> { | 180 | 1.28k | if ptr.is_null() { | 181 | 0 | Err(Error::last_nss_error()) | 182 | | } else { | 183 | 1.28k | Ok(ptr) | 184 | | } | 185 | 1.28k | } |
Unexecuted instantiation: nss_rs::err::into_result::<nss_rs::p11::nss_p11::CERTSubjectPublicKeyInfoStr> |
186 | | |
187 | | // This can be used to implement `IntoResult` for pointer types that do not make |
188 | | // sense as smart pointers. For smart pointers use `scoped_ptr!`. |
189 | | macro_rules! impl_into_result { |
190 | | ($pointer:ty) => { |
191 | | impl $crate::err::IntoResult for *mut $pointer { |
192 | | type Ok = *mut $pointer; |
193 | | |
194 | 0 | fn into_result(self) -> Result<Self::Ok, $crate::err::Error> { |
195 | 0 | $crate::err::into_result(self) |
196 | 0 | } |
197 | | } |
198 | | }; |
199 | | } |
200 | | |
201 | | impl IntoResult for SECStatus { |
202 | | type Ok = (); |
203 | | |
204 | 328k | fn into_result(self) -> Result<(), Error> { |
205 | 328k | if self == SECSuccess { |
206 | 326k | Ok(()) |
207 | | } else { |
208 | 2.28k | Err(Error::last_nss_error()) |
209 | | } |
210 | 328k | } |
211 | | } |
212 | | |
213 | 328k | pub fn secstatus_to_res(code: SECStatus) -> Res<()> { |
214 | 328k | SECStatus::into_result(code) |
215 | 328k | } |
216 | | |
217 | | #[cfg(test)] |
218 | | #[cfg_attr(coverage_nightly, coverage(off))] |
219 | | mod tests { |
220 | | use test_fixture::fixture_init; |
221 | | |
222 | | use crate::{ |
223 | | err::{self, Error, PR_SetError, PRErrorCode, is_blocked, secstatus_to_res}, |
224 | | ssl::{SECFailure, SECSuccess}, |
225 | | }; |
226 | | |
227 | | fn set_error_code(code: PRErrorCode) { |
228 | | // This code doesn't work without initializing NSS first. |
229 | | fixture_init(); |
230 | | unsafe { |
231 | | PR_SetError(code, 0); |
232 | | } |
233 | | } |
234 | | |
235 | | #[test] |
236 | | fn error_code() { |
237 | | fixture_init(); |
238 | | assert_eq!(15 - 0x3000, err::ssl::SSL_ERROR_BAD_MAC_READ); |
239 | | assert_eq!(166 - 0x2000, err::sec::SEC_ERROR_LIBPKIX_INTERNAL); |
240 | | assert_eq!(-5998, err::nspr::PR_WOULD_BLOCK_ERROR); |
241 | | } |
242 | | |
243 | | #[test] |
244 | | fn is_ok() { |
245 | | assert!(secstatus_to_res(SECSuccess).is_ok()); |
246 | | } |
247 | | |
248 | | #[test] |
249 | | fn is_err() { |
250 | | set_error_code(err::ssl::SSL_ERROR_BAD_MAC_READ); |
251 | | let r = secstatus_to_res(SECFailure); |
252 | | assert!(r.is_err()); |
253 | | match r.unwrap_err() { |
254 | | Error::Nss { name, code, desc } => { |
255 | | assert_eq!(name, "SSL_ERROR_BAD_MAC_READ"); |
256 | | assert_eq!(code, -12273); |
257 | | assert_eq!( |
258 | | desc, |
259 | | "SSL received a record with an incorrect Message Authentication Code." |
260 | | ); |
261 | | } |
262 | | _ => unreachable!(), |
263 | | } |
264 | | } |
265 | | |
266 | | #[test] |
267 | | fn is_err_zero_code() { |
268 | | set_error_code(0); |
269 | | let r = secstatus_to_res(SECFailure); |
270 | | assert!(r.is_err()); |
271 | | match r.unwrap_err() { |
272 | | Error::Nss { name, code, .. } => { |
273 | | assert_eq!(name, "UNKNOWN_ERROR"); |
274 | | assert_eq!(code, 0); |
275 | | // Note that we don't test |desc| here because that comes from |
276 | | // strerror(0), which is platform-dependent. |
277 | | } |
278 | | _ => unreachable!(), |
279 | | } |
280 | | } |
281 | | |
282 | | #[test] |
283 | | fn blocked() { |
284 | | set_error_code(err::nspr::PR_WOULD_BLOCK_ERROR); |
285 | | let r = secstatus_to_res(SECFailure); |
286 | | assert!(r.is_err()); |
287 | | assert!(is_blocked(&r)); |
288 | | match r.unwrap_err() { |
289 | | Error::Nss { name, code, desc } => { |
290 | | assert_eq!(name, "PR_WOULD_BLOCK_ERROR"); |
291 | | assert_eq!(code, -5998); |
292 | | assert_eq!(desc, "The operation would have blocked"); |
293 | | } |
294 | | _ => panic!("bad error type"), |
295 | | } |
296 | | } |
297 | | |
298 | | #[test] |
299 | | #[expect(invalid_from_utf8, reason = "Testing error conversion.")] |
300 | | fn error_from_std_errors() { |
301 | | use std::ffi::CString; |
302 | | assert_eq!( |
303 | | Error::from(CString::new("a\0b").unwrap_err()), |
304 | | Error::Internal |
305 | | ); |
306 | | assert_eq!( |
307 | | Error::from(std::str::from_utf8(&[0xff]).unwrap_err()), |
308 | | Error::String |
309 | | ); |
310 | | } |
311 | | |
312 | | #[test] |
313 | | fn mozpkix_error_codes_are_negative() { |
314 | | use crate::err::mozpkix::*; |
315 | | let codes = [ |
316 | | MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE, |
317 | | MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY, |
318 | | MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE, |
319 | | MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA, |
320 | | MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH, |
321 | | MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE, |
322 | | MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE, |
323 | | MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH, |
324 | | MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING, |
325 | | MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG, |
326 | | MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING, |
327 | | MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING, |
328 | | MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME, |
329 | | MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED, |
330 | | MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT, |
331 | | MOZILLA_PKIX_ERROR_MITM_DETECTED, |
332 | | END_OF_LIST, |
333 | | ]; |
334 | | for (i, &code) in codes.iter().enumerate() { |
335 | | assert_eq!( |
336 | | code, |
337 | | -16384 + i32::try_from(i).unwrap(), |
338 | | "mozpkix code at index {i}" |
339 | | ); |
340 | | } |
341 | | } |
342 | | } |