blob: 7ab88fb7576306a19af43708611ad41b030346fb [file] [log] [blame]
// Copyright 2022 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.
//! Nearby Presence-specific usage of LDT.
#![no_std]
#![forbid(unsafe_code)]
#![deny(
missing_docs,
clippy::indexing_slicing,
clippy::unwrap_used,
clippy::panic,
clippy::expect_used
)]
#[cfg(test)]
mod np_adv_test_vectors;
#[cfg(test)]
mod tests;
use array_view::ArrayView;
use core::fmt;
use crypto_provider::hmac::Hmac;
use crypto_provider::CryptoProvider;
use ldt::{Ldt, LdtError, LdtKey, Mix, Padder, Swap, XorPadder};
use ldt_tbc::TweakableBlockCipher;
use np_hkdf::legacy_ldt_expanded_salt;
use xts_aes::{XtsAes128, XtsAes128Key, XtsAes256, XtsAes256Key};
/// Max LDT-XTS-AES data size: `(2 * AES block size) - 1`
pub const LDT_XTS_AES_MAX_LEN: usize = 31;
/// Legacy (v0) format uses 14-byte metadata key
pub const NP_LEGACY_METADATA_KEY_LEN: usize = 14;
/// The salt included in an NP advertisement
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LegacySalt {
/// Salt bytes extracted from the incoming NP advertisement
bytes: [u8; 2],
}
impl LegacySalt {
/// Returns the salt as a byte array.
pub fn bytes(&self) -> &[u8; 2] {
&self.bytes
}
}
impl From<[u8; 2]> for LegacySalt {
fn from(arr: [u8; 2]) -> Self {
Self { bytes: arr }
}
}
/// Config for one individual cipher, corresponding to a particular NP identity/credential
pub struct LdtAdvCipherConfig {
/// The key seed in the NP credential from which other keys will be derived
key_seed: [u8; 32],
/// The metadata key HMAC in the NP credential
metadata_key_hmac: [u8; 32],
}
impl LdtAdvCipherConfig {
/// Build a config from the provided key seed and metadata key hmac.
pub fn new(key_seed: [u8; 32], metadata_key_mac: [u8; 32]) -> Self {
Self {
key_seed,
metadata_key_hmac: metadata_key_mac,
}
}
/// Build an LdtAdvCipher using XTS-AES128 and keys derived from the key seed.
pub fn build_adv_decrypter_xts_aes_128<C: CryptoProvider>(&self) -> LdtAdvDecrypterAes128<C> {
let hkdf = np_hkdf::NpKeySeedHkdf::new(&self.key_seed);
LdtAdvDecrypter {
ldt: ldt_xts_aes_128::<C>(&hkdf.legacy_ldt_key()),
metadata_key_hmac: self.metadata_key_hmac,
metadata_key_hmac_key: hkdf.legacy_metadata_key_hmac_key(),
}
}
}
/// Decrypts and validates a NP legacy format advertisement encrypted with LDT.
///
/// Use an [LdtAdvCipherConfig] to build one from an NP `key_seed`.
///
/// `B` is the underlying block cipher block size.
/// `O` is the max output size (must be 2 * B - 1).
/// `T` is the tweakable block cipher used by LDT.
/// `M` is the mix function used by LDT.
pub struct LdtAdvDecrypter<
const B: usize,
const O: usize,
T: TweakableBlockCipher<B>,
M: Mix,
C: CryptoProvider,
> {
ldt: Ldt<B, T, M>,
metadata_key_hmac: [u8; 32],
metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C>,
}
/// An LdtAdvCipher with block size set appropriately for AES.
pub type LdtAdvDecrypterAes128<C> = LdtAdvDecrypter<
{ crypto_provider::aes::BLOCK_SIZE },
LDT_XTS_AES_MAX_LEN,
xts_aes::XtsAes128<C>,
Swap,
C,
>;
impl<const B: usize, const O: usize, T, M, C> LdtAdvDecrypter<B, O, T, M, C>
where
T: TweakableBlockCipher<B>,
M: Mix,
C: CryptoProvider,
{
/// Decrypt an advertisement payload using the provided padder.
///
/// If the plaintext's metadata key matches this item's MAC, return the plaintext, otherwise `None`.
///
/// # Errors
/// - If `payload` has a length outside of `[B, B * 2)`.
/// - If the decrypted plaintext fails its HMAC validation
pub fn decrypt_and_verify<P: Padder<B, T>>(
&self,
payload: &[u8],
padder: &P,
) -> Result<ArrayView<u8, O>, LdtAdvDecryptError> {
assert_eq!(B * 2 - 1, O); // should be compiled away
// have to check length before passing to LDT to ensure copying into the buffer is safe
if payload.len() < B || payload.len() > O {
return Err(LdtAdvDecryptError::InvalidLength(payload.len()));
}
// we copy to avoid exposing plaintext that hasn't been validated w/ hmac
let mut buffer = [0_u8; O];
buffer[..payload.len()].copy_from_slice(payload);
#[allow(clippy::expect_used)]
self.ldt
.decrypt(&mut buffer[..payload.len()], padder)
.map_err(|e| match e {
LdtError::InvalidLength(l) => LdtAdvDecryptError::InvalidLength(l),
})
.and_then(|_| {
let mut hmac = self.metadata_key_hmac_key.build_hmac();
hmac.update(&buffer[..NP_LEGACY_METADATA_KEY_LEN]);
hmac.verify_slice(&self.metadata_key_hmac)
.map_err(|_| LdtAdvDecryptError::MacMismatch)
.map(|_| {
ArrayView::try_from_array(buffer, payload.len())
.expect("this will never be hit because the length is validated above")
})
})
}
/// Encrypt the payload in place using the provided padder.
///
/// No validation is done to ensure that the metadata key is correct.
///
/// # Errors
/// - If `payload` has a length outside of `[B, B * 2)`.
// Leaving it in place, but deprecating it, to avoid breaking ldt_np_adv_ffi which will be
// replaced by a much more expansive FFI API soon.
#[deprecated]
pub fn encrypt<P: Padder<B, T>>(&self, payload: &mut [u8], padder: &P) -> Result<(), LdtError> {
assert_eq!(B * 2 - 1, O); // should be compiled away
self.ldt.encrypt(payload, padder)
}
/// Construct a cipher from its component parts.
///
/// See also [LdtAdvCipherConfig] to build a cipher from an NP key seed.
pub fn new(
ldt: Ldt<B, T, M>,
metadata_key_hmac: [u8; 32],
metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C>,
) -> Self {
Self {
ldt,
metadata_key_hmac,
metadata_key_hmac_key,
}
}
}
/// Errors that can occur during [LdtAdvCipher.decrypt_and_verify].
#[derive(Debug, PartialEq, Eq)]
pub enum LdtAdvDecryptError {
/// The ciphertext data was an invalid length.
InvalidLength(usize),
/// The MAC calculated from the plaintext did not match the expected value
MacMismatch,
}
impl fmt::Display for LdtAdvDecryptError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LdtAdvDecryptError::InvalidLength(len) => {
write!(f, "Adv decrypt error: invalid length ({len})")
}
LdtAdvDecryptError::MacMismatch => write!(f, "Adv decrypt error: MAC mismatch"),
}
}
}
/// Build a XorPadder by HKDFing the NP advertisement salt
pub fn salt_padder<const B: usize, C: CryptoProvider>(salt: LegacySalt) -> XorPadder<{ B }> {
// Assuming that the tweak size == the block size here, which it is for XTS.
// If that's ever not true, yet another generic parameter will address that.
XorPadder::from(legacy_ldt_expanded_salt::<B, C>(&salt.bytes))
}
/// [Ldt] parameterized for XTS-AES-128 with the [Swap] mix function.
pub type LdtXtsAes128<C> = Ldt<{ crypto_provider::aes::BLOCK_SIZE }, XtsAes128<C>, Swap>;
/// Build an [Ldt] with [xts_aes::Xts]-AES-128 and the [Swap] mix function.
pub fn ldt_xts_aes_128<C: CryptoProvider>(key: &LdtKey<XtsAes128Key>) -> LdtXtsAes128<C> {
Ldt::new(key)
}
/// [Ldt] parameterized for XTS-AES-256 with the [Swap] mix function.
pub type LdtXtsAes256<C> = Ldt<{ crypto_provider::aes::BLOCK_SIZE }, XtsAes256<C>, Swap>;
/// Build an [Ldt] with [xts_aes::Xts]-AES-256 and the [Swap] mix function.
pub fn ldt_xts_aes_256<C: CryptoProvider>(key: &LdtKey<XtsAes256Key>) -> LdtXtsAes256<C> {
Ldt::new(key)
}