blob: 9e6361d90dcef1604e1e087a863948c2d17abb69 [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.
//! Traits defining credential books. These are used in the deserialization path to provide
//! the credentials to try for either advertisement version.
//! See [`CredentialBookBuilder`] for batteries-included implementations.
use crate::credential::source::{
CredentialSource, DiscoveryCredentialSource, SliceCredentialSource,
};
use crate::credential::v0::{V0DiscoveryCryptoMaterial, V0};
use crate::credential::v1::{
SignedSectionIdentityResolutionMaterial, SignedSectionVerificationMaterial,
UnsignedSectionIdentityResolutionMaterial, UnsignedSectionVerificationMaterial,
V1DiscoveryCryptoMaterial, V1,
};
#[cfg(feature = "alloc")]
use crate::credential::ReferencedMatchedCredential;
use crate::credential::{
DiscoveryCryptoMaterial, MatchableCredential, MatchedCredential, ProtocolVersion,
};
use core::borrow::Borrow;
use core::marker::PhantomData;
use crypto_provider::CryptoProvider;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
/// A collection of credentials to try when attempting to deserialize
/// advertisements of either advertisement version which is
/// valid for the given lifetime.
pub trait CredentialBook<'a>
where
Self: 'a,
{
/// The type of the matched credentials for the credentials
/// held within this credential-book. May lend from the credential-book.
type Matched: MatchedCredential;
/// The type of V0 crypto-materials yielded by this credential-book.
type V0Crypto: V0DiscoveryCryptoMaterial;
/// The type of V1 crypto-materials yielded by this credential-book.
type V1Crypto: V1DiscoveryCryptoMaterial;
/// The iterator type returned from [`CredentialBook#v0_iter()`],
/// which yields `(crypto-material, match_data)` pairs.
/// This is a lending iterator which may borrow things from `self`.
type V0Iterator: Iterator<Item = (Self::V0Crypto, Self::Matched)>;
/// The iterator type returned from [`CredentialBook#v1_iter()`],
/// which yields `(crypto-material, match_data)` pairs.
/// This is a lending iterator which may borrow things from `self`.
type V1Iterator: Iterator<Item = (Self::V1Crypto, Self::Matched)>;
/// Returns an iterator over V0 credentials in this credential book.
fn v0_iter(&'a self) -> Self::V0Iterator;
/// Returns an iterator over V1 credentials in this credential book.
fn v1_iter(&'a self) -> Self::V1Iterator;
}
/// Convenient marker struct for building credential-books with
/// some given match-data type.
pub struct CredentialBookBuilder<M: MatchedCredential> {
_marker: PhantomData<M>,
}
impl<M: MatchedCredential> CredentialBookBuilder<M> {
/// Constructs a [`CachedSliceCredentialBook`] from the given slices of discovery credentials,
/// populating its internal buffers of cached credential crypto-materials for up to the
/// given number of credentials in each version (up to `N0` credentials cached in V0,
/// and up to `N1` credentials cached in `V1`.)
pub fn build_cached_slice_book<'a, const N0: usize, const N1: usize, P: CryptoProvider>(
v0_creds: &'a [MatchableCredential<V0, M>],
v1_creds: &'a [MatchableCredential<V1, M>],
) -> CachedSliceCredentialBook<'a, M, N0, N1> {
let v0_source = SliceCredentialSource::new(v0_creds);
let v0_cache = init_cache_from_source::<V0, _, N0, P>(&v0_source);
let v0_source = CachedCredentialSource::<_, _, N0>::new(v0_source, v0_cache);
let v1_source = SliceCredentialSource::new(v1_creds);
let v1_cache = init_cache_from_source::<V1, _, N1, P>(&v1_source);
let v1_source = CachedCredentialSource::<_, _, N1>::new(v1_source, v1_cache);
CredentialBookFromSources::new(v0_source, v1_source)
}
#[cfg(feature = "alloc")]
/// Constructs a new credential-book which owns all of the given credentials,
/// and maintains pre-calculated cryptographic information about them
/// for speedy advertisement deserialization.
pub fn build_precalculated_owned_book<P: CryptoProvider>(
v0_iter: impl IntoIterator<Item = MatchableCredential<V0, M>>,
v1_iter: impl IntoIterator<Item = MatchableCredential<V1, M>>,
) -> PrecalculatedOwnedCredentialBook<M> {
let v0_source = PrecalculatedOwnedCredentialSource::<V0, M>::new::<P>(v0_iter);
let v1_source = PrecalculatedOwnedCredentialSource::<V1, M>::new::<P>(v1_iter);
CredentialBookFromSources::new(v0_source, v1_source)
}
}
// Now, for the implementation details. External implementors still need
// to be able to reference these types (since the returned types from
// [`CredentialBookBuilder`]'s convenience methods are just type-aliases),
// and if they want, they can use them as building-blocks to construct
// their own [`CredentialBook`]s, but they're largely just scaffolding.
/// A structure bundling both V0 and V1 credential-sources to define
/// a credential-book which owns both of these independent sources.
pub struct CredentialBookFromSources<S0, S1> {
/// The source for V0 credentials.
v0_source: S0,
/// The source for V1 credentials.
v1_source: S1,
}
impl<'a, S0, S1> CredentialBookFromSources<S0, S1>
where
Self: 'a,
S0: CredentialSource<'a, V0>,
S1: CredentialSource<'a, V1, Matched = <S0 as CredentialSource<'a, V0>>::Matched>,
<S0 as CredentialSource<'a, V0>>::Crypto: V0DiscoveryCryptoMaterial,
<S1 as CredentialSource<'a, V1>>::Crypto: V1DiscoveryCryptoMaterial,
{
/// Constructs a new [`CredentialBook`] out of the two given
/// credential-sources, one for v0 and one for v1. The match-data
/// of both credential sources must be the same.
pub fn new(v0_source: S0, v1_source: S1) -> Self {
Self { v0_source, v1_source }
}
}
impl<'a, S0, S1> CredentialBook<'a> for CredentialBookFromSources<S0, S1>
where
Self: 'a,
S0: CredentialSource<'a, V0>,
S1: CredentialSource<'a, V1, Matched = <S0 as CredentialSource<'a, V0>>::Matched>,
<S0 as CredentialSource<'a, V0>>::Crypto: V0DiscoveryCryptoMaterial,
<S1 as CredentialSource<'a, V1>>::Crypto: V1DiscoveryCryptoMaterial,
{
type Matched = <S0 as CredentialSource<'a, V0>>::Matched;
type V0Crypto = <S0 as CredentialSource<'a, V0>>::Crypto;
type V1Crypto = <S1 as CredentialSource<'a, V1>>::Crypto;
type V0Iterator = <S0 as CredentialSource<'a, V0>>::Iterator;
type V1Iterator = <S1 as CredentialSource<'a, V1>>::Iterator;
fn v0_iter(&'a self) -> Self::V0Iterator {
self.v0_source.iter()
}
fn v1_iter(&'a self) -> Self::V1Iterator {
self.v1_source.iter()
}
}
/// Type-level function used internally
/// by [`CachedCredentialSource`] in order to uniformly
/// refer to the "precalculated" crypto-material variants
/// for each protocol version.
pub(crate) mod precalculated_for_version {
use crate::credential::v0::{
PrecalculatedV0DiscoveryCryptoMaterial, V0DiscoveryCredential, V0,
};
use crate::credential::v1::{
PrecalculatedV1DiscoveryCryptoMaterial, V1DiscoveryCredential, V1,
};
use crate::credential::{DiscoveryCryptoMaterial, ProtocolVersion};
use crypto_provider::CryptoProvider;
pub trait MappingTrait<V: ProtocolVersion> {
type Output: DiscoveryCryptoMaterial<V>;
/// Provides pre-calculated crypto-material for the given
/// discovery credential.
fn precalculate<C: CryptoProvider>(
discovery_credential: &V::DiscoveryCredential,
) -> Self::Output;
}
pub enum Marker {}
impl MappingTrait<V0> for Marker {
type Output = PrecalculatedV0DiscoveryCryptoMaterial;
fn precalculate<C: CryptoProvider>(
discovery_credential: &V0DiscoveryCredential,
) -> PrecalculatedV0DiscoveryCryptoMaterial {
PrecalculatedV0DiscoveryCryptoMaterial::new::<C>(discovery_credential)
}
}
impl MappingTrait<V1> for Marker {
type Output = PrecalculatedV1DiscoveryCryptoMaterial;
fn precalculate<C: CryptoProvider>(
discovery_credential: &V1DiscoveryCredential,
) -> PrecalculatedV1DiscoveryCryptoMaterial {
discovery_credential.to_precalculated::<C>()
}
}
}
type PrecalculatedCryptoForProtocolVersion<V> =
<precalculated_for_version::Marker as precalculated_for_version::MappingTrait<V>>::Output;
fn precalculate_crypto_material<V: ProtocolVersion, C: CryptoProvider>(
discovery_credential: &V::DiscoveryCredential,
) -> PrecalculatedCryptoForProtocolVersion<V>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
<precalculated_for_version::Marker as precalculated_for_version::MappingTrait<V>>::precalculate::<
C,
>(discovery_credential)
}
/// Iterator type for [`CachedCredentialSource`].
pub struct CachedCredentialSourceIterator<
'a,
V: ProtocolVersion + 'a,
S: DiscoveryCredentialSource<'a, V> + 'a,
const N: usize,
> where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
/// The current index of the (enumerated) elements that we're iterating over.
/// Always counts up at every iteration, and may lie outside of the range
/// of the `cache` slice for uncached elements.
current_index: usize,
/// The cache of pre-calculated crypto-materials from the original source.
///
/// The [`self.source_iterator`]'s (up-to) first `N` elements
/// must match (up-to) the first `N` elements in the cache,
/// and they must both appear in the same order.
cache: &'a [Option<PrecalculatedCryptoForProtocolVersion<V>>; N],
/// The iterator over the credentials in the original source
source_iterator: S::Iterator,
}
impl<'a, V: ProtocolVersion + 'a, S: DiscoveryCredentialSource<'a, V> + 'a, const N: usize> Iterator
for CachedCredentialSourceIterator<'a, V, S, N>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
type Item = (PossiblyCachedDiscoveryCryptoMaterial<'a, V>, S::Matched);
fn next(&mut self) -> Option<Self::Item> {
// Regardless of what we're going to do with the crypto-materials, we still need
// to advance the underlying iterator, and bail if it runs out.
let (discovery_credential, match_data) = self.source_iterator.next()?;
// Check whether/not we have cached crypto-material for the current index
let crypto = match self.cache.get(self.current_index) {
Some(precalculated_crypto_entry) => {
let precalculated_crypto = precalculated_crypto_entry
.as_ref()
.expect("iterator still going, but cache is not full?");
PossiblyCachedDiscoveryCryptoMaterial::from_precalculated(precalculated_crypto)
}
None => PossiblyCachedDiscoveryCryptoMaterial::from_discovery_credential(
discovery_credential.borrow().clone(),
),
};
// Advance the index forward to point to the next item in the cache.
self.current_index += 1;
Some((crypto, match_data))
}
}
/// A [`CredentialSource`] which augments the externally-provided [`DiscoveryCredentialSource`] with
/// a cache containing up to the specified number of pre-calculated credentials.
pub struct CachedCredentialSource<V: ProtocolVersion, S, const N: usize>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
wrapped: S,
cache: [Option<PrecalculatedCryptoForProtocolVersion<V>>; N],
}
/// Helper function to construct a cache for a [`CachedCredentialSource`] from
/// a reference to some source of discovery-credentials.
///
/// This function needs to be kept separate from [`CachedCredentialSource#new`]
/// to get around otherwise-tricky lifetime issues around ephemerally-borrowing
/// from a moved object within the body of a function.
pub(crate) fn init_cache_from_source<'a, V: ProtocolVersion, S, const N: usize, P: CryptoProvider>(
original: &'a S,
) -> [Option<PrecalculatedCryptoForProtocolVersion<V>>; N]
where
S: DiscoveryCredentialSource<'a, V> + 'a,
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
let mut cache = [0u8; N].map(|_| None);
for (cache_entry, source_credential) in cache.iter_mut().zip(original.iter()) {
let (discovery_credential, _) = source_credential;
let precalculated_crypto_material =
precalculate_crypto_material::<V, P>(discovery_credential.borrow());
let _ = cache_entry.insert(precalculated_crypto_material);
}
cache
}
impl<'a, V: ProtocolVersion, S, const N: usize> CachedCredentialSource<V, S, N>
where
S: DiscoveryCredentialSource<'a, V> + 'a,
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
/// Constructs a [`CachedCredentialSource`] from the given [`DiscoveryCredentialSource`]
/// and the given (initial) cache contents, as constructed via the
/// [`init_cache_from_source`] helper function.
pub(crate) fn new(
wrapped: S,
cache: [Option<PrecalculatedCryptoForProtocolVersion<V>>; N],
) -> Self {
Self { wrapped, cache }
}
}
/// Internal implementation of [`PossiblyCachedDiscoveryCryptoMaterial`] to hide
/// what crypto-material variants we're actually storing in cached
/// credential-books.
pub(crate) enum PossiblyCachedDiscoveryCryptoMaterialKind<'a, V: ProtocolVersion>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
Discovery(V::DiscoveryCredential),
Precalculated(&'a PrecalculatedCryptoForProtocolVersion<V>),
}
/// Crypto-materials that are potentially references to
/// already-cached precomputed variants, or raw discovery
/// credentials.
pub struct PossiblyCachedDiscoveryCryptoMaterial<'a, V: ProtocolVersion>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
pub(crate) wrapped: PossiblyCachedDiscoveryCryptoMaterialKind<'a, V>,
}
impl<'a, V: ProtocolVersion> PossiblyCachedDiscoveryCryptoMaterial<'a, V>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
fn from_discovery_credential(discovery_credential: V::DiscoveryCredential) -> Self {
Self { wrapped: PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(discovery_credential) }
}
fn from_precalculated(precalculated: &'a PrecalculatedCryptoForProtocolVersion<V>) -> Self {
Self { wrapped: PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(precalculated) }
}
}
impl<'a, V: ProtocolVersion> DiscoveryCryptoMaterial<V>
for PossiblyCachedDiscoveryCryptoMaterial<'a, V>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
match &self.wrapped {
PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => x.metadata_nonce::<C>(),
PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => x.metadata_nonce::<C>(),
}
}
}
impl<'a> V0DiscoveryCryptoMaterial for PossiblyCachedDiscoveryCryptoMaterial<'a, V0> {
fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> {
match &self.wrapped {
PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => x.ldt_adv_cipher::<C>(),
PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => x.ldt_adv_cipher::<C>(),
}
}
}
impl<'a> V1DiscoveryCryptoMaterial for PossiblyCachedDiscoveryCryptoMaterial<'a, V1> {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
) -> SignedSectionIdentityResolutionMaterial {
match &self.wrapped {
PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
x.signed_identity_resolution_material::<C>()
}
PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
x.signed_identity_resolution_material::<C>()
}
}
}
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionIdentityResolutionMaterial {
match &self.wrapped {
PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
x.unsigned_identity_resolution_material::<C>()
}
PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
x.unsigned_identity_resolution_material::<C>()
}
}
}
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
match &self.wrapped {
PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
x.signed_verification_material::<C>()
}
PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
x.signed_verification_material::<C>()
}
}
}
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial {
match &self.wrapped {
PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
x.unsigned_verification_material::<C>()
}
PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
x.unsigned_verification_material::<C>()
}
}
}
}
impl<'a, V: ProtocolVersion, S, const N: usize> CredentialSource<'a, V>
for CachedCredentialSource<V, S, N>
where
Self: 'a,
S: DiscoveryCredentialSource<'a, V> + 'a,
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
PossiblyCachedDiscoveryCryptoMaterial<'a, V>: DiscoveryCryptoMaterial<V>,
{
type Matched = <S as DiscoveryCredentialSource<'a, V>>::Matched;
type Crypto = PossiblyCachedDiscoveryCryptoMaterial<'a, V>;
type Iterator = CachedCredentialSourceIterator<'a, V, S, N>;
fn iter(&'a self) -> Self::Iterator {
CachedCredentialSourceIterator {
current_index: 0,
cache: &self.cache,
source_iterator: self.wrapped.iter(),
}
}
}
/// A simple credentials source for environments which are,
/// for all practical purposes, not space-constrained, and hence
/// can store an arbitrary amount of pre-calculated crypto-materials.
///
/// Requires `alloc` as a result of internally leveraging a `Vec`.
#[cfg(feature = "alloc")]
pub struct PrecalculatedOwnedCredentialSource<V: ProtocolVersion, M: MatchedCredential>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
credentials: Vec<(PrecalculatedCryptoForProtocolVersion<V>, M)>,
}
#[cfg(feature = "alloc")]
impl<'a, V: ProtocolVersion + 'a, M: MatchedCredential + 'a>
PrecalculatedOwnedCredentialSource<V, M>
where
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
{
/// Pre-calculates crypto material for the given credentials, and constructs a
/// credentials source which holds owned copies of this crypto-material.
pub fn new<P: CryptoProvider>(
credential_iter: impl IntoIterator<Item = MatchableCredential<V, M>>,
) -> Self {
let credentials = credential_iter
.into_iter()
.map(|credential| {
(
precalculate_crypto_material::<V, P>(&credential.discovery_credential),
credential.match_data,
)
})
.collect();
Self { credentials }
}
}
#[cfg(feature = "alloc")]
fn reference_crypto_and_match_data<C, M: MatchedCredential>(
pair_ref: &(C, M),
) -> (&C, ReferencedMatchedCredential<M>) {
let (c, m) = pair_ref;
(c, m.into())
}
#[cfg(feature = "alloc")]
impl<'a, V: ProtocolVersion, M: MatchedCredential> CredentialSource<'a, V>
for PrecalculatedOwnedCredentialSource<V, M>
where
Self: 'a,
precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
&'a PrecalculatedCryptoForProtocolVersion<V>: DiscoveryCryptoMaterial<V>,
{
type Matched = ReferencedMatchedCredential<'a, M>;
type Crypto = &'a PrecalculatedCryptoForProtocolVersion<V>;
type Iterator = core::iter::Map<
core::slice::Iter<'a, (PrecalculatedCryptoForProtocolVersion<V>, M)>,
fn(
&'a (PrecalculatedCryptoForProtocolVersion<V>, M),
)
-> (&'a PrecalculatedCryptoForProtocolVersion<V>, ReferencedMatchedCredential<'a, M>),
>;
fn iter(&'a self) -> Self::Iterator {
self.credentials.iter().map(reference_crypto_and_match_data)
}
}
/// Type-alias for credential sources which are provided via slice credential-sources
/// with a pre-calculated credential cache layered on top.
pub type CachedSliceCredentialSource<'a, V, M, const N: usize> =
CachedCredentialSource<V, SliceCredentialSource<'a, V, M>, N>;
/// A [`CredentialBook`] whose sources for V0 and V1 credentials come from the given slices
/// of discovery credentials, with crypto-materials for up to the given number of credentials
/// from the beginning of each slice kept in an in-memory cache.
pub type CachedSliceCredentialBook<'a, M, const N0: usize, const N1: usize> =
CredentialBookFromSources<
CachedSliceCredentialSource<'a, V0, M, N0>,
CachedSliceCredentialSource<'a, V1, M, N1>,
>;
#[cfg(feature = "alloc")]
/// A credential-book which owns all of its (non-matched) credential data,
/// and maintains pre-calculated cryptographic information about all
/// stored credentials for speedy advertisement deserialization.
///
/// Use this credential book if memory usage is not terribly tight,
/// and you're operating in an environment with an allocator.
pub type PrecalculatedOwnedCredentialBook<M> = CredentialBookFromSources<
PrecalculatedOwnedCredentialSource<V0, M>,
PrecalculatedOwnedCredentialSource<V1, M>,
>;