blob: b00fd618b43b39e5869ef5e19ceb040c935efa64 [file] [log] [blame]
// Copyright 2023 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.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
use crypto_provider_default::CryptoProviderImpl;
use ldt_np_adv::*;
use np_adv::credential::matched::{
EmptyMatchedCredential, HasIdentityMatch, MetadataMatchedCredential,
};
use np_adv::legacy::serialize::{AdvBuilder, LdtEncoder};
use np_adv::{
credential::{
book::CredentialBookBuilder,
v0::{V0BroadcastCredential, V0DiscoveryCredential, V0},
MatchableCredential,
},
legacy::{
data_elements::tx_power::TxPowerDataElement, deserialize::*, V0AdvertisementContents,
},
shared_data::*,
*,
};
use serde::{Deserialize, Serialize};
#[rustfmt::skip]
#[test]
fn v0_deser_plaintext() {
let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
0,
0,
CryptoProviderImpl,
>(&[], &[]);
let arena = deserialization_arena!();
let adv = deserialize_advertisement::<_, CryptoProviderImpl>(
arena,
&[
0x00, // version header
0x15, 0x03, // Length 1 Tx Power DE with value 3
],
&cred_book,
)
.expect("Should be a valid advertisement")
.into_v0()
.expect("Should be V0");
match adv {
V0AdvertisementContents::Plaintext(p) => {
assert_eq!(
vec![DeserializedDataElement::TxPower(TxPowerDataElement::from(
TxPower::try_from(3).unwrap()
))],
p.data_elements().collect::<Result<Vec<_>, _>>().unwrap()
);
}
_ => panic!("this example is plaintext"),
}
}
/// Sample contents for some encrypted identity metadata
/// which includes just a name and an e-mail address.
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct IdentityMetadata {
name: String,
email: String,
}
impl IdentityMetadata {
/// Serialize this identity metadata to a JSON byte-string.
fn to_bytes(&self) -> Vec<u8> {
serde_json::to_vec(&self).expect("serialization should always succeed")
}
/// Attempt to deserialize identity metadata from a JSON byte-string.
fn try_from_bytes(serialized: &[u8]) -> Option<Self> {
serde_json::from_slice(serialized).ok()
}
}
#[rustfmt::skip]
#[test]
fn v0_deser_ciphertext() {
// These are kept fixed in this example for reproducibility.
// In practice, these should instead be derived from a cryptographically-secure
// random number generator.
let key_seed = [0x11_u8; 32];
let identity_token = V0IdentityToken::from([0x33; V0_IDENTITY_TOKEN_LEN]);
let broadcast_cred = V0BroadcastCredential::new(key_seed, identity_token);
let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
let identity_token_hmac: [u8; 32] =
hkdf.v0_identity_token_hmac_key().calculate_hmac::<CryptoProviderImpl>(&identity_token.bytes());
// Serialize and encrypt some identity metadata (sender-side)
let sender_metadata =
IdentityMetadata { name: "Alice".to_string(), email: "alice@gmail.com".to_string() };
let sender_metadata_bytes = sender_metadata.to_bytes();
let encrypted_sender_metadata = MetadataMatchedCredential::<Vec<u8>>::
encrypt_from_plaintext::<V0, CryptoProviderImpl>(&hkdf,
identity_token,
&sender_metadata_bytes);
// output of building a packet using AdvBuilder
let adv = &[
0x04, // version header
0x22, 0x22, // salt
// ciphertext for metadata key & txpower DE
0xD8, 0x22, 0x12, 0xEF, 0x16, 0xDB, 0xF8, 0x72, 0xF2, 0xA3, 0xA7,
0xC0, 0xFA, 0x52, 0x48, 0xEC
];
// make sure output hasn't changed
let mut builder = AdvBuilder::new(LdtEncoder::<CryptoProviderImpl>::new([0x22; 2].into(), &broadcast_cred));
builder.add_data_element(TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap();
assert_eq!(adv, builder.into_advertisement().unwrap().as_slice());
let discovery_credential = V0DiscoveryCredential::new(key_seed, identity_token_hmac);
let credentials: [MatchableCredential<V0, MetadataMatchedCredential<_>>; 1] =
[MatchableCredential { discovery_credential, match_data: encrypted_sender_metadata }];
let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(
&credentials,
&[],
);
let matched = match deserialize_advertisement::<_, CryptoProviderImpl>(
deserialization_arena!(),
adv,
&cred_book,
)
.expect("Should be a valid advertisement")
.into_v0()
.expect("Should be V0")
{
V0AdvertisementContents::Decrypted(c) => c,
_ => panic!("this example is ciphertext"),
};
let decrypted_metadata_bytes = matched
.decrypt_metadata::<CryptoProviderImpl>()
.expect("Sender metadata should be decryptable");
let decrypted_metadata = IdentityMetadata::try_from_bytes(&decrypted_metadata_bytes)
.expect("Sender metadata should be deserializable");
assert_eq!(sender_metadata, decrypted_metadata);
let decrypted = matched.contents();
assert_eq!(identity_token, decrypted.identity_token());
assert_eq!(
vec![DeserializedDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(3).unwrap())),],
decrypted.data_elements().collect::<Result<Vec<_>, _>>().unwrap(),
);
}