blob: 59f6a42d96cecb16332a248e526b9ac4589b030e [file] [log] [blame]
// 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)
}
}