-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require "mc.class"
local prop_def = require "macros.property_def"
local bios_factory = require 'factory.bios_factory'
local bs_util = require 'util.base_util'
local singleton = require 'mc.singleton'
local json = require 'cjson'
local vos = require 'utils.vos'
local log = require 'mc.logging'
local cert_core = require 'certificate.core'
local ipmi = require 'ipmi'
local base_messages = require 'messages.base'
local custom_messages = require 'messages.custom'
local secure_boot_options_object = require 'pojo.secure_boot_options_object'
local utils = require 'mc.utils'
local utils_core = require 'utils.core'
local clsmgmt = require 'mc.class_mgnt'
local comp_code = ipmi.types.Cc

local CERTIFICATE_TYPE_SINGLE_PEM < const > = 'PEM'

local secure_boot_options_service = class()

function secure_boot_options_service:ctor(db)
    self.db = db
    self.obj_collection = {}
end

function secure_boot_options_service:delete_object(obj, position)
    local system_id = obj:get_system_id()
    local secure_boot_obj = self.obj_collection[system_id]
    if not secure_boot_obj then
        return
    end
    if secure_boot_obj.position == position then
        clsmgmt('SecureBootOptions'):remove(secure_boot_obj.secure_boot_options_obj)
        self.obj_collection[system_id] = nil
    end
end

function secure_boot_options_service:add_object(sys_id, obj, position)
    if self.obj_collection[sys_id] then
        self.obj_collection[sys_id]:update_object(obj, position)
        return
    end

    self.obj_collection[sys_id] = secure_boot_options_object.new(self.db, sys_id, obj, position)
    obj:register_mdb_objects()
end

function secure_boot_options_service:get_obj(sys_id)
    return self.obj_collection[sys_id]
end

function secure_boot_options_service:set_prop(prop, val, sys_id)
    local obj = self.obj_collection[sys_id]
    if not obj then
        return false
    end

    return obj:set_prop(prop, val)
end

function secure_boot_options_service:get_prop(prop, sys_id)
    local obj = self.obj_collection[sys_id]
    if not obj then
        return false
    end
    return obj:get_prop(prop)
end

local function error_process(ret)
    local switch = {
        [prop_def.SECUREBOOT_METHOD_EXPIRED_CERT] = function ()
            error(custom_messages.CertificateExpired())
        end,
        [prop_def.SECUREBOOT_METHOD_BIOS_STATE_UNSUPPORT] = function ()
            error(custom_messages.BiosStateNotAllowed())
        end,
        [prop_def.SECUREBOOT_METHOD_PEM_FORMAT_ERR] = function ()
            error(custom_messages.PemFormatErr())
        end,
        [prop_def.SECUREBOOT_METHOD_STRING_TOO_LONG] = function ()
            error(custom_messages.StringValueTooLong("CertificateString", prop_def.CERT_MAX_SIZE))
        end,
        [prop_def.SECUREBOOT_METHOD_IMPORT_LIMIT] = function ()
            error(custom_messages.ImportLimitExceed())
        end,
        [prop_def.SECUREBOOT_METHOD_ERR] = function ()
            error(custom_messages.WrongFileFormat())
        end
    }
    if switch[ret] then
        switch[ret]()
    end
    error(base_messages.InternalError())
end

local function check_bios_startup_state(sys_id)
    local bios_ser = bios_factory.get_service('bios_service')
    if bios_ser == nil then
        log:debug("bios_service not found")
        return prop_def.SECUREBOOT_METHOD_ERR
    end
    local startup_state = bios_ser:get_prop(prop_def.SYSTEM_STARTUP_STATE, sys_id)
    if startup_state == prop_def.BIOS_STARTUP_STATE_OFF or startup_state == prop_def.BIOS_STARTUP_POST_STAGE_FINISH then
        return prop_def.SECUREBOOT_METHOD_OK
    else 
        return prop_def.SECUREBOOT_METHOD_BIOS_STATE_UNSUPPORT
    end
end

-- 组装导入单个证书的json对象
local function makeup_import_item(cert_string, cert_type)
    local target = {}
    target[prop_def.INTERACT_KEY_OPERATION] = prop_def.CERT_OPERATION_ADD
    target[prop_def.INTERACT_KEY_CERTIFICATE_STRING] = cert_string
    target[prop_def.INTERACT_KEY_CERTIFICATE_TYPE] = cert_type
    target[prop_def.INTERACT_KEY_SIZE] = string.len(cert_string)
    return target
