aboutsummaryrefslogtreecommitdiff
path: root/nearby/presence/np_hkdf/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'nearby/presence/np_hkdf/src/lib.rs')
-rw-r--r--nearby/presence/np_hkdf/src/lib.rs221
1 files changed, 221 insertions, 0 deletions
diff --git a/nearby/presence/np_hkdf/src/lib.rs b/nearby/presence/np_hkdf/src/lib.rs
new file mode 100644
index 0000000..c33e802
--- /dev/null
+++ b/nearby/presence/np_hkdf/src/lib.rs
@@ -0,0 +1,221 @@
+// 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.
+
+//! Wrappers around NP's usage of HKDF.
+//!
+//! All HKDF calls should happen in this module and expose the correct result type for
+//! each derived key use case.
+#![no_std]
+#![forbid(unsafe_code)]
+#![deny(
+ missing_docs,
+ clippy::indexing_slicing,
+ clippy::unwrap_used,
+ clippy::panic,
+ clippy::expect_used
+)]
+
+extern crate core;
+
+use core::marker;
+use crypto_provider::{aes::Aes128Key, hkdf::Hkdf, hmac::Hmac, CryptoProvider};
+
+pub mod v1_salt;
+
+/// A wrapper around the common NP usage of HMAC-SHA256.
+///
+/// These are generally derived via HKDF, but could be used for any HMAC-SHA256 key.
+#[derive(Debug)]
+pub struct NpHmacSha256Key<C: CryptoProvider> {
+ /// Nearby Presence uses 32-byte HMAC keys.
+ ///
+ /// Inside the HMAC algorithm they will be padded to 64 bytes.
+ key: [u8; 32],
+ c_phantom: marker::PhantomData<C>,
+}
+
+impl<C: CryptoProvider> NpHmacSha256Key<C> {
+ /// Build a fresh HMAC instance.
+ ///
+ /// Since each HMAC is modified as data is fed to it, HMACs should not be reused.
+ ///
+ /// See also [Self::calculate_hmac] for simple use cases.
+ pub fn build_hmac(&self) -> C::HmacSha256 {
+ C::HmacSha256::new_from_key(self.key)
+ }
+
+ /// Returns a reference to the underlying key bytes.
+ pub fn as_bytes(&self) -> &[u8; 32] {
+ &self.key
+ }
+
+ /// Build an HMAC, update it with the provided `data`, and finalize it, returning the resulting
+ /// MAC. This is convenient for one-and-done HMAC usage rather than incrementally accumulating
+ /// the final MAC.
+ pub fn calculate_hmac(&self, data: &[u8]) -> [u8; 32] {
+ let mut hmac = self.build_hmac();
+ hmac.update(data);
+ hmac.finalize()
+ }
+}
+
+impl<C: CryptoProvider> From<[u8; 32]> for NpHmacSha256Key<C> {
+ fn from(key: [u8; 32]) -> Self {
+ Self {
+ key,
+ c_phantom: Default::default(),
+ }
+ }
+}
+
+impl<C: CryptoProvider> Clone for NpHmacSha256Key<C> {
+ fn clone(&self) -> Self {
+ Self {
+ key: self.key,
+ c_phantom: Default::default(),
+ }
+ }
+}
+
+/// Salt use for all NP HKDFs
+const NP_HKDF_SALT: &[u8] = b"Google Nearby";
+
+/// A wrapper around an NP key seed for deriving HKDF-SHA256 sub keys.
+pub struct NpKeySeedHkdf<C: CryptoProvider> {
+ hkdf: C::HkdfSha256,
+}
+
+impl<C: CryptoProvider> NpKeySeedHkdf<C> {
+ /// Build an HKDF from a NP credential key seed
+ pub fn new(key_seed: &[u8; 32]) -> Self {
+ Self {
+ hkdf: np_hkdf::<C>(key_seed),
+ }
+ }
+
+ /// LDT key used to decrypt a legacy advertisement
+ #[allow(clippy::expect_used)]
+ pub fn legacy_ldt_key(&self) -> ldt::LdtKey<xts_aes::XtsAes128Key> {
+ ldt::LdtKey::from_concatenated(
+ &self
+ .hkdf_array(b"Legacy LDT key")
+ .expect("LDT key is a valid length"),
+ )
+ }
+
+ /// HMAC key used when verifying the raw metadata key extracted from an advertisement
+ #[allow(clippy::expect_used)]
+ pub fn legacy_metadata_key_hmac_key(&self) -> NpHmacSha256Key<C> {
+ self.hkdf_array(b"Legacy metadata key verification HMAC key")
+ .expect("Hmac keys are a valid length")
+ .into()
+ }
+
+ /// AES-GCM IV used when decrypting metadata
+ #[allow(clippy::expect_used)]
+ pub fn legacy_metadata_iv(&self) -> [u8; 12] {
+ self.hkdf_array(b"Legacy Metadata IV")
+ .expect("IV is a valid length")
+ }
+
+ /// AES-GCM IV used when decrypting metadata.
+ ///
+ /// Shared between signed and unsigned since they use the same credential.
+ #[allow(clippy::expect_used)]
+ pub fn extended_metadata_iv(&self) -> [u8; 12] {
+ self.hkdf_array(b"Metadata IV")
+ .expect("IV is a valid length")
+ }
+
+ /// HMAC key used when verifying the raw metadata key extracted from an advertisement
+ #[allow(clippy::expect_used)]
+ pub fn extended_unsigned_metadata_key_hmac_key(&self) -> NpHmacSha256Key<C> {
+ self.hkdf_array(b"Unsigned Section metadata key HMAC key")
+ .expect("Hmac keys are a valid length")
+ .into()
+ }
+
+ /// AES128 key used when decrypting an extended unsigned section
+ #[allow(clippy::expect_used)]
+ pub fn extended_unsigned_section_aes_key(&self) -> Aes128Key {
+ self.hkdf_array(b"Unsigned Section AES key")
+ .expect("AES128 keys are a valid length")
+ .into()
+ }
+
+ /// HMAC-SHA256 key used when verifying an extended unsigned section
+ #[allow(clippy::expect_used)]
+ pub fn extended_unsigned_section_mic_hmac_key(&self) -> NpHmacSha256Key<C> {
+ self.hkdf_array(b"Unsigned Section HMAC key")
+ .expect("Hmac keys are a valid length")
+ .into()
+ }
+
+ /// HMAC key used when verifying the raw metadata key extracted from an extended signed advertisement
+ #[allow(clippy::expect_used)]
+ pub fn extended_signed_metadata_key_hmac_key(&self) -> NpHmacSha256Key<C> {
+ self.hkdf_array(b"Signed Section metadata key HMAC key")
+ .expect("Hmac keys are a valid length")
+ .into()
+ }
+
+ /// AES128 key used when decrypting an extended signed section
+ #[allow(clippy::expect_used)]
+ pub fn extended_signed_section_aes_key(&self) -> Aes128Key {
+ self.hkdf_array(b"Signed Section AES key")
+ .expect("AES128 keys are a valid length")
+ .into()
+ }
+
+ /// Derive a length `N` array using the provided `info`
+ /// Returns None if N > 255 * 32.
+ fn hkdf_array<const N: usize>(&self, info: &[u8]) -> Option<[u8; N]> {
+ let mut arr = [0_u8; N];
+ self.hkdf.expand(info, &mut arr).map(|_| arr).ok()
+ }
+}
+
+/// Expand a legacy salt into the expanded salt used with XOR padding in LDT.
+#[allow(clippy::expect_used)]
+pub fn legacy_ldt_expanded_salt<const B: usize, C: CryptoProvider>(salt: &[u8; 2]) -> [u8; B] {
+ simple_np_hkdf_expand::<B, C>(salt, b"Legacy LDT salt pad")
+ // the padded salt is the tweak size of a tweakable block cipher, which shouldn't be
+ // anywhere close to the max HKDF size (255 * 32)
+ .expect("Tweak size is a valid HKDF size")
+}
+
+/// Expand a legacy (short) raw metadata key into an AES128 key.
+#[allow(clippy::expect_used)]
+pub fn legacy_metadata_expanded_key<C: CryptoProvider>(raw_metadata_key: &[u8; 14]) -> [u8; 16] {
+ simple_np_hkdf_expand::<16, C>(raw_metadata_key, b"Legacy metadata key expansion")
+ .expect("AES128 key is a valid HKDF size")
+}
+
+/// Build an HKDF using the NP HKDF salt, calculate output, and discard the HKDF.
+/// If using the NP key seed as IKM, see [NpKeySeedHkdf] instead.
+///
+/// Returns None if the requested size is > 255 * 32 bytes.
+fn simple_np_hkdf_expand<const N: usize, C: CryptoProvider>(
+ ikm: &[u8],
+ info: &[u8],
+) -> Option<[u8; N]> {
+ let mut buf = [0; N];
+ let hkdf = np_hkdf::<C>(ikm);
+ hkdf.expand(info, &mut buf[..]).map(|_| buf).ok()
+}
+
+/// Build an HKDF using the NP HKDF salt and supplied ikm
+fn np_hkdf<C: CryptoProvider>(ikm: &[u8]) -> C::HkdfSha256 {
+ C::HkdfSha256::new(Some(NP_HKDF_SALT), ikm)
+}