blob: 7d060f2e17f76b22ebd5d2c0c8b2e82f963b9d04 [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.
//! Cryptographic materials for v1 advertisement-format credentials.
use crate::credential::{
protocol_version_seal, BroadcastCryptoMaterial, DiscoveryCryptoMaterial, ProtocolVersion,
};
use crate::MetadataKey;
use crypto_provider::{aes::Aes128Key, ed25519, CryptoProvider, CryptoRng};
use np_hkdf::UnsignedSectionKeys;
/// Cryptographic information about a particular V1 discovery credential
/// necessary to match and decrypt encrypted V1 sections.
#[derive(Clone)]
pub struct V1DiscoveryCredential {
key_seed: [u8; 32],
expected_unsigned_metadata_key_hmac: [u8; 32],
expected_signed_metadata_key_hmac: [u8; 32],
pub_key: ed25519::RawPublicKey,
}
impl V1DiscoveryCredential {
/// Construct a V1 discovery credential from the provided identity data.
pub fn new<C: CryptoProvider>(
key_seed: [u8; 32],
expected_unsigned_metadata_key_hmac: [u8; 32],
expected_signed_metadata_key_hmac: [u8; 32],
pub_key: np_ed25519::PublicKey<C>,
) -> Self {
Self {
key_seed,
expected_unsigned_metadata_key_hmac,
expected_signed_metadata_key_hmac,
pub_key: pub_key.to_bytes(),
}
}
/// Constructs pre-calculated crypto material from this discovery credential.
pub(crate) fn to_precalculated<C: CryptoProvider>(
&self,
) -> PrecalculatedV1DiscoveryCryptoMaterial {
let signed_identity_resolution_material = self.signed_identity_resolution_material::<C>();
let unsigned_identity_resolution_material =
self.unsigned_identity_resolution_material::<C>();
let signed_verification_material = self.signed_verification_material::<C>();
let unsigned_verification_material = self.unsigned_verification_material::<C>();
let metadata_nonce = self.metadata_nonce::<C>();
PrecalculatedV1DiscoveryCryptoMaterial {
signed_identity_resolution_material,
unsigned_identity_resolution_material,
signed_verification_material,
unsigned_verification_material,
metadata_nonce,
}
}
}
impl DiscoveryCryptoMaterial<V1> for V1DiscoveryCredential {
fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
V1::metadata_nonce_from_key_seed::<C>(&self.key_seed)
}
}
impl V1DiscoveryCryptoMaterial for V1DiscoveryCredential {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
) -> SignedSectionIdentityResolutionMaterial {
let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac(
&hkdf,
self.expected_signed_metadata_key_hmac,
)
}
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionIdentityResolutionMaterial {
let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac(
&hkdf,
self.expected_unsigned_metadata_key_hmac,
)
}
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
SignedSectionVerificationMaterial { pub_key: self.pub_key }
}
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial {
let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&hkdf).as_bytes();
UnsignedSectionVerificationMaterial { mic_hmac_key }
}
}
/// Type-level identifier for the V1 protocol version.
#[derive(Debug, Clone)]
pub enum V1 {}
impl protocol_version_seal::ProtocolVersionSeal for V1 {}
impl ProtocolVersion for V1 {
type DiscoveryCredential = V1DiscoveryCredential;
type MetadataKey = MetadataKey;
fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12] {
let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed);
hkdf.extended_metadata_nonce()
}
fn expand_metadata_key<C: CryptoProvider>(metadata_key: MetadataKey) -> MetadataKey {
metadata_key
}
fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> MetadataKey {
MetadataKey(rng.gen())
}
}
/// Trait which exists purely to be able to restrict the protocol
/// version of certain type-bounds to V1.
pub trait V1ProtocolVersion: ProtocolVersion {}
impl V1ProtocolVersion for V1 {}
/// Cryptographic materials necessary for determining whether or not
/// a given V1 advertisement section matches an identity.
/// Per the construction of the V1 specification, this is also
/// the information necessary to decrypt the raw byte contents
/// of an encrypted V1 section.
#[derive(Clone)]
pub(crate) struct SectionIdentityResolutionMaterial {
/// The AES key for decrypting section ciphertext
pub(crate) aes_key: Aes128Key,
/// The metadata key HMAC key for deriving and verifying the identity metadata
/// key HMAC against the expected value.
pub(crate) metadata_key_hmac_key: [u8; 32],
/// The expected metadata key HMAC to check against for an identity match.
pub(crate) expected_metadata_key_hmac: [u8; 32],
}
/// Cryptographic materials necessary for determining whether or not
/// a given V1 signed advertisement section matches an identity.
#[derive(Clone)]
pub struct SignedSectionIdentityResolutionMaterial(SectionIdentityResolutionMaterial);
impl SignedSectionIdentityResolutionMaterial {
#[cfg(test)]
pub(crate) fn from_raw(raw: SectionIdentityResolutionMaterial) -> Self {
Self(raw)
}
/// Extracts the underlying section-identity resolution material carried around
/// within this wrapper for resolution of signed sections.
pub(crate) fn into_raw_resolution_material(self) -> SectionIdentityResolutionMaterial {
self.0
}
#[cfg(any(test, feature = "devtools"))]
/// Gets the underlying section-identity resolution material carried around
/// within this wrapper for resolution of signed sections.
pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial {
&self.0
}
/// Constructs identity-resolution material for a signed section whose
/// discovery credential leverages the provided HKDF and has the given
/// expected metadata-key HMAC.
pub(crate) fn from_hkdf_and_expected_metadata_key_hmac<C: CryptoProvider>(
hkdf: &np_hkdf::NpKeySeedHkdf<C>,
expected_metadata_key_hmac: [u8; 32],
) -> Self {
Self(SectionIdentityResolutionMaterial {
aes_key: hkdf.extended_signed_section_aes_key(),
metadata_key_hmac_key: *hkdf.extended_signed_metadata_key_hmac_key().as_bytes(),
expected_metadata_key_hmac,
})
}
}
/// Cryptographic materials necessary for determining whether or not
/// a given V1 MIC advertisement section matches an identity.
#[derive(Clone)]
pub struct UnsignedSectionIdentityResolutionMaterial(SectionIdentityResolutionMaterial);
impl UnsignedSectionIdentityResolutionMaterial {
#[cfg(test)]
pub(crate) fn from_raw(raw: SectionIdentityResolutionMaterial) -> Self {
Self(raw)
}
/// Extracts the underlying section-identity resolution material carried around
/// within this wrapper for resolution of unsigned sections.
pub(crate) fn into_raw_resolution_material(self) -> SectionIdentityResolutionMaterial {
self.0
}
/// Gets the underlying section-identity resolution material carried around
/// within this wrapper for resolution of unsigned sections.
#[cfg(any(test, feature = "devtools"))]
pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial {
&self.0
}
/// Constructs identity-resolution material for an unsigned (MIC) section whose
/// discovery credential leverages the provided HKDF and has the given
/// expected metadata-key HMAC.
pub(crate) fn from_hkdf_and_expected_metadata_key_hmac<C: CryptoProvider>(
hkdf: &np_hkdf::NpKeySeedHkdf<C>,
expected_metadata_key_hmac: [u8; 32],
) -> Self {
Self(SectionIdentityResolutionMaterial {
aes_key: UnsignedSectionKeys::aes_key(hkdf),
metadata_key_hmac_key: *hkdf.extended_unsigned_metadata_key_hmac_key().as_bytes(),
expected_metadata_key_hmac,
})
}
}
/// Crypto materials for V1 signed sections which are not employed in identity resolution,
/// but may be necessary to verify a signed section.
#[derive(Clone)]
pub struct SignedSectionVerificationMaterial {
/// The np_ed25519 public key to be
/// used for signature verification of signed sections.
pub(crate) pub_key: ed25519::RawPublicKey,
}
impl SignedSectionVerificationMaterial {
/// Gets the np_ed25519 public key for the given identity,
/// used for signature verification of signed sections.
pub(crate) fn signature_verification_public_key<C: CryptoProvider>(
&self,
) -> np_ed25519::PublicKey<C> {
np_ed25519::PublicKey::from_bytes(&self.pub_key).expect("Should only contain valid keys")
}
}
/// Crypto materials for V1 unsigned sections which are not employed in identity resolution,
/// but may be necessary to fully decrypt an unsigned section.
#[derive(Clone)]
pub struct UnsignedSectionVerificationMaterial {
/// The MIC HMAC key for verifying the integrity of unsigned sections.
pub(crate) mic_hmac_key: [u8; 32],
}
impl UnsignedSectionVerificationMaterial {
/// Returns the MIC HMAC key for unsigned sections
pub(crate) fn mic_hmac_key<C: CryptoProvider>(&self) -> np_hkdf::NpHmacSha256Key<C> {
self.mic_hmac_key.into()
}
}
// Space-time tradeoffs:
// - Calculating an HKDF from the key seed costs about 2us on a gLinux laptop, and occupies 80b.
// - Calculating an AES (16b) or HMAC (32b) key from the HKDF costs about 700ns.
// The right tradeoff may also vary by use case. For frequently used identities we should
// probably pre-calculate everything. For occasionally used ones, or ones that are loaded from
// disk, used once, and discarded, we might want to precalculate on a separate thread or the
// like.
// The AES key and metadata key HMAC key are the most frequently used ones, as the MIC HMAC key
// is only used on the matching identity, not all identities.
/// Cryptographic material for an individual NP credential used to decrypt and verify v1 sections.
pub trait V1DiscoveryCryptoMaterial: DiscoveryCryptoMaterial<V1> {
/// Constructs or copies the identity resolution material for signed sections
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
) -> SignedSectionIdentityResolutionMaterial;
/// Constructs or copies the identity resolution material for unsigned sections
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionIdentityResolutionMaterial;
/// Constructs or copies non-identity-resolution deserialization material for signed
/// sections.
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial;
/// Constructs or copies non-identity-resolution deserialization material for unsigned
/// sections.
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial;
}
/// V1 [`DiscoveryCryptoMaterial`] that minimizes CPU time when providing key material at
/// the expense of occupied memory
pub struct PrecalculatedV1DiscoveryCryptoMaterial {
pub(crate) signed_identity_resolution_material: SignedSectionIdentityResolutionMaterial,
pub(crate) unsigned_identity_resolution_material: UnsignedSectionIdentityResolutionMaterial,
pub(crate) signed_verification_material: SignedSectionVerificationMaterial,
pub(crate) unsigned_verification_material: UnsignedSectionVerificationMaterial,
pub(crate) metadata_nonce: [u8; 12],
}
impl DiscoveryCryptoMaterial<V1> for PrecalculatedV1DiscoveryCryptoMaterial {
fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
self.metadata_nonce
}
}
impl V1DiscoveryCryptoMaterial for PrecalculatedV1DiscoveryCryptoMaterial {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
) -> SignedSectionIdentityResolutionMaterial {
self.signed_identity_resolution_material.clone()
}
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionIdentityResolutionMaterial {
self.unsigned_identity_resolution_material.clone()
}
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
self.signed_verification_material.clone()
}
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial {
self.unsigned_verification_material.clone()
}
}
// Implementations for reference types -- we don't provide a blanket impl for references
// due to the potential to conflict with downstream crates' implementations.
impl<'a> DiscoveryCryptoMaterial<V1> for &'a V1DiscoveryCredential {
fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
(*self).metadata_nonce::<C>()
}
}
impl<'a> V1DiscoveryCryptoMaterial for &'a V1DiscoveryCredential {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
) -> SignedSectionIdentityResolutionMaterial {
(*self).signed_identity_resolution_material::<C>()
}
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionIdentityResolutionMaterial {
(*self).unsigned_identity_resolution_material::<C>()
}
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
(*self).signed_verification_material::<C>()
}
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial {
(*self).unsigned_verification_material::<C>()
}
}
impl<'a> DiscoveryCryptoMaterial<V1> for &'a PrecalculatedV1DiscoveryCryptoMaterial {
fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
(*self).metadata_nonce::<C>()
}
}
impl<'a> V1DiscoveryCryptoMaterial for &'a PrecalculatedV1DiscoveryCryptoMaterial {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
) -> SignedSectionIdentityResolutionMaterial {
(*self).signed_identity_resolution_material::<C>()
}
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionIdentityResolutionMaterial {
(*self).unsigned_identity_resolution_material::<C>()
}
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
(*self).signed_verification_material::<C>()
}
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial {
(*self).unsigned_verification_material::<C>()
}
}
/// Extension of [`BroadcastCryptoMaterial`] for `V1` to add
/// crypto-materials which are necessary to sign V1 sections.
pub trait SignedBroadcastCryptoMaterial: BroadcastCryptoMaterial<V1> {
/// Gets the advertisement-signing private key for constructing
/// signature-verified V1 sections.
///
/// The private key is returned in an opaque, crypto-provider-independent
/// form to provide a safeguard against leaking the bytes of the key.
fn signing_key(&self) -> ed25519::PrivateKey;
/// Constructs the V1 discovery credential which may be used to discover
/// V1 advertisement sections broadcasted using this broadcast crypto-material
fn derive_v1_discovery_credential<C: CryptoProvider>(&self) -> V1DiscoveryCredential {
let key_seed = self.key_seed();
let metadata_key = self.metadata_key();
let pub_key = self.signing_key().derive_public_key::<C::Ed25519>();
let pub_key = np_ed25519::PublicKey::<C>::new(pub_key);
let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
let unsigned = hkdf
.extended_unsigned_metadata_key_hmac_key()
.calculate_hmac(metadata_key.0.as_slice());
let signed =
hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(metadata_key.0.as_slice());
V1DiscoveryCredential::new(key_seed, unsigned, signed, pub_key)
}
}
/// Concrete implementation of a [`SignedBroadcastCryptoMaterial`] which keeps the key
/// seed, the V1 metadata key, and the signing key contiguous in memory.
///
/// For more flexible expression of broadcast
/// credentials, feel free to directly implement [`SignedBroadcastCryptoMaterial`]
/// for your own broadcast-credential-storing data-type.
pub struct SimpleSignedBroadcastCryptoMaterial {
key_seed: [u8; 32],
metadata_key: MetadataKey,
signing_key: ed25519::PrivateKey,
}
impl SimpleSignedBroadcastCryptoMaterial {
/// Builds some simple V1 signed broadcast crypto-materials out of
/// the provided key-seed, metadata-key, and signing key.
pub fn new(
key_seed: [u8; 32],
metadata_key: MetadataKey,
signing_key: ed25519::PrivateKey,
) -> Self {
Self { key_seed, metadata_key, signing_key }
}
}
impl BroadcastCryptoMaterial<V1> for SimpleSignedBroadcastCryptoMaterial {
fn key_seed(&self) -> [u8; 32] {
self.key_seed
}
fn metadata_key(&self) -> MetadataKey {
self.metadata_key
}
}
impl SignedBroadcastCryptoMaterial for SimpleSignedBroadcastCryptoMaterial {
fn signing_key(&self) -> ed25519::PrivateKey {
self.signing_key.clone()
}
}