nss/pk11/
sym_key.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#[cfg(feature = "keydb")]
6use crate::util::get_last_error;
7use crate::{
8    error::*,
9    pk11::{context::HashAlgorithm, slot, types::SymKey},
10    util::{assert_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
11};
12#[cfg(feature = "keydb")]
13use std::ffi::{c_char, CString};
14use std::{
15    mem,
16    os::raw::{c_uchar, c_uint, c_ulong},
17    ptr,
18};
19
20pub fn hkdf_expand(
21    digest_alg: &HashAlgorithm,
22    key_bytes: &[u8],
23    info: &[u8],
24    len: usize,
25) -> Result<Vec<u8>> {
26    assert_nss_initialized();
27    let mech = digest_alg.as_hkdf_mechanism();
28    // Most of the following code is inspired by the Firefox WebCrypto implementation:
29    // https://searchfox.org/mozilla-central/rev/ee3905439acbf81e9c829ece0b46d09d2fa26c5c/dom/crypto/WebCryptoTask.cpp#2530-2597
30    // Except that we only do the expand part, which explains why we use null pointers below.
31    let mut hkdf_params = nss_sys::CK_NSS_HKDFParams {
32        bExtract: nss_sys::CK_FALSE,
33        pSalt: ptr::null_mut(),
34        ulSaltLen: 0,
35        bExpand: nss_sys::CK_TRUE,
36        pInfo: info.as_ptr() as *mut u8,
37        ulInfoLen: c_ulong::try_from(info.len())?,
38    };
39    let mut params = nss_sys::SECItem {
40        type_: nss_sys::SECItemType::siBuffer as u32,
41        data: &mut hkdf_params as *mut _ as *mut c_uchar,
42        len: u32::try_from(mem::size_of::<nss_sys::CK_NSS_HKDFParams>())?,
43    };
44    let base_key = import_sym_key(mech.into(), nss_sys::CKA_WRAP.into(), key_bytes)?;
45    let derived_len = i32::try_from(len)?;
46    let sym_key = unsafe {
47        SymKey::from_ptr(
48            // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
49            // derived symmetric key and don't matter because we ignore them anyway.
50            nss_sys::PK11_Derive(
51                base_key.as_mut_ptr(),
52                mech.into(),
53                &mut params,
54                nss_sys::CKM_SHA512_HMAC.into(),
55                nss_sys::CKA_SIGN.into(),
56                derived_len,
57            ),
58        )?
59    };
60    map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
61    // SAFETY: This doesn't leak, because the SECItem* returned by PK11_GetKeyData just refers to a
62    // buffer managed by `sym_key` which we copy into `out`.
63    let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
64    if u32::try_from(len)? > key_data.len {
65        return Err(ErrorKind::InternalError.into());
66    }
67    let buf = unsafe { sec_item_as_slice(&mut key_data)? };
68    Ok(buf.to_vec())
69}
70
71/// Safe wrapper around PK11_ImportSymKey that
72/// de-allocates memory when the key goes out of
73/// scope.
74pub(crate) fn import_sym_key(
75    mechanism: nss_sys::CK_MECHANISM_TYPE,
76    operation: nss_sys::CK_ATTRIBUTE_TYPE,
77    buf: &[u8],
78) -> Result<SymKey> {
79    assert_nss_initialized();
80    let mut item = nss_sys::SECItem {
81        type_: nss_sys::SECItemType::siBuffer as u32,
82        data: buf.as_ptr() as *mut c_uchar,
83        len: c_uint::try_from(buf.len())?,
84    };
85    let slot = slot::get_internal_slot()?;
86    unsafe {
87        SymKey::from_ptr(nss_sys::PK11_ImportSymKey(
88            slot.as_mut_ptr(),
89            mechanism,
90            nss_sys::PK11Origin::PK11_OriginUnwrap as u32,
91            operation,
92            &mut item,
93            ptr::null_mut(),
94        ))
95    }
96}
97
98/// Check weather a primary password has been set and NSS needs to be authenticated.
99/// Only available with the `keydb` feature.
100#[cfg(feature = "keydb")]
101pub fn authentication_with_primary_password_is_needed() -> Result<bool> {
102    let slot = slot::get_internal_key_slot()?;
103
104    unsafe {
105        Ok(
106            nss_sys::PK11_NeedLogin(slot.as_mut_ptr()) == nss_sys::PR_TRUE
107                && nss_sys::PK11_IsLoggedIn(slot.as_mut_ptr(), ptr::null_mut()) != nss_sys::PR_TRUE,
108        )
109    }
110}
111
112/// Authorize NSS key store against a user-provided primary password.
113/// Only available with the `keydb` feature.
114#[cfg(feature = "keydb")]
115pub fn authenticate_with_primary_password(primary_password: &str) -> Result<bool> {
116    let slot = slot::get_internal_key_slot()?;
117
118    let password_cstr = CString::new(primary_password).map_err(|_| ErrorKind::NulError)?;
119    unsafe {
120        Ok(
121            nss_sys::PK11_CheckUserPassword(slot.as_mut_ptr(), password_cstr.as_ptr())
122                == nss_sys::SECStatus::SECSuccess,
123        )
124    }
125}
126
127/// Retrieve a key, identified by `name`, from the internal NSS key store. If none exists, create
128/// one, persist, and return.
129/// Only available with the `keydb` feature.
130#[cfg(feature = "keydb")]
131pub fn get_or_create_aes256_key(name: &str) -> Result<Vec<u8>> {
132    let sym_key = match get_aes256_key(name) {
133        Ok(sym_key) => sym_key,
134        Err(_) => create_aes256_key(name)?,
135    };
136    let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
137    if key_data.len != nss_sys::AES_256_KEY_LENGTH {
138        return Err(ErrorKind::InvalidKeyLength.into());
139    }
140    let buf = unsafe { sec_item_as_slice(&mut key_data)? };
141    // SAFETY: `to_vec` copies the data out before there's any chance for `sym_key` to be
142    // destroyed.
143    Ok(buf.to_vec())
144}
145
146#[cfg(feature = "keydb")]
147fn get_aes256_key(name: &str) -> Result<SymKey> {
148    let slot = slot::get_internal_key_slot()?;
149    let name = CString::new(name).map_err(|_| ErrorKind::NulError)?;
150    let sym_key = unsafe {
151        SymKey::from_ptr(nss_sys::PK11_ListFixedKeysInSlot(
152            slot.as_mut_ptr(),
153            name.as_ptr() as *mut c_char,
154            ptr::null_mut(),
155        ))
156    };
157    match sym_key {
158        Ok(sym_key) => {
159            // See
160            // https://searchfox.org/mozilla-central/source/security/manager/ssl/NSSKeyStore.cpp#163-201
161            // Unfortunately we can't use PK11_ExtractKeyValue(symKey.get()) here because softoken
162            // marks all token objects of type CKO_SECRET_KEY as sensitive. So we have to wrap and
163            // unwrap symKey to obtain a non-sensitive copy of symKey as a session object.
164            let wrapping_key = unsafe {
165                SymKey::from_ptr(nss_sys::PK11_KeyGen(
166                    slot.as_mut_ptr(),
167                    nss_sys::CKM_AES_KEY_GEN,
168                    ptr::null_mut(),
169                    16,
170                    ptr::null_mut(),
171                ))
172                .map_err(|_| get_last_error())?
173            };
174            let mut wrap_len = nss_sys::SECItem {
175                type_: nss_sys::SECItemType::siBuffer as u32,
176                data: ptr::null_mut(),
177                len: 0,
178            };
179            map_nss_secstatus(|| unsafe {
180                nss_sys::PK11_WrapSymKey(
181                    nss_sys::CKM_AES_KEY_WRAP_KWP,
182                    ptr::null_mut(),
183                    wrapping_key.as_mut_ptr(),
184                    sym_key.as_mut_ptr(),
185                    &mut wrap_len,
186                )
187            })
188            .map_err(|_| get_last_error())?;
189            // PK11_UnwrapSymKey takes an int keySize
190            if wrap_len.len > u32::MAX - 8 {
191                return Err(ErrorKind::InvalidKeyLength.into());
192            }
193            // Allocate an extra 8 bytes for CKM_AES_KEY_WRAP_KWP overhead.
194            let wrapped_key = unsafe {
195                nss_sys::SECITEM_AllocItem(ptr::null_mut(), ptr::null_mut(), wrap_len.len + 8)
196            };
197            map_nss_secstatus(|| unsafe {
198                nss_sys::PK11_WrapSymKey(
199                    nss_sys::CKM_AES_KEY_WRAP_KWP,
200                    ptr::null_mut(),
201                    wrapping_key.as_mut_ptr(),
202                    sym_key.as_mut_ptr(),
203                    wrapped_key,
204                )
205            })
206            .map_err(|_| get_last_error())?;
207            let sym_key = unsafe {
208                SymKey::from_ptr(nss_sys::PK11_UnwrapSymKey(
209                    wrapping_key.as_mut_ptr(),
210                    nss_sys::CKM_AES_KEY_WRAP_KWP,
211                    ptr::null_mut(),
212                    wrapped_key,
213                    nss_sys::CKM_AES_GCM.into(),
214                    (nss_sys::CKA_ENCRYPT | nss_sys::CKA_DECRYPT).into(),
215                    wrap_len.len as i32,
216                ))
217            }
218            .map_err(|_| get_last_error())?;
219
220            map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
221            Ok(sym_key)
222        }
223        Err(e) => Err(e),
224    }
225}
226
227#[cfg(feature = "keydb")]
228fn create_aes256_key(name: &str) -> Result<SymKey> {
229    let mut key_bytes: [u8; nss_sys::AES_256_KEY_LENGTH as usize] =
230        [0; nss_sys::AES_256_KEY_LENGTH as usize];
231    map_nss_secstatus(|| unsafe {
232        nss_sys::PK11_GenerateRandom(key_bytes.as_mut_ptr(), nss_sys::AES_256_KEY_LENGTH as i32)
233    })?;
234    match import_and_persist_sym_key(
235        nss_sys::CKM_AES_GCM.into(),
236        nss_sys::PK11Origin::PK11_OriginGenerated,
237        (nss_sys::CKA_ENCRYPT | nss_sys::CKA_DECRYPT).into(),
238        &key_bytes,
239    ) {
240        Ok(sym_key) => {
241            let name = CString::new(name).map_err(|_| ErrorKind::NulError)?;
242            unsafe { nss_sys::PK11_SetSymKeyNickname(sym_key.as_mut_ptr(), name.as_ptr()) };
243            Ok(sym_key)
244        }
245        Err(e) => Err(e),
246    }
247}
248
249/// Safe wrapper around PK11_ImportSymKey that
250/// de-allocates memory when the key goes out of
251/// scope, and persists key in key4.db.
252#[cfg(feature = "keydb")]
253fn import_and_persist_sym_key(
254    mechanism: nss_sys::CK_MECHANISM_TYPE,
255    origin: nss_sys::PK11Origin,
256    operation: nss_sys::CK_ATTRIBUTE_TYPE,
257    buf: &[u8],
258) -> Result<SymKey> {
259    let mut item = nss_sys::SECItem {
260        type_: nss_sys::SECItemType::siBuffer as u32,
261        data: buf.as_ptr() as *mut c_uchar,
262        len: c_uint::try_from(buf.len())?,
263    };
264    let slot = slot::get_internal_key_slot()?;
265    unsafe {
266        SymKey::from_ptr(nss_sys::PK11_ImportSymKeyWithFlags(
267            slot.as_mut_ptr(),
268            mechanism,
269            origin as u32,
270            operation,
271            &mut item,
272            nss_sys::CK_FLAGS::default(),
273            nss_sys::PR_TRUE,
274            ptr::null_mut(),
275        ))
276    }
277}