|
| 1 | +import os |
| 2 | +import subprocess |
| 3 | +import tkinter as tk |
| 4 | +from tkinter import messagebox, simpledialog |
| 5 | +from datetime import datetime |
| 6 | +import hashlib |
| 7 | +import webbrowser |
| 8 | +from ruamel.yaml import YAML |
| 9 | + |
| 10 | +yaml = YAML() |
| 11 | +yaml.preserve_quotes = True # 保留引号 |
| 12 | + |
| 13 | +# 加载配置文件 |
| 14 | +def load_config(): |
| 15 | + with open("Config.yaml", "r") as file: |
| 16 | + config = yaml.load(file) |
| 17 | + return config |
| 18 | + |
| 19 | +# 加载密码文件 |
| 20 | +def load_passwords(): |
| 21 | + if os.path.exists("Password.yaml"): |
| 22 | + with open("Password.yaml", "r") as file: |
| 23 | + passwords = yaml.load(file) |
| 24 | + if passwords is None: |
| 25 | + passwords = {} |
| 26 | + else: |
| 27 | + passwords = {} |
| 28 | + return passwords |
| 29 | + |
| 30 | +# 保存密码 |
| 31 | +def save_password(pfx_file, password): |
| 32 | + passwords = load_passwords() |
| 33 | + |
| 34 | + if os.path.exists("Password.yaml"): |
| 35 | + with open("Password.yaml", "r") as file: |
| 36 | + yaml_data = yaml.load(file) |
| 37 | + if yaml_data is None: |
| 38 | + yaml_data = {} |
| 39 | + else: |
| 40 | + yaml_data = {} |
| 41 | + |
| 42 | + yaml_data.update(passwords) # 更新而不是覆盖 |
| 43 | + yaml_data[pfx_file] = password # 添加新的密码项 |
| 44 | + |
| 45 | + with open("Password.yaml", "w") as file: |
| 46 | + yaml.dump(yaml_data, file) |
| 47 | + |
| 48 | +# 删除指定 PFX 文件的密码 |
| 49 | +def delete_password(pfx_file): |
| 50 | + passwords = load_passwords() |
| 51 | + |
| 52 | + if pfx_file in passwords: |
| 53 | + del passwords[pfx_file] |
| 54 | + |
| 55 | + with open("Password.yaml", "w") as file: |
| 56 | + yaml.dump(passwords, file) |
| 57 | + |
| 58 | +# 读取 SignTool_Config.yaml 文件 |
| 59 | +def load_ps_script_template(): |
| 60 | + with open(os.path.join("PFX", "SignTool_Config.yaml"), "r") as file: |
| 61 | + config = yaml.load(file) |
| 62 | + return config.get("PowerShellScript", "") |
| 63 | + |
| 64 | +# 替换 PowerShell 脚本中的变量 |
| 65 | +def replace_ps_script_variables(template, pfx_name, pfx_subject, pfx_password): |
| 66 | + return template.format(pfx_name=pfx_name, pfx_subject=pfx_subject, pfx_password=pfx_password) |
| 67 | + |
| 68 | +# 创建并执行 PowerShell 脚本 |
| 69 | +def create_and_run_ps_script(ps_script): |
| 70 | + ps_script_path = os.path.join("PFX", "Build.ps1") |
| 71 | + with open(ps_script_path, "w") as file: |
| 72 | + file.write(ps_script) |
| 73 | + |
| 74 | + # 执行 PowerShell 脚本 |
| 75 | + subprocess.run(["powershell", "-ExecutionPolicy", "Bypass", "-File", ps_script_path], check=True) |
| 76 | + |
| 77 | +# 显示主菜单 |
| 78 | +def show_main_menu(window): |
| 79 | + for widget in window.winfo_children(): |
| 80 | + widget.destroy() |
| 81 | + |
| 82 | + label = tk.Label(window, text="Please select an option:", font=("Arial", 14)) |
| 83 | + label.pack(pady=20) |
| 84 | + |
| 85 | + inject_button = tk.Button(window, text="Inject PFX", command=lambda: inject_pfx(window), width=15) |
| 86 | + inject_button.pack(pady=10) |
| 87 | + |
| 88 | + create_button = tk.Button(window, text="Create PFX", command=lambda: create_pfx_menu(window), width=15) |
| 89 | + create_button.pack(pady=10) |
| 90 | + |
| 91 | + about_button = tk.Button(window, text="About", command=show_about, width=15) |
| 92 | + about_button.pack(pady=10) |
| 93 | + |
| 94 | +# 注入 PFX 文件 |
| 95 | +def inject_pfx(window): |
| 96 | + config = load_config() |
| 97 | + pfx_folder = config.get("PFX", "PFX") |
| 98 | + files = [f for f in os.listdir(pfx_folder) if f.endswith(".pfx")] |
| 99 | + |
| 100 | + for widget in window.winfo_children(): |
| 101 | + widget.destroy() |
| 102 | + |
| 103 | + label = tk.Label(window, text="Select a PFX file to inject:", font=("Arial", 14)) |
| 104 | + label.pack(pady=20) |
| 105 | + |
| 106 | + if not files: |
| 107 | + messagebox.showinfo("Info", f"No PFX files found in the folder: {pfx_folder}") |
| 108 | + show_main_menu(window) |
| 109 | + else: |
| 110 | + for file in files: |
| 111 | + button = tk.Button(window, text=file, command=lambda f=file: select_pfx(window, f)) |
| 112 | + button.pack(pady=5) |
| 113 | + |
| 114 | + back_button = tk.Button(window, text="Back", command=lambda: show_main_menu(window), width=15) |
| 115 | + back_button.pack(pady=20) |
| 116 | + |
| 117 | +# 选择 PFX 文件 |
| 118 | +def select_pfx(window, file): |
| 119 | + passwords = load_passwords() |
| 120 | + if file in passwords: |
| 121 | + list_exe_files(window, file) |
| 122 | + else: |
| 123 | + password = simpledialog.askstring("Input", f"Enter password for {file}:", show='*') |
| 124 | + if password: |
| 125 | + save_password(file, password) |
| 126 | + inject_pfx(window) |
| 127 | + |
| 128 | +# 列出所有 EXE 文件 |
| 129 | +def list_exe_files(window, pfx_file): |
| 130 | + for widget in window.winfo_children(): |
| 131 | + widget.destroy() |
| 132 | + |
| 133 | + exe_files = [] |
| 134 | + start_dir = os.getcwd() |
| 135 | + for root, dirs, files in os.walk(start_dir): |
| 136 | + depth = root[len(start_dir):].count(os.sep) |
| 137 | + if depth < 10: # 控制遍历深度 |
| 138 | + for file in files: |
| 139 | + if file.lower().endswith(".exe"): # 忽略大小写 |
| 140 | + exe_files.append(os.path.join(root, file)) |
| 141 | + |
| 142 | + label = tk.Label(window, text="Found .exe files:", font=("Arial", 14)) |
| 143 | + label.pack(pady=20) |
| 144 | + |
| 145 | + if not exe_files: |
| 146 | + no_files_label = tk.Label(window, text="No .exe files found.", font=("Arial", 12)) |
| 147 | + no_files_label.pack(pady=10) |
| 148 | + else: |
| 149 | + for exe in exe_files: |
| 150 | + frame = tk.Frame(window, width=800) # 调整宽度 |
| 151 | + frame.pack(fill="x", padx=10, pady=2) |
| 152 | + |
| 153 | + # 计算 SHA256 哈希 |
| 154 | + sha256_hash = hashlib.sha256() |
| 155 | + with open(exe, "rb") as f: |
| 156 | + for byte_block in iter(lambda: f.read(4096), b""): |
| 157 | + sha256_hash.update(byte_block) |
| 158 | + sha256_digest = sha256_hash.hexdigest() |
| 159 | + |
| 160 | + exe_label = tk.Label(frame, text=exe, font=("Arial", 10), anchor="w", justify="left") |
| 161 | + exe_label.pack(side="left", fill="x", expand=True) |
| 162 | + |
| 163 | + sha256_label = tk.Label(frame, text=sha256_digest, font=("Arial", 10), anchor="e", justify="right") |
| 164 | + sha256_label.pack(side="left", padx=5) |
| 165 | + |
| 166 | + add_button = tk.Button(frame, text="+", width=2, command=lambda e=exe: sign_exe(window, pfx_file, e)) |
| 167 | + add_button.pack(side="left", padx=5) |
| 168 | + |
| 169 | + info_button = tk.Button(frame, text="i", width=2, command=lambda e=exe: show_signature_info(e)) |
| 170 | + info_button.pack(side="right", padx=5) |
| 171 | + |
| 172 | + back_button = tk.Button(window, text="Back", command=lambda: inject_pfx(window), width=15) |
| 173 | + back_button.pack(pady=20) |
| 174 | + |
| 175 | +# 显示 EXE 文件的签名信息 |
| 176 | +def show_signature_info(exe_file): |
| 177 | + config = load_config() |
| 178 | + sign_tool_path = config.get("SignToolPath") |
| 179 | + |
| 180 | + if not sign_tool_path or not os.path.exists(sign_tool_path): |
| 181 | + messagebox.showerror("Error", "SignToolPath not found in Config.yaml or invalid path. Please check the configuration.") |
| 182 | + return |
| 183 | + |
| 184 | + try: |
| 185 | + command = [sign_tool_path, "verify", "/pa", exe_file] |
| 186 | + result = subprocess.run(command, capture_output=True, text=True) |
| 187 | + if result.returncode == 0: |
| 188 | + messagebox.showinfo("Signature Info", result.stdout) |
| 189 | + else: |
| 190 | + messagebox.showinfo("No Signature", "No valid signature found.") |
| 191 | + except Exception as e: |
| 192 | + messagebox.showerror("Error", f"An error occurred: {str(e)}") |
| 193 | + |
| 194 | +# 签名 EXE 文件 |
| 195 | +def sign_exe(window, pfx_file, exe_file): |
| 196 | + config = load_config() |
| 197 | + sign_tool_path = config.get("SignToolPath") |
| 198 | + timestamp_url = config.get("TimeStampUrl") |
| 199 | + |
| 200 | + if not sign_tool_path or not timestamp_url: |
| 201 | + messagebox.showerror("Error", "SignToolPath or TimeStampUrl not found in Config.yaml. Please check the configuration.") |
| 202 | + return |
| 203 | + |
| 204 | + passwords = load_passwords() |
| 205 | + password = passwords.get(pfx_file) |
| 206 | + |
| 207 | + try: |
| 208 | + # 直接使用 PFX 文件和密码进行签名 |
| 209 | + command = [ |
| 210 | + sign_tool_path, |
| 211 | + "sign", |
| 212 | + "/f", os.path.join(config["PFX"], pfx_file), |
| 213 | + "/p", password, |
| 214 | + "/tr", timestamp_url, |
| 215 | + "/td", "sha256", |
| 216 | + "/fd", "sha256", |
| 217 | + exe_file |
| 218 | + ] |
| 219 | + subprocess.run(command, check=True) |
| 220 | + |
| 221 | + # 获取当前时间 |
| 222 | + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| 223 | + |
| 224 | + # 签名成功提示 |
| 225 | + messagebox.showinfo("Signed Successfully", f""" |
| 226 | + {os.path.basename(exe_file)} |
| 227 | + {exe_file} |
| 228 | + {pfx_file} |
| 229 | + {current_time} |
| 230 | + """) |
| 231 | + |
| 232 | + except subprocess.CalledProcessError: |
| 233 | + messagebox.showerror("Error", "Signing failed. Please check the password and try again.") |
| 234 | + delete_password(pfx_file) |
| 235 | + select_pfx(window, pfx_file) |
| 236 | + |
| 237 | +# 使用 PowerShell 创建 PFX 证书页面 |
| 238 | +def create_pfx_menu(window): |
| 239 | + config = load_config() |
| 240 | + pfx_folder = config.get("PFX") |
| 241 | + |
| 242 | + if not pfx_folder: |
| 243 | + messagebox.showerror("Error", "PFX storage path not found in Config.yaml. Please check the configuration.") |
| 244 | + return |
| 245 | + |
| 246 | + for widget in window.winfo_children(): |
| 247 | + widget.destroy() |
| 248 | + |
| 249 | + label = tk.Label(window, text="Create a new PFX certificate:", font=("Arial", 14)) |
| 250 | + label.pack(pady=20) |
| 251 | + |
| 252 | + tk.Label(window, text="PFX File Name:", font=("Arial", 12)).pack(pady=5) |
| 253 | + pfx_name_entry = tk.Entry(window, width=30) |
| 254 | + pfx_name_entry.pack(pady=5) |
| 255 | + |
| 256 | + tk.Label(window, text="PFX Display Name:", font=("Arial", 12)).pack(pady=5) |
| 257 | + pfx_display_name_entry = tk.Entry(window, width=30) |
| 258 | + pfx_display_name_entry.pack(pady=5) |
| 259 | + |
| 260 | + tk.Label(window, text="PFX Password:", font=("Arial", 12)).pack(pady=5) |
| 261 | + pfx_password_entry = tk.Entry(window, show='*', width=30) |
| 262 | + pfx_password_entry.pack(pady=5) |
| 263 | + |
| 264 | + def validate_and_create_pfx(): |
| 265 | + pfx_name = pfx_name_entry.get().strip() |
| 266 | + pfx_display_name = pfx_display_name_entry.get().strip() |
| 267 | + pfx_password = pfx_password_entry.get().strip() |
| 268 | + |
| 269 | + if not pfx_name or not pfx_display_name or not pfx_password: |
| 270 | + messagebox.showerror("Error", "All fields are required.") |
| 271 | + return |
| 272 | + |
| 273 | + if any(char in pfx_name for char in r'\/:*?"<>|'): |
| 274 | + messagebox.showerror("Error", "PFX file name contains invalid characters.") |
| 275 | + return |
| 276 | + |
| 277 | + if not os.path.exists(pfx_folder): |
| 278 | + os.makedirs(pfx_folder) |
| 279 | + |
| 280 | + # 加载 PowerShell 脚本模板 |
| 281 | + ps_script_template = load_ps_script_template() |
| 282 | + |
| 283 | + # 替换模板中的变量 |
| 284 | + ps_script = replace_ps_script_variables(ps_script_template, pfx_name, pfx_display_name, pfx_password) |
| 285 | + |
| 286 | + try: |
| 287 | + # 生成并执行 PowerShell 脚本 |
| 288 | + create_and_run_ps_script(ps_script) |
| 289 | + |
| 290 | + # 保存密码 |
| 291 | + save_password(f"{pfx_name}.pfx", pfx_password) |
| 292 | + |
| 293 | + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| 294 | + timestamp_url = config.get("TimeStampUrl", "") |
| 295 | + |
| 296 | + # 创建成功提示 |
| 297 | + messagebox.showinfo("PFX Created Successfully", f""" |
| 298 | + {pfx_name}.pfx |
| 299 | + {pfx_display_name} |
| 300 | + {timestamp_url} |
| 301 | + {current_time} |
| 302 | + """) |
| 303 | + |
| 304 | + show_main_menu(window) |
| 305 | + |
| 306 | + except subprocess.CalledProcessError: |
| 307 | + messagebox.showerror("Error", "Failed to create the PFX file. Please check the configuration.") |
| 308 | + |
| 309 | + cancel_button = tk.Button(window, text="Cancel", command=lambda: show_main_menu(window), width=15) |
| 310 | + cancel_button.pack(side="left", padx=10, pady=20) |
| 311 | + |
| 312 | + create_button = tk.Button(window, text="Create", command=validate_and_create_pfx, width=15) |
| 313 | + create_button.pack(side="right", padx=10, pady=20) |
| 314 | + |
| 315 | +# 关于 |
| 316 | +def show_about(): |
| 317 | + webbrowser.open("https://github.com/Canmi21/AutoPFX") |
| 318 | + messagebox.showinfo("About AutoPFX", """ |
| 319 | + AutoPFX GUI 2024 X Github.com |
| 320 | + OpenSource Software MIT license. |
| 321 | + Copyright (C) Canmi(Canmi21), all right reserved. |
| 322 | + """) |
| 323 | + |
| 324 | +# 程序入口 |
| 325 | +def main(): |
| 326 | + window = tk.Tk() |
| 327 | + window.title("AutoPFX Command Interface") |
| 328 | + window.geometry("800x600") |
| 329 | + |
| 330 | + show_main_menu(window) |
| 331 | + |
| 332 | + window.mainloop() |
| 333 | + |
| 334 | +if __name__ == "__main__": |
| 335 | + main() |
0 commit comments