end

local function check_import_cert_string(cert_string)
    if string.len(cert_string) > prop_def.CERT_MAX_SIZE then
        return prop_def.SECUREBOOT_METHOD_STRING_TOO_LONG
    end

    local ok, info = pcall(function()
        return cert_core.get_cert_info_from_mem(cert_string)
    end)
    if not ok then
        log:error("invalid certificate format")
        return prop_def.SECUREBOOT_METHOD_PEM_FORMAT_ERR
    end
    local not_after = info['ValidNotAfter']
    local not_before = info['ValidNotBefore']
    local now = os.time()
    if not_after < now or not_before > now then
        log:error("certificate expired")
        return prop_def.SECUREBOOT_METHOD_EXPIRED_CERT
    end
    return prop_def.SECUREBOOT_METHOD_OK
end

-- 将待导入的证书文本写入到json文件中,type区分写入的区域
local function import_to_file(path, json_obj, db_type, sec_obj)
    local E_OK = 1
    local file_json = {}
    if bs_util.is_file_exist(path) == false or vos.get_file_length(path) == 0 then
        file_json = bs_util.create_default_cer_json()
    else
        file_json = bs_util.get_file_json(path)
    end
    if file_json == nil then
        return prop_def.SECUREBOOT_METHOD_ERR
    end
    if #file_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][db_type][prop_def.INTERACT_KEY_CERTIFICATES] >= prop_def.BIOS_MAX_CERT_NUM then
        return prop_def.SECUREBOOT_METHOD_IMPORT_LIMIT
    end
    table.insert(file_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][db_type][prop_def.INTERACT_KEY_CERTIFICATES], json_obj)
    local file_data = json.encode(file_json)
    if bs_util.write_file_data(path, file_data) ~= E_OK then
        return prop_def.SECUREBOOT_METHOD_ERR
    end
    utils_core.chmod_s(path, utils.S_IRUSR | utils.S_IWUSR)
    sec_obj:update_cer_info_db(file_data)

    return prop_def.SECUREBOOT_METHOD_OK
end

function secure_boot_options_service:import_secure_boot_certificate(ctx, db_type, cert_string, cert_type, sys_id)
    local sec_obj = self.obj_collection[sys_id]
    if not sec_obj then
        bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s failed", db_type))
        error_process(prop_def.SECUREBOOT_METHOD_INVALID_PARAM)
    end

    local ret = check_bios_startup_state(sys_id)
    if ret ~= prop_def.SECUREBOOT_METHOD_OK then
        bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s failed", db_type))
        error_process(ret)
    end
    if db_type ~= prop_def.INTERACT_KEY_SECUREBOOT_DB and db_type ~= prop_def.INTERACT_KEY_SECUREBOOT_DBX then
        bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s failed", db_type))
        error(base_messages.PropertyValueFormatError(db_type, "Database"))
    end
    if cert_type ~= CERTIFICATE_TYPE_SINGLE_PEM then
        bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s failed", db_type))
        error(base_messages.PropertyValueFormatError(cert_type, "CertificateType"))
    end
    ret = check_import_cert_string(cert_string)
    if ret ~= prop_def.SECUREBOOT_METHOD_OK then
        bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s failed", db_type))
        error_process(ret)
    end
    local added_json = makeup_import_item(cert_string, cert_type)
    ret = import_to_file(sec_obj.new_secureboot, added_json, db_type, sec_obj)
    if ret ~= prop_def.SECUREBOOT_METHOD_OK then
        bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s failed", db_type))
        error_process(ret)
    end
    bs_util.record_operation(ctx, sys_id, string.format("Import BIOS SecureBoot %s successfully", db_type))
    return ret
end

-- 组装导入单个证书的json对象
local function makeup_reset_item(creset_type)
    local target = {}
    target[prop_def.INTERACT_KEY_OPERATION] = creset_type
    return target
end

