* Copyright (c) 2023 Huawei Device Co., Ltd.
* 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
*
* http://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::cert_chain_utils::PemCollection;
use super::cert_path_utils::{TrustCertPath, CertType, CertStatus, activate_cert};
use super::cert_utils::{get_cert_path, get_trusted_certs};
use super::cs_hisysevent;
use super::profile_utils::{add_profile_cert_path, add_enterprise_certs};
use hilog_rust::{error, hilog, info, HiLogLabel, LogType};
use openssl::error::ErrorStack;
use std::ffi::{c_char, CString};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::option::Option;
use std::ptr;
use std::thread;
use std::time::{Duration, Instant};
use std::path::Path;
const LOG_LABEL: HiLogLabel = HiLogLabel {
log_type: LogType::LogCore,
domain: 0xd005a06,
tag: "CODE_SIGN",
};
const CERT_DATA_MAX_SIZE: usize = 8192;
const PROC_KEY_FILE_PATH: &str = "/proc/keys";
const KEYRING_TYPE: &str = "keyring";
const FSVERITY_KEYRING_NAME: &str = ".fs-verity";
const LOCAL_KEY_NAME: &str = "local_key";
const CODE_SIGN_KEY_NAME_PREFIX: &str = "fs_verity_key";
const PROFILE_STORE_EL1: &str = "/data/service/el1/public/profiles";
const PROFILE_SEARCH_SLEEP_TIME: u64 = 200;
const PROFILE_SEARCH_SLEEP_OUT_TIME: u64 = 600;
const SUCCESS: i32 = 0;
type KeySerial = i32;
pub enum HisyseventKeyError {
LocalKeyEmpty = 1,
}
extern "C" {
fn InitLocalCertificate(cert_data: *mut u8, cert_size: *mut usize) -> i32;
fn AddKey(
type_name: *const c_char,
description: *const c_char,
payload: *const u8,
plen: usize,
ring_id: KeySerial,
) -> KeySerial;
fn KeyctlRestrictKeyring(
ring_id: KeySerial,
type_name: *const u8,
restriction: *const u8,
) -> KeySerial;
fn CheckUserUnlock() -> bool;
fn WaitForBootCompletion() -> bool;
}
fn print_openssl_error_stack(error_stack: ErrorStack) {
for error in error_stack.errors() {
error!(LOG_LABEL, "{}", @public(error.to_string()));
}
}
fn get_local_key() -> Option<Vec<u8>> {
let mut cert_size = CERT_DATA_MAX_SIZE;
let mut cert_data = Vec::with_capacity(cert_size);
let pcert = cert_data.as_mut_ptr();
unsafe {
let ret = InitLocalCertificate(pcert, &mut cert_size);
if ret == 0 {
cert_data.set_len(cert_size);
Some(cert_data)
} else {
error!(LOG_LABEL, "InitLocalCertificate failed {}", @public(ret));
None
}
}
}
fn parse_key_info(line: String) -> Option<KeySerial> {
let attrs: Vec<&str> = line.split_whitespace().collect();
if attrs.len() != 10 {
return None;
}
if attrs[7] == KEYRING_TYPE && attrs[8].strip_suffix(':') == Some(FSVERITY_KEYRING_NAME) {
match KeySerial::from_str_radix(attrs[0], 16) {
Ok(x) => Some(x),
Err(error) => {
error!(LOG_LABEL, "Convert KeySerial failed: {}", error);
None
}
}
} else {
None
}
}
fn add_key(key_id: KeySerial, key_name: &str, cert_data: &Vec<u8>) -> i32 {
let type_name = CString::new("asymmetric").expect("type name is invalid");
let keyname = CString::new(key_name).expect("keyname is invalid");
unsafe {
let ret: i32 = AddKey(
type_name.as_ptr(),
keyname.as_ptr(),
cert_data.as_ptr(),
cert_data.len(),
key_id,
);
ret
}
}
fn add_key_list(key_id: KeySerial, certs: Vec<Vec<u8>>, key_name_prefix: &str) -> i32 {
let prefix = String::from(key_name_prefix);
for (i, cert_data) in certs.iter().enumerate() {
let key_name = prefix.clone() + &i.to_string();
let ret = add_key(key_id, key_name.as_str(), cert_data);
if ret < 0 {
return ret;
}
}
SUCCESS
}
fn get_keyring_id() -> Result<KeySerial, ()> {
let file = File::open(PROC_KEY_FILE_PATH).expect("Open /proc/keys failed");
let lines = BufReader::new(file).lines();
for line in lines.flatten() {
if line.contains(KEYRING_TYPE) && line.contains(FSVERITY_KEYRING_NAME) {
if let Some(keyid) = parse_key_info(line) {
return Ok(keyid);
}
}
}
error!(LOG_LABEL, "Get .fs-verity keyring id failed.");
Err(())
}
fn add_trusted_keys(key_id: KeySerial, root_cert: &PemCollection) {
info!(LOG_LABEL, "Add trusted keys");
let certs = match root_cert.to_der() {
Ok(der) => der,
Err(e) => {
print_openssl_error_stack(e);
Vec::new()
}
};
if certs.is_empty() {
error!(LOG_LABEL, "empty trusted certs!");
}
let ret = add_key_list(key_id, certs, CODE_SIGN_KEY_NAME_PREFIX);
if ret < 0 {
cs_hisysevent::report_add_key_err("code_sign_keys", ret);
}
}
fn activate_cert_list(certs: Vec<Vec<u8>>, cert_status: CertStatus, cert_type: CertType) {
for cert in certs.iter() {
if activate_cert(cert, cert_status, cert_type).is_err() {
error!(LOG_LABEL, "Activate cert error");
}
}
}
fn activate_trusted_certs(root_cert: &PemCollection) {
let certs = match root_cert.to_der() {
Ok(der) => der,
Err(e) => {
print_openssl_error_stack(e);
Vec::new()
}
};
if certs.is_empty() {
error!(LOG_LABEL, "empty trusted certs!");
}
activate_cert_list(certs, CertStatus::BeforeUnlock, CertType::Other);
}
fn check_and_add_cert_path(root_cert: &PemCollection, cert_paths: &TrustCertPath) -> bool {
if Path::new(PROFILE_STORE_EL1).exists() {
if add_profile_cert_path(root_cert, cert_paths).is_err() {
error!(LOG_LABEL, "Add cert path from local profile err.");
}
if add_enterprise_certs(root_cert).is_err() {
error!(LOG_LABEL, "Add enterprise cert err.");
}
info!(LOG_LABEL, "Finished cert path adding.");
true
} else {
false
}
}
fn add_profile_cert_path_thread(
root_cert: PemCollection,
cert_paths: TrustCertPath,
) -> std::thread::JoinHandle<()> {
thread::spawn(move || {
info!(LOG_LABEL, "Starting enable developer cert.");
let start_time = Instant::now();
loop {
if check_and_add_cert_path(&root_cert, &cert_paths) {
break;
} else if start_time.elapsed() >= Duration::from_secs(PROFILE_SEARCH_SLEEP_OUT_TIME) {
error!(LOG_LABEL, "Timeout while waiting for PROFILE_STORE_EL1.");
break;
} else {
thread::sleep(Duration::from_millis(PROFILE_SEARCH_SLEEP_TIME));
}
}
})
}
fn add_local_key(key_id: KeySerial) -> Option<Vec<u8>> {
let local_key = get_local_key();
if let Some(cert_data) = &local_key {
info!(LOG_LABEL, "Add local keys");
let ret = add_key(key_id, LOCAL_KEY_NAME, cert_data);
if ret < 0 {
cs_hisysevent::report_add_key_err("local_key", ret);
error!(LOG_LABEL, "Enable local key failed");
}
} else {
cs_hisysevent::report_add_key_err("local_key", HisyseventKeyError::LocalKeyEmpty as i32);
info!(LOG_LABEL, "Get local key empty.");
}
local_key
}
fn restrict_keys(key_id: KeySerial) {
info!(LOG_LABEL, "Restricting keys");
unsafe {
if KeyctlRestrictKeyring(key_id, ptr::null(), ptr::null()) < 0 {
error!(LOG_LABEL, "Restrict keyring err");
}
}
}
fn activate_local_cert(cert_data: Vec<u8>) {
info!(LOG_LABEL, "Activating local cert");
if activate_cert(&cert_data, CertStatus::AfterUnlock, CertType::Local).is_err() {
error!(LOG_LABEL, "Activate local cert error");
}
}
fn enable_local_keys_after_user_unlock(key_id: KeySerial) {
info!(LOG_LABEL, "Waiting for boot completion before adding local key...");
if !unsafe { WaitForBootCompletion() } {
error!(LOG_LABEL, "WaitForBootCompletion timed out, proceeding with local key");
} else {
info!(LOG_LABEL, "Boot completed, adding local key");
}
let local_key = add_local_key(key_id);
restrict_keys(key_id);
if !unsafe { CheckUserUnlock() } {
return;
}
if let Some(cert_data) = local_key {
activate_local_cert(cert_data);
}
}
pub fn enable_all_keys() {
let key_id = match get_keyring_id() {
Ok(id) => id,
Err(_) => {
error!(LOG_LABEL, "Failed to get keyring ID.");
return;
},
};
let root_cert = get_trusted_certs();
add_trusted_keys(key_id, &root_cert);
activate_trusted_certs(&root_cert);
let cert_paths = get_cert_path();
if cert_paths.add_cert_paths().is_err() {
error!(LOG_LABEL, "Add trusted cert path err.");
}
let cert_thread = add_profile_cert_path_thread(root_cert, cert_paths);
enable_local_keys_after_user_unlock(key_id);
if let Err(e) = cert_thread.join() {
error!(LOG_LABEL, "add cert path thread panicked: {:?}", e);
}
info!(LOG_LABEL, "Fnished enable all keys.");
}