mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(rust): split core library into activate/license/heartbeat modules with build.rs and C ABI tests
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "craft-core"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
description = "CraftLabs 授权核心库 — Rust 实现,导出 craft_* C ABI。目标平台:Linux(主)> Windows(次)> macOS(最低)"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
name = "craftlabs_auth_bitanswer"
|
||||
|
||||
[dependencies]
|
||||
obfstr = "0.4"
|
||||
sha2 = "0.10"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.52", features = ["Win32_System_Diagnostics_Debug"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
sha2 = "0.10"
|
||||
|
||||
[features]
|
||||
default = ["security-hardening"]
|
||||
security-hardening = []
|
||||
@@ -0,0 +1,36 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let src_dir = PathBuf::from(&manifest_dir).join("src");
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hash_dir(&src_dir, &mut hasher);
|
||||
let digest = hasher.finalize();
|
||||
let hash_hex = format!("{:x}", digest);
|
||||
|
||||
println!("cargo:rustc-env=BUILD_SRC_HASH={}", hash_hex);
|
||||
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
fs::write(out_dir.join("build_hash.txt"), format!("{}\n", hash_hex)).unwrap();
|
||||
}
|
||||
|
||||
fn hash_dir(dir: &PathBuf, hasher: &mut Sha256) {
|
||||
if let Ok(entries) = fs::read_dir(dir) {
|
||||
let mut paths: Vec<_> = entries.filter_map(|e| e.ok()).collect();
|
||||
paths.sort_by_key(|e| e.file_name());
|
||||
for entry in paths {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
hash_dir(&path, hasher);
|
||||
} else if path.extension().map_or(false, |ext| ext == "rs") {
|
||||
if let Ok(content) = fs::read(&path) {
|
||||
hasher.update(&content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
LIBRARY craftlabs_auth_bitanswer
|
||||
EXPORTS
|
||||
craft_initialize @1 NONAME
|
||||
craft_activate @2 NONAME
|
||||
craft_check_license @3 NONAME
|
||||
craft_get_license_info @4 NONAME
|
||||
craft_free_license_info @5 NONAME
|
||||
craft_has_feature @6 NONAME
|
||||
craft_release @7 NONAME
|
||||
craft_heartbeat @8 NONAME
|
||||
craft_destroy @9 NONAME
|
||||
@@ -0,0 +1,9 @@
|
||||
// Activation logic — communicates with BitAnswer cloud or self-hosted backend
|
||||
|
||||
use crate::CraftResult;
|
||||
|
||||
/// Core activation logic — communicates with BitAnswer cloud or self-hosted backend
|
||||
pub fn core_activate(_license_key: &str, _config_json: &str) -> CraftResult {
|
||||
// TODO: actual network communication based on config provider
|
||||
crate::ok_result()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Heartbeat logic — periodic license validation
|
||||
|
||||
use crate::{CraftContext, CraftResult};
|
||||
|
||||
pub fn do_heartbeat(_ctx: &CraftContext) -> CraftResult {
|
||||
crate::ok_result()
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// CraftLabs 授权核心库 — Rust 实现
|
||||
// 导出 C ABI 接口,与 native/include/craftlabs_auth.h 一致
|
||||
// 对齐 docs/平台架构思路.md §3.1
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
|
||||
mod activate;
|
||||
mod heartbeat;
|
||||
mod license;
|
||||
mod security;
|
||||
|
||||
pub struct CraftContext {
|
||||
dummy: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CraftResult {
|
||||
pub success: i32,
|
||||
pub message: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LicenseInfo {
|
||||
pub is_licensed: i32,
|
||||
pub expiration_date: i64,
|
||||
pub feature_names: *const *const c_char,
|
||||
pub feature_values: *const i32,
|
||||
pub feature_count: i32,
|
||||
}
|
||||
|
||||
unsafe fn c_str_to_string(ptr: *const c_char) -> String {
|
||||
if ptr.is_null() {
|
||||
String::new()
|
||||
} else {
|
||||
CStr::from_ptr(ptr).to_string_lossy().into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
static OK_MSG: &[u8] = b"ok\0";
|
||||
static FAIL_MSG: &[u8] = b"failure\0";
|
||||
|
||||
fn ok_result() -> CraftResult {
|
||||
CraftResult {
|
||||
success: 1,
|
||||
message: OK_MSG.as_ptr() as *const c_char,
|
||||
}
|
||||
}
|
||||
|
||||
fn fail_result() -> CraftResult {
|
||||
CraftResult {
|
||||
success: 0,
|
||||
message: FAIL_MSG.as_ptr() as *const c_char,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext {
|
||||
let _config = unsafe { c_str_to_string(config_json) };
|
||||
|
||||
#[cfg(feature = "security-hardening")]
|
||||
{
|
||||
security::anti_debug::anti_debug_check();
|
||||
let _ = security::integrity::integrity_check();
|
||||
}
|
||||
|
||||
let ctx = Box::new(CraftContext::new());
|
||||
Box::into_raw(ctx)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_activate(
|
||||
handle: *mut CraftContext,
|
||||
license_key: *const c_char,
|
||||
config_json: *const c_char,
|
||||
) -> CraftResult {
|
||||
if handle.is_null() {
|
||||
return fail_result();
|
||||
}
|
||||
let key = unsafe { c_str_to_string(license_key) };
|
||||
let config = unsafe { c_str_to_string(config_json) };
|
||||
activate::core_activate(&key, &config)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_check_license(handle: *mut CraftContext) -> CraftResult {
|
||||
if handle.is_null() {
|
||||
return fail_result();
|
||||
}
|
||||
license::check_license(unsafe { &*handle })
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_get_license_info(handle: *mut CraftContext) -> *mut LicenseInfo {
|
||||
if handle.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
license::get_license_info(unsafe { &*handle })
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) {
|
||||
if !info.is_null() {
|
||||
unsafe {
|
||||
drop(Box::from_raw(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_has_feature(
|
||||
handle: *mut CraftContext,
|
||||
feature_name: *const c_char,
|
||||
) -> i32 {
|
||||
if handle.is_null() {
|
||||
return 0;
|
||||
}
|
||||
let name = unsafe { c_str_to_string(feature_name) };
|
||||
if license::has_feature(unsafe { &*handle }, &name) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult {
|
||||
if handle.is_null() {
|
||||
return fail_result();
|
||||
}
|
||||
license::release_license(unsafe { &mut *handle })
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult {
|
||||
if handle.is_null() {
|
||||
return fail_result();
|
||||
}
|
||||
heartbeat::do_heartbeat(unsafe { &*handle })
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn craft_destroy(handle: *mut CraftContext) {
|
||||
if !handle.is_null() {
|
||||
unsafe {
|
||||
drop(Box::from_raw(handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// License state management
|
||||
|
||||
use crate::{CraftContext, CraftResult, LicenseInfo};
|
||||
use std::ptr;
|
||||
|
||||
/// License state machine
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LicenseState {
|
||||
Uninitialized,
|
||||
Active,
|
||||
Expired,
|
||||
Released,
|
||||
}
|
||||
|
||||
impl CraftContext {
|
||||
pub fn new() -> Self {
|
||||
CraftContext { dummy: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_license(_ctx: &CraftContext) -> CraftResult {
|
||||
crate::ok_result()
|
||||
}
|
||||
|
||||
pub fn get_license_info(_ctx: &CraftContext) -> *mut LicenseInfo {
|
||||
let info = Box::new(LicenseInfo {
|
||||
is_licensed: 1,
|
||||
expiration_date: 0,
|
||||
feature_names: ptr::null(),
|
||||
feature_values: ptr::null(),
|
||||
feature_count: 0,
|
||||
});
|
||||
Box::into_raw(info)
|
||||
}
|
||||
|
||||
pub fn has_feature(_ctx: &CraftContext, _feature_name: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn release_license(_ctx: &mut CraftContext) -> CraftResult {
|
||||
crate::ok_result()
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_debugger_present() -> bool {
|
||||
if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
|
||||
for line in status.lines() {
|
||||
if line.starts_with("TracerPid:") {
|
||||
if let Some(pid_str) = line.split_whitespace().nth(1) {
|
||||
if let Ok(pid) = pid_str.parse::<i32>() {
|
||||
return pid != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn is_debugger_present() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn is_debugger_present() -> bool {
|
||||
unsafe { windows_sys::Win32::System::Diagnostics::Debug::IsDebuggerPresent() != 0 }
|
||||
}
|
||||
|
||||
pub fn anti_debug_check() {
|
||||
if is_debugger_present() {
|
||||
std::process::abort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const EXPECTED_HASH: &str = env!("BUILD_SRC_HASH");
|
||||
|
||||
pub fn verify_integrity() -> bool {
|
||||
let lib_path = match get_own_library_path() {
|
||||
Some(path) => path,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let runtime_hash = match compute_file_sha256(&lib_path) {
|
||||
Some(hash) => hash,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
tracing_mode_check(runtime_hash)
|
||||
}
|
||||
|
||||
fn get_own_library_path() -> Option<PathBuf> {
|
||||
std::env::current_exe().ok()
|
||||
}
|
||||
|
||||
fn compute_file_sha256(path: &PathBuf) -> Option<String> {
|
||||
let data = fs::read(path).ok()?;
|
||||
let hash = Sha256::digest(&data);
|
||||
Some(format!("{:x}", hash))
|
||||
}
|
||||
|
||||
fn tracing_mode_check(_runtime_hash: String) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn integrity_check() -> Result<(), &'static str> {
|
||||
if verify_integrity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Integrity check failed")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod anti_debug;
|
||||
pub mod integrity;
|
||||
pub mod string_encrypt;
|
||||
@@ -0,0 +1,26 @@
|
||||
use obfstr::obfstr;
|
||||
|
||||
#[inline]
|
||||
pub fn error_activate_failed() -> String {
|
||||
obfstr!("Activation failed: invalid license key").to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn error_handle_null() -> String {
|
||||
obfstr!("Error: null handle").to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn error_network_timeout() -> String {
|
||||
obfstr!("Network timeout contacting license server").to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn api_activate_path() -> String {
|
||||
obfstr!("/api/v1/activate").to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn api_heartbeat_path() -> String {
|
||||
obfstr!("/api/v1/heartbeat").to_string()
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
#[link(name = "craftlabs_auth_bitanswer")]
|
||||
extern "C" {
|
||||
fn craft_initialize(config_json: *const c_char) -> *mut std::ffi::c_void;
|
||||
fn craft_activate(
|
||||
handle: *mut std::ffi::c_void,
|
||||
license_key: *const c_char,
|
||||
config_json: *const c_char,
|
||||
) -> CraftResult;
|
||||
fn craft_check_license(handle: *mut std::ffi::c_void) -> CraftResult;
|
||||
fn craft_get_license_info(handle: *mut std::ffi::c_void) -> *mut LicenseInfo;
|
||||
fn craft_free_license_info(info: *mut LicenseInfo);
|
||||
fn craft_has_feature(handle: *mut std::ffi::c_void, feature_name: *const c_char) -> i32;
|
||||
fn craft_release(handle: *mut std::ffi::c_void) -> CraftResult;
|
||||
fn craft_heartbeat(handle: *mut std::ffi::c_void) -> CraftResult;
|
||||
fn craft_destroy(handle: *mut std::ffi::c_void);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct CraftResult {
|
||||
success: i32,
|
||||
message: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct LicenseInfo {
|
||||
is_licensed: i32,
|
||||
expiration_date: i64,
|
||||
feature_names: *const *const c_char,
|
||||
feature_values: *const i32,
|
||||
feature_count: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initialize_and_destroy() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initialize_with_null_config() {
|
||||
let handle = unsafe { craft_initialize(std::ptr::null()) };
|
||||
assert!(!handle.is_null());
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_activate() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let key = CString::new("test-license-key").unwrap();
|
||||
let cfg = CString::new("{}").unwrap();
|
||||
let result = unsafe { craft_activate(handle, key.as_ptr(), cfg.as_ptr()) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_activate_null_handle() {
|
||||
let key = CString::new("test-key").unwrap();
|
||||
let cfg = CString::new("{}").unwrap();
|
||||
let result = unsafe { craft_activate(std::ptr::null_mut(), key.as_ptr(), cfg.as_ptr()) };
|
||||
assert_eq!(result.success, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_license() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let result = unsafe { craft_check_license(handle) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_license_null_handle() {
|
||||
let result = unsafe { craft_check_license(std::ptr::null_mut()) };
|
||||
assert_eq!(result.success, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_license_info() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let info = unsafe { craft_get_license_info(handle) };
|
||||
assert!(!info.is_null());
|
||||
|
||||
unsafe {
|
||||
assert_eq!((*info).is_licensed, 1);
|
||||
craft_free_license_info(info);
|
||||
}
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_license_info_null_handle() {
|
||||
let info = unsafe { craft_get_license_info(std::ptr::null_mut()) };
|
||||
assert!(info.is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_feature() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let feature = CString::new("premium").unwrap();
|
||||
let result = unsafe { craft_has_feature(handle, feature.as_ptr()) };
|
||||
assert_eq!(result, 1);
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_feature_null_handle() {
|
||||
let feature = CString::new("premium").unwrap();
|
||||
let result = unsafe { craft_has_feature(std::ptr::null_mut(), feature.as_ptr()) };
|
||||
assert_eq!(result, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_release() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let result = unsafe { craft_release(handle) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_release_null_handle() {
|
||||
let result = unsafe { craft_release(std::ptr::null_mut()) };
|
||||
assert_eq!(result.success, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_heartbeat() {
|
||||
let config = CString::new("{}").unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let result = unsafe { craft_heartbeat(handle) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_heartbeat_null_handle() {
|
||||
let result = unsafe { craft_heartbeat(std::ptr::null_mut()) };
|
||||
assert_eq!(result.success, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_lifecycle() {
|
||||
let config = CString::new(r#"{"provider":"bitanswer"}"#).unwrap();
|
||||
let handle = unsafe { craft_initialize(config.as_ptr()) };
|
||||
assert!(!handle.is_null());
|
||||
|
||||
let key = CString::new("TEST-LICENSE-KEY").unwrap();
|
||||
let cfg = CString::new("{}").unwrap();
|
||||
let result = unsafe { craft_activate(handle, key.as_ptr(), cfg.as_ptr()) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
let result = unsafe { craft_check_license(handle) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
let info = unsafe { craft_get_license_info(handle) };
|
||||
assert!(!info.is_null());
|
||||
unsafe {
|
||||
assert_eq!((*info).is_licensed, 1);
|
||||
craft_free_license_info(info);
|
||||
}
|
||||
|
||||
let feature = CString::new("advanced").unwrap();
|
||||
let has_feature = unsafe { craft_has_feature(handle, feature.as_ptr()) };
|
||||
assert_eq!(has_feature, 1);
|
||||
|
||||
let result = unsafe { craft_heartbeat(handle) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
let result = unsafe { craft_release(handle) };
|
||||
assert_eq!(result.success, 1);
|
||||
|
||||
unsafe { craft_destroy(handle) };
|
||||
}
|
||||
Reference in New Issue
Block a user