1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::error::*;
use nss_sys::*;
use std::{convert::TryFrom, ffi::CString, os::raw::c_char, sync::Once};

// This is the NSS version that this crate is claiming to be compatible with.
// We check it at runtime using `NSS_VersionCheck`.
pub const COMPATIBLE_NSS_VERSION: &str = "3.26";

static NSS_INIT: Once = Once::new();

pub fn ensure_nss_initialized() {
    NSS_INIT.call_once(|| {
        let version_ptr = CString::new(COMPATIBLE_NSS_VERSION).unwrap();
        if unsafe { NSS_VersionCheck(version_ptr.as_ptr()) == PR_FALSE } {
            panic!("Incompatible NSS version!")
        }
        let empty = CString::default();
        let flags = NSS_INIT_READONLY
            | NSS_INIT_NOCERTDB
            | NSS_INIT_NOMODDB
            | NSS_INIT_FORCEOPEN
            | NSS_INIT_OPTIMIZESPACE;
        let context = unsafe {
            NSS_InitContext(
                empty.as_ptr(),
                empty.as_ptr(),
                empty.as_ptr(),
                empty.as_ptr(),
                std::ptr::null_mut(),
                flags,
            )
        };
        if context.is_null() {
            let error = get_last_error();
            panic!("Could not initialize NSS: {}", error);
        }
    })
}

pub fn map_nss_secstatus<F>(callback: F) -> Result<()>
where
    F: FnOnce() -> SECStatus,
{
    if callback() == SECStatus::SECSuccess {
        return Ok(());
    }
    Err(get_last_error())
}

/// Retrieve and wrap the last NSS/NSPR error in the current thread.
#[cold]
pub fn get_last_error() -> Error {
    let error_code = unsafe { PR_GetError() };
    let error_text: String = usize::try_from(unsafe { PR_GetErrorTextLength() })
        .map(|error_text_len| {
            let mut out_str = vec![0u8; error_text_len + 1];
            unsafe { PR_GetErrorText(out_str.as_mut_ptr() as *mut c_char) };
            CString::new(&out_str[0..error_text_len])
                .unwrap_or_else(|_| CString::default())
                .to_str()
                .unwrap_or_else(|_| "")
                .to_owned()
        })
        .unwrap_or_else(|_| "".to_string());
    ErrorKind::NSSError(error_code, error_text).into()
}

pub(crate) trait ScopedPtr
where
    Self: std::marker::Sized,
{
    type RawType;
    unsafe fn from_ptr(ptr: *mut Self::RawType) -> Result<Self>;
    fn as_ptr(&self) -> *const Self::RawType;
    fn as_mut_ptr(&self) -> *mut Self::RawType;
}

// The macro defines a wrapper around pointers refering to types allocated by NSS,
// calling their NSS destructor method when they go out of scope to avoid memory leaks.
// The `as_ptr`/`as_mut_ptr` are provided to retrieve the raw pointers to pass to
// NSS functions that consume them.
#[macro_export]
macro_rules! scoped_ptr {
    ($scoped:ident, $target:ty, $dtor:path) => {
        pub struct $scoped {
            ptr: *mut $target,
        }

        impl crate::util::ScopedPtr for $scoped {
            type RawType = $target;

            #[allow(dead_code)]
            unsafe fn from_ptr(ptr: *mut $target) -> crate::error::Result<$scoped> {
                if !ptr.is_null() {
                    Ok($scoped { ptr })
                } else {
                    Err(crate::error::ErrorKind::InternalError.into())
                }
            }

            #[inline]
            fn as_ptr(&self) -> *const $target {
                self.ptr
            }

            #[inline]
            fn as_mut_ptr(&self) -> *mut $target {
                self.ptr
            }
        }

        impl Drop for $scoped {
            fn drop(&mut self) {
                assert!(!self.ptr.is_null());
                unsafe { $dtor(self.ptr) };
            }
        }
    };
}

pub(crate) unsafe fn sec_item_as_slice(sec_item: &mut SECItem) -> Result<&mut [u8]> {
    let sec_item_buf_len = usize::try_from(sec_item.len)?;
    let buf = std::slice::from_raw_parts_mut(sec_item.data, sec_item_buf_len);
    Ok(buf)
}