910e62b5创建于 1月15日历史提交
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::{open_aes_256_gcm, RequestError};
use crate::recovery_key_store;
use alloc::{collections::btree_map::BTreeMap, vec::Vec};
use cbor::{cbor, MapKey, Value};

// The HKDF "info" parameter used when deriving a PIN data key from a security
// domain secret.
pub(crate) const KEY_PURPOSE_PIN_DATA_KEY: &[u8] =
    b"KeychainApplicationKey:chrome:GPM PIN data wrapping key";

#[derive(PartialEq, Debug)]
pub(crate) struct VaultCohortDetails {
    /// The serial number for the cert.xml file last used to select a cohort.
    pub cert_xml_serial_number: i64,
    // The cohort public key selected for the Vault entry for the PIN.
    pub cohort_public_key: Vec<u8>,
}

impl TryFrom<&alloc::collections::BTreeMap<cbor::MapKey, cbor::Value>> for VaultCohortDetails {
    type Error = ();

    fn try_from(
        map: &alloc::collections::BTreeMap<cbor::MapKey, cbor::Value>,
    ) -> Result<VaultCohortDetails, Self::Error> {
        let Some(Value::Int(cert_xml_serial_number)) = map.get(&MapKey::Int(6)) else {
            return Err(());
        };
        let Some(Value::Bytestring(cohort_public_key)) = map.get(&MapKey::Int(7)) else {
            return Err(());
        };
        Ok(VaultCohortDetails {
            cert_xml_serial_number: *cert_xml_serial_number,
            cohort_public_key: cohort_public_key.to_vec(),
        })
    }
}

/// A representation of the PIN data after unwrapping with the
/// security domain secret.
#[derive(PartialEq, Debug)]
pub(crate) struct Data {
    /// The hash of the PIN. Usually hashed with scrypt, but this code only
    /// deals with hashes and so isn't affected by the choice of algorithm so
    /// long as the output is 256 bits long.
    pub pin_hash: [u8; 32],
    /// An AES-256-GCM key used to encrypt claimed PIN hashes.
    pub claim_key: [u8; 32],
    /// The recovery key store counter ID. Used when refreshing the recovery
    /// key store.
    pub counter_id: [u8; recovery_key_store::COUNTER_ID_LEN],
    /// The recovery key store vault handle. Doesn't include the type byte. Used
    /// when refreshing the recovery key store.
    pub vault_handle_without_type: [u8; recovery_key_store::VAULT_HANDLE_LEN - 1],
    // Details of the Vault cohort that corresponds to this PIN. Some clients
    // may not set this value.
    pub vault_cohort_details: Option<VaultCohortDetails>,
}

impl TryFrom<Vec<u8>> for Data {
    type Error = ();

    /// Parse a `Data` from CBOR bytes.
    fn try_from(pin_data: Vec<u8>) -> Result<Data, Self::Error> {
        let parsed = cbor::parse(pin_data).map_err(|_| ())?;
        let Value::Map(map) = parsed else {
            return Err(());
        };
        let Some(Value::Bytestring(pin_hash)) = map.get(&MapKey::Int(1)) else {
            return Err(());
        };
        // 2 corresponded to the generation, which was removed. Do not reuse.
        let Some(Value::Bytestring(claim_key)) = map.get(&MapKey::Int(3)) else {
            return Err(());
        };
        let Some(Value::Bytestring(counter_id)) = map.get(&MapKey::Int(4)) else {
            return Err(());
        };
        let Some(Value::Bytestring(vault_handle)) = map.get(&MapKey::Int(5)) else {
            return Err(());
        };
        let vault_cohort_details = match VaultCohortDetails::try_from(&map) {
            Ok(result) => Some(result),
            Err(_) => None,
        };
        Ok(Data {
            pin_hash: pin_hash.as_ref().try_into().map_err(|_| ())?,
            claim_key: claim_key.as_ref().try_into().map_err(|_| ())?,
            counter_id: counter_id.as_ref().try_into().map_err(|_| ())?,
            vault_handle_without_type: vault_handle.as_ref().try_into().map_err(|_| ())?,
            vault_cohort_details,
        })
    }
}

impl Data {
    /// Serialize as CBOR.
    #[allow(unused_parens)]
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut map: BTreeMap<cbor::MapKey, cbor::Value> = BTreeMap::default();
        map.insert(cbor::MapKey::Int(1), cbor!((&self.pin_hash)));
        // 2 corresponded to the generation, which was removed. Do not reuse.
        map.insert(cbor::MapKey::Int(3), cbor!((&self.claim_key)));
        map.insert(cbor::MapKey::Int(4), cbor!((&self.counter_id)));
        map.insert(
            cbor::MapKey::Int(5),
            cbor!((&self.vault_handle_without_type)),
        );
        if let Some(vault_cohort_details) = &self.vault_cohort_details {
            map.insert(
                cbor::MapKey::Int(6),
                cbor!((vault_cohort_details.cert_xml_serial_number)),
            );
            map.insert(
                cbor::MapKey::Int(7),
                cbor!((vault_cohort_details.cohort_public_key.clone())),
            );
        }
        cbor::Value::from(map).to_bytes()
    }

    /// Decrypt a wrapped PIN.
    pub fn from_wrapped(
        wrapped_pin_data: &[u8],
        security_domain_secret: &[u8],
    ) -> Result<Data, RequestError> {
        let mut pin_data_key = [0u8; 32];
        // unwrap: this only fails if the output is too long, but the output length
        // is fixed at 32.
        crypto::hkdf_sha256(
            security_domain_secret,
            &[],
            KEY_PURPOSE_PIN_DATA_KEY,
            &mut pin_data_key,
        )
        .unwrap();
        open_aes_256_gcm(&pin_data_key, wrapped_pin_data, &[])
            .ok_or(RequestError::Debug("PIN data decryption failed"))?
            .try_into()
            .map_err(|_| RequestError::Debug("invalid PIN data"))
    }

    /// Wrap PIN `Data`.
    pub fn encrypt(&self, security_domain_secret: &[u8]) -> Vec<u8> {
        let mut pin_data_key = [0u8; 32];
        // unwrap: this only fails if the output is too long, but the output length
        // is fixed at 32.
        crypto::hkdf_sha256(
            security_domain_secret,
            &[],
            KEY_PURPOSE_PIN_DATA_KEY,
            &mut pin_data_key,
        )
        .unwrap();

        let mut nonce = [0u8; crypto::NONCE_LEN];
        crypto::rand_bytes(&mut nonce);
        let mut serialized = self.to_bytes();
        crypto::aes_256_gcm_seal_in_place(&pin_data_key, &nonce, &[], &mut serialized);
        [nonce.as_ref(), &serialized].concat()
    }
}