#!/bin/bash
# 新增内网环境下 DKMS 自定义签名逻辑
# **签名路径选择逻辑**：

# prepare_signing() 被调用
#     │
#     ├── 检测到 /etc/dkms/token 文件
#     │   └── 使用 dkms-custom-sign-file（自定义签名平台）
#     │       ├── 读取 token 中的 API 地址和证书 ID
#     │       ├── 计算模块 hash 并请求签名平台
#     │       └── 将 PKCS#7 签名附加到内核模块
#     │
#     └── 未检测到 token 文件
#         └── 继续执行原生 DKMS 签名流程（MOK 密钥签名）

# 使用方式

# 内部机器（自定义签名平台）

#   1. 申请 token：Tmaneger 签名平台
#   2. 放置 token 文件：
#       sudo tee /etc/dkms/token < your-token.json
#       sudo chmod 600 /etc/dkms/token

#   3. 安装 dkms 后，后续所有 DKMS 模块构建会自动使用自定义签名平台

# 外部机器（原生 MOK 签名）

#   无需任何额外配置。没有 token 文件时，dkms 自动使用原生签名流程（MOK 密钥），与上游行为一致。


set -e

LOG_FILE="${DKMS_CUSTOM_SIGN_LOG:-/var/log/dkms-custom-sign.log}"
# 日志函数
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

log_error() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2
}

# 配置项
TOKEN_FILE="/etc/dkms/token"

# 查找 sign-file 工具（用于附加签名到内核模块）
find_sign_file_tool() {
    local kernel_ver
    # 使用当前运行的内核版本
    kernel_ver="$(uname -r)"

    local kernel_scripts_dir="/usr/src/kernels/${kernel_ver}/scripts"
    local kernel_sign_file="${kernel_scripts_dir}/sign-file"
    local kernel_sign_file_c="${kernel_scripts_dir}/sign-file.c"

    if [[ ! -d "${kernel_scripts_dir}" ]]; then
        log_error "kernel-devel 未安装或损坏，尝试执行: dnf install kernel-devel-$(uname -r) -y"
        return 1
    fi

    is_elf() {
        if command -v file >/dev/null 2>&1; then
            file -b "$1" 2>/dev/null | grep -q "ELF"
            return $?
        fi
        if command -v readelf >/dev/null 2>&1; then
            readelf -h "$1" >/dev/null 2>&1
            return $?
        fi
        return 1
    }

    if [[ ! -f "${kernel_sign_file}" ]] || ! is_elf "${kernel_sign_file}"; then
        # 注意: 本函数通过 stdout 返回路径，所有日志必须重定向到 stderr，避免污染返回值
        log "sign-file 不是可执行的 ELF 文件，从源码编译: ${kernel_sign_file_c}" >&2
        if [[ ! -f "${kernel_sign_file_c}" ]]; then
            log_error "找不到 sign-file 源码: ${kernel_sign_file_c}, kernel-devel 未安装或损坏，尝试执行: dnf install kernel-devel-$(uname -r) -y"
            return 1
        fi

        local gcc_output
        if ! gcc_output=$(gcc -o "${kernel_sign_file}" "${kernel_sign_file_c}" -lcrypto 2>&1); then
            [[ -n "$gcc_output" ]] && echo "$gcc_output" >> "$LOG_FILE"
            log_error "编译 sign_file 失败: ${kernel_sign_file_c}"
            return 1
        fi
        [[ -n "$gcc_output" ]] && echo "$gcc_output" >> "$LOG_FILE"

        chmod +x "${kernel_sign_file}" 2>/dev/null || true
        log "已重新编译 sign_file: ${kernel_sign_file}" >&2
    fi

    echo "${kernel_sign_file}"
    return 0
}