-- 将待生效的重置动作记录到json文件中,并删除待导入证书,type区分操作的区域
local function reset_to_file(path, json_obj, db_type, sec_obj)
    local E_OK = 1
    local file_json = {}
    if bs_util.is_file_exist(path) == false or vos.get_file_length(path) == 0 or vos.get_file_length(path) == 1 then
        file_json = bs_util.create_default_cer_json()
    else
        file_json = bs_util.get_file_json(path)
    end
    if file_json == nil then
        return prop_def.SECUREBOOT_METHOD_ERR
    end
    file_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][db_type][prop_def.INTERACT_KEY_CERTIFICATES] = {}
    table.insert(file_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][db_type][prop_def.INTERACT_KEY_CERTIFICATES], json_obj)
    local file_data = json.encode(file_json)
    if bs_util.write_file_data(path, file_data) ~= E_OK then
        return prop_def.SECUREBOOT_METHOD_ERR
    end
    utils_core.chmod_s(path, utils.S_IRUSR | utils.S_IWUSR)
    sec_obj:update_cer_info_db(file_data)

    return prop_def.SECUREBOOT_METHOD_OK
end

function secure_boot_options_service:reset_secure_boot_certificate(ctx, db_type, reset_type, sys_id)
    local sec_obj = self.obj_collection[sys_id]
    if not sec_obj then
        bs_util.record_operation(ctx, sys_id, string.format("Reset BIOS SecureBoot %s failed", db_type))
        error_process(prop_def.SECUREBOOT_METHOD_INVALID_PARAM)
    end

    local ret = check_bios_startup_state(sys_id)
    if ret ~= prop_def.SECUREBOOT_METHOD_OK then
        bs_util.record_operation(ctx, sys_id, string.format("Reset BIOS SecureBoot %s failed", db_type))
        error_process(ret)
    end
    if db_type ~= prop_def.INTERACT_KEY_SECUREBOOT_DB and db_type ~= prop_def.INTERACT_KEY_SECUREBOOT_DBX then
        bs_util.record_operation(ctx, sys_id, string.format("Reset BIOS SecureBoot %s failed", db_type))
        error(base_messages.PropertyValueFormatError(db_type, "Database"))
    end
    if reset_type ~= prop_def.CERT_OPERATION_DELETE and reset_type ~= prop_def.CERT_OPERATION_RESET then
        bs_util.record_operation(ctx, sys_id, string.format("Reset BIOS SecureBoot %s failed", db_type))
        error(base_messages.PropertyValueFormatError("******", "ResetKeysType"))
    end
    local added_json = makeup_reset_item(reset_type)
    local ok, res = pcall(function()
        return reset_to_file(sec_obj.new_secureboot, added_json, db_type, sec_obj)
    end)

    if not ok or res ~= prop_def.SECUREBOOT_METHOD_OK then
        bs_util.record_operation(ctx, sys_id, string.format("Reset BIOS SecureBoot %s failed", db_type))
        error_process(res)
    end
    bs_util.record_operation(ctx, sys_id, string.format("Reset BIOS SecureBoot %s successfully", db_type))
    return res
end

-- 获取证书issuer的公共信息
local function get_issuer_common_parts(cert_detail)
    local issuer_json = {}
    issuer_json[prop_def.CERT_DETAILS_COUNTRY] = cert_detail['IssuerCountry']
    issuer_json[prop_def.CERT_DETAILS_STATE] = cert_detail['IssuerState']
    issuer_json[prop_def.CERT_DETAILS_CITY] = cert_detail['IssuerLocation']
    issuer_json[prop_def.CERT_DETAILS_ORGANIZATION] = cert_detail['IssuerOrgnization']
    issuer_json[prop_def.CERT_DETAILS_ORGANIZATION_UNIT] = cert_detail['IssuerOrgnizationUnit']
    issuer_json[prop_def.CERT_DETAILS_COMMON_NAME] = cert_detail['IssuerCommonName']
    issuer_json[prop_def.CERT_DETAILS_EMAIL] = cert_detail['IssuerEmail']
    return issuer_json
end
-- 获取证书subject的公共信息
local function get_subject_common_parts(cert_detail)
    local subject_json = {}
    subject_json[prop_def.CERT_DETAILS_COUNTRY] = cert_detail['SubjectCountry']
    subject_json[prop_def.CERT_DETAILS_STATE] = cert_detail['SubjectState']
    subject_json[prop_def.CERT_DETAILS_CITY] = cert_detail['SubjectLocation']
    subject_json[prop_def.CERT_DETAILS_ORGANIZATION] = cert_detail['SubjectOrgnization']
    subject_json[prop_def.CERT_DETAILS_ORGANIZATION_UNIT] = cert_detail['SubjectOrgnizationUnit']
    subject_json[prop_def.CERT_DETAILS_COMMON_NAME] = cert_detail['SubjectCommonName']
    subject_json[prop_def.CERT_DETAILS_EMAIL] = cert_detail['SubjectEmail']
    return subject_json
