定价统一:专业版包年 ¥99 / 永久 ¥199(与 OpsXShell 一致);授权真实链路验证
- /pricing 与落地页:¥299 终身→¥199 永久、¥69→¥99、个人版更名专业版(3/5 台设备); 补 Apipost 离线版式购买叙事(一机一码/当场发证/无需注册/年 2 次免费换绑) - 授权服务器(licence.rlwj.cn):opsxapi 永久计划 299→199;注册 opsxdb 产品与 trial/yearly/perpetual 计划——三产品公开 API 均为 0/99/199 - 真实链路验证:管理端签发 opsxapi 在线 LIC-(activate/verify 全通,Ed25519 签名下发)+ 离线 LICOFF-(绑本机 device_id);新增 4 项 Rust 单测用真实 fixture 跑通 (离线验签/换设备拒绝/篡改拒绝/在线响应签名)
This commit is contained in:
1 parent
c304b9f251
commit
0f8e1f75da
7 files changed
+57
-1
No files matched your search
@@ -432,3 +432,54 @@ pub fn deactivate(app: &AppHandle) -> Result<LicenseStatus, String> {
|
||||
let _ = fs::remove_file(&path);
|
||||
read_status(app)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
/// 真实服务器签发的离线证(licence.rlwj.cn,product=opsxapi)
|
||||
const REAL_OFFLINE: &str = include_str!("../../../tests/fixtures/licoff-opsxapi.txt");
|
||||
const REAL_DEVICE: &str = include_str!("../../../tests/fixtures/device-id.txt");
|
||||
|
||||
#[test]
|
||||
fn offline_serial_validates_for_bound_device() {
|
||||
let key = REAL_OFFLINE.trim();
|
||||
assert!(is_offline_license_key(key));
|
||||
let claims = verify_offline_serial(key, REAL_DEVICE.trim())
|
||||
.expect("真实离线证应通过本产品+设备校验");
|
||||
assert_eq!(claims.product_code, "opsxapi");
|
||||
assert!(claims.exp > claims.iat);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offline_serial_rejects_other_device() {
|
||||
assert!(verify_offline_serial(REAL_OFFLINE.trim(), "00000000-0000-0000-0000-000000000000").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offline_serial_rejects_tampered_payload() {
|
||||
// 篡改 base64 载荷中段一个字符(保持格式合法,签名必失效)
|
||||
let key = REAL_OFFLINE.trim().to_string();
|
||||
let (payload, sig) = key.strip_prefix("LICOFF-1.").unwrap().rsplit_once('.').unwrap();
|
||||
let mut chars: Vec<char> = payload.chars().collect();
|
||||
let mid = chars.len() / 2;
|
||||
chars[mid] = if chars[mid] == 'A' { 'B' } else { 'A' };
|
||||
let tampered = format!("LICOFF-1.{}.{}", chars.into_iter().collect::<String>(), sig);
|
||||
assert_ne!(key, tampered);
|
||||
assert!(verify_offline_serial(&tampered, REAL_DEVICE.trim()).is_err());
|
||||
}
|
||||
|
||||
/// 真实服务器 /v1/license/verify 响应的 Ed25519 签名(hex)与签名原文
|
||||
#[test]
|
||||
fn online_response_sig_verifies_with_embedded_key() {
|
||||
// 1) 原文直接验(证明公钥同源)
|
||||
let msg = include_str!("../../../tests/fixtures/verify-sig-msg.txt");
|
||||
let sig_hex = include_str!("../../../tests/fixtures/verify-sig.txt").trim();
|
||||
let sig = Signature::from_slice(&hex::decode(sig_hex).unwrap()).unwrap();
|
||||
offline_verifying_key().unwrap().verify(msg.as_bytes(), &sig).expect("服务器签名应通过内嵌公钥校验");
|
||||
// 2) 完整响应 JSON 走 verify_response_sig(证明客户端重组串与服务器一致)
|
||||
let payload: Value = serde_json::from_str(include_str!("../../../tests/fixtures/verify-response.json")).unwrap();
|
||||
assert!(verify_response_sig(&payload).is_ok());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user