# 使用自定义签名平台签名
custom_sign() {
    log "使用自定义签名平台签名模块: ${MODULE_PATH}"
    
    # 1. 验证模块文件存在
    if [[ ! -f "${MODULE_PATH}" ]]; then
        log_error "模块文件不存在: ${MODULE_PATH}"
        return 1
    fi
    
    # 2. 读取 token 并解析配置
    if [[ ! -f "$TOKEN_FILE" ]]; then
        log_error "Token 文件不存在: $TOKEN_FILE"
        return 1
    fi
    
    local token
    token=$(<"$TOKEN_FILE")
    if [[ -z "$token" ]]; then
        log_error "Token 文件为空"
        return 1
    fi

    # 从 token 文件中解析签名配置
    local sign_hash signing_api_url certificate_id
    if command -v jq >/dev/null 2>&1; then
        sign_hash=$(jq -r '.hash_algo' < "$TOKEN_FILE")
        signing_api_url=$(jq -r '.url' < "$TOKEN_FILE")
        certificate_id=$(jq -r '.uuid' < "$TOKEN_FILE")
    else
        sign_hash=$(grep -o '"hash_algo":"[^"]*"' "$TOKEN_FILE" | sed 's/"hash_algo":"//;s/"$//')
        signing_api_url=$(grep -o '"url":"[^"]*"' "$TOKEN_FILE" | sed 's/"url":"//;s/"$//')
        certificate_id=$(grep -o '"uuid":"[^"]*"' "$TOKEN_FILE" | sed 's/"uuid":"//;s/"$//')
    fi

    if [[ -z "$sign_hash" || "$sign_hash" == "null" ]]; then
        log_error "Token 文件中缺少 hash_algo 字段"
        return 1
    fi
    if [[ -z "$signing_api_url" || "$signing_api_url" == "null" ]]; then
        log_error "Token 文件中缺少 url 字段"
        return 1
    fi
    if [[ -z "$certificate_id" || "$certificate_id" == "null" ]]; then
        log_error "Token 文件中缺少 uuid 字段"
        return 1
    fi

    log "签名配置: hash_algo=$sign_hash, api_url=$signing_api_url, uuid=$certificate_id"
    
    # 3. 计算模块 hash
    log "计算模块 hash (算法: sha256)..."
    local kmod_hash
    kmod_hash=$(sha256sum "${MODULE_PATH}" | awk '{print $1}')

    log "模块 hash: $kmod_hash"

    # 5. 调用签名平台 API
    log "请求签名平台..."
    local temp_response
    temp_response=$(mktemp)
    
    local http_code
    http_code=$(curl -s -w "%{http_code}" -o "$temp_response" \
        -X POST \
        -H "Content-Type: application/json" \
        -d "{\"data\": \"${kmod_hash}\", \"certificate_id\": \"${certificate_id}\", \"data_type\": \"HASH_VALUE\", \"hash_algorithm\": \"SHA256\"}" \
        "${signing_api_url}/certificates/sign")
        
    if [[ "$http_code" != "200" ]]; then
        log_error "签名平台返回错误: HTTP $http_code"
        echo "--- 签名平台响应内容 ---" >> "$LOG_FILE"
        cat "$temp_response" >> "$LOG_FILE"
        echo "" >> "$LOG_FILE"
        rm -f "$temp_response"
        return 1
    fi
    
    # 5. 提取 PKCS#7 签名
    log "提取签名数据..."
    local pkcs7_signature
    
    # 假设 API 返回 JSON: {"signature": "base64-encoded-pkcs7", "status": "success"}
    # 根据您的实际 API 调整
    if command -v jq >/dev/null 2>&1; then
        pkcs7_signature=$(jq -r '.pkcs7_detached' < "$temp_response")
    else
        # 简单的 grep/sed 方案（不推荐用于生产）
        pkcs7_signature=$(grep -o '"pkcs7_detached":"[^"]*"' "$temp_response" | sed 's/"pkcs7_detached":"//;s/"$//')
    fi

    if [[ -z "$pkcs7_signature" || "$pkcs7_signature" == "null" ]]; then
        log_error "无法从响应中提取签名"
        echo "--- 签名平台响应内容 ---" >> "$LOG_FILE"
        cat "$temp_response" >> "$LOG_FILE"
        echo "" >> "$LOG_FILE"
        rm -f "$temp_response"
        return 1
    fi
    
    rm -f "$temp_response"
    
    # 6. 保存签名到临时文件
    local temp_sig
    temp_sig=$(mktemp --suffix=.pem)
    {
        echo "-----BEGIN PKCS7-----"
        echo "$pkcs7_signature"
        echo "-----END PKCS7-----"
    } > "$temp_sig"

    openssl pkcs7 -inform PEM -outform DER -in "$temp_sig" -out "$temp_sig.der"
    
    if [[ ! -s "$temp_sig" ]]; then
        log_error "签名文件创建失败"
        rm -f "$temp_sig"
        return 1
    fi
    
    # 7. 使用内核 sign-file -s 附加签名到模块
    log "附加签名到模块..."
    
    # 查找 sign-file 工具
    local sign_file_tool
    if ! sign_file_tool=$(find_sign_file_tool); then
        log_error "找不到 sign-file 工具"
        rm -f "$temp_sig"
        return 1
    fi
    
    log "使用 sign-file 工具: $sign_file_tool"
    
    # 使用 -s 参数附加已有签名
    # 用法: sign_file -s <raw_sig> <hash_algo> <cert> <module>
    local sign_output
    if ! sign_output=$("$sign_file_tool" -s "$temp_sig.der" "$sign_hash" "noused" "${MODULE_PATH}" 2>&1); then
        [[ -n "$sign_output" ]] && echo "$sign_output" >> "$LOG_FILE"
        log_error "附加签名失败"
        rm -f "$temp_sig.der"
        return 1
    fi
    [[ -n "$sign_output" ]] && echo "$sign_output" >> "$LOG_FILE"
    
    rm -f "$temp_sig.der"
    
    log "模块签名成功: ${MODULE_PATH}"

    return 0
}

