openssl/
error.rs

1//! Errors returned by OpenSSL library.
2//!
3//! OpenSSL errors are stored in an `ErrorStack`.  Most methods in the crate
4//! returns a `Result<T, ErrorStack>` type.
5//!
6//! # Examples
7//!
8//! ```
9//! use openssl::error::ErrorStack;
10//! use openssl::bn::BigNum;
11//!
12//! let an_error = BigNum::from_dec_str("Cannot parse letters");
13//! match an_error {
14//!     Ok(_)  => (),
15//!     Err(e) => println!("Parsing Error: {:?}", e),
16//! }
17//! ```
18use cfg_if::cfg_if;
19use libc::{c_char, c_int};
20use std::borrow::Cow;
21#[cfg(any(boringssl, awslc))]
22use std::convert::TryInto;
23use std::error;
24use std::ffi::CStr;
25use std::fmt;
26use std::io;
27use std::ptr;
28use std::str;
29
30#[cfg(not(any(boringssl, awslc)))]
31type ErrType = libc::c_ulong;
32#[cfg(any(boringssl, awslc))]
33type ErrType = libc::c_uint;
34
35/// Collection of [`Error`]s from OpenSSL.
36///
37/// [`Error`]: struct.Error.html
38#[derive(Debug, Clone)]
39pub struct ErrorStack(Vec<Error>);
40
41impl ErrorStack {
42    /// Returns the contents of the OpenSSL error stack.
43    pub fn get() -> ErrorStack {
44        let mut vec = vec![];
45        while let Some(err) = Error::get() {
46            vec.push(err);
47        }
48        ErrorStack(vec)
49    }
50
51    /// Pushes the errors back onto the OpenSSL error stack.
52    pub fn put(&self) {
53        for error in self.errors() {
54            error.put();
55        }
56    }
57}
58
59impl ErrorStack {
60    /// Returns the errors in the stack.
61    pub fn errors(&self) -> &[Error] {
62        &self.0
63    }
64}
65
66impl fmt::Display for ErrorStack {
67    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
68        if self.0.is_empty() {
69            return fmt.write_str("OpenSSL error");
70        }
71
72        let mut first = true;
73        for err in &self.0 {
74            if !first {
75                fmt.write_str(", ")?;
76            }
77            write!(fmt, "{}", err)?;
78            first = false;
79        }
80        Ok(())
81    }
82}
83
84impl error::Error for ErrorStack {}
85
86impl From<ErrorStack> for io::Error {
87    fn from(e: ErrorStack) -> io::Error {
88        io::Error::new(io::ErrorKind::Other, e)
89    }
90}
91
92impl From<ErrorStack> for fmt::Error {
93    fn from(_: ErrorStack) -> fmt::Error {
94        fmt::Error
95    }
96}
97
98/// An error reported from OpenSSL.
99#[derive(Clone)]
100pub struct Error {
101    code: ErrType,
102    file: ShimStr,
103    line: c_int,
104    func: Option<ShimStr>,
105    data: Option<Cow<'static, str>>,
106}
107
108unsafe impl Sync for Error {}
109unsafe impl Send for Error {}
110
111impl Error {
112    /// Returns the first error on the OpenSSL error stack.
113    pub fn get() -> Option<Error> {
114        unsafe {
115            ffi::init();
116
117            let mut file = ptr::null();
118            let mut line = 0;
119            let mut func = ptr::null();
120            let mut data = ptr::null();
121            let mut flags = 0;
122            match ERR_get_error_all(&mut file, &mut line, &mut func, &mut data, &mut flags) {
123                0 => None,
124                code => {
125                    // The memory referenced by data is only valid until that slot is overwritten
126                    // in the error stack, so we'll need to copy it off if it's dynamic
127                    let data = if flags & ffi::ERR_TXT_STRING != 0 {
128                        let bytes = CStr::from_ptr(data as *const _).to_bytes();
129                        let data = str::from_utf8(bytes).unwrap();
130                        #[cfg(not(any(boringssl, awslc)))]
131                        let data = if flags & ffi::ERR_TXT_MALLOCED != 0 {
132                            Cow::Owned(data.to_string())
133                        } else {
134                            Cow::Borrowed(data)
135                        };
136                        #[cfg(any(boringssl, awslc))]
137                        let data = Cow::Borrowed(data);
138                        Some(data)
139                    } else {
140                        None
141                    };
142
143                    let file = ShimStr::new(file);
144
145                    let func = if func.is_null() {
146                        None
147                    } else {
148                        Some(ShimStr::new(func))
149                    };
150
151                    Some(Error {
152                        code,
153                        file,
154                        line,
155                        func,
156                        data,
157                    })
158                }
159            }
160        }
161    }
162
163    /// Pushes the error back onto the OpenSSL error stack.
164    pub fn put(&self) {
165        self.put_error();
166
167        unsafe {
168            let data = match self.data {
169                Some(Cow::Borrowed(data)) => Some((data.as_ptr() as *mut c_char, 0)),
170                Some(Cow::Owned(ref data)) => {
171                    let ptr = ffi::CRYPTO_malloc(
172                        (data.len() + 1) as _,
173                        concat!(file!(), "\0").as_ptr() as _,
174                        line!() as _,
175                    ) as *mut c_char;
176                    if ptr.is_null() {
177                        None
178                    } else {
179                        ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
180                        *ptr.add(data.len()) = 0;
181                        Some((ptr, ffi::ERR_TXT_MALLOCED))
182                    }
183                }
184                None => None,
185            };
186            if let Some((ptr, flags)) = data {
187                ffi::ERR_set_error_data(ptr, flags | ffi::ERR_TXT_STRING);
188            }
189        }
190    }
191
192    #[cfg(ossl300)]
193    fn put_error(&self) {
194        unsafe {
195            ffi::ERR_new();
196            ffi::ERR_set_debug(
197                self.file.as_ptr(),
198                self.line,
199                self.func.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
200            );
201            ffi::ERR_set_error(self.library_code(), self.reason_code(), ptr::null());
202        }
203    }
204
205    #[cfg(not(ossl300))]
206    fn put_error(&self) {
207        #[cfg(not(any(boringssl, awslc)))]
208        let line = self.line;
209        #[cfg(any(boringssl, awslc))]
210        let line = self.line.try_into().unwrap();
211        unsafe {
212            ffi::ERR_put_error(
213                self.library_code(),
214                ffi::ERR_GET_FUNC(self.code),
215                self.reason_code(),
216                self.file.as_ptr(),
217                line,
218            );
219        }
220    }
221
222    /// Returns the raw OpenSSL error code for this error.
223    pub fn code(&self) -> ErrType {
224        self.code
225    }
226
227    /// Returns the name of the library reporting the error, if available.
228    pub fn library(&self) -> Option<&'static str> {
229        unsafe {
230            let cstr = ffi::ERR_lib_error_string(self.code);
231            if cstr.is_null() {
232                return None;
233            }
234            let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
235            Some(str::from_utf8(bytes).unwrap())
236        }
237    }
238
239    /// Returns the raw OpenSSL error constant for the library reporting the
240    /// error.
241    // On AWS-LC and BoringSSL ERR_GET_{LIB,FUNC,REASON} are `unsafe`, but on
242    // OpenSSL/LibreSSL they're safe.
243    #[allow(unused_unsafe)]
244    pub fn library_code(&self) -> libc::c_int {
245        unsafe { ffi::ERR_GET_LIB(self.code) }
246    }
247
248    /// Returns the name of the function reporting the error.
249    pub fn function(&self) -> Option<RetStr<'_>> {
250        self.func.as_ref().map(|s| s.as_str())
251    }
252
253    /// Returns the reason for the error.
254    pub fn reason(&self) -> Option<&'static str> {
255        unsafe {
256            let cstr = ffi::ERR_reason_error_string(self.code);
257            if cstr.is_null() {
258                return None;
259            }
260            let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
261            Some(str::from_utf8(bytes).unwrap())
262        }
263    }
264
265    /// Returns the raw OpenSSL error constant for the reason for the error.
266    // On AWS-LC and BoringSSL ERR_GET_{LIB,FUNC,REASON} are `unsafe`, but on
267    // OpenSSL/LibreSSL they're safe.
268    #[allow(unused_unsafe)]
269    pub fn reason_code(&self) -> libc::c_int {
270        unsafe { ffi::ERR_GET_REASON(self.code) }
271    }
272
273    /// Returns the name of the source file which encountered the error.
274    pub fn file(&self) -> RetStr<'_> {
275        self.file.as_str()
276    }
277
278    /// Returns the line in the source file which encountered the error.
279    pub fn line(&self) -> u32 {
280        self.line as u32
281    }
282
283    /// Returns additional data describing the error.
284    #[allow(clippy::option_as_ref_deref)]
285    pub fn data(&self) -> Option<&str> {
286        self.data.as_ref().map(|s| &**s)
287    }
288}
289
290impl fmt::Debug for Error {
291    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
292        let mut builder = fmt.debug_struct("Error");
293        builder.field("code", &self.code());
294        if let Some(library) = self.library() {
295            builder.field("library", &library);
296        }
297        if let Some(function) = self.function() {
298            builder.field("function", &function);
299        }
300        if let Some(reason) = self.reason() {
301            builder.field("reason", &reason);
302        }
303        builder.field("file", &self.file());
304        builder.field("line", &self.line());
305        if let Some(data) = self.data() {
306            builder.field("data", &data);
307        }
308        builder.finish()
309    }
310}
311
312impl fmt::Display for Error {
313    // On AWS-LC and BoringSSL ERR_GET_{LIB,FUNC,REASON} are `unsafe`, but on
314    // OpenSSL/LibreSSL they're safe.
315    #[allow(unused_unsafe)]
316    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(fmt, "error:{:08X}", self.code())?;
318        match self.library() {
319            Some(l) => write!(fmt, ":{}", l)?,
320            None => write!(fmt, ":lib({})", self.library_code())?,
321        }
322        match self.function() {
323            Some(f) => write!(fmt, ":{}", f)?,
324            None => write!(fmt, ":func({})", unsafe { ffi::ERR_GET_FUNC(self.code()) })?,
325        }
326        match self.reason() {
327            Some(r) => write!(fmt, ":{}", r)?,
328            None => write!(fmt, ":reason({})", self.reason_code())?,
329        }
330        write!(
331            fmt,
332            ":{}:{}:{}",
333            self.file(),
334            self.line(),
335            self.data().unwrap_or("")
336        )
337    }
338}
339
340impl error::Error for Error {}
341
342cfg_if! {
343    if #[cfg(ossl300)] {
344        use std::ffi::{CString};
345        use ffi::ERR_get_error_all;
346
347        type RetStr<'a> = &'a str;
348
349        #[derive(Clone)]
350        struct ShimStr(CString);
351
352        impl ShimStr {
353            unsafe fn new(s: *const c_char) -> Self {
354                ShimStr(CStr::from_ptr(s).to_owned())
355            }
356
357            fn as_ptr(&self) -> *const c_char {
358                self.0.as_ptr()
359            }
360
361            fn as_str(&self) -> &str {
362                self.0.to_str().unwrap()
363            }
364        }
365    } else {
366        #[allow(bad_style)]
367        unsafe extern "C" fn ERR_get_error_all(
368            file: *mut *const c_char,
369            line: *mut c_int,
370            func: *mut *const c_char,
371            data: *mut *const c_char,
372            flags: *mut c_int,
373        ) -> ErrType {
374            let code = ffi::ERR_get_error_line_data(file, line, data, flags);
375            *func = ffi::ERR_func_error_string(code);
376            code
377        }
378
379        type RetStr<'a> = &'static str;
380
381        #[derive(Clone)]
382        struct ShimStr(*const c_char);
383
384        impl ShimStr {
385            unsafe fn new(s: *const c_char) -> Self {
386                ShimStr(s)
387            }
388
389            fn as_ptr(&self) -> *const c_char {
390                self.0
391            }
392
393            fn as_str(&self) -> &'static str {
394                unsafe {
395                    CStr::from_ptr(self.0).to_str().unwrap()
396                }
397            }
398        }
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    #[cfg(not(ossl310))]
405    use crate::nid::Nid;
406
407    #[test]
408    // Due to a bug in OpenSSL 3.1.0, this test can hang there. Skip for now.
409    #[cfg(not(ossl310))]
410    fn test_error_library_code() {
411        let stack = Nid::create("not-an-oid", "invalid", "invalid").unwrap_err();
412        let errors = stack.errors();
413        #[cfg(not(any(boringssl, awslc)))]
414        assert_eq!(errors[0].library_code(), ffi::ERR_LIB_ASN1);
415        #[cfg(any(boringssl, awslc))]
416        assert_eq!(errors[0].library_code(), ffi::ERR_LIB_OBJ as libc::c_int);
417    }
418}