1 // Copyright 2023 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 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
16 
17 use crypto_provider::{ed25519, CryptoProvider, CryptoRng};
18 use crypto_provider_default::CryptoProviderImpl;
19 use np_adv::credential::matched::{
20     EmptyMatchedCredential, MetadataMatchedCredential, WithMatchedCredential,
21 };
22 use np_adv::extended::deserialize::{Section, V1DeserializedSection};
23 use np_adv::extended::{V1IdentityToken, V1_ENCODING_UNENCRYPTED};
24 use np_adv::{
25     credential::{
26         book::CredentialBookBuilder,
27         v1::{V1BroadcastCredential, V1DiscoveryCredential, V1},
28         MatchableCredential,
29     },
30     deserialization_arena, deserialize_advertisement,
31     extended::{
32         data_elements::TxPowerDataElement,
33         deserialize::VerificationMode,
34         serialize::{
35             AdvBuilder, AdvertisementType, SignedEncryptedSectionEncoder, SingleTypeDataElement,
36             UnencryptedSectionEncoder,
37         },
38         NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT,
39     },
40     shared_data::TxPower,
41     AdvDeserializationError, AdvDeserializationErrorDetailsHazmat,
42 };
43 use np_hkdf::{v1_salt, DerivedSectionKeys};
44 use serde::{Deserialize, Serialize};
45 
46 type Ed25519ProviderImpl = <CryptoProviderImpl as CryptoProvider>::Ed25519;
47 
48 #[test]
v1_deser_plaintext()49 fn v1_deser_plaintext() {
50     let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
51     let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap();
52     section_builder
53         .add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(6).unwrap()))
54         .unwrap();
55     section_builder.add_to_advertisement::<CryptoProviderImpl>();
56     let adv = adv_builder.into_advertisement();
57 
58     let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
59         0,
60         0,
61         CryptoProviderImpl,
62     >(&[], &[]);
63 
64     let arena = deserialization_arena!();
65     let contents =
66         deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
67             .expect("Should be a valid advertisemement")
68             .into_v1()
69             .expect("Should be V1");
70 
71     assert_eq!(0, contents.invalid_sections_count());
72 
73     let sections = contents.sections().collect::<Vec<_>>();
74 
75     assert_eq!(1, sections.len());
76 
77     let section = match &sections[0] {
78         V1DeserializedSection::Plaintext(s) => s,
79         _ => panic!("this is a plaintext adv"),
80     };
81     let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap();
82     assert_eq!(1, data_elements.len());
83 
84     let de = &data_elements[0];
85     assert_eq!(v1_salt::DataElementOffset::from(0), de.offset());
86     assert_eq!(TxPowerDataElement::DE_TYPE, de.de_type());
87     assert_eq!(&[6], de.contents());
88 }
89 
90 /// Sample contents for some encrypted identity metadata
91 /// which consists of a UUID together with a display name
92 /// and a general location.
93 #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
94 struct IdentityMetadata {
95     uuid: String,
96     display_name: String,
97     location: String,
98 }
99 
100 impl IdentityMetadata {
101     /// Serialize this identity metadata to a json byte-string.
to_bytes(&self) -> Vec<u8>102     fn to_bytes(&self) -> Vec<u8> {
103         serde_json::to_vec(self).expect("Identity metadata serialization is infallible")
104     }
105     /// Attempt to deserialize identity metadata from a json byte-string.
try_from_bytes(serialized: &[u8]) -> Option<Self>106     fn try_from_bytes(serialized: &[u8]) -> Option<Self> {
107         serde_json::from_slice(serialized).ok()
108     }
109 }
110 
111 #[test]
v1_deser_ciphertext()112 fn v1_deser_ciphertext() {
113     // identity material
114     let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
115     let token_array: [u8; 16] = rng.gen();
116     let identity_token = V1IdentityToken::from(token_array);
117     let private_key = ed25519::PrivateKey::generate::<Ed25519ProviderImpl>();
118     let key_seed = rng.gen();
119     let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
120 
121     let broadcast_cred = V1BroadcastCredential::new(key_seed, identity_token, private_key.clone());
122 
123     // Serialize and encrypt some identity metadata (sender-side)
124     let sender_metadata = IdentityMetadata {
125         uuid: "378845e1-2616-420d-86f5-674177a7504d".to_string(),
126         display_name: "Alice".to_string(),
127         location: "Wonderland".to_string(),
128     };
129     let sender_metadata_bytes = sender_metadata.to_bytes();
130     let encrypted_sender_metadata = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::<
131         V1,
132         CryptoProviderImpl,
133     >(&hkdf, identity_token, &sender_metadata_bytes);
134 
135     // prepare advertisement
136     let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
137 
138     let mut section_builder = adv_builder
139         .section_builder(SignedEncryptedSectionEncoder::new_random_salt::<CryptoProviderImpl>(
140             &mut rng,
141             &broadcast_cred,
142         ))
143         .unwrap();
144     section_builder
145         .add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(7).unwrap()))
146         .unwrap();
147     section_builder.add_to_advertisement::<CryptoProviderImpl>();
148     let adv = adv_builder.into_advertisement();
149 
150     let discovery_credential = V1DiscoveryCredential::new(
151         key_seed,
152         [0; 32],
153         [0; 32], // Zeroing out MIC HMAC, since it's unused in examples here.
154         hkdf.v1_signature_keys()
155             .identity_token_hmac_key()
156             .calculate_hmac::<CryptoProviderImpl>(identity_token.bytes()),
157         private_key.derive_public_key::<Ed25519ProviderImpl>(),
158     );
159 
160     let credentials: [MatchableCredential<V1, MetadataMatchedCredential<_>>; 1] =
161         [MatchableCredential {
162             discovery_credential,
163             match_data: encrypted_sender_metadata.clone(),
164         }];
165     let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(
166         &[],
167         &credentials,
168     );
169     let arena = deserialization_arena!();
170     let contents =
171         deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
172             .expect("Should be a valid advertisement")
173             .into_v1()
174             .expect("Should be V1");
175 
176     assert_eq!(0, contents.invalid_sections_count());
177 
178     let sections = contents.sections().collect::<Vec<_>>();
179     assert_eq!(1, sections.len());
180 
181     let matched: &WithMatchedCredential<_, _> = match &sections[0] {
182         V1DeserializedSection::Decrypted(d) => d,
183         _ => panic!("this is a ciphertext adv"),
184     };
185 
186     let decrypted_metadata_bytes = matched
187         .decrypt_metadata::<CryptoProviderImpl>()
188         .expect("Sender metadata should be decryptable");
189     let decrypted_metadata = IdentityMetadata::try_from_bytes(&decrypted_metadata_bytes)
190         .expect("Sender metadata should be deserializable");
191     assert_eq!(sender_metadata, decrypted_metadata);
192 
193     let section = matched.contents();
194 
195     assert_eq!(VerificationMode::Signature, section.verification_mode());
196     assert_eq!(&identity_token, section.identity_token());
197 
198     let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap();
199     assert_eq!(1, data_elements.len());
200 
201     let de = &data_elements[0];
202     assert_eq!(v1_salt::DataElementOffset::from(0), de.offset());
203     assert_eq!(TxPowerDataElement::DE_TYPE, de.de_type());
204     assert_eq!(&[7], de.contents());
205 
206     // Uncomment if you need to regenerate C++ v1_private_identity_tests data
207     // {
208     //     use test_helper::hex_bytes;
209     //     use np_adv::extended::salt::MultiSalt;
210     //     use np_adv_credential_matched::MatchedCredential;
211     //     println!("adv:\n{}", hex_bytes(adv.as_slice()));
212     //     println!("key seed:\n{}", hex_bytes(key_seed));
213     //     println!(
214     //         "identity token hmac:\n{}",
215     //         hex_bytes(
216     //             hkdf.v1_signature_keys()
217     //                 .identity_token_hmac_key()
218     //                 .calculate_hmac(identity_token.bytes())
219     //         )
220     //     );
221     //     println!("public key:\n{}", hex_bytes(key_pair.public().to_bytes()));
222     //     println!(
223     //         "encrypted metadata:\n{}",
224     //         hex_bytes(encrypted_sender_metadata.fetch_encrypted_metadata().unwrap())
225     //     );
226     //     std::println!("offset is: {:?}", de.offset());
227     //     let derived_salt = match section.salt() {
228     //         MultiSalt::Short(_) => panic!(),
229     //         MultiSalt::Extended(s) => {
230     //             s.derive::<16, CryptoProviderImpl>(Some(de.offset())).unwrap()
231     //         }
232     //     };
233     //     println!("DE derived salt:\n{}", hex_bytes(derived_salt));
234     //     panic!();
235     // }
236 }
237 
238 #[test]
v1_deser_no_section()239 fn v1_deser_no_section() {
240     // TODO: we shouldn't allow this invalid advertisement to be serialized
241     let adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
242     let adv = adv_builder.into_advertisement();
243     let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
244         0,
245         0,
246         CryptoProviderImpl,
247     >(&[], &[]);
248     let arena = deserialization_arena!();
249     let v1_deserialize_error =
250         deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
251             .expect_err(" Expected an error");
252     assert_eq!(
253         v1_deserialize_error,
254         AdvDeserializationError::ParseError {
255             details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
256         }
257     );
258 }
259 
260 #[test]
v1_deser_plaintext_over_max_sections()261 fn v1_deser_plaintext_over_max_sections() {
262     let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
263     for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT {
264         let mut section_builder = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap();
265         section_builder
266             .add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(7).unwrap()))
267             .unwrap();
268         section_builder.add_to_advertisement::<CryptoProviderImpl>();
269     }
270     let mut adv = adv_builder.into_advertisement().as_slice().to_vec();
271     // Push an extra section
272     adv.extend_from_slice(
273         [
274             0x01, // Section header
275             V1_ENCODING_UNENCRYPTED,
276         ]
277         .as_slice(),
278     );
279     let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
280         0,
281         0,
282         CryptoProviderImpl,
283     >(&[], &[]);
284     let arena = deserialization_arena!();
285     assert_eq!(
286         deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
287             .unwrap_err(),
288         AdvDeserializationError::ParseError {
289             details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
290         }
291     );
292 }
293