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 §ions[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 §ions[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