#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
鸿蒙 p7b 证书提取工具
跨平台支持：Windows / macOS / Linux
依赖：keytool（JDK）或 openssl
"""

import os
import sys
import re
import subprocess
import shutil
from datetime import datetime


def color_text(text, color_code):
    """终端彩色输出，Windows 10+ 支持 ANSI"""
    if sys.platform == "win32":
        # Windows 启用 ANSI 支持
        os.system("")
    return f"\033[{color_code}m{text}\033[0m"


RED = "0;31"
YELLOW = "1;33"
GREEN = "0;32"
BLUE = "0;34"
CYAN = "0;36"


def prompt_path():
    """交互式输入文件路径"""
    path = input("请输入 .p7b 文件路径: ").strip()
    # 处理引号包裹的路径（Windows 拖拽文件到终端常带引号）
    if (path.startswith('"') and path.endswith('"')) or \
       (path.startswith("'") and path.endswith("'")):
        path = path[1:-1]
    return path


def validate_file(path):
    """校验文件"""
    if not path:
        print(color_text("❌ 未输入文件路径", RED))
        sys.exit(1)

    if not os.path.isfile(path):
        print(color_text(f"❌ 文件不存在: {path}", RED))
        sys.exit(1)

    _, ext = os.path.splitext(path)
    if ext.lower() != ".p7b":
        print(color_text("❌ 文件类型不正确，请输入 .p7b 后缀的文件", RED))
        sys.exit(1)

    if os.path.getsize(path) == 0:
        print(color_text("❌ 文件为空", RED))
        sys.exit(1)

    print(color_text(f"✅ 文件检查通过: {path}", GREEN))


def detect_cert_type(content):
    """检测证书类型"""
    if "development-certificate" in content:
        print(f"{color_text('🔍 证书类型:', BLUE)} {color_text('调试证书 (development-certificate)', YELLOW)}")
        return "development-certificate"
    elif "distribution-certificate" in content:
        print(f"{color_text('🔍 证书类型:', BLUE)} {color_text('发布证书 (distribution-certificate)', GREEN)}")
        return "distribution-certificate"
    else:
        print(color_text("❌ 未在文件中找到 development-certificate 或 distribution-certificate 字段", RED))
        print(color_text("请确认该文件是否为有效的鸿蒙签名 .p7b 文件", YELLOW))
        sys.exit(1)


def extract_metadata(text):
    """从 p7b 内嵌 JSON 提取元数据并打印"""
    def ext(pattern, default=""):
        m = re.search(pattern, text)
        return m.group(1) if m else default

    bundle_name = ext(r'"bundle-name"\s*:\s*"([^"]*)"')
    app_id = ext(r'"app-identifier"\s*:\s*"([^"]*)"')
    dev_id = ext(r'"developer-id"\s*:\s*"([^"]*)"')
    app_feature = ext(r'"app-feature"\s*:\s*"([^"]*)"')
    version_name = ext(r'"version-name"\s*:\s*"([^"]*)"')
    version_code = ext(r'"version-code"\s*:\s*(\d+)')
    uuid_val = ext(r'"uuid"\s*:\s*"([^"]*)"')

    # 有效期时间戳 → 可读日期
    not_before = ext(r'"not-before"\s*:\s*(\d+)')
    not_after = ext(r'"not-after"\s*:\s*(\d+)')
    validity_from = datetime.fromtimestamp(int(not_before)).strftime("%Y-%m-%d %H:%M:%S") if not_before else ""
    validity_to = datetime.fromtimestamp(int(not_after)).strftime("%Y-%m-%d %H:%M:%S") if not_after else ""

    # 调试设备 UDID 列表
    devices_match = re.findall(r'"device-ids"\s*:\s*\[([^\]]*)\]', text)
    debug_devices = []
    if devices_match:
        debug_devices = re.findall(r'"([^"]*)"', devices_match[0])

    # 显示
    print(color_text("════════════════════════════════════════════", CYAN))
    print(color_text("             应用签名信息", CYAN))
    print(color_text("════════════════════════════════════════════", CYAN))
    print()
    if bundle_name:   print(f"📦 {color_text('Bundle Name:', GREEN)}    {bundle_name}")
    if app_id:        print(f"🆔 {color_text('App Identifier:', GREEN)} {app_id}")
    if dev_id:        print(f"👤 {color_text('Developer ID:', GREEN)}   {dev_id}")
    if app_feature:   print(f"📋 {color_text('App Feature:', GREEN)}    {app_feature}")
    if version_name:  print(f"📌 {color_text('Version:', GREEN)}        {version_name} (code: {version_code})")
    if uuid_val:      print(f"🔗 {color_text('UUID:', GREEN)}          {uuid_val}")
    if validity_from: print(f"📅 {color_text('有效期:', GREEN)}        {validity_from} → {validity_to}")

    if debug_devices:
        print()
        print(color_text("📱 授权调试设备 UDID:", BLUE))
        for d in debug_devices:
            print(f"   {d}")
    print()


def read_p7b_text(path):
    """
    读取 p7b 文件中的可读文本。
    二进制 p7b 内嵌 JSON，用 errors='ignore' 跳过不可解码字节。
    """
    with open(path, "rb") as f:
        raw = f.read()
    # 将换行统一为空格，保证整段 JSON 在一行里
    text = raw.decode("utf-8", errors="ignore").replace("\n", " ").replace("\r", " ")
    return text


def extract_cert_text(content, cert_type, p7b_path):
    """从文本中提取证书内容"""
    # 匹配 cert_type 字段后的 BEGIN...END 块
    pattern = re.compile(
        rf'{re.escape(cert_type)}.*?(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)',
        re.DOTALL
    )
    match = pattern.search(content)
    if match:
        return match.group(1)

    # 未匹配到，尝试 openssl 提取（标准 PKCS#7 格式）
    if shutil.which("openssl"):
        print(color_text("⚠️ 文本中未找到证书标记，尝试使用 openssl 解析...", YELLOW))
        for fmt in ["PEM", "DER"]:
            try:
                result = subprocess.run(
                    ["openssl", "pkcs7", "-inform", fmt, "-in", p7b_path, "-print_certs"],
                    capture_output=True, text=True
                )
                pem_match = re.search(
                    r'(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)',
                    result.stdout, re.DOTALL
                )
                if pem_match:
                    return pem_match.group(1)
            except Exception:
                pass

    print(color_text("❌ 无法从 .p7b 文件中提取证书内容", RED))
    sys.exit(1)


def build_cer_file(cert_text, cer_path):
    """
    生成 .cer 文件。
    去掉字面量 \n 和所有空白，提取纯 base64，再按 76 字符换行组装标准 PEM。
    """
    # 去掉 BEGIN/END 标记
    body = re.sub(r'-----BEGIN CERTIFICATE-----', '', cert_text)
    body = re.sub(r'-----END CERTIFICATE-----', '', body)
    # 去掉字面量 \n（两个字符 \ 和 n）
    body = body.replace("\\n", "")
    # 去掉所有空白
    body = re.sub(r'\s+', '', body)

    if not body:
        print(color_text("❌ 证书 base64 内容为空", RED))
        sys.exit(1)

    # 按 76 字符换行
    lines = [body[i:i+76] for i in range(0, len(body), 76)]
    pem = "-----BEGIN CERTIFICATE-----\n" + "\n".join(lines) + "\n-----END CERTIFICATE-----\n"

    with open(cer_path, "w", encoding="utf-8") as f:
        f.write(pem)

    print(color_text(f"✅ 证书文件已生成: {cer_path}", GREEN))


def find_keytool():
    """查找 keytool 可执行文件路径（支持 Windows/Mac/Linux）"""
    # 1. 先在 PATH 中查找
    keytool = shutil.which("keytool")
    if keytool:
        return keytool

    # 2. 检查 JAVA_HOME 环境变量
    java_home = os.environ.get("JAVA_HOME")
    if java_home:
        keytool_path = os.path.join(java_home, "bin", "keytool.exe" if sys.platform == "win32" else "keytool")
        if os.path.isfile(keytool_path):
            return keytool_path

    # 3. Windows: 搜索常见 JDK 安装位置
    if sys.platform == "win32":
        search_paths = [
            r"C:\Program Files\Java",
            r"C:\Program Files (x86)\Java",
            r"C:\Program Files\Huawei\DevEco Studio",
            os.path.expanduser(r"~\.jdks"),
        ]
        for base in search_paths:
            if not os.path.isdir(base):
                continue
            # 遍历子目录查找 keytool.exe
            for root, dirs, files in os.walk(base):
                if "keytool.exe" in files:
                    return os.path.join(root, "keytool.exe")
                # 限制搜索深度，避免太慢
                if root.count(os.sep) - base.count(os.sep) > 3:
                    dirs.clear()

    return None


def run_keytool(cer_path):
    """使用 keytool 查询证书指纹"""
    keytool = find_keytool()
    if not keytool:
        return None

    # Windows 中文系统 keytool 输出 GBK 编码，其他系统 UTF-8
    encodings = ["gbk", "utf-8", "latin-1"] if sys.platform == "win32" else ["utf-8", "latin-1"]

    for enc in encodings:
        try:
            result = subprocess.run(
                [keytool, "-printcert", "-file", cer_path],
                capture_output=True, text=True, encoding=enc, errors="replace"
            )
            if result.returncode == 0:
                return result.stdout
        except Exception:
            continue
    return None


def run_openssl(cer_path):
    """使用 openssl 查询证书指纹（keytool 不可用时的降级方案）"""
    if not shutil.which("openssl"):
        return None, None

    try:
        # 输出证书详情
        detail = subprocess.run(
            ["openssl", "x509", "-in", cer_path, "-noout", "-text"],
            capture_output=True, text=True
        ).stdout

        # 获取 SHA256 指纹
        fp_result = subprocess.run(
            ["openssl", "x509", "-in", cer_path, "-noout", "-fingerprint", "-sha256"],
            capture_output=True, text=True
        )
        fingerprint_line = fp_result.stdout.strip()
        return detail, fingerprint_line
    except Exception:
        return None, None


def extract_fingerprint(keytool_out):
    """从 keytool 输出中提取 SHA256 指纹（去掉冒号，64 位小写）"""
    for line in keytool_out.splitlines():
        if "SHA256:" in line:
            fp = line.split("SHA256:")[-1].strip()
            fp = fp.replace(":", "").replace(" ", "").lower()
            return fp
    return None


def main():
    p7b_path = prompt_path()

    print()
    validate_file(p7b_path)
    print()

    # 读取文件文本
    content = read_p7b_text(p7b_path)

    # 检测证书类型
    cert_type = detect_cert_type(content)

    # 提取并显示 JSON 元数据
    extract_metadata(content)
    print()

    # 提取证书文本
    cert_text = extract_cert_text(content, cert_type, p7b_path)

    # 生成 .cer 文件（名称加 _tmp 标识，避免覆盖原文件）
    base, _ = os.path.splitext(p7b_path)
    cer_path = base + "_tmp.cer"
    build_cer_file(cert_text, cer_path)
    print()

    # 查询证书指纹
    print(color_text("════════════════════════════════════════════", CYAN))
    print(color_text("           证书详细信息", CYAN))
    print(color_text("════════════════════════════════════════════", CYAN))
    print()

    keytool_out = run_keytool(cer_path)
    if keytool_out:
        print(f"{color_text('📋 证书详情:', BLUE)}")
        print("─" * 40)
        print(keytool_out.strip())
        print("─" * 40)
        print()

        fingerprint = extract_fingerprint(keytool_out)
        if fingerprint:
            print(color_text(f"✅ SHA256 指纹（64位，无冒号）:", GREEN))
            print(color_text(fingerprint, GREEN))
        else:
            print(color_text("⚠️ 未获取到 SHA256 指纹", YELLOW))
    else:
        # 降级到 openssl
        detail, fp_line = run_openssl(cer_path)
        if detail:
            print(color_text("⚠️ keytool 未安装，使用 openssl 替代查询", YELLOW))
            print("─" * 40)
            print("\n".join(detail.splitlines()[:30]))
            print("─" * 40)
            print()

            if fp_line:
                # 格式: sha256 Fingerprint=XX:XX:XX...
                fp = fp_line.split("=", 1)[-1] if "=" in fp_line else fp_line
                fp = fp.replace(":", "").replace(" ", "").lower()
                print(color_text(f"✅ SHA256 指纹（64位，无冒号）:", GREEN))
                print(color_text(fp, GREEN))
        else:
            print(color_text("❌ 未找到 keytool 或 openssl，请先安装 JDK 或 OpenSSL", RED))
            sys.exit(1)

    print()
    print(color_text("════════════════════════════════════════════", CYAN))
    print(f"📂 .cer 证书文件: {color_text(cer_path, GREEN)}")
    print(color_text("════════════════════════════════════════════", CYAN))


if __name__ == "__main__":
    main()