end

-- 根据SSL接口返回的Usage值，转换成字符串
local function parse_cert_usage(keyusage)
    local keyusage_str = {}
    local key_pair = {
        [prop_def.KU_DIGITAL_SIGNATURE] = prop_def.CERT_DETAILS_KEY_USAGE_DIGITAL_SIGNATURE,
        [prop_def.KU_NON_REPUDIATION] = prop_def.CERT_DETAILS_KEY_USAGE_NON_REPUDIATION,
        [prop_def.KU_KEY_ENCIPHERMENT] = prop_def.CERT_DETAILS_KEY_USAGE_KEY_ENCIPHERMENT,
        [prop_def.KU_DATA_ENCIPHERMENT] = prop_def.CERT_DETAILS_KEY_USAGE_DATA_ENCIPHERMENT,
        [prop_def.KU_KEY_AGREEMENT] = prop_def.CERT_DETAILS_KEY_USAGE_KEY_AGREEMENT,
        [prop_def.KU_KEY_CERT_SIGN] = prop_def.CERT_DETAILS_KEY_USAGE_KEY_CERT_SIGN,
        [prop_def.KU_CRL_SIGN] = prop_def.CERT_DETAILS_KEY_USAGE_CRL_SGINING,
        [prop_def.KU_ENCIPHER_ONLY] = prop_def.CERT_DETAILS_KEY_USAGE_ENCIPHER_ONLY,
        [prop_def.KU_DECIPHER_ONLY] = prop_def.CERT_DETAILS_KEY_USAGE_DECIPHER_ONLY
    }
    for k, v in pairs(key_pair) do
        if k & keyusage ~= 0 then
            table.insert(keyusage_str, v)
        end
    end
    return keyusage_str
end

local function parse_cert_info(cert_array)
    local formatted_array = {}
    local single = {}
    for _, cert in pairs(cert_array) do
        if cert[prop_def.INTERACT_KEY_CERTIFICATE_STRING] ~= nil then
            single[prop_def.INTERACT_KEY_CERTIFICATE_STRING] = cert[prop_def.INTERACT_KEY_CERTIFICATE_STRING]
            single[prop_def.INTERACT_KEY_CERTIFICATE_TYPE] = cert[prop_def.INTERACT_KEY_CERTIFICATE_TYPE]
            single[prop_def.INTERACT_KEY_UEFI_SIGNATUREOWNER] = cert[prop_def.INTERACT_KEY_UEFI_SIGNATUREOWNER]
            local cert_detail = cert_core.get_cert_info_from_mem(cert[prop_def.INTERACT_KEY_CERTIFICATE_STRING])
            single[prop_def.CERT_DETAILS_ISSUER] = get_issuer_common_parts(cert_detail)
            single[prop_def.CERT_DETAILS_SUBJECT] = get_subject_common_parts(cert_detail)
            single[prop_def.CERT_DETAILS_VALID_NOT_BEFORE] = os.date('%b %d %Y UTC', cert_detail['ValidNotBefore'])
            single[prop_def.CERT_DETAILS_VALID_NOT_AFTER] = os.date('%b %d %Y UTC', cert_detail['ValidNotAfter'])
            if cert_detail['KeyUsage'] ~= nil and cert_detail['KeyUsage'] ~= 0 then
                single[prop_def.CERT_DETAILS_KEY_USAGE] = parse_cert_usage(cert_detail['KeyUsage'])
            end
            single[prop_def.CERT_DETAILS_SERIAL_NUMBER] = cert_detail['SerialNumber']
            single[prop_def.CERT_DETAILS_FINGER_PRINT] = cert_detail['Fingerprint']
            single[prop_def.CERT_DETAILS_FINGER_PRINT_HASH_ALGO] = 'TPM_ALG_SHA256'
            single[prop_def.CERT_DETAILS_SIGNATURE_ALGO] = cert_detail['SignatureAlgorithm']
            table.insert(formatted_array, single)
        end
    end
    return formatted_array
end

