| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use crypto_provider::CryptoProvider; |
| use np_adv::extended::salt::{DeSalt, MultiSalt, Unsalted}; |
| use np_adv::{extended::de_type::*, extended::serialize::*}; |
| use sink::Sink; |
| use std::fmt::{Display, Formatter}; |
| |
| /// An advertisement builder for V1 advertisements where the |
| /// presence/absence of salt is determined at run-time instead of compile-time. |
| pub struct BoxedAdvBuilder { |
| adv_builder: Box<AdvBuilder>, |
| } |
| |
| impl From<AdvBuilder> for BoxedAdvBuilder { |
| fn from(adv_builder: AdvBuilder) -> Self { |
| let adv_builder = Box::new(adv_builder); |
| BoxedAdvBuilder { adv_builder } |
| } |
| } |
| |
| /// Error possibly generated when attempting to add a section to |
| /// a BoxedAdvBuilder. |
| #[derive(Debug)] |
| pub enum BoxedAddSectionError { |
| /// An error which was generated by the underlying AdvBuilder wrapped by the BoxedAdvBuilder |
| Underlying(AddSectionError), |
| } |
| |
| impl Display for BoxedAddSectionError { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| match self { |
| BoxedAddSectionError::Underlying(u) => { |
| write!(f, "{0}", u) |
| } |
| } |
| } |
| } |
| |
| impl std::error::Error for BoxedAddSectionError {} |
| |
| impl From<AddSectionError> for BoxedAddSectionError { |
| fn from(wrapped: AddSectionError) -> Self { |
| BoxedAddSectionError::Underlying(wrapped) |
| } |
| } |
| |
| fn wrap_owning_section_builder<S: Into<BoxedSectionBuilder<AdvBuilder>>>( |
| maybe_section_builder: Result<S, (AdvBuilder, AddSectionError)>, |
| ) -> Result<BoxedSectionBuilder<AdvBuilder>, (BoxedAdvBuilder, BoxedAddSectionError)> { |
| match maybe_section_builder { |
| Ok(section_builder) => Ok(section_builder.into()), |
| Err((adv_builder, err)) => Err((adv_builder.into(), err.into())), |
| } |
| } |
| |
| fn wrap_mut_ref_section_builder<'a, S: Into<BoxedSectionBuilder<&'a mut AdvBuilder>>>( |
| maybe_section_builder: Result<S, AddSectionError>, |
| ) -> Result<BoxedSectionBuilder<&'a mut AdvBuilder>, BoxedAddSectionError> { |
| let section_builder = maybe_section_builder?; |
| Ok(section_builder.into()) |
| } |
| |
| impl BoxedAdvBuilder { |
| /// Gets the current count of sections which have been added |
| /// to this advertisement builder. Does not count currently- |
| /// outstanding section builders. |
| pub fn section_count(&self) -> usize { |
| self.adv_builder.section_count() |
| } |
| |
| /// Create a section builder using the given identity, |
| /// taking ownership of this advertisement builder. |
| /// |
| /// Unlike `BoxedAdvBuilder#section_builder`, the returned |
| /// section builder will take ownership of this advertisement |
| /// builder, if the operation was successful. Otherwise, |
| /// this advertisement builder will be returned back to the |
| /// caller unaltered as part of the `Err` arm. |
| pub fn into_section_builder( |
| self, |
| identity: BoxedEncoder, |
| ) -> Result<BoxedSectionBuilder<AdvBuilder>, (Self, BoxedAddSectionError)> { |
| match identity { |
| BoxedEncoder::Unencrypted => wrap_owning_section_builder( |
| self.adv_builder.into_section_builder(UnencryptedSectionEncoder), |
| ), |
| BoxedEncoder::MicEncrypted(ident) => { |
| wrap_owning_section_builder(self.adv_builder.into_section_builder(ident)) |
| } |
| } |
| } |
| |
| /// Create a section builder using the given encoder. |
| /// |
| /// Returns `Err` if the underlying advertisement builder |
| /// yields an error when attempting to add a new section |
| /// (typically because there's no more available adv space), |
| /// or if the requested identity requires salt, and the |
| /// advertisement builder is salt-less. |
| pub fn section_builder( |
| &mut self, |
| encoder: BoxedEncoder, |
| ) -> Result<BoxedSectionBuilder<&mut AdvBuilder>, BoxedAddSectionError> { |
| match encoder { |
| BoxedEncoder::Unencrypted => wrap_mut_ref_section_builder( |
| self.adv_builder.section_builder(UnencryptedSectionEncoder), |
| ), |
| BoxedEncoder::MicEncrypted(ident) => { |
| wrap_mut_ref_section_builder(self.adv_builder.section_builder(ident)) |
| } |
| } |
| } |
| |
| /// Convert the builder into an encoded advertisement. |
| pub fn into_advertisement(self) -> EncodedAdvertisement { |
| self.adv_builder.into_advertisement() |
| } |
| } |
| |
| /// A wrapped v1 encoder whose type is given at run-time. |
| pub enum BoxedEncoder { |
| /// Unencrypted encoder. |
| Unencrypted, |
| /// An encrypted encoder leveraging MIC for verification. |
| MicEncrypted(MicEncryptedSectionEncoder<MultiSalt>), |
| } |
| |
| /// A `SectionBuilder` whose corresponding Identity |
| /// and salted-ness is given at run-time instead of |
| /// at compile-time. |
| pub enum BoxedSectionBuilder<R: AsMut<AdvBuilder>> { |
| /// A builder for a public section. |
| Public(Box<SectionBuilder<R, UnencryptedSectionEncoder>>), |
| /// A builder for a MIC-verified section. |
| MicEncrypted(Box<SectionBuilder<R, MicEncryptedSectionEncoder<MultiSalt>>>), |
| } |
| |
| impl BoxedSectionBuilder<AdvBuilder> { |
| /// Gets the count of the sections which were added to the advertisement |
| /// prior to the creation of this section builder. |
| pub fn previously_added_section_count(&self) -> usize { |
| match self { |
| BoxedSectionBuilder::Public(x) => x.previously_added_section_count(), |
| BoxedSectionBuilder::MicEncrypted(x) => x.previously_added_section_count(), |
| } |
| } |
| /// Add this builder to the advertisement that created it, |
| /// returning the containing advertisement builder. |
| pub fn add_to_advertisement<C: CryptoProvider>(self) -> BoxedAdvBuilder { |
| let adv_builder = match self { |
| BoxedSectionBuilder::Public(x) => x.add_to_advertisement::<C>(), |
| BoxedSectionBuilder::MicEncrypted(x) => x.add_to_advertisement::<C>(), |
| }; |
| BoxedAdvBuilder::from(adv_builder) |
| } |
| } |
| |
| impl BoxedSectionBuilder<&mut AdvBuilder> { |
| /// Add this builder to the advertisement that created it. |
| pub fn add_to_advertisement<C: CryptoProvider>(self) { |
| match self { |
| BoxedSectionBuilder::Public(x) => x.add_to_advertisement::<C>(), |
| BoxedSectionBuilder::MicEncrypted(x) => x.add_to_advertisement::<C>(), |
| } |
| } |
| } |
| |
| impl<R: AsMut<AdvBuilder>> BoxedSectionBuilder<R> { |
| /// Returns true if this wraps a section builder which |
| /// leverages some encrypted identity. |
| pub fn is_encrypted(&self) -> bool { |
| match self { |
| BoxedSectionBuilder::Public(_) => false, |
| BoxedSectionBuilder::MicEncrypted(_) => true, |
| } |
| } |
| /// Gets the derived salt of a DE with a given type code, |
| /// if this section-builder corresponds to an encrypted section that can |
| /// provide per-DE salts. |
| /// Otherwise, returns nothing. |
| /// |
| /// Suitable for scenarios (like FFI) where a custom DE impl would be inappropriate |
| /// for DE construction, and interaction with the client is preferred. |
| pub fn de_salt(&self, de_type: DeType) -> Option<DeSalt> { |
| match self { |
| BoxedSectionBuilder::Public(_) => None, |
| BoxedSectionBuilder::MicEncrypted(x) => x.de_salt(de_type), |
| } |
| } |
| /// Attempts to add a data element to the section. |
| pub fn add_de(&mut self, de: &dyn DynWriteDataElement) -> Result<(), BoxedAddDataElementError> { |
| // Perform a sneaky little trick to check the salt |
| // and propagate a salt mismatch error before we ever |
| // actually commit a write. |
| // |
| // To do this, we pretend as if we have an acceptable |
| // `WriteDataElement#add_de` implementation which actually |
| // invokes the `DynWriteDataElement` methods under the hood, |
| // but upon encountering a salt mismatch, it first exfiltrates |
| // the information that this exception occurred to a state |
| // variable in this calling method, and then pretends as if |
| // writing failed despite not writing anything. |
| // We then transmogrify this to the correct error or |
| // return value before returning from this method. |
| // |
| // The `RefCell` shenanigans here are panic-free due to |
| // the fact that they're all completely local to this method. |
| struct SaltErrorSmuggler<'a> { |
| de: &'a dyn DynWriteDataElement, |
| smuggled_salt_error: core::cell::RefCell<bool>, |
| } |
| |
| impl ProvidesDEType for SaltErrorSmuggler<'_> { |
| fn de_type(&self) -> DeType { |
| self.de.de_type() |
| } |
| } |
| |
| impl WriteDataElement for SaltErrorSmuggler<'_> { |
| type Salt = Option<DeSalt>; |
| fn write_de_contents<S: Sink<u8>>( |
| &self, |
| salt: Option<DeSalt>, |
| sink: &mut S, |
| ) -> Option<()> { |
| let sink: &mut dyn Sink<u8> = sink; |
| match self.de.write_de_contents(salt, sink.into()) { |
| Ok(()) => Some(()), |
| Err(BoxedWriteDataElementError::InsufficientSpace) => None, |
| Err(BoxedWriteDataElementError::NoDerivedSalt) => { |
| *self.smuggled_salt_error.borrow_mut() &= true; |
| None |
| } |
| } |
| } |
| } |
| let smuggler = |
| SaltErrorSmuggler { de, smuggled_salt_error: core::cell::RefCell::new(false) }; |
| |
| let unmodified_ret_val = match self { |
| BoxedSectionBuilder::Public(x) => x.add_de(&smuggler), |
| BoxedSectionBuilder::MicEncrypted(x) => x.add_de(&smuggler), |
| }; |
| // Unbox what we smuggled, and return what we need to. |
| let smuggled_salt_error = smuggler.smuggled_salt_error.into_inner(); |
| match (unmodified_ret_val, smuggled_salt_error) { |
| (Err(AddDataElementError::InsufficientSpace), false) => { |
| Err(BoxedAddDataElementError::InsufficientSpace) |
| } |
| (Err(AddDataElementError::InsufficientSpace), true) => { |
| Err(BoxedAddDataElementError::NoDerivedSalt) |
| } |
| (Err(AddDataElementError::DuplicateDataElementTypeCode), _) => { |
| Err(BoxedAddDataElementError::DuplicateDataElementTypeCode) |
| } |
| (Ok(()), _) => Ok(()), |
| } |
| } |
| } |
| |
| impl<R: AsMut<AdvBuilder>> From<SectionBuilder<R, UnencryptedSectionEncoder>> |
| for BoxedSectionBuilder<R> |
| { |
| fn from(section_builder: SectionBuilder<R, UnencryptedSectionEncoder>) -> Self { |
| BoxedSectionBuilder::Public(Box::new(section_builder)) |
| } |
| } |
| |
| impl<R: AsMut<AdvBuilder>> From<SectionBuilder<R, MicEncryptedSectionEncoder<MultiSalt>>> |
| for BoxedSectionBuilder<R> |
| { |
| fn from(section_builder: SectionBuilder<R, MicEncryptedSectionEncoder<MultiSalt>>) -> Self { |
| BoxedSectionBuilder::MicEncrypted(Box::new(section_builder)) |
| } |
| } |
| |
| /// Mutable trait object reference to a `Sink<u8>` |
| pub struct DynSink<'a> { |
| wrapped: &'a mut dyn Sink<u8>, |
| } |
| |
| impl Sink<u8> for DynSink<'_> { |
| fn try_extend_from_slice(&mut self, items: &[u8]) -> Option<()> { |
| self.wrapped.try_extend_from_slice(items) |
| } |
| fn try_push(&mut self, item: u8) -> Option<()> { |
| self.wrapped.try_push(item) |
| } |
| } |
| |
| impl<'a> From<&'a mut dyn Sink<u8>> for DynSink<'a> { |
| fn from(wrapped: &'a mut dyn Sink<u8>) -> Self { |
| DynSink { wrapped } |
| } |
| } |
| |
| /// Errors which may be raised by [`DynWriteDataElement#write_de_contents`]. |
| #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
| pub enum BoxedWriteDataElementError { |
| /// The space in the enclosing section was exhausted |
| /// and/or the data element contents are over the max DE size. |
| InsufficientSpace, |
| /// The surrounding section does not have an extended |
| /// salt, but a derived salt is expected by the data element. |
| NoDerivedSalt, |
| } |
| |
| #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
| /// Errors which may be raised by [`BoxedSectionBuilder#add_de`]. |
| pub enum BoxedAddDataElementError { |
| /// The space in the enclosing section was exhausted |
| /// and/or the data element contents are over the max DE size. |
| InsufficientSpace, |
| /// The surrounding section does not have an extended |
| /// salt, but a derived salt is expected by the data element. |
| NoDerivedSalt, |
| /// A data element with the same type code already |
| /// was added to the section. |
| DuplicateDataElementTypeCode, |
| } |
| |
| /// A version of the WriteDataElement trait which is object-safe |
| pub trait DynWriteDataElement { |
| /// Gets the data-element type for the data element |
| fn de_type(&self) -> DeType; |
| /// Writes the contents of the DE payload to the given DynSink, |
| /// given a salt which may or may not be populated. |
| fn write_de_contents( |
| &self, |
| salt: Option<DeSalt>, |
| sink: DynSink, |
| ) -> Result<(), BoxedWriteDataElementError>; |
| } |
| |
| /// Helper trait to check whether/not dynamic conversion |
| /// from an `Option<DeSalt>` to a desired salt type is possible. |
| pub trait TryCoerceSalt: Sized { |
| /// Attempts to coerce a DE salt into a |
| /// salt of this type. |
| fn try_coerce(salt: Option<DeSalt>) -> Option<Self>; |
| } |
| |
| impl TryCoerceSalt for DeSalt { |
| fn try_coerce(salt: Option<DeSalt>) -> Option<DeSalt> { |
| salt |
| } |
| } |
| |
| impl TryCoerceSalt for Unsalted { |
| fn try_coerce(_salt: Option<DeSalt>) -> Option<Unsalted> { |
| Some(Unsalted) |
| } |
| } |
| |
| impl<T: WriteDataElement> DynWriteDataElement for T |
| where |
| <T as WriteDataElement>::Salt: TryCoerceSalt, |
| { |
| fn de_type(&self) -> DeType { |
| ProvidesDEType::de_type(self) |
| } |
| fn write_de_contents( |
| &self, |
| salt: Option<DeSalt>, |
| mut sink: DynSink, |
| ) -> Result<(), BoxedWriteDataElementError> { |
| let coerced_salt = <<T as WriteDataElement>::Salt as TryCoerceSalt>::try_coerce(salt) |
| .ok_or(BoxedWriteDataElementError::NoDerivedSalt)?; |
| WriteDataElement::write_de_contents(self, coerced_salt, &mut sink) |
| .ok_or(BoxedWriteDataElementError::InsufficientSpace) |
| } |
| } |