• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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