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