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 //! Credential-related data-types and functions
15 
16 use crate::common::*;
17 use crate::utils::{FfiEnum, LocksLongerThan};
18 use crypto_provider::{ed25519, CryptoProvider};
19 use crypto_provider_default::CryptoProviderImpl;
20 use handle_map::{declare_handle_map, HandleLike, HandleMapFullError, HandleMapTryAllocateError};
21 use np_adv::extended;
22 use std::sync::Arc;
23 
24 type Ed25519ProviderImpl = <CryptoProviderImpl as CryptoProvider>::Ed25519;
25 
26 /// Cryptographic information about a particular V0 discovery credential
27 /// necessary to match and decrypt encrypted V0 advertisements.
28 #[repr(C)]
29 pub struct V0DiscoveryCredential {
30     key_seed: [u8; 32],
31     identity_token_hmac: [u8; 32],
32 }
33 
34 impl V0DiscoveryCredential {
35     /// Constructs a new V0 discovery credential with the given 32-byte key-seed
36     /// and the given 32-byte HMAC for the (14-byte) legacy metadata key.
new(key_seed: [u8; 32], identity_token_hmac: [u8; 32]) -> Self37     pub fn new(key_seed: [u8; 32], identity_token_hmac: [u8; 32]) -> Self {
38         Self { key_seed, identity_token_hmac }
39     }
into_internal(self) -> np_adv::credential::v0::V0DiscoveryCredential40     fn into_internal(self) -> np_adv::credential::v0::V0DiscoveryCredential {
41         np_adv::credential::v0::V0DiscoveryCredential::new(self.key_seed, self.identity_token_hmac)
42     }
43 }
44 
45 /// Cryptographic information about a particular V1 discovery credential
46 /// necessary to match and decrypt encrypted V1 advertisement sections.
47 #[repr(C)]
48 pub struct V1DiscoveryCredential {
49     key_seed: [u8; 32],
50     expected_mic_short_salt_identity_token_hmac: [u8; 32],
51     expected_mic_extended_salt_identity_token_hmac: [u8; 32],
52     expected_signature_identity_token_hmac: [u8; 32],
53     pub_key: [u8; 32],
54 }
55 
56 impl V1DiscoveryCredential {
57     /// Constructs a new V1 discovery credential with the given 32-byte key-seed,
58     /// unsigned-variant HMAC of the metadata key, the signed-variant HMAC of
59     /// the metadata key, and the given public key for signature verification.
new( key_seed: [u8; 32], expected_mic_short_salt_identity_token_hmac: [u8; 32], expected_mic_extended_salt_identity_token_hmac: [u8; 32], expected_signature_identity_token_hmac: [u8; 32], pub_key: [u8; 32], ) -> Self60     pub fn new(
61         key_seed: [u8; 32],
62         expected_mic_short_salt_identity_token_hmac: [u8; 32],
63         expected_mic_extended_salt_identity_token_hmac: [u8; 32],
64         expected_signature_identity_token_hmac: [u8; 32],
65         pub_key: [u8; 32],
66     ) -> Self {
67         Self {
68             key_seed,
69             expected_mic_short_salt_identity_token_hmac,
70             expected_mic_extended_salt_identity_token_hmac,
71             expected_signature_identity_token_hmac,
72             pub_key,
73         }
74     }
into_internal( self, ) -> Result<np_adv::credential::v1::V1DiscoveryCredential, ed25519::InvalidPublicKeyBytes>75     fn into_internal(
76         self,
77     ) -> Result<np_adv::credential::v1::V1DiscoveryCredential, ed25519::InvalidPublicKeyBytes> {
78         let public_key = ed25519::PublicKey::from_bytes::<Ed25519ProviderImpl>(self.pub_key)?;
79         Ok(np_adv::credential::v1::V1DiscoveryCredential::new(
80             self.key_seed,
81             self.expected_mic_short_salt_identity_token_hmac,
82             self.expected_mic_extended_salt_identity_token_hmac,
83             self.expected_signature_identity_token_hmac,
84             public_key,
85         ))
86     }
87 }
88 
89 /// A [`MatchedCredential`] implementation for the purpose of
90 /// capturing match-data details across the FFI boundary.
91 /// Since we can't know what plaintext match-data the client
92 /// wants to keep around, we just expose an ID for them to do
93 /// their own look-up.
94 ///
95 /// For the encrypted metadata, we need a slightly richer
96 /// representation, since we need to be able to decrypt
97 /// the metadata as part of an API call. Internally, we
98 /// keep this as an atomic-reference-counted pointer to
99 /// a byte array, and never expose this raw pointer across
100 /// the FFI boundary.
101 #[derive(Debug, Clone)]
102 pub struct MatchedCredential {
103     cred_id: u32,
104     encrypted_metadata_bytes: Arc<[u8]>,
105 }
106 
107 impl MatchedCredential {
108     /// Constructs a new matched credential from the given match-id
109     /// (some arbitrary `u32` identifier) and encrypted metadata bytes,
110     /// copied from the given slice.
new(cred_id: u32, encrypted_metadata_bytes: &[u8]) -> Self111     pub fn new(cred_id: u32, encrypted_metadata_bytes: &[u8]) -> Self {
112         Self::from_arc_bytes(cred_id, encrypted_metadata_bytes.to_vec().into())
113     }
114     /// Constructs a new matched credential from the given match-id
115     /// (some arbitrary `u32` identifier) and encrypted metadata bytes.
from_arc_bytes(cred_id: u32, encrypted_metadata_bytes: Arc<[u8]>) -> Self116     pub fn from_arc_bytes(cred_id: u32, encrypted_metadata_bytes: Arc<[u8]>) -> Self {
117         Self { cred_id, encrypted_metadata_bytes }
118     }
119     /// Gets the pre-specified numerical identifier for this matched-credential.
id(&self) -> u32120     pub(crate) fn id(&self) -> u32 {
121         self.cred_id
122     }
123 }
124 
125 impl PartialEq<MatchedCredential> for MatchedCredential {
eq(&self, other: &Self) -> bool126     fn eq(&self, other: &Self) -> bool {
127         self.id() == other.id()
128     }
129 }
130 
131 impl Eq for MatchedCredential {}
132 
133 impl np_adv::credential::matched::MatchedCredential for MatchedCredential {
134     type EncryptedMetadata = Arc<[u8]>;
135     type EncryptedMetadataFetchError = core::convert::Infallible;
fetch_encrypted_metadata(&self) -> Result<Arc<[u8]>, core::convert::Infallible>136     fn fetch_encrypted_metadata(&self) -> Result<Arc<[u8]>, core::convert::Infallible> {
137         Ok(self.encrypted_metadata_bytes.clone())
138     }
139 }
140 
141 /// Internals of a credential slab,
142 /// an intermediate used in the construction
143 /// of a credential-book.
144 pub struct CredentialSlabInternals {
145     v0_creds:
146         Vec<np_adv::credential::MatchableCredential<np_adv::credential::v0::V0, MatchedCredential>>,
147     v1_creds:
148         Vec<np_adv::credential::MatchableCredential<np_adv::credential::v1::V1, MatchedCredential>>,
149 }
150 
151 impl CredentialSlabInternals {
new() -> Self152     pub(crate) fn new() -> Self {
153         Self { v0_creds: Vec::new(), v1_creds: Vec::new() }
154     }
155     /// Adds the given V0 discovery credential with the given
156     /// identity match-data onto the end of the V0 credentials
157     /// currently stored in this slab.
add_v0( &mut self, discovery_credential: V0DiscoveryCredential, match_data: MatchedCredential, )158     pub(crate) fn add_v0(
159         &mut self,
160         discovery_credential: V0DiscoveryCredential,
161         match_data: MatchedCredential,
162     ) {
163         let matchable_credential = np_adv::credential::MatchableCredential {
164             discovery_credential: discovery_credential.into_internal(),
165             match_data,
166         };
167         self.v0_creds.push(matchable_credential);
168     }
169     /// Adds the given V1 discovery credential with the given
170     /// identity match-data onto the end of the V1 credentials
171     /// currently stored in this slab.
add_v1( &mut self, discovery_credential: V1DiscoveryCredential, match_data: MatchedCredential, ) -> Result<(), ed25519::InvalidPublicKeyBytes>172     pub(crate) fn add_v1(
173         &mut self,
174         discovery_credential: V1DiscoveryCredential,
175         match_data: MatchedCredential,
176     ) -> Result<(), ed25519::InvalidPublicKeyBytes> {
177         discovery_credential.into_internal().map(|dc| {
178             let matchable_credential =
179                 np_adv::credential::MatchableCredential { discovery_credential: dc, match_data };
180             self.v1_creds.push(matchable_credential);
181         })
182     }
183 }
184 
185 /// Discriminant for `CreateCredentialSlabResult`
186 #[repr(u8)]
187 pub enum CreateCredentialSlabResultKind {
188     /// There was no space left to create a new credential slab
189     NoSpaceLeft = 0,
190     /// We created a new credential slab behind the given handle.
191     /// The associated payload may be obtained via
192     /// `CreateCredentialSlabResult#into_success()`.
193     Success = 1,
194 }
195 
196 /// Result type for `create_credential_slab`
197 #[repr(C)]
198 #[allow(missing_docs)]
199 pub enum CreateCredentialSlabResult {
200     NoSpaceLeft,
201     Success(CredentialSlab),
202 }
203 
204 impl From<Result<CredentialSlab, HandleMapFullError>> for CreateCredentialSlabResult {
from(result: Result<CredentialSlab, HandleMapFullError>) -> Self205     fn from(result: Result<CredentialSlab, HandleMapFullError>) -> Self {
206         match result {
207             Ok(slab) => CreateCredentialSlabResult::Success(slab),
208             Err(_) => CreateCredentialSlabResult::NoSpaceLeft,
209         }
210     }
211 }
212 
213 /// Result type for trying to add a V1 credential to a credential-slab.
214 #[repr(u8)]
215 pub enum AddV1CredentialToSlabResult {
216     /// We succeeded in adding the credential to the slab.
217     Success = 0,
218     /// The handle to the slab was actually invalid.
219     InvalidHandle = 1,
220     /// The provided public key bytes do not actually represent a valid "edwards y" format
221     /// or that said compressed point is not actually a point on the curve.
222     InvalidPublicKeyBytes = 2,
223 }
224 
225 /// Result type for trying to add a V0 credential to a credential-slab.
226 #[repr(u8)]
227 pub enum AddV0CredentialToSlabResult {
228     /// We succeeded in adding the credential to the slab.
229     Success = 0,
230     /// The handle to the slab was actually invalid.
231     InvalidHandle = 1,
232 }
233 
234 /// A `#[repr(C)]` handle to a value of type `CredentialSlabInternals`
235 #[repr(C)]
236 #[derive(Clone, Copy, PartialEq, Eq)]
237 pub struct CredentialSlab {
238     handle_id: u64,
239 }
240 
241 declare_handle_map!(
242     credential_slab,
243     crate::common::default_handle_map_dimensions(),
244     super::CredentialSlab,
245     super::CredentialSlabInternals
246 );
247 
248 impl CredentialSlab {
249     /// Adds the given V0 discovery credential with some associated match-data to this credential
250     /// slab. This uses the handle but does not transfer ownership of it.
add_v0( &self, discovery_credential: V0DiscoveryCredential, match_data: MatchedCredential, ) -> AddV0CredentialToSlabResult251     pub fn add_v0(
252         &self,
253         discovery_credential: V0DiscoveryCredential,
254         match_data: MatchedCredential,
255     ) -> AddV0CredentialToSlabResult {
256         match self.get_mut() {
257             Ok(mut write_guard) => {
258                 write_guard.add_v0(discovery_credential, match_data);
259                 AddV0CredentialToSlabResult::Success
260             }
261             Err(_) => AddV0CredentialToSlabResult::InvalidHandle,
262         }
263     }
264     /// Adds the given V1 discovery credential with some associated match-data to this credential
265     /// slab. This uses the handle but does not transfer ownership of it.
add_v1( &self, discovery_credential: V1DiscoveryCredential, match_data: MatchedCredential, ) -> AddV1CredentialToSlabResult266     pub fn add_v1(
267         &self,
268         discovery_credential: V1DiscoveryCredential,
269         match_data: MatchedCredential,
270     ) -> AddV1CredentialToSlabResult {
271         match self.get_mut() {
272             Ok(mut write_guard) => match write_guard.add_v1(discovery_credential, match_data) {
273                 Ok(_) => AddV1CredentialToSlabResult::Success,
274                 Err(_) => AddV1CredentialToSlabResult::InvalidPublicKeyBytes,
275             },
276             Err(_) => AddV1CredentialToSlabResult::InvalidHandle,
277         }
278     }
279 }
280 
281 /// Allocates a new credential-slab, returning a handle to the created object. The caller is given
282 /// ownership of the created handle.
create_credential_slab() -> CreateCredentialSlabResult283 pub fn create_credential_slab() -> CreateCredentialSlabResult {
284     CredentialSlab::allocate(CredentialSlabInternals::new).into()
285 }
286 
287 impl FfiEnum for CreateCredentialSlabResult {
288     type Kind = CreateCredentialSlabResultKind;
kind(&self) -> Self::Kind289     fn kind(&self) -> Self::Kind {
290         match self {
291             CreateCredentialSlabResult::NoSpaceLeft => CreateCredentialSlabResultKind::NoSpaceLeft,
292             CreateCredentialSlabResult::Success(_) => CreateCredentialSlabResultKind::Success,
293         }
294     }
295 }
296 
297 impl CreateCredentialSlabResult {
298     declare_enum_cast! {into_success, Success, CredentialSlab }
299 }
300 
301 /// Internal, Rust-side implementation of a credential-book.
302 /// See [`CredentialBook`] for the FFI-side handles.
303 pub struct CredentialBookInternals {
304     pub(crate) book: np_adv::credential::book::PrecalculatedOwnedCredentialBook<MatchedCredential>,
305 }
306 
307 impl CredentialBookInternals {
create_from_slab(credential_slab: CredentialSlabInternals) -> Self308     fn create_from_slab(credential_slab: CredentialSlabInternals) -> Self {
309         let book = np_adv::credential::book::CredentialBookBuilder::build_precalculated_owned_book::<
310             CryptoProviderImpl,
311         >(credential_slab.v0_creds, credential_slab.v1_creds);
312         Self { book }
313     }
314 }
315 
316 /// A `#[repr(C)]` handle to a value of type `CredentialBookInternals`
317 #[repr(C)]
318 #[derive(Clone, Copy, PartialEq, Eq)]
319 pub struct CredentialBook {
320     handle_id: u64,
321 }
322 
323 declare_handle_map!(
324     credential_book,
325     crate::common::default_handle_map_dimensions(),
326     super::CredentialBook,
327     super::CredentialBookInternals
328 );
329 
330 /// Discriminant for `CreateCredentialBookResult`
331 #[repr(u8)]
332 pub enum CreateCredentialBookResultKind {
333     /// We created a new credential book behind the given handle.
334     /// The associated payload may be obtained via
335     /// `CreateCredentialBookResult#into_success()`.
336     Success = 0,
337     /// There was no space left to create a new credential book
338     NoSpaceLeft = 1,
339     /// The slab that we tried to create a credential-book from
340     /// actually was an invalid handle.
341     InvalidSlabHandle = 2,
342 }
343 
344 /// Result type for `create_credential_book`
345 #[repr(u8)]
346 #[allow(missing_docs)]
347 pub enum CreateCredentialBookResult {
348     Success(CredentialBook) = 0,
349     NoSpaceLeft = 1,
350     InvalidSlabHandle = 2,
351 }
352 
353 impl LocksLongerThan<CredentialSlab> for CredentialBook {}
354 
355 /// Allocates a new credential-book, returning a handle to the created object. This takes ownership
356 /// of the `credential_slab` handle except in the case where `NoSpaceLeft` is the error returned.
357 /// In that case the caller will retain ownership of the slab handle. The caller is given ownership
358 /// of the returned credential book handle if present.
create_credential_book_from_slab( credential_slab: CredentialSlab, ) -> CreateCredentialBookResult359 pub fn create_credential_book_from_slab(
360     credential_slab: CredentialSlab,
361 ) -> CreateCredentialBookResult {
362     // The credential-book allocation is on the outside, since we should ensure
363     // that we have a slot available for construction before we try to deallocate
364     // the credential-slab which was passed in.
365     let op_result = CredentialBook::try_allocate(|| {
366         credential_slab.deallocate().map(CredentialBookInternals::create_from_slab)
367     });
368     match op_result {
369         Ok(book) => CreateCredentialBookResult::Success(book),
370         Err(HandleMapTryAllocateError::ValueProviderFailed(_)) => {
371             // Unable to deallocate the referenced credential-slab
372             CreateCredentialBookResult::InvalidSlabHandle
373         }
374         Err(HandleMapTryAllocateError::HandleMapFull) => {
375             // Unable to allocate space for a new credential-book
376             CreateCredentialBookResult::NoSpaceLeft
377         }
378     }
379 }
380 
381 impl FfiEnum for CreateCredentialBookResult {
382     type Kind = CreateCredentialBookResultKind;
kind(&self) -> Self::Kind383     fn kind(&self) -> Self::Kind {
384         match self {
385             CreateCredentialBookResult::Success(_) => CreateCredentialBookResultKind::Success,
386             CreateCredentialBookResult::NoSpaceLeft => CreateCredentialBookResultKind::NoSpaceLeft,
387             CreateCredentialBookResult::InvalidSlabHandle => {
388                 CreateCredentialBookResultKind::InvalidSlabHandle
389             }
390         }
391     }
392 }
393 
394 impl CreateCredentialBookResult {
395     declare_enum_cast! {into_success, Success, CredentialBook}
396 }
397 
398 /// Deallocates a credential-book by its handle. This takes ownership of the credential book
399 /// handle.
deallocate_credential_book(credential_book: CredentialBook) -> DeallocateResult400 pub fn deallocate_credential_book(credential_book: CredentialBook) -> DeallocateResult {
401     credential_book.deallocate().map(|_| ()).into()
402 }
403 
404 /// Deallocates a credential-slab by its handle. This takes ownership of the credential slab
405 /// handle.
deallocate_credential_slab(credential_slab: CredentialSlab) -> DeallocateResult406 pub fn deallocate_credential_slab(credential_slab: CredentialSlab) -> DeallocateResult {
407     credential_slab.deallocate().map(|_| ()).into()
408 }
409 
410 /// Cryptographic information about a particular V0 broadcast credential
411 /// necessary to LDT-encrypt V0 advertisements.
412 #[repr(C)]
413 pub struct V0BroadcastCredential {
414     key_seed: [u8; 32],
415     identity_token: [u8; 14],
416 }
417 
418 impl V0BroadcastCredential {
419     /// Constructs a new `V0BroadcastCredential` from the given
420     /// key-seed and 14-byte metadata key.
421     ///
422     /// Safety: Since this representation requires transmission
423     /// of the raw bytes of sensitive cryptographic info over FFI,
424     /// foreign-lang code around how this information is maintained
425     /// deserves close scrutiny.
new(key_seed: [u8; 32], identity_token: ldt_np_adv::V0IdentityToken) -> Self426     pub fn new(key_seed: [u8; 32], identity_token: ldt_np_adv::V0IdentityToken) -> Self {
427         Self { key_seed, identity_token: identity_token.bytes() }
428     }
into_internal(self) -> np_adv::credential::v0::V0BroadcastCredential429     pub(crate) fn into_internal(self) -> np_adv::credential::v0::V0BroadcastCredential {
430         np_adv::credential::v0::V0BroadcastCredential::new(
431             self.key_seed,
432             self.identity_token.into(),
433         )
434     }
435 }
436 
437 /// Cryptographic information about a particular V1 broadcast credential
438 /// necessary to encrypt V1 MIC-verified and signature-verified sections.
439 #[repr(C)]
440 pub struct V1BroadcastCredential {
441     key_seed: [u8; 32],
442     identity_token: [u8; 16],
443     private_key: [u8; 32],
444 }
445 
446 impl V1BroadcastCredential {
447     /// Constructs a new `V1BroadcastCredential` from the given
448     /// key-seed, 16-byte metadata key, and the raw bytes
449     /// of the ed25519 private key.
450     ///
451     /// Safety: Since this representation requires transmission
452     /// of the raw bytes of an ed25519 private key (and other
453     /// sensitive cryptographic info) over FFI, foreign-lang
454     /// code around how this information is maintained
455     /// deserves close scrutiny.
new( key_seed: [u8; 32], identity_token: extended::V1IdentityToken, private_key: [u8; 32], ) -> Self456     pub const fn new(
457         key_seed: [u8; 32],
458         identity_token: extended::V1IdentityToken,
459         private_key: [u8; 32],
460     ) -> Self {
461         Self { key_seed, identity_token: identity_token.into_bytes(), private_key }
462     }
into_internal(self) -> np_adv::credential::v1::V1BroadcastCredential463     pub(crate) fn into_internal(self) -> np_adv::credential::v1::V1BroadcastCredential {
464         let permit = crypto_provider::ed25519::RawPrivateKeyPermit::default();
465         np_adv::credential::v1::V1BroadcastCredential::new(
466             self.key_seed,
467             self.identity_token.into(),
468             crypto_provider::ed25519::PrivateKey::from_raw_private_key(self.private_key, &permit),
469         )
470     }
471 }
472