1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Nearby Presence-specific usage of LDT.
16 #![no_std]
17 #![forbid(unsafe_code)]
18 #![deny(
19 missing_docs,
20 clippy::indexing_slicing,
21 clippy::unwrap_used,
22 clippy::panic,
23 clippy::expect_used
24 )]
25
26 #[cfg(test)]
27 mod np_adv_test_vectors;
28 #[cfg(test)]
29 mod tests;
30
31 use array_view::ArrayView;
32 use core::fmt;
33 use crypto_provider::aes::BLOCK_SIZE;
34 use crypto_provider::hmac::Hmac;
35 use crypto_provider::CryptoProvider;
36 use ldt::{LdtDecryptCipher, LdtEncryptCipher, LdtError, Mix, Padder, Swap, XorPadder};
37 use ldt_tbc::TweakableBlockCipher;
38 use np_hkdf::{legacy_ldt_expanded_salt, NpHmacSha256Key, NpKeySeedHkdf};
39 use xts_aes::XtsAes128;
40
41 /// Max LDT-XTS-AES data size: `(2 * AES block size) - 1`
42 pub const LDT_XTS_AES_MAX_LEN: usize = 31;
43 /// Legacy (v0) format uses 14-byte metadata key
44 pub const NP_LEGACY_METADATA_KEY_LEN: usize = 14;
45
46 /// The salt included in an NP advertisement
47 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
48 pub struct LegacySalt {
49 /// Salt bytes extracted from the incoming NP advertisement
50 bytes: [u8; 2],
51 }
52
53 impl LegacySalt {
54 /// Returns the salt as a byte array.
bytes(&self) -> &[u8; 2]55 pub fn bytes(&self) -> &[u8; 2] {
56 &self.bytes
57 }
58 }
59
60 impl From<[u8; 2]> for LegacySalt {
from(arr: [u8; 2]) -> Self61 fn from(arr: [u8; 2]) -> Self {
62 Self { bytes: arr }
63 }
64 }
65
66 /// [LdtEncryptCipher] parameterized for XTS-AES-128 with the [Swap] mix function.
67 pub type LdtEncrypterXtsAes128<C> = LdtEncryptCipher<{ BLOCK_SIZE }, XtsAes128<C>, Swap>;
68
69 /// A Nearby Presence specific LDT decrypter which verifies the hmac tag of the given payload
70 /// parameterized for XTS-AES-128 with the [Swap] mix function.
71 pub type LdtNpAdvDecrypterXtsAes128<C> =
72 LdtNpAdvDecrypter<{ BLOCK_SIZE }, LDT_XTS_AES_MAX_LEN, XtsAes128<C>, Swap, C>;
73
74 /// Build a Nearby Presence specific LDT XTS-AES-128 decrypter from a provided [NpKeySeedHkdf] and
75 /// metadata_key_hmac, with the [Swap] mix function
build_np_adv_decrypter_from_key_seed<C: CryptoProvider>( key_seed: &NpKeySeedHkdf<C>, metadata_key_tag: [u8; 32], ) -> LdtNpAdvDecrypterXtsAes128<C>76 pub fn build_np_adv_decrypter_from_key_seed<C: CryptoProvider>(
77 key_seed: &NpKeySeedHkdf<C>,
78 metadata_key_tag: [u8; 32],
79 ) -> LdtNpAdvDecrypterXtsAes128<C> {
80 build_np_adv_decrypter(
81 &key_seed.legacy_ldt_key(),
82 metadata_key_tag,
83 key_seed.legacy_metadata_key_hmac_key(),
84 )
85 }
86
87 /// Build a Nearby Presence specific LDT XTS-AES-128 decrypter from precalculated cipher components,
88 /// with the [Swap] mix function
build_np_adv_decrypter<C: CryptoProvider>( ldt_key: &ldt::LdtKey<xts_aes::XtsAes128Key>, metadata_key_tag: [u8; 32], metadata_key_hmac_key: NpHmacSha256Key<C>, ) -> LdtNpAdvDecrypterXtsAes128<C>89 pub fn build_np_adv_decrypter<C: CryptoProvider>(
90 ldt_key: &ldt::LdtKey<xts_aes::XtsAes128Key>,
91 metadata_key_tag: [u8; 32],
92 metadata_key_hmac_key: NpHmacSha256Key<C>,
93 ) -> LdtNpAdvDecrypterXtsAes128<C> {
94 LdtNpAdvDecrypter {
95 ldt_decrypter: LdtXtsAes128Decrypter::<C>::new(ldt_key),
96 metadata_key_tag,
97 metadata_key_hmac_key,
98 }
99 }
100
101 // [LdtDecryptCipher] parameterized for XTS-AES-128 with the [Swap] mix function.
102 type LdtXtsAes128Decrypter<C> = LdtDecryptCipher<{ BLOCK_SIZE }, XtsAes128<C>, Swap>;
103
104 /// Decrypts and validates a NP legacy format advertisement encrypted with LDT.
105 ///
106 /// `B` is the underlying block cipher block size.
107 /// `O` is the max output size (must be 2 * B - 1).
108 /// `T` is the tweakable block cipher used by LDT.
109 /// `M` is the mix function used by LDT.
110 pub struct LdtNpAdvDecrypter<
111 const B: usize,
112 const O: usize,
113 T: TweakableBlockCipher<B>,
114 M: Mix,
115 C: CryptoProvider,
116 > {
117 ldt_decrypter: LdtDecryptCipher<B, T, M>,
118 metadata_key_tag: [u8; 32],
119 metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C>,
120 }
121
122 impl<const B: usize, const O: usize, T, M, C> LdtNpAdvDecrypter<B, O, T, M, C>
123 where
124 T: TweakableBlockCipher<B>,
125 M: Mix,
126 C: CryptoProvider,
127 {
128 /// Decrypt an advertisement payload using the provided padder.
129 ///
130 /// If the plaintext's metadata key matches this item's MAC, return the plaintext, otherwise `None`.
131 ///
132 /// # Errors
133 /// - If `payload` has a length outside of `[B, B * 2)`.
134 /// - If the decrypted plaintext fails its HMAC validation
decrypt_and_verify<P: Padder<B, T>>( &self, payload: &[u8], padder: &P, ) -> Result<ArrayView<u8, O>, LdtAdvDecryptError>135 pub fn decrypt_and_verify<P: Padder<B, T>>(
136 &self,
137 payload: &[u8],
138 padder: &P,
139 ) -> Result<ArrayView<u8, O>, LdtAdvDecryptError> {
140 assert_eq!(B * 2 - 1, O); // should be compiled away
141
142 // have to check length before passing to LDT to ensure copying into the buffer is safe
143 if payload.len() < B || payload.len() > O {
144 return Err(LdtAdvDecryptError::InvalidLength(payload.len()));
145 }
146
147 // we copy to avoid exposing plaintext that hasn't been validated w/ hmac
148 let mut buffer = [0_u8; O];
149 buffer[..payload.len()].copy_from_slice(payload);
150
151 #[allow(clippy::expect_used)]
152 self.ldt_decrypter
153 .decrypt(&mut buffer[..payload.len()], padder)
154 .map_err(|e| match e {
155 LdtError::InvalidLength(l) => LdtAdvDecryptError::InvalidLength(l),
156 })
157 .and_then(|_| {
158 let mut hmac = self.metadata_key_hmac_key.build_hmac();
159 hmac.update(&buffer[..NP_LEGACY_METADATA_KEY_LEN]);
160 hmac.verify_slice(&self.metadata_key_tag)
161 .map_err(|_| LdtAdvDecryptError::MacMismatch)
162 .map(|_| {
163 ArrayView::try_from_array(buffer, payload.len())
164 .expect("this will never be hit because the length is validated above")
165 })
166 })
167 }
168 }
169
170 /// Errors that can occur during [LdtNpAdvDecrypter::decrypt_and_verify].
171 #[derive(Debug, PartialEq, Eq)]
172 pub enum LdtAdvDecryptError {
173 /// The ciphertext data was an invalid length.
174 InvalidLength(usize),
175 /// The MAC calculated from the plaintext did not match the expected value
176 MacMismatch,
177 }
178
179 impl fmt::Display for LdtAdvDecryptError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 match self {
182 LdtAdvDecryptError::InvalidLength(len) => {
183 write!(f, "Adv decrypt error: invalid length ({len})")
184 }
185 LdtAdvDecryptError::MacMismatch => write!(f, "Adv decrypt error: MAC mismatch"),
186 }
187 }
188 }
189 /// Build a XorPadder by HKDFing the NP advertisement salt
salt_padder<const B: usize, C: CryptoProvider>(salt: LegacySalt) -> XorPadder<190 pub fn salt_padder<const B: usize, C: CryptoProvider>(salt: LegacySalt) -> XorPadder<{ B }> {
191 // Assuming that the tweak size == the block size here, which it is for XTS.
192 // If that's ever not true, yet another generic parameter will address that.
193 XorPadder::from(legacy_ldt_expanded_salt::<B, C>(&salt.bytes))
194 }
195