Skip to main content

variant_ssl/
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                        let data = if flags & ffi::ERR_TXT_MALLOCED != 0 {
131                            Cow::Owned(data.to_string())
132                        } else {
133                            Cow::Borrowed(data)
134                        };
135                        Some(data)
136                    } else {
137                        None
138                    };
139
140                    let file = ShimStr::new(file);
141
142                    let func = if func.is_null() {
143                        None
144                    } else {
145                        Some(ShimStr::new(func))
146                    };
147
148                    Some(Error {
149                        code,
150                        file,
151                        line,
152                        func,
153                        data,
154                    })
155                }
156            }
157        }
158    }
159
160    /// Pushes the error back onto the OpenSSL error stack.
161    pub fn put(&self) {
162        self.put_error();
163
164        unsafe {
165            let data = match self.data {
166                Some(Cow::Borrowed(data)) => Some((data.as_ptr() as *mut c_char, 0)),
167                Some(Cow::Owned(ref data)) => {
168                    let ptr = ffi::CRYPTO_malloc(
169                        (data.len() + 1) as _,
170                        concat!(file!(), "\0").as_ptr() as _,
171                        line!() as _,
172                    ) as *mut c_char;
173                    if ptr.is_null() {
174                        None
175                    } else {
176                        ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
177                        *ptr.add(data.len()) = 0;
178                        Some((ptr, ffi::ERR_TXT_MALLOCED))
179                    }
180                }
181                None => None,
182            };
183            if let Some((ptr, flags)) = data {
184                ffi::ERR_set_error_data(ptr, flags | ffi::ERR_TXT_STRING);
185            }
186        }
187    }
188
189    #[cfg(ossl300)]
190    fn put_error(&self) {
191        unsafe {
192            ffi::ERR_new();
193            ffi::ERR_set_debug(
194                self.file.as_ptr(),
195                self.line,
196                self.func.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
197            );
198            ffi::ERR_set_error(self.library_code(), self.reason_code(), ptr::null());
199        }
200    }
201
202    #[cfg(not(ossl300))]
203    fn put_error(&self) {
204        #[cfg(not(any(boringssl, awslc)))]
205        let line = self.line;
206        #[cfg(any(boringssl, awslc))]
207        let line = self.line.try_into().unwrap();
208        unsafe {
209            ffi::ERR_put_error(
210                self.library_code(),
211                ffi::ERR_GET_FUNC(self.code),
212                self.reason_code(),
213                self.file.as_ptr(),
214                line,
215            );
216        }
217    }
218
219    /// Returns the raw OpenSSL error code for this error.
220    pub fn code(&self) -> ErrType {
221        self.code
222    }
223
224    /// Returns the name of the library reporting the error, if available.
225    pub fn library(&self) -> Option<&'static str> {
226        unsafe {
227            let cstr = ffi::ERR_lib_error_string(self.code);
228            if cstr.is_null() {
229                return None;
230            }
231            let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
232            Some(str::from_utf8(bytes).unwrap())
233        }
234    }
235
236    /// Returns the raw OpenSSL error constant for the library reporting the
237    /// error.
238    // On AWS-LC and BoringSSL ERR_GET_{LIB,FUNC,REASON} are `unsafe`, but on
239    // OpenSSL/LibreSSL they're safe.
240    #[allow(unused_unsafe)]
241    pub fn library_code(&self) -> libc::c_int {
242        unsafe { ffi::ERR_GET_LIB(self.code) }
243    }
244
245    /// Returns the name of the function reporting the error.
246    pub fn function(&self) -> Option<RetStr<'_>> {
247        self.func.as_ref().map(|s| s.as_str())
248    }
249
250    /// Returns the reason for the error.
251    pub fn reason(&self) -> Option<&'static str> {
252        unsafe {
253            let cstr = ffi::ERR_reason_error_string(self.code);
254            if cstr.is_null() {
255                return None;
256            }
257            let bytes = CStr::from_ptr(cstr as *const _).to_bytes();
258            Some(str::from_utf8(bytes).unwrap())
259        }
260    }
261
262    /// Returns the raw OpenSSL error constant for the reason for the error.
263    // On AWS-LC and BoringSSL ERR_GET_{LIB,FUNC,REASON} are `unsafe`, but on
264    // OpenSSL/LibreSSL they're safe.
265    #[allow(unused_unsafe)]
266    pub fn reason_code(&self) -> libc::c_int {
267        unsafe { ffi::ERR_GET_REASON(self.code) }
268    }
269
270    /// Returns the name of the source file which encountered the error.
271    pub fn file(&self) -> RetStr<'_> {
272        self.file.as_str()
273    }
274
275    /// Returns the line in the source file which encountered the error.
276    pub fn line(&self) -> u32 {
277        self.line as u32
278    }
279
280    /// Returns additional data describing the error.
281    #[allow(clippy::option_as_ref_deref)]
282    pub fn data(&self) -> Option<&str> {
283        self.data.as_ref().map(|s| &**s)
284    }
285}
286
287impl fmt::Debug for Error {
288    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
289        let mut builder = fmt.debug_struct("Error");
290        builder.field("code", &self.code());
291        if let Some(library) = self.library() {
292            builder.field("library", &library);
293        }
294        if let Some(function) = self.function() {
295            builder.field("function", &function);
296        }
297        if let Some(reason) = self.reason() {
298            builder.field("reason", &reason);
299        }
300        builder.field("file", &self.file());
301        builder.field("line", &self.line());
302        if let Some(data) = self.data() {
303            builder.field("data", &data);
304        }
305        builder.finish()
306    }
307}
308
309impl fmt::Display for Error {
310    // On AWS-LC and BoringSSL ERR_GET_{LIB,FUNC,REASON} are `unsafe`, but on
311    // OpenSSL/LibreSSL they're safe.
312    #[allow(unused_unsafe)]
313    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(fmt, "error:{:08X}", self.code())?;
315        match self.library() {
316            Some(l) => write!(fmt, ":{}", l)?,
317            None => write!(fmt, ":lib({})", self.library_code())?,
318        }
319        match self.function() {
320            Some(f) => write!(fmt, ":{}", f)?,
321            None => write!(fmt, ":func({})", unsafe { ffi::ERR_GET_FUNC(self.code()) })?,
322        }
323        match self.reason() {
324            Some(r) => write!(fmt, ":{}", r)?,
325            None => write!(fmt, ":reason({})", self.reason_code())?,
326        }
327        write!(
328            fmt,
329            ":{}:{}:{}",
330            self.file(),
331            self.line(),
332            self.data().unwrap_or("")
333        )
334    }
335}
336
337impl error::Error for Error {}
338
339cfg_if! {
340    if #[cfg(ossl300)] {
341        use std::ffi::{CString};
342        use ffi::ERR_get_error_all;
343
344        type RetStr<'a> = &'a str;
345
346        #[derive(Clone)]
347        struct ShimStr(CString);
348
349        impl ShimStr {
350            unsafe fn new(s: *const c_char) -> Self {
351                ShimStr(CStr::from_ptr(s).to_owned())
352            }
353
354            fn as_ptr(&self) -> *const c_char {
355                self.0.as_ptr()
356            }
357
358            fn as_str(&self) -> &str {
359                self.0.to_str().unwrap()
360            }
361        }
362    } else {
363        #[allow(bad_style)]
364        unsafe extern "C" fn ERR_get_error_all(
365            file: *mut *const c_char,
366            line: *mut c_int,
367            func: *mut *const c_char,
368            data: *mut *const c_char,
369            flags: *mut c_int,
370        ) -> ErrType {
371            let code = ffi::ERR_get_error_line_data(file, line, data, flags);
372            *func = ffi::ERR_func_error_string(code);
373            code
374        }
375
376        type RetStr<'a> = &'static str;
377
378        #[derive(Clone)]
379        struct ShimStr(*const c_char);
380
381        impl ShimStr {
382            unsafe fn new(s: *const c_char) -> Self {
383                ShimStr(s)
384            }
385
386            fn as_ptr(&self) -> *const c_char {
387                self.0
388            }
389
390            fn as_str(&self) -> &'static str {
391                unsafe {
392                    CStr::from_ptr(self.0).to_str().unwrap()
393                }
394            }
395        }
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    #[cfg(not(ossl310))]
402    use crate::nid::Nid;
403
404    #[test]
405    // Due to a bug in OpenSSL 3.1.0, this test can hang there. Skip for now.
406    #[cfg(not(ossl310))]
407    fn test_error_library_code() {
408        let stack = Nid::create("not-an-oid", "invalid", "invalid").unwrap_err();
409        let errors = stack.errors();
410        #[cfg(not(any(boringssl, awslc)))]
411        assert_eq!(errors[0].library_code(), ffi::ERR_LIB_ASN1);
412        #[cfg(any(boringssl, awslc))]
413        assert_eq!(errors[0].library_code(), ffi::ERR_LIB_OBJ as libc::c_int);
414    }
415}