blob: 1981022f1bb24ef159d1412cd593d693c8641656 [file] [log] [blame]
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001// Copyright 2020, The Android Open Source Project
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(dead_code)]
16
17//! This module implements methods to load legacy keystore key blob files.
18
19use crate::{
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -080020 error::{Error as KsError, ResponseCode},
21 key_parameter::{KeyParameter, KeyParameterValue},
22 super_key::SuperKeyManager,
23 utils::uid_to_android_user,
24};
25use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
26 SecurityLevel::SecurityLevel, Tag::Tag, TagType::TagType,
27};
28use anyhow::{Context, Result};
29use keystore2_crypto::{aes_gcm_decrypt, derive_key_from_password, ZVec};
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -080030use std::{convert::TryInto, fs::File, path::Path, path::PathBuf};
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +000031use std::{
32 fs,
33 io::{ErrorKind, Read, Result as IoResult},
34};
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -080035
36const SUPPORTED_LEGACY_BLOB_VERSION: u8 = 3;
37
38mod flags {
39 /// This flag is deprecated. It is here to support keys that have been written with this flag
40 /// set, but we don't create any new keys with this flag.
41 pub const ENCRYPTED: u8 = 1 << 0;
42 /// This flag is deprecated. It indicates that the blob was generated and thus owned by a
43 /// software fallback Keymaster implementation. Keymaster 1.0 was the last Keymaster version
44 /// that could be accompanied by a software fallback. With the removal of Keymaster 1.0
45 /// support, this flag is obsolete.
46 pub const FALLBACK: u8 = 1 << 1;
47 /// KEYSTORE_FLAG_SUPER_ENCRYPTED is for blobs that are already encrypted by KM but have
48 /// an additional layer of password-based encryption applied. The same encryption scheme is used
49 /// as KEYSTORE_FLAG_ENCRYPTED. The latter is deprecated.
50 pub const SUPER_ENCRYPTED: u8 = 1 << 2;
51 /// KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION is for blobs that are part of device encryption
52 /// flow so it receives special treatment from keystore. For example this blob will not be super
53 /// encrypted, and it will be stored separately under a unique UID instead. This flag should
54 /// only be available to system uid.
55 pub const CRITICAL_TO_DEVICE_ENCRYPTION: u8 = 1 << 3;
56 /// The blob is associated with the security level Strongbox as opposed to TEE.
57 pub const STRONGBOX: u8 = 1 << 4;
58}
59
60/// Lagacy key blob types.
61mod blob_types {
62 /// A generic blob used for non sensitive unstructured blobs.
63 pub const GENERIC: u8 = 1;
64 /// This key is a super encryption key encrypted with AES128
65 /// and a password derived key.
66 pub const SUPER_KEY: u8 = 2;
67 // Used to be the KEY_PAIR type.
68 const _RESERVED: u8 = 3;
69 /// A KM key blob.
70 pub const KM_BLOB: u8 = 4;
71 /// A legacy key characteristics file. This has only a single list of Authorizations.
72 pub const KEY_CHARACTERISTICS: u8 = 5;
73 /// A key characteristics cache has both a hardware enforced and a software enforced list
74 /// of authorizations.
75 pub const KEY_CHARACTERISTICS_CACHE: u8 = 6;
76 /// Like SUPER_KEY but encrypted with AES256.
77 pub const SUPER_KEY_AES256: u8 = 7;
78}
79
80/// Error codes specific to the legacy blob module.
81#[derive(thiserror::Error, Debug, Eq, PartialEq)]
82pub enum Error {
83 /// Returned by the legacy blob module functions if an input stream
84 /// did not have enough bytes to read.
85 #[error("Input stream had insufficient bytes to read.")]
86 BadLen,
87 /// This error code is returned by `Blob::decode_alias` if it encounters
88 /// an invalid alias filename encoding.
89 #[error("Invalid alias filename encoding.")]
90 BadEncoding,
91}
92
93/// The blob payload, optionally with all information required to decrypt it.
94#[derive(Debug, Eq, PartialEq)]
95pub enum BlobValue {
96 /// A generic blob used for non sensitive unstructured blobs.
97 Generic(Vec<u8>),
98 /// A legacy key characteristics file. This has only a single list of Authorizations.
99 Characteristics(Vec<u8>),
100 /// A key characteristics cache has both a hardware enforced and a software enforced list
101 /// of authorizations.
102 CharacteristicsCache(Vec<u8>),
103 /// A password encrypted blob. Includes the initialization vector, the aead tag, the
104 /// ciphertext data, a salt, and a key size. The latter two are used for key derivation.
105 PwEncrypted {
106 /// Initialization vector.
107 iv: Vec<u8>,
108 /// Aead tag for integrity verification.
109 tag: Vec<u8>,
110 /// Ciphertext.
111 data: Vec<u8>,
112 /// Salt for key derivation.
113 salt: Vec<u8>,
114 /// Key sise for key derivation. This selects between AES128 GCM and AES256 GCM.
115 key_size: usize,
116 },
117 /// An encrypted blob. Includes the initialization vector, the aead tag, and the
118 /// ciphertext data. The key can be selected from context, i.e., the owner of the key
119 /// blob.
120 Encrypted {
121 /// Initialization vector.
122 iv: Vec<u8>,
123 /// Aead tag for integrity verification.
124 tag: Vec<u8>,
125 /// Ciphertext.
126 data: Vec<u8>,
127 },
128 /// Holds the plaintext key blob either after unwrapping an encrypted blob or when the
129 /// blob was stored in "plaintext" on disk. The "plaintext" of a key blob is not actual
130 /// plaintext because all KeyMint blobs are encrypted with a device bound key. The key
131 /// blob in this Variant is decrypted only with respect to any extra layer of encryption
132 /// that Keystore added.
133 Decrypted(ZVec),
134}
135
136/// Represents a loaded legacy key blob file.
137#[derive(Debug, Eq, PartialEq)]
138pub struct Blob {
139 flags: u8,
140 value: BlobValue,
141}
142
143/// This object represents a path that holds a legacy Keystore blob database.
144pub struct LegacyBlobLoader {
145 path: PathBuf,
146}
147
148fn read_bool(stream: &mut dyn Read) -> Result<bool> {
149 const SIZE: usize = std::mem::size_of::<bool>();
150 let mut buffer: [u8; SIZE] = [0; SIZE];
151 stream.read_exact(&mut buffer).map(|_| buffer[0] != 0).context("In read_ne_bool.")
152}
153
154fn read_ne_u32(stream: &mut dyn Read) -> Result<u32> {
155 const SIZE: usize = std::mem::size_of::<u32>();
156 let mut buffer: [u8; SIZE] = [0; SIZE];
157 stream.read_exact(&mut buffer).map(|_| u32::from_ne_bytes(buffer)).context("In read_ne_u32.")
158}
159
160fn read_ne_i32(stream: &mut dyn Read) -> Result<i32> {
161 const SIZE: usize = std::mem::size_of::<i32>();
162 let mut buffer: [u8; SIZE] = [0; SIZE];
163 stream.read_exact(&mut buffer).map(|_| i32::from_ne_bytes(buffer)).context("In read_ne_i32.")
164}
165
166fn read_ne_i64(stream: &mut dyn Read) -> Result<i64> {
167 const SIZE: usize = std::mem::size_of::<i64>();
168 let mut buffer: [u8; SIZE] = [0; SIZE];
169 stream.read_exact(&mut buffer).map(|_| i64::from_ne_bytes(buffer)).context("In read_ne_i64.")
170}
171
172impl Blob {
173 /// This blob was generated with a fallback software KM device.
174 pub fn is_fallback(&self) -> bool {
175 self.flags & flags::FALLBACK != 0
176 }
177
178 /// This blob is encrypted and needs to be decrypted with the user specific master key
179 /// before use.
180 pub fn is_encrypted(&self) -> bool {
181 self.flags & (flags::SUPER_ENCRYPTED | flags::ENCRYPTED) != 0
182 }
183
184 /// This blob is critical to device encryption. It cannot be encrypted with the super key
185 /// because it is itself part of the key derivation process for the key encrypting the
186 /// super key.
187 pub fn is_critical_to_device_encryption(&self) -> bool {
188 self.flags & flags::CRITICAL_TO_DEVICE_ENCRYPTION != 0
189 }
190
191 /// This blob is associated with the Strongbox security level.
192 pub fn is_strongbox(&self) -> bool {
193 self.flags & flags::STRONGBOX != 0
194 }
195
196 /// Returns the payload data of this blob file.
197 pub fn value(&self) -> &BlobValue {
198 &self.value
199 }
200
201 /// Consume this blob structure and extract the payload.
202 pub fn take_value(self) -> BlobValue {
203 self.value
204 }
205}
206
207impl LegacyBlobLoader {
208 const IV_SIZE: usize = keystore2_crypto::IV_LENGTH;
209 const GCM_TAG_LENGTH: usize = keystore2_crypto::TAG_LENGTH;
210 const SALT_SIZE: usize = keystore2_crypto::SALT_LENGTH;
211
212 // The common header has the following structure:
213 // version (1 Byte)
214 // blob_type (1 Byte)
215 // flags (1 Byte)
216 // info (1 Byte)
217 // initialization_vector (16 Bytes)
218 // integrity (MD5 digest or gcb tag) (16 Bytes)
219 // length (4 Bytes)
220 const COMMON_HEADER_SIZE: usize = 4 + Self::IV_SIZE + Self::GCM_TAG_LENGTH + 4;
221
222 const VERSION_OFFSET: usize = 0;
223 const TYPE_OFFSET: usize = 1;
224 const FLAGS_OFFSET: usize = 2;
225 const SALT_SIZE_OFFSET: usize = 3;
226 const LENGTH_OFFSET: usize = 4 + Self::IV_SIZE + Self::GCM_TAG_LENGTH;
227 const IV_OFFSET: usize = 4;
228 const AEAD_TAG_OFFSET: usize = Self::IV_OFFSET + Self::IV_SIZE;
229 const DIGEST_OFFSET: usize = Self::IV_OFFSET + Self::IV_SIZE;
230
231 /// Construct a new LegacyBlobLoader with a root path of `path` relative to which it will
232 /// expect legacy key blob files.
233 pub fn new(path: &Path) -> Self {
234 Self { path: path.to_owned() }
235 }
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000236
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800237 /// Encodes an alias string as ascii character sequence in the range
238 /// ['+' .. '.'] and ['0' .. '~'].
239 /// Bytes with values in the range ['0' .. '~'] are represented as they are.
240 /// All other bytes are split into two characters as follows:
241 ///
242 /// msb a a | b b b b b b
243 ///
244 /// The most significant bits (a) are encoded:
245 /// a a character
246 /// 0 0 '+'
247 /// 0 1 ','
248 /// 1 0 '-'
249 /// 1 1 '.'
250 ///
251 /// The 6 lower bits are represented with the range ['0' .. 'o']:
252 /// b(hex) character
253 /// 0x00 '0'
254 /// ...
255 /// 0x3F 'o'
256 ///
257 /// The function cannot fail because we have a representation for each
258 /// of the 256 possible values of each byte.
259 pub fn encode_alias(name: &str) -> String {
260 let mut acc = String::new();
261 for c in name.bytes() {
262 match c {
263 b'0'..=b'~' => {
264 acc.push(c as char);
265 }
266 c => {
267 acc.push((b'+' + (c as u8 >> 6)) as char);
268 acc.push((b'0' + (c & 0x3F)) as char);
269 }
270 };
271 }
272 acc
273 }
274
275 /// This function reverses the encoding described in `encode_alias`.
276 /// This function can fail, because not all possible character
277 /// sequences are valid code points. And even if the encoding is valid,
278 /// the result may not be a valid UTF-8 sequence.
279 pub fn decode_alias(name: &str) -> Result<String> {
280 let mut multi: Option<u8> = None;
281 let mut s = Vec::<u8>::new();
282 for c in name.bytes() {
283 multi = match (c, multi) {
284 // m is set, we are processing the second part of a multi byte sequence
285 (b'0'..=b'o', Some(m)) => {
286 s.push(m | (c - b'0'));
287 None
288 }
289 (b'+'..=b'.', None) => Some((c - b'+') << 6),
290 (b'0'..=b'~', None) => {
291 s.push(c);
292 None
293 }
294 _ => {
295 return Err(Error::BadEncoding)
296 .context("In decode_alias: could not decode filename.")
297 }
298 };
299 }
300 if multi.is_some() {
301 return Err(Error::BadEncoding).context("In decode_alias: could not decode filename.");
302 }
303
304 String::from_utf8(s).context("In decode_alias: encoded alias was not valid UTF-8.")
305 }
306
307 fn new_from_stream(stream: &mut dyn Read) -> Result<Blob> {
308 let mut buffer = Vec::new();
309 stream.read_to_end(&mut buffer).context("In new_from_stream.")?;
310
311 if buffer.len() < Self::COMMON_HEADER_SIZE {
312 return Err(Error::BadLen).context("In new_from_stream.")?;
313 }
314
315 let version: u8 = buffer[Self::VERSION_OFFSET];
316
317 let flags: u8 = buffer[Self::FLAGS_OFFSET];
318 let blob_type: u8 = buffer[Self::TYPE_OFFSET];
319 let is_encrypted = flags & (flags::ENCRYPTED | flags::SUPER_ENCRYPTED) != 0;
320 let salt = match buffer[Self::SALT_SIZE_OFFSET] as usize {
321 Self::SALT_SIZE => Some(&buffer[buffer.len() - Self::SALT_SIZE..buffer.len()]),
322 _ => None,
323 };
324
325 if version != SUPPORTED_LEGACY_BLOB_VERSION {
326 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
327 .context(format!("In new_from_stream: Unknown blob version: {}.", version));
328 }
329
330 let length = u32::from_be_bytes(
331 buffer[Self::LENGTH_OFFSET..Self::LENGTH_OFFSET + 4].try_into().unwrap(),
332 ) as usize;
333 if buffer.len() < Self::COMMON_HEADER_SIZE + length {
334 return Err(Error::BadLen).context(format!(
335 "In new_from_stream. Expected: {} got: {}.",
336 Self::COMMON_HEADER_SIZE + length,
337 buffer.len()
338 ));
339 }
340 let value = &buffer[Self::COMMON_HEADER_SIZE..Self::COMMON_HEADER_SIZE + length];
341 let iv = &buffer[Self::IV_OFFSET..Self::IV_OFFSET + Self::IV_SIZE];
342 let tag = &buffer[Self::AEAD_TAG_OFFSET..Self::AEAD_TAG_OFFSET + Self::GCM_TAG_LENGTH];
343
344 match (blob_type, is_encrypted, salt) {
345 (blob_types::GENERIC, _, _) => {
346 Ok(Blob { flags, value: BlobValue::Generic(value.to_vec()) })
347 }
348 (blob_types::KEY_CHARACTERISTICS, _, _) => {
349 Ok(Blob { flags, value: BlobValue::Characteristics(value.to_vec()) })
350 }
351 (blob_types::KEY_CHARACTERISTICS_CACHE, _, _) => {
352 Ok(Blob { flags, value: BlobValue::CharacteristicsCache(value.to_vec()) })
353 }
354 (blob_types::SUPER_KEY, _, Some(salt)) => Ok(Blob {
355 flags,
356 value: BlobValue::PwEncrypted {
357 iv: iv.to_vec(),
358 tag: tag.to_vec(),
359 data: value.to_vec(),
360 key_size: keystore2_crypto::AES_128_KEY_LENGTH,
361 salt: salt.to_vec(),
362 },
363 }),
364 (blob_types::SUPER_KEY_AES256, _, Some(salt)) => Ok(Blob {
365 flags,
366 value: BlobValue::PwEncrypted {
367 iv: iv.to_vec(),
368 tag: tag.to_vec(),
369 data: value.to_vec(),
370 key_size: keystore2_crypto::AES_256_KEY_LENGTH,
371 salt: salt.to_vec(),
372 },
373 }),
374 (blob_types::KM_BLOB, true, _) => Ok(Blob {
375 flags,
376 value: BlobValue::Encrypted {
377 iv: iv.to_vec(),
378 tag: tag.to_vec(),
379 data: value.to_vec(),
380 },
381 }),
382 (blob_types::KM_BLOB, false, _) => Ok(Blob {
383 flags,
384 value: BlobValue::Decrypted(value.try_into().context("In new_from_stream.")?),
385 }),
386 (blob_types::SUPER_KEY, _, None) | (blob_types::SUPER_KEY_AES256, _, None) => {
387 Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
388 .context("In new_from_stream: Super key without salt for key derivation.")
389 }
390 _ => Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
391 "In new_from_stream: Unknown blob type. {} {}",
392 blob_type, is_encrypted
393 )),
394 }
395 }
396
397 /// Parses a legacy key blob file read from `stream`. A `decrypt` closure
398 /// must be supplied, that is primed with the appropriate key.
399 /// The callback takes the following arguments:
400 /// * ciphertext: &[u8] - The to-be-deciphered message.
401 /// * iv: &[u8] - The initialization vector.
402 /// * tag: Option<&[u8]> - AEAD tag if AES GCM is selected.
403 /// * salt: Option<&[u8]> - An optional salt. Used for password key derivation.
404 /// * key_size: Option<usize> - An optional key size. Used for pw key derivation.
405 ///
406 /// If no super key is available, the callback must return
407 /// `Err(KsError::Rc(ResponseCode::LOCKED))`. The callback is only called
408 /// if the to-be-read blob is encrypted.
409 pub fn new_from_stream_decrypt_with<F>(mut stream: impl Read, decrypt: F) -> Result<Blob>
410 where
411 F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>,
412 {
413 let blob =
414 Self::new_from_stream(&mut stream).context("In new_from_stream_decrypt_with.")?;
415
416 match blob.value() {
417 BlobValue::Encrypted { iv, tag, data } => Ok(Blob {
418 flags: blob.flags,
419 value: BlobValue::Decrypted(
420 decrypt(&data, &iv, &tag, None, None)
421 .context("In new_from_stream_decrypt_with.")?,
422 ),
423 }),
424 BlobValue::PwEncrypted { iv, tag, data, salt, key_size } => Ok(Blob {
425 flags: blob.flags,
426 value: BlobValue::Decrypted(
427 decrypt(&data, &iv, &tag, Some(salt), Some(*key_size))
428 .context("In new_from_stream_decrypt_with.")?,
429 ),
430 }),
431 _ => Ok(blob),
432 }
433 }
434
435 fn tag_type(tag: Tag) -> TagType {
436 TagType((tag.0 as u32 & 0xFF000000u32) as i32)
437 }
438
439 /// Read legacy key parameter file content.
440 /// Depending on the file type a key characteristics file stores one (TYPE_KEY_CHARACTERISTICS)
441 /// or two (TYPE_KEY_CHARACTERISTICS_CACHE) key parameter lists. The format of the list is as
442 /// follows:
443 ///
444 /// +------------------------------+
445 /// | 32 bit indirect_size |
446 /// +------------------------------+
447 /// | indirect_size bytes of data | This is where the blob data is stored
448 /// +------------------------------+
449 /// | 32 bit element_count | Number of key parameter entries.
450 /// | 32 bit elements_size | Total bytes used by entries.
451 /// +------------------------------+
452 /// | elements_size bytes of data | This is where the elements are stored.
453 /// +------------------------------+
454 ///
455 /// Elements have a 32 bit header holding the tag with a tag type encoded in the
456 /// four most significant bits (see android/hardware/secruity/keymint/TagType.aidl).
457 /// The header is immediately followed by the payload. The payload size depends on
458 /// the encoded tag type in the header:
459 /// BOOLEAN : 1 byte
460 /// ENUM, ENUM_REP, UINT, UINT_REP : 4 bytes
461 /// ULONG, ULONG_REP, DATETIME : 8 bytes
462 /// BLOB, BIGNUM : 8 bytes see below.
463 ///
464 /// Bignum and blob payload format:
465 /// +------------------------+
466 /// | 32 bit blob_length | Length of the indirect payload in bytes.
467 /// | 32 bit indirect_offset | Offset from the beginning of the indirect section.
468 /// +------------------------+
469 pub fn read_key_parameters(stream: &mut &[u8]) -> Result<Vec<KeyParameterValue>> {
470 let indirect_size =
471 read_ne_u32(stream).context("In read_key_parameters: While reading indirect size.")?;
472
473 let indirect_buffer = stream
474 .get(0..indirect_size as usize)
475 .ok_or(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
476 .context("In read_key_parameters: While reading indirect buffer.")?;
477
478 // update the stream position.
479 *stream = &stream[indirect_size as usize..];
480
481 let element_count =
482 read_ne_u32(stream).context("In read_key_parameters: While reading element count.")?;
483 let element_size =
484 read_ne_u32(stream).context("In read_key_parameters: While reading element size.")?;
485
486 let elements_buffer = stream
487 .get(0..element_size as usize)
488 .ok_or(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
489 .context("In read_key_parameters: While reading elements buffer.")?;
490
491 // update the stream position.
492 *stream = &stream[element_size as usize..];
493
494 let mut element_stream = &elements_buffer[..];
495
496 let mut params: Vec<KeyParameterValue> = Vec::new();
497 for _ in 0..element_count {
498 let tag = Tag(read_ne_i32(&mut element_stream).context("In read_key_parameters.")?);
499 let param = match Self::tag_type(tag) {
500 TagType::ENUM | TagType::ENUM_REP | TagType::UINT | TagType::UINT_REP => {
501 KeyParameterValue::new_from_tag_primitive_pair(
502 tag,
503 read_ne_i32(&mut element_stream).context("While reading integer.")?,
504 )
505 .context("Trying to construct integer/enum KeyParameterValue.")
506 }
507 TagType::ULONG | TagType::ULONG_REP | TagType::DATE => {
508 KeyParameterValue::new_from_tag_primitive_pair(
509 tag,
510 read_ne_i64(&mut element_stream).context("While reading long integer.")?,
511 )
512 .context("Trying to construct long KeyParameterValue.")
513 }
514 TagType::BOOL => {
515 if read_bool(&mut element_stream).context("While reading long integer.")? {
516 KeyParameterValue::new_from_tag_primitive_pair(tag, 1)
517 .context("Trying to construct boolean KeyParameterValue.")
518 } else {
519 Err(anyhow::anyhow!("Invalid."))
520 }
521 }
522 TagType::BYTES | TagType::BIGNUM => {
523 let blob_size = read_ne_u32(&mut element_stream)
524 .context("While reading blob size.")?
525 as usize;
526 let indirect_offset = read_ne_u32(&mut element_stream)
527 .context("While reading indirect offset.")?
528 as usize;
529 KeyParameterValue::new_from_tag_primitive_pair(
530 tag,
531 indirect_buffer
532 .get(indirect_offset..indirect_offset + blob_size)
533 .context("While reading blob value.")?
534 .to_vec(),
535 )
536 .context("Trying to construct blob KeyParameterValue.")
537 }
538 TagType::INVALID => Err(anyhow::anyhow!("Invalid.")),
539 _ => {
540 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
541 .context("In read_key_parameters: Encountered bogus tag type.");
542 }
543 };
544 if let Ok(p) = param {
545 params.push(p);
546 }
547 }
548
549 Ok(params)
550 }
551
552 fn read_characteristics_file(
553 &self,
554 uid: u32,
555 prefix: &str,
556 alias: &str,
557 hw_sec_level: SecurityLevel,
558 ) -> Result<Vec<KeyParameter>> {
559 let blob = Self::read_generic_blob(&self.make_chr_filename(uid, alias, prefix))
560 .context("In read_characteristics_file")?;
561
562 let blob = match blob {
563 None => return Ok(Vec::new()),
564 Some(blob) => blob,
565 };
566
567 let mut stream = match blob.value() {
568 BlobValue::Characteristics(data) => &data[..],
569 BlobValue::CharacteristicsCache(data) => &data[..],
570 _ => {
571 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(concat!(
572 "In read_characteristics_file: ",
573 "Characteristics file does not hold key characteristics."
574 ))
575 }
576 };
577
578 let hw_list = match blob.value() {
579 // The characteristics cache file has two lists and the first is
580 // the hardware enforced list.
581 BlobValue::CharacteristicsCache(_) => Some(
582 Self::read_key_parameters(&mut stream)
583 .context("In read_characteristics_file.")?
584 .into_iter()
585 .map(|value| KeyParameter::new(value, hw_sec_level)),
586 ),
587 _ => None,
588 };
589
590 let sw_list = Self::read_key_parameters(&mut stream)
591 .context("In read_characteristics_file.")?
592 .into_iter()
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000593 .map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE));
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800594
595 Ok(hw_list.into_iter().flatten().chain(sw_list).collect())
596 }
597
598 // This is a list of known prefixes that the Keystore 1.0 SPI used to use.
599 // * USRPKEY was used for private and secret key material, i.e., KM blobs.
600 // * USRSKEY was used for secret key material, i.e., KM blobs, before Android P.
601 // * CACERT was used for key chains or free standing public certificates.
602 // * USRCERT was used for public certificates of USRPKEY entries. But KeyChain also
603 // used this for user installed certificates without private key material.
604
605 fn read_km_blob_file(&self, uid: u32, alias: &str) -> Result<Option<(Blob, String)>> {
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000606 let mut iter = ["USRPKEY", "USRSKEY"].iter();
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800607
608 let (blob, prefix) = loop {
609 if let Some(prefix) = iter.next() {
610 if let Some(blob) =
611 Self::read_generic_blob(&self.make_blob_filename(uid, alias, prefix))
612 .context("In read_km_blob_file.")?
613 {
614 break (blob, prefix);
615 }
616 } else {
617 return Ok(None);
618 }
619 };
620
621 Ok(Some((blob, prefix.to_string())))
622 }
623
624 fn read_generic_blob(path: &Path) -> Result<Option<Blob>> {
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000625 let mut file = match Self::with_retry_interrupted(|| File::open(path)) {
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800626 Ok(file) => file,
627 Err(e) => match e.kind() {
628 ErrorKind::NotFound => return Ok(None),
629 _ => return Err(e).context("In read_generic_blob."),
630 },
631 };
632
633 Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?))
634 }
635
636 /// This function constructs the blob file name which has the form:
637 /// user_<android user id>/<uid>_<alias>.
638 fn make_blob_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800639 let user_id = uid_to_android_user(uid);
640 let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000641 let mut path = self.make_user_path_name(user_id);
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800642 path.push(format!("{}_{}", uid, encoded_alias));
643 path
644 }
645
646 /// This function constructs the characteristics file name which has the form:
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000647 /// user_<android user id>/.<uid>_chr_<prefix>_<alias>.
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800648 fn make_chr_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800649 let user_id = uid_to_android_user(uid);
650 let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000651 let mut path = self.make_user_path_name(user_id);
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800652 path.push(format!(".{}_chr_{}", uid, encoded_alias));
653 path
654 }
655
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000656 fn make_super_key_filename(&self, user_id: u32) -> PathBuf {
657 let mut path = self.make_user_path_name(user_id);
658 path.push(".masterkey");
659 path
660 }
661
662 fn make_user_path_name(&self, user_id: u32) -> PathBuf {
663 let mut path = self.path.clone();
664 path.push(&format!("user_{}", user_id));
665 path
666 }
667
668 /// Returns if the legacy blob database is empty, i.e., there are no entries matching "user_*"
669 /// in the database dir.
670 pub fn is_empty(&self) -> Result<bool> {
671 let dir = Self::with_retry_interrupted(|| fs::read_dir(self.path.as_path()))
672 .context("In is_empty: Failed to open legacy blob database.")?;
673 for entry in dir {
674 if (*entry.context("In is_empty: Trying to access dir entry")?.file_name())
675 .to_str()
676 .map_or(false, |f| f.starts_with("user_"))
677 {
678 return Ok(false);
679 }
680 }
681 Ok(true)
682 }
683
684 /// Returns if the legacy blob database is empty for a given user, i.e., there are no entries
685 /// matching "user_*" in the database dir.
686 pub fn is_empty_user(&self, user_id: u32) -> Result<bool> {
687 let mut user_path = self.path.clone();
688 user_path.push(format!("user_{}", user_id));
689 if !user_path.as_path().is_dir() {
690 return Ok(true);
691 }
692 Ok(Self::with_retry_interrupted(|| user_path.read_dir())
693 .context("In is_empty_user: Failed to open legacy user dir.")?
694 .next()
695 .is_none())
696 }
697
698 fn extract_alias(encoded_alias: &str) -> Option<String> {
699 // We can check the encoded alias because the prefixes we are interested
700 // in are all in the printable range that don't get mangled.
701 for prefix in &["USRPKEY_", "USRSKEY_", "USRCERT_", "CACERT_"] {
702 if let Some(alias) = encoded_alias.strip_prefix(prefix) {
703 return Self::decode_alias(&alias).ok();
704 }
705 }
706 None
707 }
708
709 /// List all entries for a given user. The strings are unchanged file names, i.e.,
710 /// encoded with UID prefix.
711 fn list_user(&self, user_id: u32) -> Result<Vec<String>> {
712 let path = self.make_user_path_name(user_id);
713 let dir =
714 Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
715 format!("In list_user: Failed to open legacy blob database. {:?}", path)
716 })?;
717 let mut result: Vec<String> = Vec::new();
718 for entry in dir {
719 let file_name = entry.context("In list_user: Trying to access dir entry")?.file_name();
720 if let Some(f) = file_name.to_str() {
721 result.push(f.to_string())
722 }
723 }
724 Ok(result)
725 }
726
727 /// List all keystore entries belonging to the given uid.
728 pub fn list_keystore_entries_for_uid(&self, uid: u32) -> Result<Vec<String>> {
729 let user_id = uid_to_android_user(uid);
730
731 let user_entries = self
732 .list_user(user_id)
733 .context("In list_keystore_entries_for_uid: Trying to list user.")?;
734
735 let uid_str = format!("{}_", uid);
736
737 let mut result: Vec<String> = user_entries
738 .into_iter()
739 .filter_map(|v| {
740 if !v.starts_with(&uid_str) {
741 return None;
742 }
743 let encoded_alias = &v[uid_str.len()..];
744 Self::extract_alias(encoded_alias)
745 })
746 .collect();
747
748 result.sort_unstable();
749 result.dedup();
750 Ok(result)
751 }
752
753 fn with_retry_interrupted<F, T>(f: F) -> IoResult<T>
754 where
755 F: Fn() -> IoResult<T>,
756 {
757 loop {
758 match f() {
759 Ok(v) => return Ok(v),
760 Err(e) => match e.kind() {
761 ErrorKind::Interrupted => continue,
762 _ => return Err(e),
763 },
764 }
765 }
766 }
767
768 /// Deletes a keystore entry. Also removes the user_<uid> directory on the
769 /// last migration.
770 pub fn remove_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> {
771 let mut something_was_deleted = false;
772 let prefixes = ["USRPKEY", "USRSKEY"];
773 for prefix in &prefixes {
774 let path = self.make_blob_filename(uid, alias, prefix);
775 if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
776 match e.kind() {
777 // Only a subset of keys are expected.
778 ErrorKind::NotFound => continue,
779 // Log error but ignore.
780 _ => log::error!("Error while deleting key blob entries. {:?}", e),
781 }
782 }
783 let path = self.make_chr_filename(uid, alias, prefix);
784 if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
785 match e.kind() {
786 ErrorKind::NotFound => {
787 log::info!("No characteristics file found for legacy key blob.")
788 }
789 // Log error but ignore.
790 _ => log::error!("Error while deleting key blob entries. {:?}", e),
791 }
792 }
793 something_was_deleted = true;
794 // Only one of USRPKEY and USRSKEY can be present. So we can end the loop
795 // if we reach this point.
796 break;
797 }
798
799 let prefixes = ["USRCERT", "CACERT"];
800 for prefix in &prefixes {
801 let path = self.make_blob_filename(uid, alias, prefix);
802 if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
803 match e.kind() {
804 // USRCERT and CACERT are optional either or both may or may not be present.
805 ErrorKind::NotFound => continue,
806 // Log error but ignore.
807 _ => log::error!("Error while deleting key blob entries. {:?}", e),
808 }
809 something_was_deleted = true;
810 }
811 }
812
813 if something_was_deleted {
814 let user_id = uid_to_android_user(uid);
815 if self
816 .is_empty_user(user_id)
817 .context("In remove_keystore_entry: Trying to check for empty user dir.")?
818 {
819 let user_path = self.make_user_path_name(user_id);
820 Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok();
821 }
822 }
823
824 Ok(something_was_deleted)
825 }
826
827 /// Load a legacy key blob entry by uid and alias.
828 pub fn load_by_uid_alias(
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800829 &self,
830 uid: u32,
831 alias: &str,
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000832 key_manager: Option<&SuperKeyManager>,
833 ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800834 let km_blob = self.read_km_blob_file(uid, alias).context("In load_by_uid_alias.")?;
835
836 let km_blob = match km_blob {
837 Some((km_blob, prefix)) => {
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000838 let km_blob = match km_blob {
839 Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
840 // Unwrap the key blob if required and if we have key_manager.
841 Blob { flags, value: BlobValue::Encrypted { ref iv, ref tag, ref data } } => {
842 if let Some(key_manager) = key_manager {
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800843 let decrypted = match key_manager
844 .get_per_boot_key_by_user_id(uid_to_android_user(uid))
845 {
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000846 Some(key) => aes_gcm_decrypt(data, iv, tag, &(key.get_key()))
Hasini Gunasinghe0e161452021-01-27 19:34:37 +0000847 .context(
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800848 "In load_by_uid_alias: while trying to decrypt legacy blob.",
849 )?,
850 None => {
851 return Err(KsError::Rc(ResponseCode::LOCKED)).context(format!(
852 concat!(
853 "In load_by_uid_alias: ",
854 "User {} has not unlocked the keystore yet.",
855 ),
856 uid_to_android_user(uid)
857 ))
858 }
859 };
860 Blob { flags, value: BlobValue::Decrypted(decrypted) }
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000861 } else {
862 km_blob
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800863 }
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000864 }
865 _ => {
866 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800867 "In load_by_uid_alias: Found wrong blob type in legacy key blob file.",
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000868 )
869 }
870 };
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800871
872 let hw_sec_level = match km_blob.is_strongbox() {
873 true => SecurityLevel::STRONGBOX,
874 false => SecurityLevel::TRUSTED_ENVIRONMENT,
875 };
876 let key_parameters = self
877 .read_characteristics_file(uid, &prefix, alias, hw_sec_level)
878 .context("In load_by_uid_alias.")?;
879 Some((km_blob, key_parameters))
880 }
881 None => None,
882 };
883
884 let user_cert =
885 match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT"))
886 .context("In load_by_uid_alias: While loading user cert.")?
887 {
888 Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data),
889 None => None,
890 _ => {
891 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
892 "In load_by_uid_alias: Found unexpected blob type in USRCERT file",
893 )
894 }
895 };
896
897 let ca_cert = match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT"))
898 .context("In load_by_uid_alias: While loading ca cert.")?
899 {
900 Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data),
901 None => None,
902 _ => {
903 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
904 .context("In load_by_uid_alias: Found unexpected blob type in CACERT file")
905 }
906 };
907
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000908 Ok((km_blob, user_cert, ca_cert))
909 }
910
911 /// Returns true if the given user has a super key.
912 pub fn has_super_key(&self, user_id: u32) -> bool {
913 self.make_super_key_filename(user_id).is_file()
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800914 }
915
916 /// Load and decrypt legacy super key blob.
917 pub fn load_super_key(&self, user_id: u32, pw: &[u8]) -> Result<Option<ZVec>> {
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000918 let path = self.make_super_key_filename(user_id);
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800919 let blob = Self::read_generic_blob(&path)
920 .context("In load_super_key: While loading super key.")?;
921
922 let blob = match blob {
923 Some(blob) => match blob {
924 Blob {
925 value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size }, ..
926 } => {
927 let key = derive_key_from_password(pw, Some(&salt), key_size)
928 .context("In load_super_key: Failed to derive key from password.")?;
929 let blob = aes_gcm_decrypt(&data, &iv, &tag, &key).context(
930 "In load_super_key: while trying to decrypt legacy super key blob.",
931 )?;
932 Some(blob)
933 }
934 _ => {
935 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
936 "In load_super_key: Found wrong blob type in legacy super key blob file.",
937 )
938 }
939 },
940 None => None,
941 };
942
943 Ok(blob)
944 }
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +0000945
946 /// Removes the super key for the given user from the legacy database.
947 /// If this was the last entry in the user's database, this function removes
948 /// the user_<uid> directory as well.
949 pub fn remove_super_key(&self, user_id: u32) {
950 let path = self.make_super_key_filename(user_id);
951 Self::with_retry_interrupted(|| fs::remove_file(path.as_path())).ok();
952 if self.is_empty_user(user_id).ok().unwrap_or(false) {
953 let path = self.make_user_path_name(user_id);
954 Self::with_retry_interrupted(|| fs::remove_dir(path.as_path())).ok();
955 }
956 }
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800957}
958
959#[cfg(test)]
960mod test {
961 use super::*;
962 use anyhow::anyhow;
963 use keystore2_crypto::aes_gcm_decrypt;
964 use rand::Rng;
965 use std::string::FromUtf8Error;
966 mod legacy_blob_test_vectors;
967 use crate::error;
968 use crate::legacy_blob::test::legacy_blob_test_vectors::*;
Janis Danisevskis2a8330a2021-01-20 15:34:26 -0800969 use keystore2_test_utils::TempDir;
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -0800970
971 #[test]
972 fn decode_encode_alias_test() {
973 static ALIAS: &str = "#({}test[])😗";
974 static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G";
975 // Second multi byte out of range ------v
976 static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y";
977 // Incomplete multi byte ------------------------v
978 static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+";
979 // Our encoding: ".`-O-H-G"
980 // is UTF-8: 0xF0 0x9F 0x98 0x97
981 // is UNICODE: U+1F617
982 // is 😗
983 // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8.
984 static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G";
985
986 assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS));
987 assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap());
988 assert_eq!(
989 Some(&Error::BadEncoding),
990 LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1)
991 .unwrap_err()
992 .root_cause()
993 .downcast_ref::<Error>()
994 );
995 assert_eq!(
996 Some(&Error::BadEncoding),
997 LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2)
998 .unwrap_err()
999 .root_cause()
1000 .downcast_ref::<Error>()
1001 );
1002 assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8)
1003 .unwrap_err()
1004 .root_cause()
1005 .downcast_ref::<FromUtf8Error>()
1006 .is_some());
1007
1008 for _i in 0..100 {
1009 // Any valid UTF-8 string should be en- and decoded without loss.
1010 let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>();
1011 let random_alias = alias_str.as_bytes();
1012 let encoded = LegacyBlobLoader::encode_alias(&alias_str);
1013 let decoded = match LegacyBlobLoader::decode_alias(&encoded) {
1014 Ok(d) => d,
1015 Err(_) => panic!(format!("random_alias: {:x?}\nencoded {}", random_alias, encoded)),
1016 };
1017 assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>());
1018 }
1019 }
1020
1021 #[test]
1022 fn read_golden_key_blob_test() -> anyhow::Result<()> {
1023 let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| {
1024 Err(anyhow!("should not be called"))
1025 })?;
1026 assert!(!blob.is_encrypted());
1027 assert!(!blob.is_fallback());
1028 assert!(!blob.is_strongbox());
1029 assert!(!blob.is_critical_to_device_encryption());
1030 assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec()));
1031
1032 let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
1033 &mut &*REAL_LEGACY_BLOB,
1034 |_, _, _, _, _| Err(anyhow!("should not be called")),
1035 )?;
1036 assert!(!blob.is_encrypted());
1037 assert!(!blob.is_fallback());
1038 assert!(!blob.is_strongbox());
1039 assert!(!blob.is_critical_to_device_encryption());
1040 assert_eq!(
1041 blob.value(),
1042 &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap())
1043 );
1044 Ok(())
1045 }
1046
1047 #[test]
1048 fn read_aes_gcm_encrypted_key_blob_test() {
1049 let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
1050 &mut &*AES_GCM_ENCRYPTED_BLOB,
1051 |d, iv, tag, salt, key_size| {
1052 assert_eq!(salt, None);
1053 assert_eq!(key_size, None);
1054 assert_eq!(
1055 iv,
1056 &[
1057 0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f,
1058 0x00, 0x00, 0x00, 0x00,
1059 ]
1060 );
1061 assert_eq!(
1062 tag,
1063 &[
1064 0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9,
1065 0xb9, 0xe0, 0x0b, 0xc3
1066 ][..]
1067 );
1068 aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.")
1069 },
1070 )
1071 .unwrap();
1072 assert!(blob.is_encrypted());
1073 assert!(!blob.is_fallback());
1074 assert!(!blob.is_strongbox());
1075 assert!(!blob.is_critical_to_device_encryption());
1076
1077 assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap()));
1078 }
1079
1080 #[test]
1081 fn read_golden_key_blob_too_short_test() {
1082 let error =
1083 LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| {
1084 Err(anyhow!("should not be called"))
1085 })
1086 .unwrap_err();
1087 assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>());
1088 }
1089
1090 #[test]
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +00001091 fn test_is_empty() {
1092 let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
1093 let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
1094
1095 assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
1096
1097 let _db = crate::database::KeystoreDB::new(temp_dir.path(), None)
1098 .expect("Failed to open database.");
1099
1100 assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
1101
1102 std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
1103
1104 assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
1105
1106 std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
1107
1108 assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
1109
1110 std::fs::remove_dir_all(&*temp_dir.build().push("user_0"))
1111 .expect("Failed to remove user_0.");
1112
1113 assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
1114
1115 std::fs::remove_dir_all(&*temp_dir.build().push("user_10"))
1116 .expect("Failed to remove user_10.");
1117
1118 assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
1119 }
1120
1121 #[test]
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001122 fn test_legacy_blobs() -> anyhow::Result<()> {
1123 let temp_dir = TempDir::new("legacy_blob_test")?;
1124 std::fs::create_dir(&*temp_dir.build().push("user_0"))?;
1125
1126 std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY)?;
1127
1128 std::fs::write(
1129 &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
1130 USRPKEY_AUTHBOUND,
1131 )?;
1132 std::fs::write(
1133 &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
1134 USRPKEY_AUTHBOUND_CHR,
1135 )?;
1136 std::fs::write(
1137 &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
1138 USRCERT_AUTHBOUND,
1139 )?;
1140 std::fs::write(
1141 &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
1142 CACERT_AUTHBOUND,
1143 )?;
1144
1145 std::fs::write(
1146 &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"),
1147 USRPKEY_NON_AUTHBOUND,
1148 )?;
1149 std::fs::write(
1150 &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"),
1151 USRPKEY_NON_AUTHBOUND_CHR,
1152 )?;
1153 std::fs::write(
1154 &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"),
1155 USRCERT_NON_AUTHBOUND,
1156 )?;
1157 std::fs::write(
1158 &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"),
1159 CACERT_NON_AUTHBOUND,
1160 )?;
1161
1162 let key_manager = crate::super_key::SuperKeyManager::new();
Janis Danisevskis7e8b4622021-02-13 10:01:59 -08001163 let mut db = crate::database::KeystoreDB::new(temp_dir.path(), None)?;
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001164 let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
1165
1166 assert_eq!(
1167 legacy_blob_loader
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +00001168 .load_by_uid_alias(10223, "authbound", Some(&key_manager))
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001169 .unwrap_err()
1170 .root_cause()
1171 .downcast_ref::<error::Error>(),
1172 Some(&error::Error::Rc(ResponseCode::LOCKED))
1173 );
1174
Hasini Gunasingheda895552021-01-27 19:34:37 +00001175 key_manager.unlock_user_key(&mut db, 0, PASSWORD, &legacy_blob_loader)?;
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001176
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +00001177 if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
1178 legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001179 {
1180 assert_eq!(flags, 4);
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +00001181 //assert_eq!(value, BlobValue::Encrypted(..));
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001182 assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
1183 assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
1184 } else {
1185 panic!("");
1186 }
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +00001187 if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
1188 legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001189 {
1190 assert_eq!(flags, 0);
1191 assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
1192 assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND);
1193 assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND);
1194 } else {
1195 panic!("");
1196 }
1197
Hasini Gunasinghe3ed5da72021-02-04 15:18:54 +00001198 legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
1199 legacy_blob_loader
1200 .remove_keystore_entry(10223, "non_authbound")
1201 .expect("This should succeed.");
1202
1203 assert_eq!(
1204 (None, None, None),
1205 legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
1206 );
1207 assert_eq!(
1208 (None, None, None),
1209 legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
1210 );
1211
1212 // The database should not be empty due to the super key.
1213 assert!(!legacy_blob_loader.is_empty()?);
1214 assert!(!legacy_blob_loader.is_empty_user(0)?);
1215
1216 // The database should be considered empty for user 1.
1217 assert!(legacy_blob_loader.is_empty_user(1)?);
1218
1219 legacy_blob_loader.remove_super_key(0);
1220
1221 // Now it should be empty.
1222 assert!(legacy_blob_loader.is_empty_user(0)?);
1223 assert!(legacy_blob_loader.is_empty()?);
1224
Janis Danisevskisa51ccbc2020-11-25 21:04:24 -08001225 Ok(())
1226 }
1227}