summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/rsclientcerts
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/rsclientcerts')
-rw-r--r--security/manager/ssl/rsclientcerts/Cargo.toml10
-rw-r--r--security/manager/ssl/rsclientcerts/src/error.rs103
-rw-r--r--security/manager/ssl/rsclientcerts/src/lib.rs11
-rw-r--r--security/manager/ssl/rsclientcerts/src/manager.rs671
-rw-r--r--security/manager/ssl/rsclientcerts/src/util.rs540
-rw-r--r--security/manager/ssl/rsclientcerts/test/certificate.binbin0 -> 909 bytes
-rw-r--r--security/manager/ssl/rsclientcerts/test/modulus.bin2
-rw-r--r--security/manager/ssl/rsclientcerts/test/rsa.binbin0 -> 270 bytes
8 files changed, 1337 insertions, 0 deletions
diff --git a/security/manager/ssl/rsclientcerts/Cargo.toml b/security/manager/ssl/rsclientcerts/Cargo.toml
new file mode 100644
index 0000000000..14a35918b8
--- /dev/null
+++ b/security/manager/ssl/rsclientcerts/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "rsclientcerts"
+version = "0.1.0"
+authors = ["Dana Keeler <dkeeler@mozilla.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+byteorder = "1.3"
+pkcs11-bindings = "0.1"
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<String>,
+}
+
+impl Error {
+ pub fn new(typ: ErrorType, file: &'static str, line: u32, info: Option<String>) -> 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<u8>)]) -> bool;
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]>;
+}
+
+pub trait Sign {
+ fn get_signature_length(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<usize, Error>;
+ fn sign(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, Error>;
+}
+
+pub trait ClientCertsBackend {
+ type Cert: CryptokiObject;
+ type Key: CryptokiObject + Sign;
+
+ #[allow(clippy::type_complexity)]
+ fn find_objects(&self) -> Result<(Vec<Self::Cert>, Vec<Self::Key>), Error>;
+}
+
+/// Helper type for sending `ManagerArguments` to the real `Manager`.
+type ManagerArgumentsSender = Sender<ManagerArguments>;
+/// Helper type for receiving `ManagerReturnValue`s from the real `Manager`.
+type ManagerReturnValueReceiver = Receiver<ManagerReturnValue>;
+
+/// 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<u8>)>),
+ Search(CK_SESSION_HANDLE, usize),
+ ClearSearch(CK_SESSION_HANDLE),
+ GetAttributes(CK_OBJECT_HANDLE, Vec<CK_ATTRIBUTE_TYPE>),
+ StartSign(
+ CK_SESSION_HANDLE,
+ CK_OBJECT_HANDLE,
+ Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ),
+ GetSignatureLength(CK_SESSION_HANDLE, Vec<u8>),
+ Sign(CK_SESSION_HANDLE, Vec<u8>),
+ 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<CK_SESSION_HANDLE, Error>),
+ CloseSession(Result<(), Error>),
+ CloseAllSessions(Result<(), Error>),
+ StartSearch(Result<(), Error>),
+ Search(Result<Vec<CK_OBJECT_HANDLE>, Error>),
+ ClearSearch(Result<(), Error>),
+ GetAttributes(Result<Vec<Option<Vec<u8>>>, Error>),
+ StartSign(Result<(), Error>),
+ GetSignatureLength(Result<usize, Error>),
+ Sign(Result<Vec<u8>, 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<JoinHandle<()>>,
+}
+
+impl ManagerProxy {
+ pub fn new<B: ClientCertsBackend + Send + 'static>(backend: B) -> Result<ManagerProxy, Error> {
+ 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<ManagerReturnValue, Error> {
+ 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<CK_SESSION_HANDLE, Error> {
+ 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<u8>)>,
+ ) -> 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<Vec<CK_OBJECT_HANDLE>, 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<CK_ATTRIBUTE_TYPE>,
+ ) -> Result<Vec<Option<Vec<u8>>>, 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<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> 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<u8>,
+ ) -> Result<usize, Error> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::GetSignatureLength(session, data),
+ ManagerReturnValue::GetSignatureLength
+ )
+ }
+
+ pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, 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<u8>)],
+) -> Result<bool, Error> {
+ 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<B: ClientCertsBackend> {
+ Cert(B::Cert),
+ Key(B::Key),
+}
+
+impl<B: ClientCertsBackend> Object<B> {
+ fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> 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<u8>,
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<usize, Error> {
+ match self {
+ Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)),
+ Object::Key(key) => key.get_signature_length(&data, params),
+ }
+ }
+
+ fn sign(
+ &mut self,
+ data: Vec<u8>,
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, 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<B: ClientCertsBackend> {
+ /// A map of session to session type (modern or legacy). Sessions can be created (opened) and
+ /// later closed.
+ sessions: BTreeMap<CK_SESSION_HANDLE, SlotType>,
+ /// A map of searches to PKCS #11 object handles that match those searches.
+ searches: BTreeMap<CK_SESSION_HANDLE, Vec<CK_OBJECT_HANDLE>>,
+ /// A map of sign operations to a pair of the object handle and optionally some params being
+ /// used by each one.
+ signs: BTreeMap<CK_SESSION_HANDLE, (CK_OBJECT_HANDLE, Option<CK_RSA_PKCS_PSS_PARAMS>)>,
+ /// A map of object handles to the underlying objects.
+ objects: BTreeMap<CK_OBJECT_HANDLE, Object<B>>,
+ /// A set of certificate identifiers (not the same as handles).
+ cert_ids: BTreeSet<Vec<u8>>,
+ /// 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<Vec<u8>>,
+ /// 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<Instant>,
+ backend: B,
+}
+
+impl<B: ClientCertsBackend> Manager<B> {
+ pub fn new(backend: B) -> Manager<B> {
+ 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<CK_SESSION_HANDLE, Error> {
+ 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<u8>)>,
+ ) -> 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<Vec<CK_OBJECT_HANDLE>, 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<CK_ATTRIBUTE_TYPE>,
+ ) -> Result<Vec<Option<Vec<u8>>>, 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<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> 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<u8>,
+ ) -> Result<usize, Error> {
+ 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<u8>) -> Result<Vec<u8>, 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, &params)
+ }
+}
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<T: TryInto<u64>>(value: T) -> Result<Vec<u8>, Error> {
+ let value_size = std::mem::size_of::<T>();
+ 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::<NativeEndian>(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<Vec<u8>, 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<u8>, Vec<u8>, Vec<u8>), 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<Sequence<'a>, 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<Sequence<'a>, 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<Vec<u8>, 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<u8>, &'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::<BigEndian>()
+ .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
+ ]
+ );
+ }
+}
diff --git a/security/manager/ssl/rsclientcerts/test/certificate.bin b/security/manager/ssl/rsclientcerts/test/certificate.bin
new file mode 100644
index 0000000000..01fbafb15c
--- /dev/null
+++ b/security/manager/ssl/rsclientcerts/test/certificate.bin
Binary files differ
diff --git a/security/manager/ssl/rsclientcerts/test/modulus.bin b/security/manager/ssl/rsclientcerts/test/modulus.bin
new file mode 100644
index 0000000000..a9b09ff53e
--- /dev/null
+++ b/security/manager/ssl/rsclientcerts/test/modulus.bin
@@ -0,0 +1,2 @@
+QDAn6=<5JhWl${%nkZukjdqzZE$~Ci8 ) I۹Lkl~i:*Ap f +$}ayW=;H7ӈC'UfZ~/:{$la[u1 ݴ% h{/"oi㴊a&%N6$,/1I#rQ1
+5 \ No newline at end of file
diff --git a/security/manager/ssl/rsclientcerts/test/rsa.bin b/security/manager/ssl/rsclientcerts/test/rsa.bin
new file mode 100644
index 0000000000..93ededb827
--- /dev/null
+++ b/security/manager/ssl/rsclientcerts/test/rsa.bin
Binary files differ