aboutsummaryrefslogtreecommitdiff
path: root/nearby/presence/ldt_np_adv/src/lib.rs
blob: 542d6a5d2b926ec30d0da39641e9ef57660cfc3e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// 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]

#[cfg(feature = "std")]
extern crate std;

#[cfg(test)]
mod np_adv_test_vectors;
#[cfg(test)]
mod tests;

use array_view::ArrayView;
use core::fmt;
use crypto_provider::{aes::BLOCK_SIZE, CryptoProvider};
use ldt::{LdtDecryptCipher, LdtEncryptCipher, LdtError, Mix, Padder, Swap, XorPadder};
use ldt_tbc::TweakableBlockCipher;
use np_hkdf::{legacy_ldt_expanded_salt, NpHmacSha256Key, NpKeySeedHkdf};
use xts_aes::XtsAes128;

/// Max LDT-XTS-AES data size: `(2 * AES block size) - 1`
pub const LDT_XTS_AES_MAX_LEN: usize = 31;

/// Legacy (v0) format uses a 14-byte metadata key
pub const NP_LEGACY_METADATA_KEY_LEN: usize = 14;

/// The salt included in an NP advertisement.
/// LDT does not use an IV but can instead incorporate the 2 byte, regularly rotated,
/// salt from the advertisement payload and XOR it with the padded tweak data.
#[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 }
    }
}

/// [LdtEncryptCipher] parameterized for XTS-AES-128 with the [Swap] mix function.
pub type LdtEncrypterXtsAes128<C> = LdtEncryptCipher<{ BLOCK_SIZE }, XtsAes128<C>, Swap>;

/// A Nearby Presence specific LDT decrypter which verifies the hmac tag of the given payload
/// parameterized for XTS-AES-128 with the [Swap] mix function.
pub type LdtNpAdvDecrypterXtsAes128<C> =
    LdtNpAdvDecrypter<{ BLOCK_SIZE }, LDT_XTS_AES_MAX_LEN, XtsAes128<C>, Swap, C>;

/// Build a Nearby Presence specific LDT XTS-AES-128 decrypter from a provided [NpKeySeedHkdf] and
/// metadata_key_hmac, with the [Swap] mix function
pub fn build_np_adv_decrypter_from_key_seed<C: CryptoProvider>(
    key_seed: &NpKeySeedHkdf<C>,
    metadata_key_tag: [u8; 32],
) -> LdtNpAdvDecrypterXtsAes128<C> {
    build_np_adv_decrypter(
        &key_seed.legacy_ldt_key(),
        metadata_key_tag,
        key_seed.legacy_metadata_key_hmac_key(),
    )
}

/// Build a Nearby Presence specific LDT XTS-AES-128 decrypter from precalculated cipher components,
/// with the [Swap] mix function
pub fn 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> {
    LdtNpAdvDecrypter {
        ldt_decrypter: LdtXtsAes128Decrypter::<C>::new(ldt_key),
        metadata_key_tag,
        metadata_key_hmac_key,
    }
}

// [LdtDecryptCipher] parameterized for XTS-AES-128 with the [Swap] mix function.
type LdtXtsAes128Decrypter<C> = LdtDecryptCipher<{ BLOCK_SIZE }, XtsAes128<C>, Swap>;

/// Decrypts and validates a NP legacy format advertisement encrypted with LDT.
///
/// A NP legacy advertisement will always be in the format of:
///
/// Header (1 byte) | Identity DE header (1 byte) | Salt (2 bytes) | Identity (14 bytes) | repeated
/// { DE header | DE payload }
///
/// Example:
/// Header (1 byte) | Identity DE header (1 byte) | Salt (2 bytes) | Identity (14 bytes) |
/// Tx power DE header (1 byte) | Tx power (1 byte) | Action DE header(1 byte) | action (1-3 bytes)
///
/// The ciphertext bytes will always start with the Identity through the end of the
/// advertisement, for example in the above [ Identity (14 bytes) | Tx power DE header (1 byte) |
/// Tx power (1 byte) | Action DE header(1 byte) | action (1-3 bytes) ] will be the ciphertext section
/// passed as the input to `decrypt_and_verify`
///
/// `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 LdtNpAdvDecrypter<
    const B: usize,
    const O: usize,
    T: TweakableBlockCipher<B>,
    M: Mix,
    C: CryptoProvider,
> {
    ldt_decrypter: LdtDecryptCipher<B, T, M>,
    metadata_key_tag: [u8; 32],
    metadata_key_hmac_key: NpHmacSha256Key<C>,
}

impl<const B: usize, const O: usize, T, M, C> LdtNpAdvDecrypter<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`.
    ///
    /// NOTE: because LDT acts as a PRP over the entire message, tampering with any bit scrambles
    /// the whole message, so we can leverage the MAC on just the metadata key to ensure integrity
    /// for the whole message.
    ///
    /// # 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_decrypter
            .decrypt(&mut buffer[..payload.len()], padder)
            .map_err(|e| match e {
                LdtError::InvalidLength(l) => LdtAdvDecryptError::InvalidLength(l),
            })
            .and_then(|_| {
                self.metadata_key_hmac_key
                    .verify_hmac(&buffer[..NP_LEGACY_METADATA_KEY_LEN], self.metadata_key_tag)
                    .map_err(|_| LdtAdvDecryptError::MacMismatch)
                    .map(|_| {
                        ArrayView::try_from_array(buffer, payload.len())
                            .expect("this will never be hit because the length is validated above")
                    })
            })
    }
}

/// Errors that can occur during [LdtNpAdvDecrypter::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))
}