From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- security/manager/ssl/rsclientcerts/src/error.rs | 103 ++++ security/manager/ssl/rsclientcerts/src/lib.rs | 11 + security/manager/ssl/rsclientcerts/src/manager.rs | 671 ++++++++++++++++++++++ security/manager/ssl/rsclientcerts/src/util.rs | 540 +++++++++++++++++ 4 files changed, 1325 insertions(+) create mode 100644 security/manager/ssl/rsclientcerts/src/error.rs create mode 100644 security/manager/ssl/rsclientcerts/src/lib.rs create mode 100644 security/manager/ssl/rsclientcerts/src/manager.rs create mode 100644 security/manager/ssl/rsclientcerts/src/util.rs (limited to 'security/manager/ssl/rsclientcerts/src') diff --git a/security/manager/ssl/rsclientcerts/src/error.rs b/security/manager/ssl/rsclientcerts/src/error.rs new file mode 100644 index 0000000000..6ef9062d32 --- /dev/null +++ b/security/manager/ssl/rsclientcerts/src/error.rs @@ -0,0 +1,103 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* 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 std::fmt; + +/// Helper macro to create an Error that knows which file and line it occurred +/// on. Can optionally have some extra information as a String. +#[macro_export] +macro_rules! error_here { + ($error_type:expr) => { + Error::new($error_type, file!(), line!(), None) + }; + ($error_type:expr, $info:expr) => { + Error::new($error_type, file!(), line!(), Some($info)) + }; +} + +/// Error type for identifying errors in this crate. Use the error_here! macro +/// to instantiate. +#[derive(Debug)] +pub struct Error { + typ: ErrorType, + file: &'static str, + line: u32, + info: Option, +} + +impl Error { + pub fn new(typ: ErrorType, file: &'static str, line: u32, info: Option) -> Error { + Error { + typ, + file, + line, + info, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(info) = &self.info { + write!(f, "{} at {}:{} ({})", self.typ, self.file, self.line, info) + } else { + write!(f, "{} at {}:{}", self.typ, self.file, self.line) + } + } +} + +impl Clone for Error { + fn clone(&self) -> Self { + Error { + typ: self.typ, + file: self.file, + line: self.line, + info: self.info.as_ref().cloned(), + } + } + + fn clone_from(&mut self, source: &Self) { + self.typ = source.typ; + self.file = source.file; + self.line = source.line; + self.info = source.info.as_ref().cloned(); + } +} + +#[derive(Copy, Clone, Debug)] +pub enum ErrorType { + /// An error in an external library or resource. + ExternalError, + /// Unexpected extra input (e.g. in an ASN.1 encoding). + ExtraInput, + /// Invalid argument. + InvalidArgument, + /// Invalid data input. + InvalidInput, + /// An internal library failure (e.g. an expected invariant failed). + LibraryFailure, + /// Truncated input (e.g. in an ASN.1 encoding). + TruncatedInput, + /// Unsupported input. + UnsupportedInput, + /// A given value could not be represented in the type used for it. + ValueTooLarge, +} + +impl fmt::Display for ErrorType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let error_type_str = match self { + ErrorType::ExternalError => "ExternalError", + ErrorType::ExtraInput => "ExtraInput", + ErrorType::InvalidArgument => "InvalidArgument", + ErrorType::InvalidInput => "InvalidInput", + ErrorType::LibraryFailure => "LibraryFailure", + ErrorType::TruncatedInput => "TruncatedInput", + ErrorType::UnsupportedInput => "UnsupportedInput", + ErrorType::ValueTooLarge => "ValueTooLarge", + }; + write!(f, "{}", error_type_str) + } +} diff --git a/security/manager/ssl/rsclientcerts/src/lib.rs b/security/manager/ssl/rsclientcerts/src/lib.rs new file mode 100644 index 0000000000..01fa31e204 --- /dev/null +++ b/security/manager/ssl/rsclientcerts/src/lib.rs @@ -0,0 +1,11 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* 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/. */ + +extern crate byteorder; +extern crate pkcs11_bindings; + +pub mod error; +pub mod manager; +pub mod util; diff --git a/security/manager/ssl/rsclientcerts/src/manager.rs b/security/manager/ssl/rsclientcerts/src/manager.rs new file mode 100644 index 0000000000..507bed3a83 --- /dev/null +++ b/security/manager/ssl/rsclientcerts/src/manager.rs @@ -0,0 +1,671 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* 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 pkcs11_bindings::*; +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread; +use std::thread::JoinHandle; +use std::time::{Duration, Instant}; + +use crate::error::{Error, ErrorType}; +use crate::error_here; +use crate::util::*; + +/// Helper enum to differentiate between sessions on the modern slot and sessions on the legacy +/// slot. The former is for EC keys and RSA keys that can be used with RSA-PSS whereas the latter is +/// for RSA keys that cannot be used with RSA-PSS. +#[derive(Clone, Copy, PartialEq)] +pub enum SlotType { + Modern, + Legacy, +} + +pub trait CryptokiObject { + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool; + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]>; +} + +pub trait Sign { + fn get_signature_length( + &mut self, + data: &[u8], + params: &Option, + ) -> Result; + fn sign( + &mut self, + data: &[u8], + params: &Option, + ) -> Result, Error>; +} + +pub trait ClientCertsBackend { + type Cert: CryptokiObject; + type Key: CryptokiObject + Sign; + + #[allow(clippy::type_complexity)] + fn find_objects(&self) -> Result<(Vec, Vec), Error>; +} + +/// Helper type for sending `ManagerArguments` to the real `Manager`. +type ManagerArgumentsSender = Sender; +/// Helper type for receiving `ManagerReturnValue`s from the real `Manager`. +type ManagerReturnValueReceiver = Receiver; + +/// Helper enum that encapsulates arguments to send from the `ManagerProxy` to the real `Manager`. +/// `ManagerArguments::Stop` is a special variant that stops the background thread and drops the +/// `Manager`. +enum ManagerArguments { + OpenSession(SlotType), + CloseSession(CK_SESSION_HANDLE), + CloseAllSessions(SlotType), + StartSearch(CK_SESSION_HANDLE, Vec<(CK_ATTRIBUTE_TYPE, Vec)>), + Search(CK_SESSION_HANDLE, usize), + ClearSearch(CK_SESSION_HANDLE), + GetAttributes(CK_OBJECT_HANDLE, Vec), + StartSign( + CK_SESSION_HANDLE, + CK_OBJECT_HANDLE, + Option, + ), + GetSignatureLength(CK_SESSION_HANDLE, Vec), + Sign(CK_SESSION_HANDLE, Vec), + Stop, +} + +/// Helper enum that encapsulates return values from the real `Manager` that are sent back to the +/// `ManagerProxy`. `ManagerReturnValue::Stop` is a special variant that indicates that the +/// `Manager` will stop. +enum ManagerReturnValue { + OpenSession(Result), + CloseSession(Result<(), Error>), + CloseAllSessions(Result<(), Error>), + StartSearch(Result<(), Error>), + Search(Result, Error>), + ClearSearch(Result<(), Error>), + GetAttributes(Result>>, Error>), + StartSign(Result<(), Error>), + GetSignatureLength(Result), + Sign(Result, Error>), + Stop(Result<(), Error>), +} + +/// Helper macro to implement the body of each public `ManagerProxy` function. Takes a +/// `ManagerProxy` instance (should always be `self`), a `ManagerArguments` representing the +/// `Manager` function to call and the arguments to use, and the qualified type of the expected +/// `ManagerReturnValue` that will be received from the `Manager` when it is done. +macro_rules! manager_proxy_fn_impl { + ($manager:ident, $argument_enum:expr, $return_type:path) => { + match $manager.proxy_call($argument_enum) { + Ok($return_type(result)) => result, + Ok(_) => Err(error_here!(ErrorType::LibraryFailure)), + Err(e) => Err(e), + } + }; +} + +/// `ManagerProxy` synchronously proxies calls from any thread to the `Manager` that runs on a +/// single thread. This is necessary because the underlying OS APIs in use are not guaranteed to be +/// thread-safe (e.g. they may use thread-local storage). Using it should be identical to using the +/// real `Manager`. +pub struct ManagerProxy { + sender: ManagerArgumentsSender, + receiver: ManagerReturnValueReceiver, + thread_handle: Option>, +} + +impl ManagerProxy { + pub fn new(backend: B) -> Result { + let (proxy_sender, manager_receiver) = channel(); + let (manager_sender, proxy_receiver) = channel(); + let thread_handle = thread::Builder::new() + .name("osclientcert".into()) + .spawn(move || { + let mut real_manager = Manager::new(backend); + while let Ok(arguments) = manager_receiver.recv() { + let results = match arguments { + ManagerArguments::OpenSession(slot_type) => { + ManagerReturnValue::OpenSession(real_manager.open_session(slot_type)) + } + ManagerArguments::CloseSession(session_handle) => { + ManagerReturnValue::CloseSession( + real_manager.close_session(session_handle), + ) + } + ManagerArguments::CloseAllSessions(slot_type) => { + ManagerReturnValue::CloseAllSessions( + real_manager.close_all_sessions(slot_type), + ) + } + ManagerArguments::StartSearch(session, attrs) => { + ManagerReturnValue::StartSearch( + real_manager.start_search(session, attrs), + ) + } + ManagerArguments::Search(session, max_objects) => { + ManagerReturnValue::Search(real_manager.search(session, max_objects)) + } + ManagerArguments::ClearSearch(session) => { + ManagerReturnValue::ClearSearch(real_manager.clear_search(session)) + } + ManagerArguments::GetAttributes(object_handle, attr_types) => { + ManagerReturnValue::GetAttributes( + real_manager.get_attributes(object_handle, attr_types), + ) + } + ManagerArguments::StartSign(session, key_handle, params) => { + ManagerReturnValue::StartSign( + real_manager.start_sign(session, key_handle, params), + ) + } + ManagerArguments::GetSignatureLength(session, data) => { + ManagerReturnValue::GetSignatureLength( + real_manager.get_signature_length(session, data), + ) + } + ManagerArguments::Sign(session, data) => { + ManagerReturnValue::Sign(real_manager.sign(session, data)) + } + ManagerArguments::Stop => ManagerReturnValue::Stop(Ok(())), + }; + let stop_after_send = matches!(&results, &ManagerReturnValue::Stop(_)); + match manager_sender.send(results) { + Ok(()) => {} + Err(_) => { + break; + } + } + if stop_after_send { + break; + } + } + }); + match thread_handle { + Ok(thread_handle) => Ok(ManagerProxy { + sender: proxy_sender, + receiver: proxy_receiver, + thread_handle: Some(thread_handle), + }), + Err(_) => Err(error_here!(ErrorType::LibraryFailure)), + } + } + + fn proxy_call(&self, args: ManagerArguments) -> Result { + match self.sender.send(args) { + Ok(()) => {} + Err(_) => { + return Err(error_here!(ErrorType::LibraryFailure)); + } + }; + let result = match self.receiver.recv() { + Ok(result) => result, + Err(_) => { + return Err(error_here!(ErrorType::LibraryFailure)); + } + }; + Ok(result) + } + + pub fn open_session(&mut self, slot_type: SlotType) -> Result { + manager_proxy_fn_impl!( + self, + ManagerArguments::OpenSession(slot_type), + ManagerReturnValue::OpenSession + ) + } + + pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::CloseSession(session), + ManagerReturnValue::CloseSession + ) + } + + pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::CloseAllSessions(slot_type), + ManagerReturnValue::CloseAllSessions + ) + } + + pub fn start_search( + &mut self, + session: CK_SESSION_HANDLE, + attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec)>, + ) -> Result<(), Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::StartSearch(session, attrs), + ManagerReturnValue::StartSearch + ) + } + + pub fn search( + &mut self, + session: CK_SESSION_HANDLE, + max_objects: usize, + ) -> Result, Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::Search(session, max_objects), + ManagerReturnValue::Search + ) + } + + pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::ClearSearch(session), + ManagerReturnValue::ClearSearch + ) + } + + pub fn get_attributes( + &self, + object_handle: CK_OBJECT_HANDLE, + attr_types: Vec, + ) -> Result>>, Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::GetAttributes(object_handle, attr_types,), + ManagerReturnValue::GetAttributes + ) + } + + pub fn start_sign( + &mut self, + session: CK_SESSION_HANDLE, + key_handle: CK_OBJECT_HANDLE, + params: Option, + ) -> Result<(), Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::StartSign(session, key_handle, params), + ManagerReturnValue::StartSign + ) + } + + pub fn get_signature_length( + &self, + session: CK_SESSION_HANDLE, + data: Vec, + ) -> Result { + manager_proxy_fn_impl!( + self, + ManagerArguments::GetSignatureLength(session, data), + ManagerReturnValue::GetSignatureLength + ) + } + + pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec) -> Result, Error> { + manager_proxy_fn_impl!( + self, + ManagerArguments::Sign(session, data), + ManagerReturnValue::Sign + ) + } + + pub fn stop(&mut self) -> Result<(), Error> { + manager_proxy_fn_impl!(self, ManagerArguments::Stop, ManagerReturnValue::Stop)?; + let thread_handle = match self.thread_handle.take() { + Some(thread_handle) => thread_handle, + None => return Err(error_here!(ErrorType::LibraryFailure)), + }; + thread_handle + .join() + .map_err(|_| error_here!(ErrorType::LibraryFailure)) + } +} + +// Determines if the attributes of a given search correspond to NSS looking for all certificates or +// private keys. Returns true if so, and false otherwise. +// These searches are of the form: +// { { type: CKA_TOKEN, value: [1] }, +// { type: CKA_CLASS, value: [CKO_CERTIFICATE or CKO_PRIVATE_KEY, as serialized bytes] } } +// (although not necessarily in that order - see nssToken_TraverseCertificates and +// nssToken_FindPrivateKeys) +fn search_is_for_all_certificates_or_keys( + attrs: &[(CK_ATTRIBUTE_TYPE, Vec)], +) -> Result { + if attrs.len() != 2 { + return Ok(false); + } + let token_bytes = vec![1_u8]; + let mut found_token = false; + let cko_certificate_bytes = serialize_uint(CKO_CERTIFICATE)?; + let cko_private_key_bytes = serialize_uint(CKO_PRIVATE_KEY)?; + let mut found_certificate_or_private_key = false; + for (attr_type, attr_value) in attrs.iter() { + if attr_type == &CKA_TOKEN && attr_value == &token_bytes { + found_token = true; + } + if attr_type == &CKA_CLASS + && (attr_value == &cko_certificate_bytes || attr_value == &cko_private_key_bytes) + { + found_certificate_or_private_key = true; + } + } + Ok(found_token && found_certificate_or_private_key) +} + +const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[ + CKA_CLASS, + CKA_TOKEN, + CKA_LABEL, + CKA_ID, + CKA_VALUE, + CKA_ISSUER, + CKA_SERIAL_NUMBER, + CKA_SUBJECT, + CKA_PRIVATE, + CKA_KEY_TYPE, + CKA_MODULUS, + CKA_EC_PARAMS, +]; + +enum Object { + Cert(B::Cert), + Key(B::Key), +} + +impl Object { + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { + match self { + Object::Cert(cert) => cert.matches(slot_type, attrs), + Object::Key(key) => key.matches(slot_type, attrs), + } + } + + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + match self { + Object::Cert(cert) => cert.get_attribute(attribute), + Object::Key(key) => key.get_attribute(attribute), + } + } + + fn id(&self) -> Result<&[u8], Error> { + self.get_attribute(CKA_ID) + .ok_or_else(|| error_here!(ErrorType::LibraryFailure)) + } + + fn get_signature_length( + &mut self, + data: Vec, + params: &Option, + ) -> Result { + match self { + Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), + Object::Key(key) => key.get_signature_length(&data, params), + } + } + + fn sign( + &mut self, + data: Vec, + params: &Option, + ) -> Result, Error> { + match self { + Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), + Object::Key(key) => key.sign(&data, params), + } + } +} + +/// The `Manager` keeps track of the state of this module with respect to the PKCS #11 +/// specification. This includes what sessions are open, which search and sign operations are +/// ongoing, and what objects are known and by what handle. +pub struct Manager { + /// A map of session to session type (modern or legacy). Sessions can be created (opened) and + /// later closed. + sessions: BTreeMap, + /// A map of searches to PKCS #11 object handles that match those searches. + searches: BTreeMap>, + /// A map of sign operations to a pair of the object handle and optionally some params being + /// used by each one. + signs: BTreeMap)>, + /// A map of object handles to the underlying objects. + objects: BTreeMap>, + /// A set of certificate identifiers (not the same as handles). + cert_ids: BTreeSet>, + /// A set of key identifiers (not the same as handles). For each id in this set, there should be + /// a corresponding identical id in the `cert_ids` set. + key_ids: BTreeSet>, + /// The next session handle to hand out. + next_session: CK_SESSION_HANDLE, + /// The next object handle to hand out. + next_handle: CK_OBJECT_HANDLE, + /// The last time the implementation looked for new objects in the backend. + /// The implementation does this search no more than once every 3 seconds. + last_scan_time: Option, + backend: B, +} + +impl Manager { + pub fn new(backend: B) -> Manager { + Manager { + sessions: BTreeMap::new(), + searches: BTreeMap::new(), + signs: BTreeMap::new(), + objects: BTreeMap::new(), + cert_ids: BTreeSet::new(), + key_ids: BTreeSet::new(), + next_session: 1, + next_handle: 1, + last_scan_time: None, + backend, + } + } + + /// When a new search session is opened (provided at least 3 seconds have elapsed since the + /// last session was opened), this searches for certificates and keys to expose. We + /// de-duplicate previously-found certificates and keys by keeping track of their IDs. + fn maybe_find_new_objects(&mut self) -> Result<(), Error> { + let now = Instant::now(); + match self.last_scan_time { + Some(last_scan_time) => { + if now.duration_since(last_scan_time) < Duration::new(3, 0) { + return Ok(()); + } + } + None => {} + } + self.last_scan_time = Some(now); + let (certs, keys) = self.backend.find_objects()?; + for cert in certs { + let object = Object::Cert(cert); + if self.cert_ids.contains(object.id()?) { + continue; + } + self.cert_ids.insert(object.id()?.to_vec()); + let handle = self.get_next_handle(); + self.objects.insert(handle, object); + } + for key in keys { + let object = Object::Key(key); + if self.key_ids.contains(object.id()?) { + continue; + } + self.key_ids.insert(object.id()?.to_vec()); + let handle = self.get_next_handle(); + self.objects.insert(handle, object); + } + Ok(()) + } + + pub fn open_session(&mut self, slot_type: SlotType) -> Result { + let next_session = self.next_session; + self.next_session += 1; + self.sessions.insert(next_session, slot_type); + Ok(next_session) + } + + pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { + self.sessions + .remove(&session) + .ok_or_else(|| error_here!(ErrorType::InvalidInput)) + .map(|_| ()) + } + + pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), Error> { + let mut to_remove = Vec::new(); + for (session, open_slot_type) in self.sessions.iter() { + if slot_type == *open_slot_type { + to_remove.push(*session); + } + } + for session in to_remove { + if self.sessions.remove(&session).is_none() { + return Err(error_here!(ErrorType::LibraryFailure)); + } + } + Ok(()) + } + + fn get_next_handle(&mut self) -> CK_OBJECT_HANDLE { + let next_handle = self.next_handle; + self.next_handle += 1; + next_handle + } + + /// PKCS #11 specifies that search operations happen in three phases: setup, get any matches + /// (this part may be repeated if the caller uses a small buffer), and end. This implementation + /// does all of the work up front and gathers all matching objects during setup and retains them + /// until they are retrieved and consumed via `search`. + pub fn start_search( + &mut self, + session: CK_SESSION_HANDLE, + attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec)>, + ) -> Result<(), Error> { + let slot_type = match self.sessions.get(&session) { + Some(slot_type) => *slot_type, + None => return Err(error_here!(ErrorType::InvalidArgument)), + }; + // If the search is for an attribute we don't support, no objects will match. This check + // saves us having to look through all of our objects. + for (attr, _) in &attrs { + if !SUPPORTED_ATTRIBUTES.contains(attr) { + self.searches.insert(session, Vec::new()); + return Ok(()); + } + } + // When NSS wants to find all certificates or all private keys, it will perform a search + // with a particular set of attributes. This implementation uses these searches as an + // indication for the backend to re-scan for new objects from tokens that may have been + // inserted or certificates that may have been imported into the OS. Since these searches + // are relatively rare, this minimizes the impact of doing these re-scans. + if search_is_for_all_certificates_or_keys(&attrs)? { + self.maybe_find_new_objects()?; + } + let mut handles = Vec::new(); + for (handle, object) in &self.objects { + if object.matches(slot_type, &attrs) { + handles.push(*handle); + } + } + self.searches.insert(session, handles); + Ok(()) + } + + /// Given a session and a maximum number of object handles to return, attempts to retrieve up to + /// that many objects from the corresponding search. Updates the search so those objects are not + /// returned repeatedly. `max_objects` must be non-zero. + pub fn search( + &mut self, + session: CK_SESSION_HANDLE, + max_objects: usize, + ) -> Result, Error> { + if max_objects == 0 { + return Err(error_here!(ErrorType::InvalidArgument)); + } + match self.searches.get_mut(&session) { + Some(search) => { + let split_at = if max_objects >= search.len() { + 0 + } else { + search.len() - max_objects + }; + let to_return = search.split_off(split_at); + if to_return.len() > max_objects { + return Err(error_here!(ErrorType::LibraryFailure)); + } + Ok(to_return) + } + None => Err(error_here!(ErrorType::InvalidArgument)), + } + } + + pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { + self.searches.remove(&session); + Ok(()) + } + + pub fn get_attributes( + &self, + object_handle: CK_OBJECT_HANDLE, + attr_types: Vec, + ) -> Result>>, Error> { + let object = match self.objects.get(&object_handle) { + Some(object) => object, + None => return Err(error_here!(ErrorType::InvalidArgument)), + }; + let mut results = Vec::with_capacity(attr_types.len()); + for attr_type in attr_types { + let result = object + .get_attribute(attr_type) + .map(|value| value.to_owned()); + results.push(result); + } + Ok(results) + } + + /// The way NSS uses PKCS #11 to sign data happens in two phases: setup and sign. This + /// implementation makes a note of which key is to be used (if it exists) during setup. When the + /// caller finishes with the sign operation, this implementation retrieves the key handle and + /// performs the signature. + pub fn start_sign( + &mut self, + session: CK_SESSION_HANDLE, + key_handle: CK_OBJECT_HANDLE, + params: Option, + ) -> Result<(), Error> { + if self.signs.contains_key(&session) { + return Err(error_here!(ErrorType::InvalidArgument)); + } + self.signs.insert(session, (key_handle, params)); + Ok(()) + } + + pub fn get_signature_length( + &mut self, + session: CK_SESSION_HANDLE, + data: Vec, + ) -> Result { + let (key_handle, params) = match self.signs.get(&session) { + Some((key_handle, params)) => (key_handle, params), + None => return Err(error_here!(ErrorType::InvalidArgument)), + }; + let key = match self.objects.get_mut(key_handle) { + Some(key) => key, + None => return Err(error_here!(ErrorType::InvalidArgument)), + }; + key.get_signature_length(data, params) + } + + pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec) -> Result, Error> { + // Performing the signature (via C_Sign, which is the only way we support) finishes the sign + // operation, so it needs to be removed here. + let (key_handle, params) = match self.signs.remove(&session) { + Some((key_handle, params)) => (key_handle, params), + None => return Err(error_here!(ErrorType::InvalidArgument)), + }; + let key = match self.objects.get_mut(&key_handle) { + Some(key) => key, + None => return Err(error_here!(ErrorType::InvalidArgument)), + }; + key.sign(data, ¶ms) + } +} diff --git a/security/manager/ssl/rsclientcerts/src/util.rs b/security/manager/ssl/rsclientcerts/src/util.rs new file mode 100644 index 0000000000..d0011a0a2e --- /dev/null +++ b/security/manager/ssl/rsclientcerts/src/util.rs @@ -0,0 +1,540 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* 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 byteorder::{BigEndian, NativeEndian, ReadBytesExt, WriteBytesExt}; +use std::convert::TryInto; + +use crate::error::{Error, ErrorType}; +use crate::error_here; + +/// Accessing fields of packed structs is unsafe (it may be undefined behavior if the field isn't +/// aligned). Since we're implementing a PKCS#11 module, we already have to trust the caller not to +/// give us bad data, so normally we would deal with this by adding an unsafe block. If we do that, +/// though, the compiler complains that the unsafe block is unnecessary. Thus, we use this macro to +/// annotate the unsafe block to silence the compiler. +#[macro_export] +macro_rules! unsafe_packed_field_access { + ($e:expr) => {{ + #[allow(unused_unsafe)] + let tmp = unsafe { $e }; + tmp + }}; +} + +// The following ENCODED_OID_BYTES_* consist of the encoded bytes of an ASN.1 +// OBJECT IDENTIFIER specifying the indicated OID (in other words, the full +// tag, length, and value). +#[cfg(target_os = "macos")] +pub const ENCODED_OID_BYTES_SECP256R1: &[u8] = + &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]; +#[cfg(target_os = "macos")] +pub const ENCODED_OID_BYTES_SECP384R1: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22]; +#[cfg(target_os = "macos")] +pub const ENCODED_OID_BYTES_SECP521R1: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23]; + +// The following OID_BYTES_* consist of the contents of the bytes of an ASN.1 +// OBJECT IDENTIFIER specifying the indicated OID (in other words, just the +// value, and not the tag or length). +#[cfg(target_os = "macos")] +pub const OID_BYTES_SHA_256: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; +#[cfg(target_os = "macos")] +pub const OID_BYTES_SHA_384: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02]; +#[cfg(target_os = "macos")] +pub const OID_BYTES_SHA_512: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; +#[cfg(target_os = "macos")] +pub const OID_BYTES_SHA_1: &[u8] = &[0x2b, 0x0e, 0x03, 0x02, 0x1a]; + +// This is a helper function to take a value and lay it out in memory how +// PKCS#11 is expecting it. +pub fn serialize_uint>(value: T) -> Result, Error> { + let value_size = std::mem::size_of::(); + let mut value_buf = Vec::with_capacity(value_size); + let value_as_u64 = value + .try_into() + .map_err(|_| error_here!(ErrorType::ValueTooLarge))?; + value_buf + .write_uint::(value_as_u64, value_size) + .map_err(|_| error_here!(ErrorType::LibraryFailure))?; + Ok(value_buf) +} + +/// Given a slice of DER bytes representing an RSA public key, extracts the bytes of the modulus +/// as an unsigned integer. Also verifies that the public exponent is present (again as an +/// unsigned integer). Finally verifies that reading these values consumes the entirety of the +/// slice. +/// RSAPublicKey ::= SEQUENCE { +/// modulus INTEGER, -- n +/// publicExponent INTEGER -- e +/// } +pub fn read_rsa_modulus(public_key: &[u8]) -> Result, Error> { + let mut sequence = Sequence::new(public_key)?; + let modulus_value = sequence.read_unsigned_integer()?; + let _exponent = sequence.read_unsigned_integer()?; + if !sequence.at_end() { + return Err(error_here!(ErrorType::ExtraInput)); + } + Ok(modulus_value.to_vec()) +} + +/// Given a slice of DER bytes representing a DigestInfo, extracts the bytes of +/// the OID of the hash algorithm and the digest. +/// DigestInfo ::= SEQUENCE { +/// digestAlgorithm DigestAlgorithmIdentifier, +/// digest Digest } +/// +/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier +/// +/// AlgorithmIdentifier ::= SEQUENCE { +/// algorithm OBJECT IDENTIFIER, +/// parameters ANY DEFINED BY algorithm OPTIONAL } +/// +/// Digest ::= OCTET STRING +pub fn read_digest_info(digest_info: &[u8]) -> Result<(&[u8], &[u8]), Error> { + let mut sequence = Sequence::new(digest_info)?; + let mut algorithm = sequence.read_sequence()?; + let oid = algorithm.read_oid()?; + algorithm.read_null()?; + if !algorithm.at_end() { + return Err(error_here!(ErrorType::ExtraInput)); + } + let digest = sequence.read_octet_string()?; + if !sequence.at_end() { + return Err(error_here!(ErrorType::ExtraInput)); + } + Ok((oid, digest)) +} + +/// Given a slice of DER bytes representing an ECDSA signature, extracts the bytes of `r` and `s` +/// as unsigned integers. Also verifies that this consumes the entirety of the slice. +/// Ecdsa-Sig-Value ::= SEQUENCE { +/// r INTEGER, +/// s INTEGER } +#[cfg(target_os = "macos")] +pub fn read_ec_sig_point(signature: &[u8]) -> Result<(&[u8], &[u8]), Error> { + let mut sequence = Sequence::new(signature)?; + let r = sequence.read_unsigned_integer()?; + let s = sequence.read_unsigned_integer()?; + if !sequence.at_end() { + return Err(error_here!(ErrorType::ExtraInput)); + } + Ok((r, s)) +} + +/// Given a slice of DER bytes representing an X.509 certificate, extracts the encoded serial +/// number, issuer, and subject. Does not verify that the remainder of the certificate is in any +/// way well-formed. +/// Certificate ::= SEQUENCE { +/// tbsCertificate TBSCertificate, +/// signatureAlgorithm AlgorithmIdentifier, +/// signatureValue BIT STRING } +/// +/// TBSCertificate ::= SEQUENCE { +/// version [0] EXPLICIT Version DEFAULT v1, +/// serialNumber CertificateSerialNumber, +/// signature AlgorithmIdentifier, +/// issuer Name, +/// validity Validity, +/// subject Name, +/// ... +/// +/// CertificateSerialNumber ::= INTEGER +/// +/// Name ::= CHOICE { -- only one possibility for now -- +/// rdnSequence RDNSequence } +/// +/// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +/// +/// Validity ::= SEQUENCE { +/// notBefore Time, +/// notAfter Time } +#[allow(clippy::type_complexity)] +pub fn read_encoded_certificate_identifiers( + certificate: &[u8], +) -> Result<(Vec, Vec, Vec), Error> { + let mut certificate_sequence = Sequence::new(certificate)?; + let mut tbs_certificate_sequence = certificate_sequence.read_sequence()?; + let _version = tbs_certificate_sequence.read_tagged_value(0)?; + let serial_number = tbs_certificate_sequence.read_encoded_sequence_component(INTEGER)?; + let _signature = tbs_certificate_sequence.read_sequence()?; + let issuer = + tbs_certificate_sequence.read_encoded_sequence_component(SEQUENCE | CONSTRUCTED)?; + let _validity = tbs_certificate_sequence.read_sequence()?; + let subject = + tbs_certificate_sequence.read_encoded_sequence_component(SEQUENCE | CONSTRUCTED)?; + Ok((serial_number, issuer, subject)) +} + +/// Helper macro for reading some bytes from a slice while checking the slice is long enough. +/// Returns a pair consisting of a slice of the bytes read and a slice of the rest of the bytes +/// from the original slice. +macro_rules! try_read_bytes { + ($data:ident, $len:expr) => {{ + if $data.len() < $len { + return Err(error_here!(ErrorType::TruncatedInput)); + } + $data.split_at($len) + }}; +} + +/// ASN.1 tag identifying an integer. +const INTEGER: u8 = 0x02; +/// ASN.1 tag identifying an octet string. +const OCTET_STRING: u8 = 0x04; +/// ASN.1 tag identifying a null value. +const NULL: u8 = 0x05; +/// ASN.1 tag identifying an object identifier (OID). +const OBJECT_IDENTIFIER: u8 = 0x06; +/// ASN.1 tag identifying a sequence. +const SEQUENCE: u8 = 0x10; +/// ASN.1 tag modifier identifying an item as constructed. +const CONSTRUCTED: u8 = 0x20; +/// ASN.1 tag modifier identifying an item as context-specific. +const CONTEXT_SPECIFIC: u8 = 0x80; + +/// A helper struct for reading items from a DER SEQUENCE (in this case, all sequences are +/// assumed to be CONSTRUCTED). +struct Sequence<'a> { + /// The contents of the SEQUENCE. + contents: Der<'a>, +} + +impl<'a> Sequence<'a> { + fn new(input: &'a [u8]) -> Result, Error> { + let mut der = Der::new(input); + let (_, _, sequence_bytes) = der.read_tlv(SEQUENCE | CONSTRUCTED)?; + // We're assuming we want to consume the entire input for now. + if !der.at_end() { + return Err(error_here!(ErrorType::ExtraInput)); + } + Ok(Sequence { + contents: Der::new(sequence_bytes), + }) + } + + // TODO: we're not exhaustively validating this integer + fn read_unsigned_integer(&mut self) -> Result<&'a [u8], Error> { + let (_, _, bytes) = self.contents.read_tlv(INTEGER)?; + if bytes.is_empty() { + return Err(error_here!(ErrorType::InvalidInput)); + } + // There may be a leading zero (we should also check that the first bit + // of the rest of the integer is set). + if bytes[0] == 0 && bytes.len() > 1 { + let (_, integer) = bytes.split_at(1); + Ok(integer) + } else { + Ok(bytes) + } + } + + fn read_octet_string(&mut self) -> Result<&'a [u8], Error> { + let (_, _, bytes) = self.contents.read_tlv(OCTET_STRING)?; + Ok(bytes) + } + + fn read_oid(&mut self) -> Result<&'a [u8], Error> { + let (_, _, bytes) = self.contents.read_tlv(OBJECT_IDENTIFIER)?; + Ok(bytes) + } + + fn read_null(&mut self) -> Result<(), Error> { + let (_, _, bytes) = self.contents.read_tlv(NULL)?; + if bytes.is_empty() { + Ok(()) + } else { + Err(error_here!(ErrorType::InvalidInput)) + } + } + + fn read_sequence(&mut self) -> Result, Error> { + let (_, _, sequence_bytes) = self.contents.read_tlv(SEQUENCE | CONSTRUCTED)?; + Ok(Sequence { + contents: Der::new(sequence_bytes), + }) + } + + fn read_tagged_value(&mut self, tag: u8) -> Result<&'a [u8], Error> { + let (_, _, tagged_value_bytes) = self + .contents + .read_tlv(CONTEXT_SPECIFIC | CONSTRUCTED | tag)?; + Ok(tagged_value_bytes) + } + + fn read_encoded_sequence_component(&mut self, tag: u8) -> Result, Error> { + let (tag, length, value) = self.contents.read_tlv(tag)?; + let mut encoded_component_bytes = length; + encoded_component_bytes.insert(0, tag); + encoded_component_bytes.extend_from_slice(value); + Ok(encoded_component_bytes) + } + + fn at_end(&self) -> bool { + self.contents.at_end() + } +} + +/// A helper struct for reading DER data. The contents are treated like a cursor, so its position +/// is updated as data is read. +struct Der<'a> { + contents: &'a [u8], +} + +impl<'a> Der<'a> { + fn new(contents: &'a [u8]) -> Der<'a> { + Der { contents } + } + + // In theory, a caller could encounter an error and try another operation, in which case we may + // be in an inconsistent state. As long as this implementation isn't exposed to code that would + // use it incorrectly (i.e. it stays in this module and we only expose a stateless API), it + // should be safe. + /// Given an expected tag, reads the next (tag, lengh, value) from the contents. Most + /// consumers will only be interested in the value, but some may want the entire encoded + /// contents, in which case the returned tuple can be concatenated. + fn read_tlv(&mut self, tag: u8) -> Result<(u8, Vec, &'a [u8]), Error> { + let contents = self.contents; + let (tag_read, rest) = try_read_bytes!(contents, 1); + if tag_read[0] != tag { + return Err(error_here!(ErrorType::InvalidInput)); + } + let mut accumulated_length_bytes = Vec::with_capacity(4); + let (length1, rest) = try_read_bytes!(rest, 1); + accumulated_length_bytes.extend_from_slice(length1); + let (length, to_read_from) = if length1[0] < 0x80 { + (length1[0] as usize, rest) + } else if length1[0] == 0x81 { + let (length, rest) = try_read_bytes!(rest, 1); + accumulated_length_bytes.extend_from_slice(length); + if length[0] < 0x80 { + return Err(error_here!(ErrorType::InvalidInput)); + } + (length[0] as usize, rest) + } else if length1[0] == 0x82 { + let (mut lengths, rest) = try_read_bytes!(rest, 2); + accumulated_length_bytes.extend_from_slice(lengths); + let length = lengths + .read_u16::() + .map_err(|_| error_here!(ErrorType::LibraryFailure))?; + if length < 256 { + return Err(error_here!(ErrorType::InvalidInput)); + } + (length as usize, rest) + } else { + return Err(error_here!(ErrorType::UnsupportedInput)); + }; + let (contents, rest) = try_read_bytes!(to_read_from, length); + self.contents = rest; + Ok((tag, accumulated_length_bytes, contents)) + } + + fn at_end(&self) -> bool { + self.contents.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn der_test_empty_input() { + let input = Vec::new(); + let mut der = Der::new(&input); + assert!(der.read_tlv(INTEGER).is_err()); + } + + #[test] + fn der_test_no_length() { + let input = vec![INTEGER]; + let mut der = Der::new(&input); + assert!(der.read_tlv(INTEGER).is_err()); + } + + #[test] + fn der_test_empty_sequence() { + let input = vec![SEQUENCE, 0]; + let mut der = Der::new(&input); + let read_result = der.read_tlv(SEQUENCE); + assert!(read_result.is_ok()); + let (tag, length, sequence_bytes) = read_result.unwrap(); + assert_eq!(tag, SEQUENCE); + assert_eq!(length, vec![0]); + assert_eq!(sequence_bytes.len(), 0); + assert!(der.at_end()); + } + + #[test] + fn der_test_not_at_end() { + let input = vec![SEQUENCE, 0, 1]; + let mut der = Der::new(&input); + let read_result = der.read_tlv(SEQUENCE); + assert!(read_result.is_ok()); + let (tag, length, sequence_bytes) = read_result.unwrap(); + assert_eq!(tag, SEQUENCE); + assert_eq!(length, vec![0]); + assert_eq!(sequence_bytes.len(), 0); + assert!(!der.at_end()); + } + + #[test] + fn der_test_wrong_tag() { + let input = vec![SEQUENCE, 0]; + let mut der = Der::new(&input); + assert!(der.read_tlv(INTEGER).is_err()); + } + + #[test] + fn der_test_truncated_two_byte_length() { + let input = vec![SEQUENCE, 0x81]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_truncated_three_byte_length() { + let input = vec![SEQUENCE, 0x82, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_truncated_data() { + let input = vec![SEQUENCE, 20, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_sequence() { + let input = vec![ + SEQUENCE, 20, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, 0, + ]; + let mut der = Der::new(&input); + let result = der.read_tlv(SEQUENCE); + assert!(result.is_ok()); + let (tag, length, value) = result.unwrap(); + assert_eq!(tag, SEQUENCE); + assert_eq!(length, vec![20]); + assert_eq!( + value, + [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, 0] + ); + assert!(der.at_end()); + } + + #[test] + fn der_test_not_shortest_two_byte_length_encoding() { + let input = vec![SEQUENCE, 0x81, 1, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_not_shortest_three_byte_length_encoding() { + let input = vec![SEQUENCE, 0x82, 0, 1, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_indefinite_length_unsupported() { + let input = vec![SEQUENCE, 0x80, 1, 2, 3, 0x00, 0x00]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_input_too_long() { + // This isn't valid DER (the contents of the SEQUENCE are truncated), but it demonstrates + // that we don't try to read too much if we're given a long length (and also that we don't + // support lengths 2^16 and up). + let input = vec![SEQUENCE, 0x83, 0x01, 0x00, 0x01, 1, 1, 1, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn empty_input_fails() { + let empty = Vec::new(); + assert!(read_rsa_modulus(&empty).is_err()); + #[cfg(target_os = "macos")] + assert!(read_ec_sig_point(&empty).is_err()); + assert!(read_encoded_certificate_identifiers(&empty).is_err()); + } + + #[test] + fn empty_sequence_fails() { + let empty = vec![SEQUENCE | CONSTRUCTED]; + assert!(read_rsa_modulus(&empty).is_err()); + #[cfg(target_os = "macos")] + assert!(read_ec_sig_point(&empty).is_err()); + assert!(read_encoded_certificate_identifiers(&empty).is_err()); + } + + #[test] + fn test_read_rsa_modulus() { + let rsa_key = include_bytes!("../test/rsa.bin"); + let result = read_rsa_modulus(rsa_key); + assert!(result.is_ok()); + let modulus = result.unwrap(); + assert_eq!(modulus, include_bytes!("../test/modulus.bin").to_vec()); + } + + #[test] + fn test_read_certificate_identifiers() { + let certificate = include_bytes!("../test/certificate.bin"); + let result = read_encoded_certificate_identifiers(certificate); + assert!(result.is_ok()); + let (serial_number, issuer, subject) = result.unwrap(); + assert_eq!( + serial_number, + &[ + 0x02, 0x14, 0x3f, 0xed, 0x7b, 0x43, 0x47, 0x8a, 0x53, 0x42, 0x5b, 0x0d, 0x50, 0xe1, + 0x37, 0x88, 0x2a, 0x20, 0x3f, 0x31, 0x17, 0x20 + ] + ); + assert_eq!( + issuer, + &[ + 0x30, 0x12, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x07, 0x54, + 0x65, 0x73, 0x74, 0x20, 0x43, 0x41 + ] + ); + assert_eq!( + subject, + &[ + 0x30, 0x1a, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x54, + 0x65, 0x73, 0x74, 0x20, 0x45, 0x6e, 0x64, 0x2d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79 + ] + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_read_digest() { + // SEQUENCE + // SEQUENCE + // OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 + // NULL + // OCTET STRING 1A7FCDB9A5F649F954885CFE145F3E93F0D1FA72BE980CC6EC82C70E1407C7D2 + let digest_info = [ + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x1, 0x65, 0x03, 0x04, 0x02, + 0x01, 0x05, 0x00, 0x04, 0x20, 0x1a, 0x7f, 0xcd, 0xb9, 0xa5, 0xf6, 0x49, 0xf9, 0x54, + 0x88, 0x5c, 0xfe, 0x14, 0x5f, 0x3e, 0x93, 0xf0, 0xd1, 0xfa, 0x72, 0xbe, 0x98, 0x0c, + 0xc6, 0xec, 0x82, 0xc7, 0x0e, 0x14, 0x07, 0xc7, 0xd2, + ]; + let result = read_digest(&digest_info); + assert!(result.is_ok()); + let digest = result.unwrap(); + assert_eq!( + digest, + &[ + 0x1a, 0x7f, 0xcd, 0xb9, 0xa5, 0xf6, 0x49, 0xf9, 0x54, 0x88, 0x5c, 0xfe, 0x14, 0x5f, + 0x3e, 0x93, 0xf0, 0xd1, 0xfa, 0x72, 0xbe, 0x98, 0x0c, 0xc6, 0xec, 0x82, 0xc7, 0x0e, + 0x14, 0x07, 0xc7, 0xd2 + ] + ); + } +} -- cgit v1.2.3