# 主逻辑
main() {
    if [[ $# -ne 4 ]]; then
        log_error "参数错误: 需要 4 个参数 (hash_algo key cert module_path)"
        echo "用法: $0 hash_algo key cert module_path" >&2
        exit 1
    fi
    MODULE_PATH="$4"

    mkdir -p "$(dirname "$LOG_FILE")"

    log "========== DKMS 自定义签名开始 =========="
    log "Token 文件: $TOKEN_FILE ($([ -f "$TOKEN_FILE" ] && echo '存在' || echo '不存在'))"
    log "模块路径: ${MODULE_PATH}"

    if [[ ! -f "$TOKEN_FILE" ]]; then
        log_error "Token 文件不存在: $TOKEN_FILE"
        log_error "dkms-custom-sign-file 仅用于自定义签名平台，需要 token 文件"
        log_error ""
        log_error "申请地址: 参考帮助文档"
        log_error "放置命令: sudo tee $TOKEN_FILE < your-token.json && sudo chmod 600 $TOKEN_FILE"
        exit 1
    fi

    local help_url
    help_url=$(jq -r '.help_url // empty' < "$TOKEN_FILE" 2>/dev/null)

    log "检测到 token 文件，使用内部自定义签名平台"
    if custom_sign; then
        log "========== 自定义签名成功 =========="
        exit 0
    else
        log_error "========== 自定义签名失败 =========="
        log_error ""
        log_error "排查步骤："
        log_error "  1. 检查 token 文件是否有效: cat $TOKEN_FILE | jq ."
        log_error "  2. 检查网络是否可达签名平台: curl -s -o /dev/null -w '%{http_code}' \$(jq -r '.url' $TOKEN_FILE)/health"
        log_error "  3. 检查 token 是否过期，如需重新申请:"
        log_error "     申请地址: 参考帮助文档"
        [[ -n "$help_url" ]] && log_error "     帮助链接: $help_url"
        log_error "     放置命令: sudo tee $TOKEN_FILE < your-token.json && sudo chmod 600 $TOKEN_FILE"
        log_error "  4. 检查 kernel-devel 是否安装: rpm -q kernel-devel-\$(uname -r)"
        log_error "  5. 查看详细日志: $LOG_FILE"
        exit 1
    fi
}

main "$@"