mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 15:46:22 +00:00
signing works
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@
|
||||
**/__pycache__/**
|
||||
nxtscape-cli-access.json
|
||||
gclient.json
|
||||
.env
|
||||
|
||||
@@ -10,6 +10,22 @@ import click
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Load .env file if it exists
|
||||
def load_env_file():
|
||||
env_file = Path(__file__).parent.parent / '.env'
|
||||
if env_file.exists():
|
||||
with open(env_file, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
os.environ[key.strip()] = value.strip()
|
||||
print(f"✓ Loaded environment from .env file")
|
||||
|
||||
# Load .env file on import
|
||||
load_env_file()
|
||||
|
||||
# Import shared components
|
||||
from context import BuildContext
|
||||
from utils import load_config, log_info, log_warning, log_error, log_success, IS_MACOS, IS_WINDOWS, IS_LINUX
|
||||
@@ -115,6 +131,7 @@ def build_main(
|
||||
gn_flags_file = None
|
||||
architectures = [arch] if arch else [] # Empty list if no arch specified
|
||||
universal = False
|
||||
certificate_name = None # For Windows signing
|
||||
if config_file:
|
||||
config = load_config(config_file)
|
||||
log_info(f"📄 Loaded config from: {config_file}")
|
||||
@@ -156,6 +173,11 @@ def build_main(
|
||||
config_chromium_src = Path(config["paths"]["chromium_src"])
|
||||
chromium_src = config_chromium_src
|
||||
log_info(f"📁 Using Chromium source from config: {chromium_src}")
|
||||
|
||||
# Get Windows signing certificate name from config
|
||||
if IS_WINDOWS and "signing" in config and "certificate_name" in config["signing"]:
|
||||
certificate_name = config["signing"]["certificate_name"]
|
||||
log_info(f"🔏 Using certificate for signing: {certificate_name}")
|
||||
|
||||
# CLI takes precedence over config
|
||||
if chromium_src_dir:
|
||||
@@ -283,7 +305,11 @@ def build_main(
|
||||
log_info(f"\n🔏 Signing {ctx.architecture} build...")
|
||||
if slack_notifications:
|
||||
notify_build_step(f"[{ctx.architecture}] Started signing")
|
||||
sign(ctx)
|
||||
# Pass certificate_name for Windows signing
|
||||
if IS_WINDOWS:
|
||||
sign(ctx, certificate_name)
|
||||
else:
|
||||
sign(ctx)
|
||||
if slack_notifications:
|
||||
notify_build_step(f"[{ctx.architecture}] Completed signing")
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Nxtscape Windows Release Build Configuration
|
||||
build:
|
||||
type: release
|
||||
architecture: x64 # Windows default architecture
|
||||
architecture: x64 # Windows default architecture
|
||||
# No universal builds on Windows
|
||||
|
||||
gn_flags:
|
||||
file: build/config/gn/flags.windows.release.gn
|
||||
|
||||
steps:
|
||||
clean: true
|
||||
git_setup: true
|
||||
apply_patches: true
|
||||
build: true
|
||||
sign: false # Windows signing requires certificate
|
||||
clean: false
|
||||
git_setup: false
|
||||
apply_patches: false
|
||||
build: false
|
||||
sign: true # Enable signing with eSigner CKA
|
||||
package: true
|
||||
|
||||
paths:
|
||||
@@ -23,16 +23,16 @@ paths:
|
||||
env:
|
||||
PYTHONPATH: scripts
|
||||
|
||||
# Signing configuration (optional - requires code signing certificate)
|
||||
# Signing configuration (using eSigner CKA certificate in Windows store)
|
||||
signing:
|
||||
# certificate_name: "Your Company Name" # Certificate subject name
|
||||
certificate_name: "FELAFAX, INC." # Your certificate subject name from SSL.com
|
||||
# Or use environment variable:
|
||||
# require_env_vars:
|
||||
# - WINDOWS_CERTIFICATE_NAME
|
||||
|
||||
# Notification settings
|
||||
notifications:
|
||||
slack: true # Enable Slack notifications for release builds
|
||||
slack: false # Enable Slack notifications for release builds
|
||||
|
||||
# Build options
|
||||
build_options:
|
||||
|
||||
@@ -4,6 +4,7 @@ Windows packaging module for Nxtscape Browser
|
||||
Based on ungoogled-chromium-windows packaging approach
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import zipfile
|
||||
@@ -161,21 +162,167 @@ def create_portable_zip(ctx: BuildContext) -> bool:
|
||||
|
||||
|
||||
def sign_binaries(ctx: BuildContext, certificate_name: Optional[str] = None) -> bool:
|
||||
"""Sign Windows binaries using signtool"""
|
||||
"""Sign Windows binaries using signtool or eSigner"""
|
||||
log_info("\n🔏 Signing Windows binaries...")
|
||||
|
||||
# Check for signing method from environment or config
|
||||
signing_method = os.environ.get('SIGNING_METHOD', 'signtool').lower()
|
||||
|
||||
# Get paths to sign
|
||||
build_output_dir = join_paths(ctx.chromium_src, ctx.out_dir)
|
||||
|
||||
# List of binaries to sign
|
||||
binaries_to_sign = [
|
||||
build_output_dir / "chrome.exe",
|
||||
build_output_dir / "mini_installer.exe"
|
||||
]
|
||||
|
||||
# Check which binaries exist
|
||||
existing_binaries = []
|
||||
for binary in binaries_to_sign:
|
||||
if binary.exists():
|
||||
existing_binaries.append(binary)
|
||||
log_info(f"Found binary to sign: {binary.name}")
|
||||
else:
|
||||
log_warning(f"Binary not found: {binary}")
|
||||
|
||||
if not existing_binaries:
|
||||
log_error("No binaries found to sign")
|
||||
return False
|
||||
|
||||
# Use eSigner if configured
|
||||
if signing_method == 'esigner' or os.environ.get('ESIGNER_USERNAME'):
|
||||
return sign_with_esigner(existing_binaries)
|
||||
|
||||
# Use PFX certificate if configured
|
||||
if os.environ.get('PFX_PATH'):
|
||||
return sign_with_pfx(existing_binaries)
|
||||
|
||||
# Otherwise use traditional certificate store signing
|
||||
if not certificate_name:
|
||||
log_warning("No certificate specified, skipping signing")
|
||||
return True
|
||||
|
||||
# Get paths to sign
|
||||
build_output_dir = join_paths(ctx.chromium_src, ctx.out_dir)
|
||||
chrome_exe = build_output_dir / "chrome.exe"
|
||||
return sign_with_certificate_store(existing_binaries, certificate_name)
|
||||
|
||||
|
||||
def sign_with_esigner(binaries: List[Path]) -> bool:
|
||||
"""Sign binaries using SSL.com CodeSignTool"""
|
||||
log_info("Using SSL.com CodeSignTool for signing...")
|
||||
|
||||
if not chrome_exe.exists():
|
||||
log_error(f"chrome.exe not found at: {chrome_exe}")
|
||||
# Check for CodeSignTool from environment or default locations
|
||||
codesigntool_path_str = os.environ.get('CODESIGNTOOL_PATH')
|
||||
if codesigntool_path_str:
|
||||
codesigntool_path = Path(codesigntool_path_str)
|
||||
log_info(f"Using CodeSignTool from env: {codesigntool_path}")
|
||||
else:
|
||||
# Try default locations
|
||||
codesigntool_path = Path("C:/src/BrowserOS/CodeSignTool-v1.3.2-windows/CodeSignTool.bat")
|
||||
if not codesigntool_path.exists():
|
||||
codesigntool_path = Path("CodeSignTool.bat")
|
||||
|
||||
if not codesigntool_path.exists():
|
||||
log_error(f"CodeSignTool.bat not found at: {codesigntool_path}")
|
||||
log_error("Set CODESIGNTOOL_PATH in .env file or download from SSL.com")
|
||||
return False
|
||||
|
||||
# Check for required environment variables
|
||||
username = os.environ.get('ESIGNER_USERNAME')
|
||||
password = os.environ.get('ESIGNER_PASSWORD')
|
||||
totp_secret = os.environ.get('ESIGNER_TOTP_SECRET')
|
||||
credential_id = os.environ.get('ESIGNER_CREDENTIAL_ID')
|
||||
|
||||
# Check if using TOTP secret or OTP
|
||||
use_otp = os.environ.get('ESIGNER_USE_OTP', 'false').lower() == 'true'
|
||||
|
||||
if use_otp:
|
||||
# Prompt for OTP code
|
||||
import getpass
|
||||
otp_code = getpass.getpass("Enter your 6-digit OTP code from authenticator app: ")
|
||||
if not otp_code or len(otp_code) != 6:
|
||||
log_error("Invalid OTP code. Must be 6 digits.")
|
||||
return False
|
||||
else:
|
||||
# Use TOTP secret
|
||||
if not totp_secret:
|
||||
log_error("Missing ESIGNER_TOTP_SECRET environment variable")
|
||||
log_error("Either set ESIGNER_TOTP_SECRET or set ESIGNER_USE_OTP=true to enter OTP manually")
|
||||
return False
|
||||
|
||||
if not all([username, password]):
|
||||
log_error("Missing required eSigner environment variables:")
|
||||
log_error(" set ESIGNER_USERNAME=your-email")
|
||||
log_error(" set ESIGNER_PASSWORD=your-password")
|
||||
log_error(" set ESIGNER_TOTP_SECRET=your-totp-secret (or set ESIGNER_USE_OTP=true)")
|
||||
log_error(" set ESIGNER_CREDENTIAL_ID=your-credential-id (optional)")
|
||||
return False
|
||||
|
||||
all_success = True
|
||||
for binary in binaries:
|
||||
try:
|
||||
log_info(f"Signing {binary.name} with CodeSignTool...")
|
||||
|
||||
# Build command
|
||||
cmd = [
|
||||
str(codesigntool_path),
|
||||
"sign",
|
||||
"-username", username,
|
||||
"-password", password,
|
||||
"-input_file_path", str(binary),
|
||||
"-output_dir_path", str(binary.parent),
|
||||
"-override" # Override the input file after signing
|
||||
]
|
||||
|
||||
# CodeSignTool doesn't support -otp flag, only -totp_secret
|
||||
# For OTP, we need to convert it to a temporary TOTP secret
|
||||
if use_otp:
|
||||
# For manual OTP, we can't use it directly - need TOTP secret
|
||||
log_warning("Note: CodeSignTool requires TOTP secret, not OTP code")
|
||||
log_error("Please set ESIGNER_TOTP_SECRET in .env file")
|
||||
log_error("You can find it in SSL.com dashboard under eSigner settings")
|
||||
return False
|
||||
else:
|
||||
cmd.extend(["-totp_secret", totp_secret])
|
||||
|
||||
if credential_id:
|
||||
cmd.extend(["-credential_id", credential_id])
|
||||
|
||||
# Note: Timestamp server is configured on SSL.com side automatically
|
||||
|
||||
run_command(cmd)
|
||||
log_success(f"{binary.name} signed successfully with CodeSignTool")
|
||||
|
||||
except Exception as e:
|
||||
log_error(f"Failed to sign {binary.name} with CodeSignTool: {e}")
|
||||
all_success = False
|
||||
|
||||
return all_success
|
||||
|
||||
|
||||
def sign_with_pfx(binaries: List[Path]) -> bool:
|
||||
"""Sign binaries using PFX certificate file"""
|
||||
pfx_path = os.environ.get('PFX_PATH')
|
||||
if not pfx_path or not Path(pfx_path).exists():
|
||||
log_error(f"PFX certificate not found at: {pfx_path}")
|
||||
return False
|
||||
|
||||
log_info(f"Using PFX certificate: {pfx_path}")
|
||||
|
||||
# Import the signing module
|
||||
from .sign_with_esigner import sign_with_local_pfx
|
||||
|
||||
all_success = True
|
||||
for binary in binaries:
|
||||
if not sign_with_local_pfx(str(binary), pfx_path):
|
||||
all_success = False
|
||||
|
||||
return all_success
|
||||
|
||||
|
||||
def sign_with_certificate_store(binaries: List[Path], certificate_name: str) -> bool:
|
||||
"""Sign binaries using certificate from Windows certificate store"""
|
||||
log_info(f"Using certificate from store: {certificate_name}")
|
||||
|
||||
# Check if signtool is available
|
||||
signtool_path = shutil.which("signtool")
|
||||
if not signtool_path:
|
||||
@@ -200,30 +347,35 @@ def sign_binaries(ctx: BuildContext, certificate_name: Optional[str] = None) ->
|
||||
log_error("signtool.exe not found. Please install Windows SDK.")
|
||||
return False
|
||||
|
||||
# Sign the main executable
|
||||
try:
|
||||
# Basic signing command - can be extended with timestamp server etc.
|
||||
cmd = [
|
||||
signtool_path,
|
||||
"sign",
|
||||
"/n", certificate_name, # Certificate name
|
||||
"/t", "http://timestamp.digicert.com", # Timestamp server
|
||||
"/fd", "sha256", # File digest algorithm
|
||||
str(chrome_exe)
|
||||
]
|
||||
|
||||
run_command(cmd)
|
||||
log_success("Binary signed successfully")
|
||||
|
||||
# Verify signature
|
||||
verify_cmd = [signtool_path, "verify", "/pa", str(chrome_exe)]
|
||||
run_command(verify_cmd)
|
||||
log_success("Signature verified successfully")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
log_error(f"Failed to sign binary: {e}")
|
||||
return False
|
||||
# Sign each binary
|
||||
all_success = True
|
||||
for binary in binaries:
|
||||
try:
|
||||
log_info(f"Signing {binary.name}...")
|
||||
|
||||
cmd = [
|
||||
signtool_path,
|
||||
"sign",
|
||||
"/n", certificate_name, # Certificate name
|
||||
"/tr", "http://ts.ssl.com", # SSL.com timestamp server for eSigner
|
||||
"/td", "sha256", # Timestamp digest algorithm
|
||||
"/fd", "sha256", # File digest algorithm
|
||||
str(binary)
|
||||
]
|
||||
|
||||
run_command(cmd)
|
||||
log_success(f"{binary.name} signed successfully")
|
||||
|
||||
# Verify signature
|
||||
verify_cmd = [signtool_path, "verify", "/pa", str(binary)]
|
||||
run_command(verify_cmd)
|
||||
log_success(f"{binary.name} signature verified successfully")
|
||||
|
||||
except Exception as e:
|
||||
log_error(f"Failed to sign {binary.name}: {e}")
|
||||
all_success = False
|
||||
|
||||
return all_success
|
||||
|
||||
|
||||
def package_universal(contexts: List[BuildContext]) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user