409 lines
13 KiB
Python
409 lines
13 KiB
Python
# client.py - RK3588板端激活程序(永久有效)
|
||
import os
|
||
import sys
|
||
import json
|
||
import hashlib
|
||
import hmac
|
||
import requests
|
||
import subprocess
|
||
import time
|
||
import logging
|
||
import argparse
|
||
from datetime import datetime
|
||
|
||
# 配置日志
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 配置
|
||
CONFIG = {
|
||
'SERVER_URL': 'http://129.211.170.245:5000/', # 修改为实际服务器地址
|
||
'KEY_FILE': '/etc/device.key', # 密钥文件存储路径
|
||
'KEY_BACKUP_FILE': '/etc/device.key.bak', # 密钥备份文件
|
||
'CHIP_ID_SOURCES': [
|
||
'/sys/devices/soc0/serial_number',
|
||
'/sys/devices/platform/ff650000.serial/serial0',
|
||
'/proc/cpuinfo'
|
||
],
|
||
'ACTIVATE_URL': '/activate',
|
||
'VERIFY_URL': '/verify',
|
||
'ACTIVATE_RETRY_COUNT': 3,
|
||
'SECRET_KEY': '69588cc5e1b701942cda09b33de5a95c02471f3aaaf610d80aaf5681f4b166e4' # 必须和服务端一致
|
||
}
|
||
|
||
|
||
def get_chip_id():
|
||
"""
|
||
获取RK3588芯片ID(增强版)
|
||
"""
|
||
# 方法1: 从多个系统文件读取
|
||
for source in CONFIG['CHIP_ID_SOURCES']:
|
||
try:
|
||
if os.path.exists(source):
|
||
with open(source, 'r') as f:
|
||
content = f.read().strip()
|
||
|
||
# 处理cpuinfo的特殊格式
|
||
if source == '/proc/cpuinfo':
|
||
for line in content.split('\n'):
|
||
if 'Serial' in line:
|
||
chip_id = line.split(':')[1].strip()
|
||
if chip_id:
|
||
logger.info(f"从{source}获取芯片ID: {chip_id}")
|
||
return chip_id
|
||
else:
|
||
if content:
|
||
logger.info(f"从{source}获取芯片ID: {content}")
|
||
return content
|
||
except Exception as e:
|
||
logger.warning(f"读取{source}失败: {e}")
|
||
|
||
# 方法2: 使用dmidecode命令
|
||
try:
|
||
result = subprocess.run(
|
||
['dmidecode', '-s', 'system-serial-number'],
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=5
|
||
)
|
||
if result.returncode == 0 and result.stdout.strip():
|
||
chip_id = result.stdout.strip()
|
||
logger.info(f"从dmidecode获取芯片ID: {chip_id}")
|
||
return chip_id
|
||
except Exception as e:
|
||
logger.warning(f"执行dmidecode失败: {e}")
|
||
|
||
# 方法3: 读取MAC地址
|
||
try:
|
||
import uuid
|
||
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
|
||
chip_id = f"MAC-{mac}"
|
||
logger.warning(f"使用MAC地址生成ID: {chip_id}")
|
||
return chip_id
|
||
except Exception as e:
|
||
logger.error(f"无法获取任何标识: {e}")
|
||
return None
|
||
|
||
|
||
def verify_key_signature(chip_id, key_data):
|
||
"""
|
||
验证密钥签名(永久有效)
|
||
"""
|
||
try:
|
||
# 构建待签名数据
|
||
data_to_sign = f"{chip_id}|{key_data['activation_id']}|{key_data['activate_time']}"
|
||
|
||
# 计算期望的签名
|
||
expected_signature = hmac.new(
|
||
CONFIG['SECRET_KEY'].encode('utf-8'),
|
||
data_to_sign.encode('utf-8'),
|
||
hashlib.sha256
|
||
).hexdigest()
|
||
|
||
# 比对签名
|
||
if key_data['signature'] != expected_signature:
|
||
logger.error("密钥签名验证失败")
|
||
return False, "密钥签名无效"
|
||
|
||
logger.info("密钥签名验证通过")
|
||
return True, "签名验证通过"
|
||
|
||
except Exception as e:
|
||
logger.error(f"签名验证异常: {e}")
|
||
return False, f"验证异常: {str(e)}"
|
||
|
||
|
||
def save_key_file(key_data):
|
||
"""
|
||
保存密钥文件到本地(永久有效)
|
||
"""
|
||
try:
|
||
# 确保目录存在
|
||
key_dir = os.path.dirname(CONFIG['KEY_FILE'])
|
||
if key_dir and not os.path.exists(key_dir):
|
||
os.makedirs(key_dir, exist_ok=True)
|
||
|
||
# 如果已有密钥文件,先备份
|
||
if os.path.exists(CONFIG['KEY_FILE']):
|
||
import shutil
|
||
shutil.copy2(CONFIG['KEY_FILE'], CONFIG['KEY_BACKUP_FILE'])
|
||
logger.info(f"已备份旧密钥文件到: {CONFIG['KEY_BACKUP_FILE']}")
|
||
|
||
# 保存新密钥文件
|
||
with open(CONFIG['KEY_FILE'], 'w') as f:
|
||
json.dump(key_data, f, indent=2)
|
||
|
||
# 设置文件权限为只读
|
||
os.chmod(CONFIG['KEY_FILE'], 0o600)
|
||
|
||
logger.info(f"密钥文件已保存: {CONFIG['KEY_FILE']}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存密钥文件失败: {e}")
|
||
return False
|
||
|
||
|
||
def load_key_file():
|
||
"""
|
||
加载本地密钥文件
|
||
"""
|
||
try:
|
||
# 尝试加载主密钥文件
|
||
if os.path.exists(CONFIG['KEY_FILE']):
|
||
with open(CONFIG['KEY_FILE'], 'r') as f:
|
||
key_data = json.load(f)
|
||
logger.info("密钥文件加载成功")
|
||
return key_data
|
||
|
||
# 尝试加载备份密钥文件
|
||
if os.path.exists(CONFIG['KEY_BACKUP_FILE']):
|
||
logger.warning("主密钥文件不存在,尝试加载备份")
|
||
with open(CONFIG['KEY_BACKUP_FILE'], 'r') as f:
|
||
key_data = json.load(f)
|
||
logger.info("备份密钥文件加载成功")
|
||
return key_data
|
||
|
||
logger.warning("密钥文件不存在")
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载密钥文件失败: {e}")
|
||
return None
|
||
|
||
|
||
def verify_local_key(chip_id):
|
||
"""
|
||
本地验证密钥(永久有效,无过期检查)
|
||
"""
|
||
# 加载密钥文件
|
||
key_data = load_key_file()
|
||
if not key_data:
|
||
return False, "密钥文件不存在"
|
||
|
||
# 验证芯片ID是否匹配
|
||
if key_data.get('chip_id') != chip_id:
|
||
logger.error(f"芯片ID不匹配: 期望 {key_data.get('chip_id')}, 实际 {chip_id}")
|
||
return False, "芯片ID不匹配"
|
||
|
||
# 验证签名
|
||
valid, message = verify_key_signature(chip_id, key_data)
|
||
if not valid:
|
||
return False, message
|
||
|
||
# 验证通过
|
||
logger.info(f"永久激活验证通过!")
|
||
logger.info(f"激活时间: {key_data['activate_time']}")
|
||
logger.info(f"激活ID: {key_data['activation_id']}")
|
||
|
||
return True, "验证通过(永久有效)"
|
||
|
||
|
||
def activate_device(chip_id):
|
||
"""
|
||
联网激活设备(永久激活)
|
||
"""
|
||
url = f"{CONFIG['SERVER_URL']}{CONFIG['ACTIVATE_URL']}"
|
||
payload = {'chip_id': chip_id}
|
||
|
||
for attempt in range(CONFIG['ACTIVATE_RETRY_COUNT']):
|
||
try:
|
||
logger.info(f"正在激活设备 (尝试 {attempt + 1}/{CONFIG['ACTIVATE_RETRY_COUNT']})...")
|
||
response = requests.post(
|
||
url,
|
||
json=payload,
|
||
timeout=10,
|
||
headers={'Content-Type': 'application/json'}
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
data = response.json()
|
||
if data.get('code') == 200:
|
||
# 保存密钥文件
|
||
key_data = data['data']
|
||
if save_key_file(key_data):
|
||
logger.info("设备永久激活成功!")
|
||
return True
|
||
else:
|
||
logger.error("保存密钥文件失败")
|
||
else:
|
||
logger.error(f"激活失败: {data.get('message')}")
|
||
else:
|
||
logger.error(f"HTTP错误: {response.status_code}")
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
logger.error(f"网络错误: {e}")
|
||
except Exception as e:
|
||
logger.error(f"激活过程中发生错误: {e}")
|
||
|
||
if attempt < CONFIG['ACTIVATE_RETRY_COUNT'] - 1:
|
||
wait_time = 2 ** attempt
|
||
logger.info(f"等待 {wait_time} 秒后重试...")
|
||
time.sleep(wait_time)
|
||
|
||
return False
|
||
|
||
|
||
def create_emergency_key(chip_id):
|
||
"""
|
||
创建应急密钥(当无法联网时使用,需人工授权)
|
||
警告:此功能仅用于紧急情况
|
||
"""
|
||
import secrets
|
||
|
||
logger.warning("正在创建应急密钥...")
|
||
|
||
# 生成应急密钥数据
|
||
emergency_key = {
|
||
'chip_id': chip_id,
|
||
'activation_id': f"EMERGENCY-{secrets.token_hex(8)}",
|
||
'activate_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||
'signature': 'EMERGENCY-MODE', # 应急模式签名
|
||
'permanent': True,
|
||
'emergency': True,
|
||
'version': '1.0'
|
||
}
|
||
|
||
# 保存应急密钥
|
||
if save_key_file(emergency_key):
|
||
logger.warning("应急密钥已创建!请确保这是授权操作!")
|
||
return True
|
||
|
||
return False
|
||
|
||
|
||
def main():
|
||
"""
|
||
主函数:程序启动验证(永久有效)
|
||
"""
|
||
parser = argparse.ArgumentParser(description='设备永久激活和验证程序')
|
||
parser.add_argument('--force-activate', action='store_true', help='强制重新激活')
|
||
parser.add_argument('--check-only', action='store_true', help='仅检查验证状态')
|
||
parser.add_argument('--emergency', action='store_true', help='创建应急密钥(仅限授权使用)')
|
||
parser.add_argument('--show-info', action='store_true', help='显示激活信息')
|
||
args = parser.parse_args()
|
||
|
||
# 获取芯片ID
|
||
chip_id = get_chip_id()
|
||
if not chip_id:
|
||
logger.error("无法获取芯片ID")
|
||
sys.exit(1)
|
||
|
||
logger.info(f"芯片ID: {chip_id}")
|
||
|
||
# 应急模式
|
||
if args.emergency:
|
||
if create_emergency_key(chip_id):
|
||
logger.info("应急密钥创建成功")
|
||
sys.exit(0)
|
||
else:
|
||
logger.error("应急密钥创建失败")
|
||
sys.exit(1)
|
||
|
||
# 显示激活信息
|
||
if args.show_info:
|
||
key_data = load_key_file()
|
||
if key_data:
|
||
print("\n" + "="*50)
|
||
print("设备激活信息")
|
||
print("="*50)
|
||
print(f"芯片ID: {key_data.get('chip_id')}")
|
||
print(f"激活时间: {key_data.get('activate_time')}")
|
||
print(f"激活ID: {key_data.get('activation_id')}")
|
||
print(f"激活类型: {'永久有效' if key_data.get('permanent') else '临时'}")
|
||
if key_data.get('emergency'):
|
||
print("警告: 应急模式激活")
|
||
print("="*50)
|
||
else:
|
||
print("设备未激活")
|
||
sys.exit(0)
|
||
|
||
# 检查是否需要激活
|
||
need_activate = args.force_activate
|
||
|
||
if not need_activate:
|
||
# 尝试本地验证
|
||
valid, message = verify_local_key(chip_id)
|
||
|
||
if valid:
|
||
logger.info(f"✅ 验证成功: {message}")
|
||
if not args.check_only:
|
||
logger.info("验证通过,启动主程序...")
|
||
# 在这里调用你的主程序
|
||
# subprocess.run(['python3', 'your_main_program.py'])
|
||
sys.exit(0)
|
||
else:
|
||
logger.warning(f"❌ 本地验证失败: {message}")
|
||
need_activate = True
|
||
|
||
# 需要联网激活
|
||
if need_activate:
|
||
logger.info("开始联网永久激活...")
|
||
if activate_device(chip_id):
|
||
# 激活后再次验证
|
||
valid, message = verify_local_key(chip_id)
|
||
if valid:
|
||
logger.info(f"✅ 激活验证成功: {message}")
|
||
logger.info("设备已永久激活,无需再次联网!")
|
||
if not args.check_only:
|
||
logger.info("激活完成,启动主程序...")
|
||
# subprocess.run(['python3', 'your_main_program.py'])
|
||
sys.exit(0)
|
||
else:
|
||
logger.error(f"❌ 激活后验证失败: {message}")
|
||
sys.exit(1)
|
||
else:
|
||
logger.error("设备激活失败,请检查网络连接")
|
||
sys.exit(1)
|
||
|
||
|
||
def install_service():
|
||
"""
|
||
安装为系统服务
|
||
"""
|
||
service_content = """[Unit]
|
||
Description=Device Permanent Activation Check Service
|
||
After=network.target
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
ExecStart=/usr/bin/python3 /opt/device_activation/client.py --check-only
|
||
RemainAfterExit=yes
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
"""
|
||
|
||
service_file = '/etc/systemd/system/device-activation.service'
|
||
try:
|
||
# 复制程序到系统目录
|
||
script_dir = '/opt/device_activation'
|
||
os.makedirs(script_dir, exist_ok=True)
|
||
|
||
import shutil
|
||
shutil.copy2(__file__, os.path.join(script_dir, 'client.py'))
|
||
os.chmod(os.path.join(script_dir, 'client.py'), 0o755)
|
||
|
||
# 创建服务文件
|
||
with open(service_file, 'w') as f:
|
||
f.write(service_content)
|
||
os.chmod(service_file, 0o644)
|
||
|
||
logger.info(f"服务文件已创建: {service_file}")
|
||
logger.info("请执行以下命令启用服务:")
|
||
logger.info("sudo systemctl daemon-reload")
|
||
logger.info("sudo systemctl enable device-activation.service")
|
||
logger.info("sudo systemctl start device-activation.service")
|
||
|
||
except Exception as e:
|
||
logger.error(f"安装服务失败: {e}")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
if len(sys.argv) > 1 and sys.argv[1] == 'install-service':
|
||
install_service()
|
||
else:
|
||
main() |