/* * 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. */ //! auth #![allow(missing_docs)] use crate::common::base::Base; use crate::common::hdctransfer::echo_client; use crate::config::{self, *}; use crate::serializer::native_struct; use crate::serializer::serialize::Serialization; use crate::{transfer, utils}; use openssl::base64; use openssl::rsa::{Padding, Rsa}; use ylong_runtime::sync::RwLock; use crate::daemon_lib::sys_para::*; use crate::utils::hdc_log::*; use std::collections::HashMap; use std::fs::File; use std::io::{self, prelude::*, Error, ErrorKind, Write}; use std::path::Path; use std::process::Command; use std::string::ToString; use std::sync::Arc; #[derive(Clone, PartialEq, Eq, Debug)] pub enum AuthStatus { BasC(bool), // basic channel created Init(String), // with plain Pubk(String, String, String), // with (plain, pubkey, confirm msg) Reject(String), // with notify msg Ok, Fail, } pub enum UserPermit { Refuse = 0, AllowOnce = 1, AllowForever = 2, } type AuthStatusMap_ = Arc>>; pub struct AuthStatusMap {} impl AuthStatusMap { fn get_instance() -> AuthStatusMap_ { static mut AUTH_STATUS_MAP: Option = None; unsafe { AUTH_STATUS_MAP .get_or_insert_with(|| Arc::new(RwLock::new(HashMap::new()))) .clone() } } pub async fn get(session_id: u32) -> AuthStatus { let instance = Self::get_instance(); let map = instance.read().await; map.get(&session_id) .unwrap_or(&AuthStatus::BasC(false)) .clone() } async fn put(session_id: u32, auth_status: AuthStatus) { crate::info!( "update auth status {:?} for session {}", auth_status, session_id ); let instance = Self::get_instance(); let mut map = instance.write().await; map.insert(session_id, auth_status); } pub async fn remove(session_id: u32) { crate::info!( "remove auth status for session {}", session_id ); let instance = Self::get_instance(); let mut map = instance.write().await; let _ = map.remove(&session_id); } } pub async fn handshake_init(task_message: TaskMessage) -> io::Result<(u32, TaskMessage)> { if task_message.command != HdcCommand::KernelHandshake { return Err(Error::new(ErrorKind::Other, "unknown command flag")); } let mut recv = native_struct::SessionHandShake::default(); recv.parse(task_message.payload)?; crate::info!("recv handshake: {:#?}", recv); if recv.banner != HANDSHAKE_MESSAGE { return Err(Error::new(ErrorKind::Other, "Recv server-hello failed")); } let session_id = recv.session_id; let channel_id = task_message.channel_id; let host_ver = recv.version.as_str(); crate::info!("client version({}) for session:{}", host_ver, session_id); create_basic_channel(session_id, channel_id, host_ver).await; if host_ver < AUTH_BASE_VERSDION { crate::info!( "client version({}) is too low, return OK for session:{}", host_ver, recv.session_id ); send_reject_message(session_id, channel_id, host_ver).await; return Ok(( recv.session_id, make_channel_close_message(channel_id).await, )); } if !is_auth_enable() { crate::info!( "auth enable is false, return OK for session:{}", recv.session_id ); return Ok(( recv.session_id, make_ok_message(session_id, channel_id).await, )); } // auth is required let buf = generate_token_wait().await; AuthStatusMap::put(session_id, AuthStatus::Init(buf.clone())).await; let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), buf, auth_type: AuthType::Publickey as u8, version: get_version(), }; crate::info!("send handshake: {:?}", send); let message = TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), }; Ok((session_id, message)) } async fn make_sign_message(session_id: u32, token: String, channel_id: u32) -> TaskMessage { let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), buf: token, auth_type: AuthType::Signature as u8, version: get_version(), }; TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), } } async fn make_bypass_message(session_id: u32, channel_id: u32, host_ver: &str) -> TaskMessage { let mut handshake_msg = "".to_string(); let hostname = get_hostname(); if host_ver < AUTH_BASE_VERSDION { handshake_msg = hostname; } else { handshake_msg = Base::tlv_append(handshake_msg, config::TAG_DEVNAME, hostname.as_str()); if is_auth_enable() { handshake_msg = Base::tlv_append(handshake_msg, config::TAG_DAEOMN_AUTHSTATUS, DAEOMN_UNAUTHORIZED); } } let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), auth_type: AuthType::OK as u8, version: get_version(), buf: handshake_msg, }; TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), } } async fn create_basic_channel(session_id: u32, channel_id: u32, host_ver: &str) { if let AuthStatus::BasC(created) = AuthStatusMap::get(session_id).await { if !created { transfer::put( session_id, make_bypass_message(session_id, channel_id, host_ver).await, ) .await; crate::info!( "create_basic_channel created success for session {}", session_id ); AuthStatusMap::put(session_id, AuthStatus::BasC(true)).await; } } crate::info!( "create_basic_channel already created for session {}", session_id ); } async fn make_emg_message(session_id: u32, channel_id: u32, emgmsg: &str) -> TaskMessage { let mut handshake_msg = "".to_string(); let hostname = get_hostname(); handshake_msg = Base::tlv_append(handshake_msg, config::TAG_DEVNAME, hostname.as_str()); handshake_msg = Base::tlv_append(handshake_msg, config::TAG_EMGMSG, emgmsg); handshake_msg = Base::tlv_append(handshake_msg, config::TAG_DAEOMN_AUTHSTATUS, DAEOMN_UNAUTHORIZED); let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), auth_type: AuthType::OK as u8, version: get_version(), buf: handshake_msg, }; TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), } } async fn make_reject_message( session_id: u32, channel_id: u32, host_ver: &str, emgmsg: &str, ) -> TaskMessage { let mut handshake_msg = "".to_string(); let hostname = get_hostname(); if host_ver < AUTH_BASE_VERSDION { handshake_msg = hostname; } else { handshake_msg = Base::tlv_append(handshake_msg, config::TAG_DEVNAME, hostname.as_str()); handshake_msg = Base::tlv_append(handshake_msg, config::TAG_EMGMSG, emgmsg); } let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), auth_type: AuthType::OK as u8, version: get_version(), buf: handshake_msg, }; TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), } } async fn send_reject_message(session_id: u32, channel_id: u32, host_ver: &str) { crate::info!("send_reject_message for session {}", session_id); let msg = "[E000001]:The sdk hdc.exe version is too low, please upgrade to the latest version."; echo_client(session_id, channel_id, msg, MessageLevel::Fail).await; transfer::put( session_id, make_reject_message(session_id, channel_id, host_ver, msg).await, ) .await; AuthStatusMap::put(session_id, AuthStatus::Reject(msg.to_string())).await; } async fn make_ok_message(session_id: u32, channel_id: u32) -> TaskMessage { crate::info!("make_ok_message for session {}", session_id); AuthStatusMap::put(session_id, AuthStatus::Ok).await; let mut succmsg = "".to_string(); let hostname = get_hostname(); succmsg = Base::tlv_append(succmsg, config::TAG_DEVNAME, hostname.as_str()); if succmsg.is_empty() { crate::error!( "append {} failed for session {}", config::TAG_DEVNAME, session_id ); } succmsg = Base::tlv_append( succmsg, config::TAG_DAEOMN_AUTHSTATUS, config::DAEOMN_AUTH_SUCCESS, ); if succmsg.is_empty() { crate::error!( "append {} failed for session {}", config::TAG_DAEOMN_AUTHSTATUS, session_id ); } succmsg = Base::tlv_append(succmsg, config::TAG_EMGMSG, ""); if succmsg.is_empty() { crate::error!( "append {} failed for session {}", config::TAG_EMGMSG, session_id ); } let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), auth_type: AuthType::OK as u8, version: get_version(), buf: succmsg, }; TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), } } pub async fn get_auth_msg(session_id: u32) -> String { match AuthStatusMap::get(session_id).await { AuthStatus::BasC(_) => "Error request.".to_string(), AuthStatus::Init(_) => "Wait handshake init finish.".to_string(), AuthStatus::Pubk(_, _, confirm) => { if confirm.is_empty() { "Wait handshake public key and signure.".to_string() } else { confirm } } AuthStatus::Reject(reject) => reject, AuthStatus::Ok => "There seems nothing wrong.".to_string(), AuthStatus::Fail => "Internal error.".to_string(), } } pub async fn make_channel_close_message(channel_id: u32) -> TaskMessage { crate::debug!("make_channel_close_message: channel {channel_id}"); TaskMessage { channel_id, command: HdcCommand::KernelChannelClose, payload: vec![0], } } pub fn get_host_pubkey_info(buf: &str) -> (String, String) { if let Some((hostname, pubkey)) = buf.split_once(HDC_HOST_DAEMON_BUF_SEPARATOR) { (hostname.to_string(), pubkey.to_string()) } else { ("".to_string(), "".to_string()) } } pub async fn get_session_id_from_msg(task_message: &TaskMessage) -> io::Result { let mut recv = native_struct::SessionHandShake::default(); recv.parse(task_message.payload.clone())?; Ok(recv.session_id) } pub async fn handshake_init_new(session_id: u32, channel_id: u32) -> io::Result<()> { let buf = generate_token_wait().await; AuthStatusMap::put(session_id, AuthStatus::Init(buf.clone())).await; let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, connect_key: "".to_string(), buf, auth_type: AuthType::Publickey as u8, version: get_version(), }; crate::info!("send handshake: {:?}", send); let message = TaskMessage { channel_id, command: HdcCommand::KernelHandshake, payload: send.serialize(), }; transfer::put(session_id, message).await; Ok(()) } pub async fn handshake_deal_pubkey( session_id: u32, channel_id: u32, token: String, buf: String, ) -> io::Result<()> { let (hostname, pubkey) = get_host_pubkey_info(buf.trim()); if pubkey.is_empty() { crate::error!("get public key from host failed"); handshake_fail( session_id, channel_id, "no public key, you may need update your hdc client".to_string(), ) .await; return Ok(()); } if hostname.is_empty() { crate::error!("get hostname from host failed"); handshake_fail( session_id, channel_id, "no hostname, you may need update your hdc client".to_string(), ) .await; return Ok(()); } let known_hosts = read_known_hosts_pubkey(); if known_hosts.contains(&pubkey) { crate::info!("pubkey matches known host({})", hostname); AuthStatusMap::put( session_id, AuthStatus::Pubk(token.clone(), pubkey, "".to_string()), ) .await; transfer::put( session_id, make_sign_message(session_id, token.clone(), channel_id).await, ) .await; return Ok(()); } let confirmmsg = concat!( "[E000002]:The device unauthorized.\n", "This server's public key is not set.\n", "Please check for a confirmation dialog on your device.\n", "Otherwise try 'hdc kill' if that seems wrong." ); AuthStatusMap::put( session_id, AuthStatus::Pubk(token.clone(), pubkey.clone(), confirmmsg.to_string()), ) .await; utils::spawn(async move { echo_client( session_id, channel_id, confirmmsg, MessageLevel::Fail, ) .await; transfer::put( session_id, make_emg_message(session_id, channel_id, confirmmsg).await, ) .await; transfer::put(session_id, make_channel_close_message(channel_id).await).await; }); match require_user_permittion(&hostname).await { UserPermit::AllowForever => { crate::info!("allow forever"); if write_known_hosts_pubkey(&pubkey).is_err() { handshake_fail( session_id, channel_id, "write public key failed".to_string(), ) .await; crate::error!("write public key failed"); return Ok(()); } AuthStatusMap::put( session_id, AuthStatus::Pubk(token.clone(), pubkey, "".to_string()), ) .await; transfer::put( session_id, make_sign_message(session_id, token.clone(), channel_id).await, ) .await; } UserPermit::AllowOnce => { crate::info!("allow once"); AuthStatusMap::put( session_id, AuthStatus::Pubk(token.clone(), pubkey, "".to_string()), ) .await; transfer::put( session_id, make_sign_message(session_id, token.clone(), channel_id).await, ) .await; } _ => { crate::info!("user refuse"); let denymsg = concat!( "[E000003]:The device unauthorized.\n", "The user denied the access for the device.\n", "Please execute 'hdc kill' and redo your command, ", "then check for a confirmation dialog on your device." ); AuthStatusMap::put(session_id, AuthStatus::Reject(denymsg.to_string())).await; echo_client(session_id, channel_id, denymsg, MessageLevel::Fail).await; transfer::put( session_id, make_emg_message(session_id, channel_id, denymsg).await, ) .await; transfer::put(session_id, make_channel_close_message(channel_id).await).await; return Ok(()); } } Ok(()) } pub async fn handshake_deal_signature( session_id: u32, channel_id: u32, token: String, pubkey: String, buf: String, ) -> io::Result<()> { match validate_signature(token, pubkey, buf).await { Ok(()) => { crate::info!("user Signature"); transfer::put(session_id, make_ok_message(session_id, channel_id).await).await; transfer::put(session_id, make_channel_close_message(channel_id).await).await; Ok(()) } Err(e) => { let errlog = e.to_string(); crate::error!("validate signature failed: {}", &errlog); handshake_fail(session_id, channel_id, errlog).await; Err(Error::new(ErrorKind::Other, "validate signature failed")) } } } pub async fn handshake_task(task_message: TaskMessage, session_id: u32) -> io::Result<()> { if let AuthStatus::Ok = AuthStatusMap::get(session_id).await { crate::info!("session {} already auth ok", session_id); return Ok(()); } let mut recv = native_struct::SessionHandShake::default(); recv.parse(task_message.payload)?; crate::info!("recv handshake: {:?}", recv); if recv.banner != HANDSHAKE_MESSAGE { return Err(Error::new(ErrorKind::Other, "Recv server-hello failed")); } let session_id = recv.session_id; let channel_id = task_message.channel_id; let host_ver = recv.version.as_str(); crate::info!("client version({}) for session:{}", host_ver, session_id); if !is_auth_enable() { crate::info!("auth enable is false, return OK for session:{}", session_id); transfer::put(session_id, make_ok_message(session_id, channel_id).await).await; return Ok(()); } create_basic_channel(session_id, channel_id, host_ver).await; if host_ver < AUTH_BASE_VERSDION { crate::info!( "client version({}) is too low, cannt access for session:{}", host_ver, recv.session_id ); send_reject_message(session_id, channel_id, host_ver).await; transfer::put(session_id, make_channel_close_message(channel_id).await).await; return Err(Error::new(ErrorKind::Other, "client version is too low")); } match AuthStatusMap::get(session_id).await { AuthStatus::BasC(created) => { crate::info!( "auth status is BasC({}) for session {}", created, session_id ); if !created { create_basic_channel(session_id, channel_id, host_ver).await; } handshake_init_new(session_id, channel_id).await } AuthStatus::Init(token) => { crate::info!("auth status is Init() for session {}", session_id); if recv.auth_type != AuthType::Publickey as u8 { return Err(Error::new( ErrorKind::Other, "wrong command, need pubkey now", )); } handshake_deal_pubkey(session_id, channel_id, token, recv.buf).await } AuthStatus::Pubk(token, pubkey, confirm) => { crate::info!( "auth status is Pubk({}) for session {}", confirm, session_id ); if recv.auth_type != AuthType::Signature as u8 { return Err(Error::new( ErrorKind::Other, "wrong command, need signature now", )); } handshake_deal_signature(session_id, channel_id, token, pubkey, recv.buf).await } AuthStatus::Reject(reject) => { crate::info!( "auth status is Reject({}) for session {}", reject, session_id ); Ok(()) } AuthStatus::Ok => { crate::info!("auth status is Ok() for session {}", session_id); Ok(()) } AuthStatus::Fail => { crate::info!("auth status is Fail() for session {}", session_id); Ok(()) } } } async fn validate_signature(plain: String, pubkey: String, signature: String) -> io::Result<()> { let signature_bytes = if let Ok(bytes) = base64::decode_block(&signature) { bytes } else { return Err(Error::new(ErrorKind::Other, "signature decode failed")); }; let rsa = if let Ok(cipher) = Rsa::public_key_from_pem(pubkey.as_bytes()) { cipher } else { return Err(Error::new(ErrorKind::Other, "pubkey convert failed")); }; let mut buf = vec![0_u8; config::RSA_BIT_NUM]; let dec_size = rsa .public_decrypt(&signature_bytes, &mut buf, Padding::PKCS1) .unwrap_or(0); if plain.as_bytes() == &buf[..dec_size] { Ok(()) } else { Err(Error::new(ErrorKind::Other, "signature not match")) } } pub fn clear_auth_pub_key_file() { if !is_auth_enable() { return; } let (_, auth_cancel) = get_dev_item("persist.hdc.daemon.auth_cancel", "_"); if auth_cancel.trim().to_lowercase() != "true" { crate::info!("auth_cancel is {}, no need clear pubkey file", auth_cancel); return; } if !set_dev_item("persist.hdc.daemon.auth_cancel", "false") { crate::error!("clear param auth_cancel failed."); } let file_name = Path::new(config::RSA_PUBKEY_PATH).join(config::RSA_PUBKEY_NAME); match std::fs::remove_file(&file_name) { Ok(_) => { crate::info!("remove pubkey file {:?} success", file_name); } Err(err) => { crate::error!("remove pubkey file {:?} failed: {}", file_name, err); } } } fn read_known_hosts_pubkey() -> Vec { let file_name = Path::new(config::RSA_PUBKEY_PATH).join(config::RSA_PUBKEY_NAME); if let Ok(keys) = std::fs::read_to_string(&file_name) { let mut key_vec = vec![]; let mut tmp_vec = vec![]; for line in keys.split('\n') { if line.contains("BEGIN PUBLIC KEY") { tmp_vec.clear(); } tmp_vec.push(line); if line.contains("END PUBLIC KEY") { key_vec.push(tmp_vec.join("\n")); } } crate::debug!("read {} known hosts from file", key_vec.len()); key_vec } else { crate::info!("pubkey file {:?} not exists", file_name); vec![] } } fn write_known_hosts_pubkey(pubkey: &String) -> io::Result<()> { let file_name = Path::new(config::RSA_PUBKEY_PATH).join(config::RSA_PUBKEY_NAME); if !file_name.exists() { crate::info!("will create pubkeys file at {:?}", file_name); if let Err(e) = std::fs::create_dir_all(config::RSA_PUBKEY_PATH) { log::error!("create pubkeys dir: {}", e.to_string()); } if let Err(e) = std::fs::File::create(&file_name) { log::error!("create pubkeys file: {}", e.to_string()); } } let _ = match std::fs::File::options().append(true).open(file_name) { Ok(mut f) => writeln!(&mut f, "{}", pubkey), Err(e) => { crate::error!("write pubkey err: {}", e.to_string()); return Err(e); } }; Ok(()) } fn show_permit_dialog() -> bool { let cmd = "/system/bin/hdcd_user_permit"; let result = Command::new(cmd).output(); match result { Ok(output) => { let msg = [output.stdout, output.stderr].concat(); let mut str = match String::from_utf8(msg) { Ok(s) => s, Err(e) => { crate::error!("show dialog over, {}.", e.to_string()); return false; } }; str = str.replace('\n', " "); crate::error!("show dialog over, {}.", str); true } Err(e) => { crate::error!("show dialog failed, {}.", e.to_string()); false } } } pub fn is_auth_enable() -> bool { #[cfg(feature = "emulator")] return false; let (_, auth_enable) = get_dev_item("const.hdc.secure", "_"); crate::error!("const.hdc.secure is {}.", auth_enable); if auth_enable.trim().to_lowercase() != "1" { return false; } // if persist.hdc.auth_bypass is set "1", will not auth, // otherwhise must be auth // the auto upgrade test will set persist.hdc.auth_bypass 1 let (_, auth_bypass) = get_dev_item("persist.hdc.auth_bypass", "_"); crate::error!("persist.hdc.auth_bypass is {}.", auth_bypass); auth_bypass.trim().to_lowercase() != "1" } async fn require_user_permittion(hostname: &str) -> UserPermit { // todo debug let default_permit = "auth_result_none"; // clear result first if !set_dev_item("persist.hdc.daemon.auth_result", default_permit) { crate::error!("debug auth result failed, so refuse this connect."); return UserPermit::Refuse; } // then write para for setting if !set_dev_item("persist.hdc.client.hostname", hostname) { crate::error!("set param({}) failed.", hostname); return UserPermit::Refuse; } if !show_permit_dialog() { crate::error!("show dialog failed, so refuse this connect."); return UserPermit::Refuse; } let permit_result = match get_dev_item("persist.hdc.daemon.auth_result", "_") { (false, _) => { crate::error!("get_dev_item auth_result failed"); UserPermit::Refuse } (true, auth_result) => { crate::error!("user permit result is:({})", auth_result); match auth_result.strip_prefix("auth_result:") { Some(result) => match result.trim() { "1" => UserPermit::AllowOnce, "2" => UserPermit::AllowForever, _ => UserPermit::Refuse, }, None => { crate::error!("get_dev_item auth_result failed"); UserPermit::Refuse } } } }; permit_result } async fn handshake_fail(session_id: u32, channel_id: u32, msg: String) { AuthStatusMap::put(session_id, AuthStatus::Fail).await; let send = native_struct::SessionHandShake { banner: HANDSHAKE_MESSAGE.to_string(), session_id, auth_type: AuthType::Fail as u8, buf: msg, ..Default::default() }; transfer::put( session_id, TaskMessage { channel_id, command: config::HdcCommand::KernelHandshake, payload: send.serialize(), }, ) .await; } async fn generate_token() -> io::Result { let mut random_file = File::open("/dev/random")?; let mut buffer = [0; HDC_HANDSHAKE_TOKEN_LEN]; random_file.read_exact(&mut buffer)?; let random_vec: Vec<_> = buffer.iter().map(|h| format!("{:02X}", h)).collect(); let token = random_vec.join(""); Ok(token) } async fn generate_token_wait() -> String { loop { match generate_token().await { Ok(token) => { break token; } Err(e) => { let errlog = e.to_string(); crate::error!("generate token failed: {}", &errlog); } } } } fn get_hostname() -> String { use libc::{c_char, sysconf, _SC_HOST_NAME_MAX}; let maxlen = unsafe { sysconf(_SC_HOST_NAME_MAX) }; let mut out = vec![0; (maxlen as usize) + 1]; let ret = unsafe { libc::gethostname(out.as_mut_ptr() as *mut c_char, out.len()) }; if ret != 0 { crate::error!("get hostname failed, {}.", std::io::Error::last_os_error()); return "".to_string(); } let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); out.resize(len, 0); let output = String::from_utf8(out) .unwrap_or("".to_string()) .trim() .to_string(); crate::info!("get hostname is {}", output); output }