-- 将bios上报的文件里的信息转换成目标格式
local function transform_certificate_json(current_json)
    local target_json = {}
    if current_json[prop_def.INTERACT_KEY_SECUREBOOT] == nil or
        current_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES] == nil or
        current_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][prop_def.INTERACT_KEY_SECUREBOOT_DB] == nil or
        current_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][prop_def.INTERACT_KEY_SECUREBOOT_DBX] == nil then
        log:error("invalid active certificate json")
        return nil
    end
    target_json[prop_def.INTERACT_KEY_SECUREBOOT] = {}
    target_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DB] = {}
    target_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DBX] = {}
    target_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DB] =
        parse_cert_info(current_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][prop_def.INTERACT_KEY_SECUREBOOT_DB][prop_def.INTERACT_KEY_CERTIFICATES])
    target_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DBX] =
        parse_cert_info(current_json[prop_def.INTERACT_KEY_SECUREBOOT][prop_def.INTERACT_KEY_SECUREBOOT_DATABASES][prop_def.INTERACT_KEY_SECUREBOOT_DBX][prop_def.INTERACT_KEY_CERTIFICATES])
    return target_json
end

-- 获取已生效证书的详细信息，按要求格式返回
function secure_boot_options_service:get_secure_boot_certificate(ctx, sys_id)
    local sec_obj = self.obj_collection[sys_id]
    if not sec_obj then
        log:debug('[bios]get secure boot certificate: sys_id(%s) invalid', sys_id)
        return
    end

    local ret = check_bios_startup_state(sys_id)
    if ret ~= prop_def.SECUREBOOT_METHOD_OK then
        log:debug('[bios]get secure boot certificate: state invalid')
        return
    end
    if not vos.get_file_accessible(sec_obj.current_secureboot) then
        log:debug('[bios]get secure boot certificate: file no accessible')
        return
    end
    local current_json = bs_util.get_file_json(sec_obj.current_secureboot)
    if current_json == nil then
        log:debug('[bios]get secure boot certificate: file json is nil')
        return
    end
    local target_json = transform_certificate_json(current_json)
    if target_json == nil then
        error_process(prop_def.SECUREBOOT_METHOD_ERR)
    end
    local target_str = json.encode(target_json)
    if target_str == nil then 
        error_process(prop_def.SECUREBOOT_METHOD_ERR)
    end
    return target_str
end

function secure_boot_options_service:set_dessaert_status(sys_id)
    local status = self:get_prop(prop_def.CERT_STATUS, sys_id)
    if status == prop_def.ASSERT_STATUS then
        log:notice('[bios]set cert status to deassert')
        log:running(log.RLOG_INFO,
            'BIOS Secure Boot Certificate is about to expire or has expired')
        return self:set_prop(prop_def.CERT_STATUS, prop_def.DEASSERT_STATUS, sys_id)
    end
    return true
end

function secure_boot_options_service:set_assert_status(sys_id)
    local status = self:get_prop(prop_def.CERT_STATUS, sys_id)
    if status == prop_def.DEASSERT_STATUS then
        log:notice('[bios]set cert status to assert')
        log:running(log.RLOG_INFO,
            'BIOS Secure Boot Certificate expired alarm deasserted')
        return self:set_prop(prop_def.CERT_STATUS, prop_def.ASSERT_STATUS, sys_id)
    end
    return true
end

-- 证书告警
function secure_boot_options_service:set_certificate_satus(req, ctx)
    local sys_id = ctx.HostId or 1

    if not self:get_prop(prop_def.CERT_STATUS, sys_id) then
        log:error('[bios]set certificate satus: invalid param.sys_id:%s', sys_id)
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set BIOS certificate overdue status failed")
        return comp_code.UnspecifiedError, 0x0007DB
    end
    local status = req.Status
    if status ~= prop_def.DEASSERT_STATUS and
        status ~= prop_def.ASSERT_STATUS then
        log:error('[bios]set certificate satus: status invalid.')
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set BIOS certificate overdue status failed")
        return comp_code.InvalidFieldRequest, 0x0007DB
    end

    local res
    if status == prop_def.DEASSERT_STATUS then
        res = self:set_dessaert_status(sys_id)
    else
        res = self:set_assert_status(sys_id)
    end

    if not res then
        log:error('[bios]set certificate satus: set bios certificate overdue status failed.')
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set BIOS certificate overdue status failed")
        return comp_code.UnspecifiedError, 0x0007DB
    end
    ipmi.ipmi_operation_log(ctx, 'BIOS', "Set BIOS certificate overdue status successfully")
    return comp_code.Success, 0x0007DB
end

return singleton(secure_boot_options_service)