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