跳到主要内容

用API集成Vribox授权

适用场景

Virbox LM提供不用写代码,只通过加壳工具实现授权集成的方案,这种方案适用于标准的授权控制业务场景;但一部分开发商有特殊需求,需要通过调用 API 方式集成 Virbox 授权,这需要开发商有一定的编码能力,通过调用API的方式,在程序中自定义的位置校验授权。适用场景如下:

  • 有特殊授权控制要求;如:版本控制,或者某种条件下,才能使用某种功能,或者多种授权组合使用等。
  • 与其他开发商有集成要求,或者多个控制单元在同一个应用程序中,无法二次加壳;
  • 盗版压力很大,对授权安全有极高要求,需要多种加密方式结合,提高安全强度;

前提条件

1、已经在Virbox 开发者网站注册并转正;

参考 注册

2、已经获取到属于自己的专属SDK;

参考 获取SDK

3、获取API密码;

打开此网址:https://developer-new.lm.virbox.com/#/login ,登录自己的开发者账号。 进入之后,将鼠标移动至右上角的开发者名称,在弹出的菜单栏中查看开发者信息。

基本说明

这里只介绍在终端用户端验证并使用授权的API调用说明,签发授权和授权管理过程,由其他文档介绍; 我们的SDK默认是部署在Windows上的,如果开发环境没有Windows,可以参考 Linux 下集成部署文档; 我们的SDK默认安装路径在 C:\Program Files (x86)\senseshield\sdk 目录;我们的SDK 是由C语言开发,提供动态库和静态库,接口描述可以到对应头文件查看;如果您使用其他语言,需要通过动态库方式加载调用,可以参考 C:\Program Files (x86)\senseshield\sdk\API 下不同语言对应的目录里的sample;

温馨提示

Virbox LM SDK提供全平台支持,包括 Windows、Linux、macOS、ARM Linux(ARM V8,ARM V7 SF,ARM V7 HF)、MIPS Linux 和 Android ; 目前标准工具盒只提供 x86 架构下的 Windows、Linux、macOS库的下载,需要其他平台的SDK,需要联系深盾,单独给您提供。

API 概述

API 类型对应头文件所在目录说明
Runtime APIss_lm_runtime.hC:\Program Files (x86)\senseshield\sdk\API\C\includeRuntime API,是许可管理运行时库,主要用于在软件运行时,校验许可,和其他许可使用功能; Runtime API 与 Virbox许可服务通讯,每个开发商的Runtime API 都不一样,只有验证专属Runtime API 密码才能调用对应的接口。
Control APIss_lm_control.hC:\Program Files (x86)\senseshield\sdk\API\C\includeControl API 是Virbox许可的管理接口;主要提供加密锁信息的查询、许可内容和状态的查询、许可会话的查询等功能。
D2C APId2c.hC:\Program Files (x86)\senseshield\sdk\API\C\includeD2C API 是D2C升级包的签发接口; 主要用于签发硬件锁升级包;我们本身提供本地签发工具开发者管理工具,也提供开发者网站SAAS签发方式。

Runtime API 库说明

由于Virbox许可服务本身具备反调试功能,为了编译开发商在开发过程中调试,我们的Runtime 库分为标准版和可调试版;这里的可调式不是指我们通过debug编译的,而是运行开发商调试;

动态库:

默认路径:C:\Program Files (x86)\senseshield\sdk\API\C\dll

ss_lm_runtime.dll,标准运行时动态库,分x86 和 x64,根据自身程序选择对应的库。使用标准库时,如果进行调试,可能会出现开始调试后10秒后,程序闪退。

ss_lm_runtime_dev.dll,允许调试库,也分为x86和x64;如果加载的是允许调试的库,就不会强制退出,所以,这个通常只在开发商开发过程中使用,发布时不建议使用。

静态库:

默认路径: C:\Program Files (x86)\senseshield\sdk\API\C\lib

ss_lm_runtime_api.lib标准版静态库,分x86 和 x64,根据自身程序选择对应的库。使用标准库时,如果进行调试,可能会出现开始调试后10秒后,程序闪退。

ss_lm_runtime_api.lib 允许调试库,也分为x86和x64;如果加载的是允许调试的库,就不会强制退出,所以,这个通常只在开发商开发过程中使用,发布时不建议使用。

基本流程

runtime_process

流程解释

  • slm_init 初始化接口,调用所有 Runtime API 必须先调用此函数进行初始化;一个进程内只需要调用一次即可。
参数说明
 slm_init(IN ST_INIT_PARAM* pst_init);

/** 初始化参数 */
typedef struct _ST_INIT_PARAM
{
/**用于版本兼容;必填参数,当前版本填入 SLM_CALLBACK_VERSION02 即可*/
SS_UINT32 version;
/** 是否接接收服务消息回调标识; 可选参数,如果不接收回调消息,不用设置; 接收回调消息传入 SLM_INIT_FLAG_NOTIFY ; 关于回调请参考下文高阶使用场景中回调部分*/
SS_UINT32 flag;
/** 回调消息函数指针,用于接收回调消息; 必填参数; 如果不接收回调消息,输入NULL; 如果接收回调消息,按照SS_UINT32 SSAPI 定义传参*/
SS_CALL_BACK pfn;
/** 通信连接超时时间(毫秒); 选填参数,如果不填或者填 0 ,默认超时时间是 7 秒; 建议使用默认超时时间*/
SS_UINT32 timeout;
/** API 密码; 必填参数,每个开发商不一样,跟库一一对应; 可从 VirboxLM 云开发者中心(https://developer.lm.virbox.com),通过“查看开发商信息”获取 */
SS_BYTE password[SLM_DEV_PASSWORD_LENGTH];
} ST_INIT_PARAM;
  • slm_login 安全登录许可接口; 登录时Virbox 许可体系会自动检测许可的有效性,包括时间、次数、并发数等;如果失效,返回对应的错误信息,如果有效返回SS_OK(0x00000000);依赖slm_init,必须先调用slm_init,才能调用slm_login; 调用时会返回当前许可会话的handle,slm_logout时销毁handle, 同一个进程内,同一个许可最多能产生256个handle。

    参数说明:

SS_UINT32 SSAPI slm_login(
IN const ST_LOGIN_PARAM* license_param,//登录许可描述参数,结构体;必填参数,设置指定指定登录特性
IN INFO_FORMAT_TYPE param_format,//许可描述字符串类型,必填参数,仅支持 #STRUCT
OUT SLM_HANDLE_INDEX * slm_handle,//返回登录之后许可句柄index值,范围在 0-256 之间
IN OUT void* auth //认证 login 函数返回是否正确,对 login 的返回值加扰,解扰后才能得到真正的返回值,防止黑客通过返回值特征找到许可登录规律。必填参数,不使用可以填 NULL。
);

/** 许可Login 结构 */
typedef struct _ST_LOGIN_PARAM
{
/** 结构体大小(必填)*/
SS_UINT32 size;
/** 要登录的许可ID(必填),为32位长整数,取值范围 0-4294967295/
SS_UINT32 license_id;
/** 指定登录会话超时时间(选填), 单位为秒。如果不填写,默认为600秒;硬件锁、软锁许可默认最小不低于 60s,最大不得超过 12小时(12 * 60 * 60 秒)。*/
SS_UINT32 timeout;
/** 许可登录的模式(必填):本地锁,网络锁,云锁,本地软锁,集团软锁(见SLM_LOGIN_MODE_XXX),如果填0,则使用SLM_LOGIN_MODE_AUTO, 可以多个类型组合,中间用 "|" 间隔 */
SS_UINT32 login_mode;
/** 许可登录的标志(可选):见SLM_LOGIN_FLAG_XXX,配合后面的其他参数,完成指定特性登录;非特殊用途,不设置此参数 */
SS_UINT32 login_flag;
/** 许可登录指定的锁唯一序列号(二进制)(可选),配合SLM_LOGIN_FLAG_LOCKSN 使用 */
SS_BYTE sn[SLM_LOCK_SN_LENGTH];
/** 网络锁服务器地址(可选),仅识别IP地址, 配合SLM_LOGIN_FLAG_SERVER使用 */
SS_CHAR server[SLM_MAX_SERVER_NAME];
/** 云锁用户token(可选),如果登录的是云锁,可以指定token来指定某个账号下的许可*/
SS_CHAR access_token[SLM_MAX_ACCESS_TOKEN_LENGTH];
/** 云锁服务器地址(可选)*/
SS_CHAR cloud_server[SLM_MAX_CLOUD_SERVER_LENGTH];
/** 碎片代码种子(可选),如果要支持碎片代码,login_flag需要指定为SLM_LOGIN_FLAG_SNIPPET*/
SS_BYTE snippet_seed[SLM_SNIPPET_SEED_LENGTH];
/** 已登录用户的 guid 或本地已绑定的软锁 guid(字符串)(可选) */
SS_CHAR user_guid[SLM_CLOUD_MAX_USER_GUID_SIZE];
} ST_LOGIN_PARAM;

  • slm_logout 许可登出,并且释放许可句柄等资源;成功返回 SS_OK,失败返回相应的错误码;与 slm_login对应;

    参数说明:

    SS_UINT32 SSAPI slm_logout(SLM_HANDLE_INDEX slm_handle);//handle是slm_login时返回的值
  • slm_cleanup 反初始化函数,与 slm_init 对应; slm_cleanup 是非线程安全的,此函数不建议开发者调用,因为程序退出时系统会自动回收没有释放的内存,若开发者调用,为了保证多线程调用 Runtime API 的安全性,此函数建议在程序退出时调用。一旦调用了此函数,以上所有API(除 #slm_init )均不可使用。

    参数说明:

    SS_UINT32 SSAPI slm_cleanup(void);//这个接口没有输入参数
基本流程示例代码
#include "stdio.h"
#include "ss_lm_runtime.h"

int main(int argc, char **argv)
{
SLM_HANDLE_INDEX hslm = 0;
BYTE original_data[TEST_ENCRYPT_DATA_SIZE ] = {"test_data1234567890"};
BYTE encrypted_data[TEST_ENCRYPT_DATA_SIZE] = {0};
BYTE decrypted_data[TEST_ENCRYPT_DATA_SIZE] = {0};

SS_UINT32 sts = SS_OK;

SS_BYTE psd[16] = { 0xDB, 0x3B, 0x83, 0x8B, 0x2E, 0x4F, 0x08, 0xF5, 0xC9, 0xEF, 0xCD, 0x1A, 0x5D, 0xD1, 0x63, 0x41 }; // 开发者API密码,每个开发者独立,必须也只能从深思开发者中心获取到。
ST_LOGIN_PARAM login_struct = {0};
ST_INIT_PARAM st_init_param = {0};

//0. 全局初始化函数,调用此方法来初始化slm_runtime
st_init_param.version = SLM_CALLBACK_VERSION02;
st_init_param.pfn = &(app_ss_msg_core);
memcpy(st_init_param.password, psd, sizeof(psd));

sts = slm_init(&(st_init_param));

//1. slm_login 登录许可
login_struct.license_id = 0; //登录0号许可
login_struct.size = sizeof(ST_LOGIN_PARAM);
login_struct.timeout = 86400;

sts = slm_login(login_struct,STRUCT, &(hslm),NULL);//
if (SS_OK == sts)
{
printf("slm_login ok,许可登录成功!\n");
}
else
{
printf("slm_login faield error = 0x%08X, 许可登录失败:%s\n",sts, slm_error_msg(sts, 1));
goto end;
}

// 2. 这里是使用许可加密对测试数据进行加密,开发者可以根据自己的需要业务需要,在login后,调用对应的高阶功能接口
printf("before encrypt: %s\n", original_data);
sts = slm_encrypt(hslm, original_data, encrypted_data, TEST_ENCRYPT_DATA_SIZE);
if (SS_OK == sts)
{
printf("slm_encrypt ok!许可加密成功\n");
//hex printf encrypted_data,可通过十六进制打印,查看加密后的数据是不可读的
}
else
{
printf("slm_encrypt faield error = 0x%08X, 加密失败: %s\n",sts, slm_error_msg(sts, 1));
goto end;
}
// 3. 使用许可解密对加密后的数据进行解密
sts = slm_decrypt(hslm, encrypted_data, decrypted_data, TEST_ENCRYPT_DATA_SIZE);
if (SS_OK == sts)
{
printf("slm_decrypt ok!许可解密成功\n");
// 在此对比原始数据original_data 和解密后数据 decrypted_data是否一致
}
else
{
printf("slm_decrypt faield error = 0x%08X, 解密失败: %s\n",sts, slm_error_msg(sts, 1));
goto end;
}

end:
slm_logout(hslm);
return sts;
}

高阶使用场景

后台检测应用

使用场景:

1、网络锁或者集团许可,保持会话有效性;

2、防止用户拔锁,插入下一个环境再次启动软件;

3、防止软件启动后,许可到期,用户不关闭软件一直使用。

使用接口: slm_keep_alive();

这个接口作用是保持已登录会话心跳; 通常许可登录后,单独启动线程每隔一段时间进行一次心跳,作用是维护许可会话一直有效,避免许可会话超时后被踢出,同时可以通过心跳确保授权的有效性,防止许可到期,或者加密锁拔出后程序继续使用;成功返回 SS_OK,失败返回响应的错误码。 在加壳中,自动集成了这个功能,对应【后台检测时间】选项。

注意

在登录网络锁或者集团软锁时,slm_keep_alive()间隔时间必须小于slm_login()传入的超时时间,否则会话超时后,服务端将超时会话踢出,会话失效后, slm_keep_alive()会报错。

代码示例:

/*这里定义一个线程,进行心跳保持*/
DWORD WINAPI __stdcall _ThreadKeepalive(void *pVoid)
{
SLM_HANDLE_INDEX slm_handle = *(SLM_HANDLE_INDEX *)(pVoid);
SS_UINT32 status = SS_OK;
while (1)
{
status = slm_keep_alive(slm_handle);
if(status != SS_OK)
{
//todo do: deal error code
}
Sleep(1000 * 60); // 六十秒钟进行一次心跳连接,保证会话的有效性
}
}
/*下面是代码片段,并不是完整代码*/
{
SS_UINT32 status = SS_OK;
SLM_HANDLE_INDEX slm_handle = 0;
ST_LOGIN_PARAM login_param = { 0 };
HANDLE hThread;
DWORD id = 0;
login_param.license_id = 1;
login_param.size = sizeof(ST_LOGIN_PARAM);
login_param.login_mode = SLM_LOGIN_MODE_LOCAL;
login_param.time_out = 30; // 设置会话为30秒超时
status = slm_login(&login_param, STRUCT, &slm_handle, NULL);
if(status != SS_OK)
{
//todo do: deal error code
return ;
}
//这里在许可login成功后,启动线程。
hThread = CreateThread(NULL, 0, _ThreadKeepalive, &slm_handle, 0, &id);
if (hThread == NULL)
{
//todo: deal error
}
}

回调消息应用

使用场景:

1、及时知道加密锁插拔,做出相应处理;

2、及时知道服务状态,做出相应处理;

回调消息是在slm_init初始化时,指定一个消息通道,当Virbox 许可服务有一些变动时,把对应的消息传入制定的地方,我们外部程序可以获取到这个消息,并对消息进行相应的处理;具体传参方式参考slm_init 参数说明;目前支持的消息包括:

a、服务启动 SS_MSG_SERVICE_START、停止SS_MSG_SERVICE_STOP;

b、加密锁插入SS_MSG_LOCK_AVAILABLE、拔出SS_MSG_LOCK_UNAVAILABLE;

c、云账号登录SS_MSG_CLOUD_USER_LOGIN、登出SS_MSG_CLOUD_USER_LOGOUT;

d、反黑相关消息,需要配合反黑插件进行处理,这里不展开介绍。

温馨提示

在加壳集成方式中,已经集成了加密锁插拔消息的回调;如果只想做加密锁插拔消息回调处理,可以通过加壳来集成。

代码示例:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <process.h>
#include "common.h"//我们的C语言sample里定义的,主要是封装了字节转换和读写文件方法
#include "ss_lm_runtime.h"


//初始化slm_init函数,设置的回调函数
SS_UINT32 SSAPI handle_service_msg( SS_UINT32 message, void* wparam, void* lparam );

int main()
{
SS_UINT32 ret = SS_OK;
ST_INIT_PARAM st_init_param = {0};
ST_LOGIN_PARAM login_param = {0};
SLM_HANDLE_INDEX slm_handle = 0;

// 开发商密码(slm_init参数使用),置为全局,供客户修改。
// 示例固定使用DEMO开发商(0300000000000009)密码,仅能访问DEMO用户锁。
// 访问非DEMO开发商用户锁必须修改密码。
// 建议:考虑到软件安全性,API密码请使用byte数组类型定义。
unsigned char api_password[16] = { 0xDB, 0x3B, 0x83, 0x8B, 0x2E, 0x4F, 0x08, 0xF5, 0xC9, 0xEF, 0xCD, 0x1A, 0x5D, 0xD1, 0x63, 0x41 };

// 初始化接口调用,参数初始化
st_init_param.version = SLM_CALLBACK_VERSION02;
st_init_param.pfn = handle_service_msg;//通过这个参数将消息传出
st_init_param.flag = SLM_INIT_FLAG_NOTIFY;//这里是表示开启回调功能
memcpy( st_init_param.password, api_password, sizeof(api_password) );
ret = slm_init( &(st_init_param) );
if(SS_OK == ret)
{
printf("slm_init ok\n");
}
else if(SS_ERROR_DEVELOPER_PASSWORD == ret)
{
printf("slm_init error : 0x%08X(SS_ERROR_DEVELOPER_PASSWORD), Please login to the Virbox Developer Center(https://developer.lm.virbox.com), get the API password, and replace the 'api_password' variable content.\n", ret);
goto CLEAR;
}
else
{
printf("slm_init error : 0x%08X\n", ret);
goto CLEAR;
}

// 安全登录许可,得到许可句柄(SLM_HANDLE_INDEX), 供其它接口使用
login_param.license_id = 1;//登录许可ID
login_param.size = sizeof(ST_LOGIN_PARAM);
login_param.timeout = 100;
ret = slm_login( &login_param, STRUCT, &(slm_handle), NULL );
if(SS_OK != ret)
{
printf("slm_login error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf("slm_login ok\n");
}

CLEAR:
// 许可登出 & 反初始化函数(slm_cleanup),与slm_init对应
if ( 0 != slm_handle )
{
ret = slm_logout(slm_handle);
if (SS_OK == ret)
{
printf("slm_logout ok.\n");
}
else
{
printf("slm_logout error : 0x%08X", ret);
}
}

printf("\npress any key exit process.\n");
getchar();
slm_cleanup();
return 0;
}

/**
服务消息处理回调函数
能够接收到硬件锁拔插消息,服务启停等消息。
*/
#define DEVICE_SN_LENGTH 16
SS_UINT32 SSAPI handle_service_msg( SS_UINT32 message, void* wparam, void* lparam )
{
SS_UINT32 ret = SS_OK;
SS_UINT32 result = 0;

char szmsg[1024]={0};
char lock_sn[DEVICE_SN_LENGTH] = {0};
char szlock_sn[DEVICE_SN_LENGTH*2 + 1] = {0};

switch(message)
{

case SS_MSG_LOCK_AVAILABLE:// 锁可用(插入锁或SS启动时锁已初始化完成),wparam 代表锁号
// 锁插入消息,可以根据锁号查询锁内许可信息,实现自动登录软件等功能。

// 将二进制记录的锁号转换成十六进制字符串
memcpy(lock_sn, wparam, DEVICE_SN_LENGTH);
bytes_to_hexstr((unsigned char*)lock_sn, DEVICE_SN_LENGTH, szlock_sn);

sprintf(szmsg, "time is %08u, SS_MSG_LOCK_AVAILABLE is 0x%08X wparam is locksn -memory address %p, lock-sn is %s",
GetTickCount(), message, wparam, szlock_sn);
break;
case SS_MSG_LOCK_UNAVAILABLE: // 锁无效(锁已拔出),wparam 代表锁号
// 锁拔出消息,对于只使用锁的应用程序,一旦加密锁拔出软件将无法继续使用,建议发现此消息提示用户保存数据,程序功能锁定等操作。

// 将二进制记录的锁号转换成十六进制字符串
memcpy(lock_sn, wparam, DEVICE_SN_LENGTH);
bytes_to_hexstr((unsigned char*)lock_sn, DEVICE_SN_LENGTH, szlock_sn);

sprintf(szmsg, "time is %08u SS_MSG_LOCK_UNAVAILABLE is 0x%08X wparam is locksn -memory address %p, lock-sn is %s",
GetTickCount(), message, wparam, szlock_sn);
break;
case SS_MSG_CLOUD_USER_LOGIN: // 云账号登录
sprintf(szmsg, "SS_MSG_CLOUD_USER_LOGIN is 0x%08X wparam is %s", message, wparam);
break;
case SS_MSG_CLOUD_USER_LOGOUT: // 云账号登出
sprintf(szmsg, "SS_MSG_CLOUD_USER_LOGOUT is 0x%08X wparam is %s", message, wparam);
break;
}
// 输出格式化后的消息内容
printf("%s\n", szmsg);
return ret;
}


数据区应用

Virbox LM的许可,除了具备限时限次限并发等特性,还有3个数据区,分别是公开区、只读取和读写区;每个数据区最大64KB,开发商可以利用这个数据区存储数据,秘钥,参数等,灵活使用,实现各种业务需求;

几个常用的使用场景:

1、将数据写入只读区,在软件需要的位置读取;这样关键数据和软件分离,提升安全性;

2、将某些参与运算时的参数经过加密放入只读区,在运算时读出;这样在没有授权的情况下,计算结果是错误的;

3、利用读写区,实现许可绑定硬件设备;

原理是将读写区初始化成固定字符,判断是否是第一次使用(判断读写区内容是否是初始状态),如果是第一次使用,就将硬件设备信息写入; 后续再次使用时,读出数据区内容,读出本地硬件信息,进行比较,控制绑定硬件设备。

4、使用开发者网站 key/value 功能,将业务控制逻辑放入许可数据区,实现不同功能模块不同控制需求;

下面是3个数据区的一些特性对比;

名称含义写入读取
ROM只读区只能由控制锁签发写入(不同许可都可以通过开发者网站签发许可写入,硬件锁还可以使用本地开发者管理工具签发写入)使用runtime接口,登录许可成功后可以读取
RAW读写区可以由控制锁签发写入,也可以在用户端,登录许可成功后,由程序修改读写区内容使用runtime接口,登录许可成功后可以读取
PUB公开区只能由控制锁签发写入可以使用control API读出(不需要登录许可),也可以使用runtime接口,登录许可成功后可以读取。
注意

如果要使用3个数据区,需要在创建产品ID时初始化数据区大小,在写许可时设置数据区内容;只读区和公开区在用户端只能读取,读写区在用户端可以通过代码进行修改。

使用接口:

/*
runtime 接口,需要登录许可成功后使用
*/

//读取数据大小
SS_UINT32 SSAPI slm_user_data_getsize(
IN SLM_HANDLE_INDEX slm_handle, //slm_login成功后返回的句柄
IN LIC_USER_DATA_TYPE type, //数据区类型
OUT SS_UINT32* pmem_size
);

//读出数据区内容
SS_UINT32 SSAPI slm_user_data_read(
IN SLM_HANDLE_INDEX slm_handle, //slm_login成功后返回的句柄
IN LIC_USER_DATA_TYPE type,//数据区类型
OUT SS_BYTE* readbuf,
IN SS_UINT32 offset, //偏移量,从哪个位置开始读取
IN SS_UINT32 len //读取长度
);

//数据区写入,只能操作读写区
SS_UINT32 SSAPI slm_user_data_write(
IN SLM_HANDLE_INDEX slm_handle,//slm_login成功后返回的句柄
IN SS_BYTE* writebuf, //写入的内容;这里注意是字节码
IN SS_UINT32 offset,//偏移量,从哪个位置开始写
IN SS_UINT32 len //写入长度
);

/*
control API 接口,不需要登录许可,但需要指定对应的容器及许可ID
*/
//读取公开区大小
SS_UINT32 SSAPI slm_ctrl_get_pub_size(
IN void* ipc, //调用 slm_ctrl_client_open()返回,与Virbox服务建立的通讯通道
IN SS_UINT32 license_id,//许可ID,要读取哪个许可ID的公开区
IN const char* desc,//设备描述,通过slm_ctrl_get_XXX_description获取,是指定要查询的设备
OUT SS_UINT32* len
);

//读取公开区数据
SS_UINT32 SSAPI slm_ctrl_read_pub_data(
IN void* ipc, //调用 slm_ctrl_client_open()返回,与Virbox服务建立的通讯通道
IN SS_UINT32 license_id,//许可ID,要读取哪个许可ID的公开区
IN const char* desc, //设备描述,通过slm_ctrl_get_XXX_description获取,是指定要查询的设备
OUT SS_BYTE* readbuf,
IN SS_UINT32 offset,//偏移量,从哪个位置开始读取
IN SS_UINT32 len//读取长度
);

代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "ss_lm_runtime.h" //runtime 库使用注意事项详情见"readme.txt"
#include "ss_iob_func.h"

/** 用户数据区最大*/
#ifndef MAX_USER_DATA_SIZE
#define MAX_USER_DATA_SIZE (64 * 1024)
#endif // MAX_USER_DATA_SIZE


int main()
{
SS_UINT32 ret = SS_OK;
ST_INIT_PARAM st_init_param = {0};
ST_LOGIN_PARAM login_param = {0};
SLM_HANDLE_INDEX slm_handle = 0;
SS_UINT32 ulRAWLen = 0;
SS_UINT32 ulROWLen = 0;
SS_BYTE testdata[65535] = {0};
SS_UINT32 ulTestData = 0;
SS_BYTE *pData = NULL;
SS_UINT32 index = 0;
SS_UINT32 nCount = 0;
SS_UINT32 nRemainLen = 0;
SS_CHAR *testWrite = "123456789";
SS_UINT32 iOffset = 0;

// 开发商密码(slm_init参数使用),置为全局,供客户修改。
// 示例固定使用DEMO开发商(0300000000000009)密码,仅能访问DEMO用户锁。
// 访问非DEMO开发商用户锁必须修改密码。
// 建议:考虑到软件安全性,API密码请使用byte数组类型定义。
unsigned char api_password[16] = { 0xDB, 0x3B, 0x83, 0x8B, 0x2E, 0x4F, 0x08, 0xF5, 0xC9, 0xEF, 0xCD, 0x1A, 0x5D, 0xD1, 0x63, 0x41 };

// 初始化接口调用,参数初始化
st_init_param.version = SLM_CALLBACK_VERSION02;
st_init_param.pfn = NULL;
st_init_param.flag = SLM_INIT_FLAG_NOTIFY;
memcpy( st_init_param.password, api_password, sizeof(api_password) );
ret = slm_init( &(st_init_param) );
if(SS_OK == ret)
{
printf("slm_init ok\n");
}
else if(SS_ERROR_DEVELOPER_PASSWORD == ret)
{
printf("slm_init error : 0x%08X(SS_ERROR_DEVELOPER_PASSWORD), Please login to the Virbox Developer Center(https://developer.lm.virbox.com), get the API password, and replace the 'api_password' variable content.\n", ret);
goto CLEAR;
}
else
{
printf("slm_init error : 0x%08X\n", ret);
goto CLEAR;
}

// 安全登录许可
login_param.license_id = 1;
login_param.size = sizeof(ST_LOGIN_PARAM);
login_param.timeout = 86400;
ret = slm_login( &login_param, STRUCT, &(slm_handle), NULL );
if(SS_OK != ret)
{
printf("slm_login error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf("slm_login ok\n");
}

/************************************************************************/
/*
只读数据区(ROM)
可以用此区作软件所需要的数据的存储,例如一些配置信息。
只读区较读写区更为常用,原因是所存储的内容不会在未授权的情况下被任意更改
更改只读区内容只有一种方法:远程的安全升级包(详见许可签发DEMO模块)
*/
/************************************************************************/

ret = slm_user_data_getsize(slm_handle, ROM, &ulROWLen);
if(SS_OK != ret)
{
printf("slm_user_data_getsize[ROM] error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf( "slm_user_data_getsize[ROM] ok rom_size : %d\n", ulROWLen );
}
if (ret == SS_OK && ulROWLen > 0)
{
pData = (SS_BYTE *)calloc(sizeof(SS_BYTE), ulROWLen);
ret = slm_user_data_read(slm_handle, ROM, pData, 0, ulROWLen);
if(SS_OK != ret)
{
printf("slm_user_data_read[ROM] error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf( "slm_user_data_read[ROM] ok\n" );
}

// 可在此处理获取到的数据

free(pData);
pData = NULL;
}
/************************************************************************/
/*
读写数据区(RAW)
读写区可以让开发商把运行过程中需要保存的数据保存在锁内,下次启动的时候可以访问。
访问之前一定要先登录到许可,如果许可失效,则无法读取使用任何该内存
*/
/************************************************************************/
// 1、RAW数据区写入(使用偏移进行写入演示)
ret = slm_user_data_getsize(slm_handle, RAW, &ulRAWLen);
if(SS_OK != ret)
{
printf("slm_user_data_getsize[RAW] error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf( "slm_user_data_getsize[RAW] ok ram_size : %d\n", ulRAWLen );
}

iOffset = 0;
ret = slm_user_data_write(slm_handle, (SS_BYTE*)testWrite, iOffset, 5);
if(SS_OK != ret)
{
printf("slm_user_data_write[RAW][OFFSET] offset=0 error : 0x%08X\n", ret);
goto CLEAR;
}
iOffset += 5;
ret = slm_user_data_write(slm_handle, (SS_BYTE*)testWrite+5, iOffset, 4);
if(SS_OK != ret)
{
printf("slm_user_data_write[RAW][OFFSET] offset=5 error : 0x%08X\n", ret);
goto CLEAR;
}
printf( "slm_user_data_write[RAW][OFFSET]: %s\n", testWrite);
ret = slm_user_data_read(slm_handle, RAW, testdata, 0, 9);
if(SS_OK != ret)
{
printf("slm_user_data_read[RAW][OFFSET] error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf("slm_user_data_read[RAW][OFFSET]: %s\n",testdata);
}


// 2、RAW数据区写入(短数据、长数据写入演示),每次最多可写入 SLM_MAX_WRITE_SIZE(1904)字节
//短数据写入
memset(testdata, '1', sizeof(testdata) );
ulTestData = 10;
if( ulTestData <= SLM_MAX_WRITE_SIZE )
{
ret = slm_user_data_write(slm_handle, testdata, 0, ulTestData);
if(SS_OK != ret)
{
printf("slm_user_data_write[RAW][SHORT] error : 0x%08X\n", ret);
goto CLEAR;
}
else
{
printf("slm_user_data_write[RAW][SHORT] ok\n");
}
}
//长数据写入,读写区写入每次有最大限制,一次最大允许写入1904字节,所以如果要写入超出限制的数据,需要分段处理;
ulTestData = ulRAWLen;
if( ( ulTestData >= SLM_MAX_WRITE_SIZE ) && ( ulTestData <= ulRAWLen ) )
{
nCount = ulTestData / SLM_MAX_WRITE_SIZE;
nRemainLen = ulTestData % SLM_MAX_WRITE_SIZE;
for(index = 0; index < nCount; index++ )
{
ret = slm_user_data_write(slm_handle, &testdata[SLM_MAX_WRITE_SIZE*index], SLM_MAX_WRITE_SIZE * index, SLM_MAX_WRITE_SIZE);
if(SS_OK != ret)
{
printf("slm_user_data_write[RAW][LONG] %d error : 0x%08X\n", index, ret);
goto CLEAR;
}
}
if( nRemainLen )
{
ret = slm_user_data_write(slm_handle, &testdata[SLM_MAX_WRITE_SIZE*index], SLM_MAX_WRITE_SIZE * index, nRemainLen);
if(SS_OK != ret)
{
printf("slm_user_data_write[RAW][LONG](remain) error : 0x%08X\n", index, ret);
goto CLEAR;
}
}
}
// 3、RAW数据区读取(短数据、长数据写读取演示),每次最多可读取 MAX_USER_DATA_SIZE(64*1024) 字节
//短数据读取
memset(testdata, 0, sizeof(testdata) );
ulTestData = 10;
if( ulTestData <= MAX_USER_DATA_SIZE )
{
ret = slm_user_data_read(slm_handle, RAW, testdata, 0, ulTestData);
if(SS_OK != ret)
{
printf("slm_user_data_read[RAW][SHORT] error : 0x%08X\n", ret);
goto CLEAR;
}
}

许可过期后如何获取公开区数据

我们前面讲到过,公开区数据是可以直接公开给用户查看的,但是想要读取某条许可的公开区数据,也是需要在成功登陆该许可的情况下才能查看,那么当软件许可过期后,是否还能查到该条许可的公开区数据?

答案是可以的,Runtime API 提供了免登录相关许可,即可获取该许可公开区数据的应用接口,开发者可以通过登录0号许可(请查看 0号许可),使用 0 号许可的登录状态,通过接口 slm_pub_data_read 来获取公开区数据。请看下面示例代码。

{
// 省略初始化等其他操作
// ...

// 假如 1 号许可已过期,如何获取 1 号许可的公开区数据
SS_UINT32 license_1 = 1;

// 安全登录许可 0
ST_LOGIN_PARAM login_param = {0};

login_param.license_id = 0;
login_param.size = sizeof(ST_LOGIN_PARAM);
login_param.timeout = 86400;
ret = slm_login( &login_param, STRUCT, &(slm_handle), NULL );

ret = slm_pub_data_getsize(slm_handle, ulLicenseID, &ulPUBLen);
if (ret == SS_OK && ulPUBLen > 0)
{
pData = (SS_BYTE *)calloc(sizeof(SS_BYTE), ulPUBLen);
ret = slm_pub_data_read(slm_handle, license_1, pData, 0, ulPUBLen);

// 可在此处理获取到的数据

free(pData);
pData = NULL;
}
if ( 0 != slm_handle )
{
slm_logout(slm_handle);
}
slm_cleanup();

CLEAR:
printf("slm_pub_data_getsize/read(PUB) ok\n");
return ;
}

硬件锁绑定计算机使用

许多客户在使用 Virbox LM 产品时,希望其软件不仅针对许可进行关联,还要对用户使用的计算机进行关联,仅第一台使用的电脑可以访问许可,软件拷贝到其他电脑上,即便插入了加密锁或登录了云账号也不能使用。针对这种使用场景,我们提供了如下的解决方案供您参考。

我们前面说到过,每一条 Virbox 许可,都拥有三条数据区,读写区将是我们这个方案中用到的一个数据区,简单的步骤描述如下:

  1. 软件第一次启动,并成功访问许可
  2. 获取本地计算机硬件信息(最好是可以唯一标识该计算机的信息,如 MAC地址、主板ID 等)
  3. 将计算机硬件信息写入到读写区内
  4. 下次软件启动时,访问许可成功后,优先读取读写区的数据和本地计算机硬件信息做对比。若信息一致则软件可以正常启动;若信息不一致,则退出许可,关闭软件。
// 省略初始化、许可登录等操作
... ...
// 这里以MAC地址作为硬件唯一标识,写入到RAW区
char *mac_addr = "AA-BB-CC-DD-EE-FF";

// 1. 程序第一次运行时
ret = slm_user_data_getsize(slm_handle, RAW, &length);
if (ret == SS_OK && length> 0 && length>= strlen(mac_addr) )
{
ret = slm_user_data_write(slm_handle, (SS_BYTE*)mac_addr, 0, strlen(mac_addr) );
printf( "slm_user_data_write(RAW) : %s\n\n", mac_addr);
}
else
{
printf("RAW size is too small to write PC_MAC\n");
return -1;
}

... ...

// 2、程序再次运行,再次获取PC_MAC信息,并读取出之前写入RAW区的PC_MAC信息
char *mac_addr = "AA-BB-CC-DD-EE-FF";

SS_BYTE pData[64] = {0};
ret = slm_user_data_read(slm_handle, RAW, pData, 0, strlen(mac_addr));
printf( "\nslm_user_data_read(RAW) : %s\n\n", pData);


//3、对比信息,如相同,则证明加密锁对应此PC,允许使用,否则不允许使用
if( strcmp(mac_addr, (char*)pData) == 0 )
{
printf( "the sense device can use in this computer!\n");
}
else
{
printf( "the sense device can not use in this computer!\n");
return -1;
}
... ...

查看加密锁内许可信息

通过如下代码,我们可以枚举当前计算机上插入的加密锁信息和锁内的许可信息

... ...
char *lic_id = NULL;
char *dev_desc = NULL;
char *lic_info = NULL;
SS_UINT32 ret = SS_OK;
Json::Reader reader; // 此处选择jsoncpp处理json数据
Json::Value root;
Json::Value lic;

ret = slm_enum_device(&dev_desc); // 首先要先遍历所有设备
if ( status == SS_OK && dev_desc != NULL && reader.parse(dev_desc, root))
{
for (int i = 0; i < root.size(); i++)
{
// 其次获取每个设备的许可ID
status = slm_enum_license_id(root[i].toStyledString().c_str(), &lic_id);
if (status == SS_OK && lic_id != NULL)
{
printf(lic_id);
printf("\n");
if (reader.parse(lic_id, lic))
{
for (int j = 0; j < lic.size(); j++)
{
// 最后获取许可的详细信息
status = slm_get_license_info(root[i].toStyledString().c_str(), lic[j].asInt(), &lic_info);
if (lic_info)
{
printf(lic_info);
printf("\n");
slm_free(lic_info);
lic_info = NULL;
}
}
}
slm_free(lic_id);
lic_id = NULL;
}
}
slm_free(dev_desc);
dev_desc = NULL;
}

... ...

64模块应用

适用场景:

软件按功能模块销售,控制不同功能模块是否有使用权;

在Virbox LM的体系中(参考许可体系),一个许可最多能设置64个模块,模块ID从1~64,每个模块的属性只能是允许或者不允许;

使用接口:

//如果返回 SS_OK,则存在此模块,如果返回SS_ERROR_LICENSE_MODULE_NOT_EXISTS,则模块不存在; 返回其他错误码,说明异常;
SS_UINT32 SSAPI slm_check_module(
IN SLM_HANDLE_INDEX slm_handle, //slm_login 返回的handle
IN SS_UINT32 module_id //模块ID 只能是1~64
);

代码示例:

#include "stdio.h"
#include "ss_lm_runtime.h"

int main(int argc, char **argv)
{
SLM_HANDLE_INDEX hslm = 0;

SS_UINT32 sts = SS_OK;
SS_UINT32 moduleid = 1;//1~64

SS_BYTE psd[16] = { 0xDB, 0x3B, 0x83, 0x8B, 0x2E, 0x4F, 0x08, 0xF5, 0xC9, 0xEF, 0xCD, 0x1A, 0x5D, 0xD1, 0x63, 0x41 }; // 开发者API密码,每个开发者独立,必须也只能从深思开发者中心获取到。
ST_LOGIN_PARAM login_struct = {0};
ST_INIT_PARAM st_init_param = {0};

//0. 全局初始化函数,调用此方法来初始化slm_runtime
st_init_param.version = SLM_CALLBACK_VERSION02;
st_init_param.pfn = &(app_ss_msg_core);
memcpy(st_init_param.password, psd, sizeof(psd));

sts = slm_init(&(st_init_param));

//1. slm_login 登录许可
login_struct.license_id = 0; //登录0号许可
login_struct.size = sizeof(ST_LOGIN_PARAM);
login_struct.timeout = 86400;

sts = slm_login(login_struct,STRUCT, &(hslm),NULL);//
if (SS_OK == sts)
{
printf("slm_login ok,许可登录成功!\n");
}
else
{
printf("slm_login faield error = 0x%08X, 许可登录失败:%s\n",sts, slm_error_msg(sts, 1));
goto end;
}

// 2. 这里检查模块有效性,如果有效,则进行下一步,进入软件业务部分,如果无效,则返回
sts = slm_check_module(hslm,moduleid);
if (SS_OK == sts)
{
printf("check module ok,模块存在,可以继续!\n");
}
else if (SS_ERROR_LICENSE_MODULE_NOT_EXISTS == sts)
{
printf("check module 模块不存在 0x%08X, %s\n",sts, slm_error_msg(sts, 1));
goto end;
}
else
{
printf("check module 出错 0x%08X, 错误原因:%s\n",sts, slm_error_msg(sts, 1));
goto end;
}

end:
slm_logout(hslm);
return sts;
}

许可加解密应用

典型使用场景:

1、将数据或者秘钥使用许可加密进行加密,放入软件,在使用软件时,验证许可后,使用许可解密解密数据或者秘钥,然后使用;

2、成果物保存时,使用许可加密;这样就可以限制,只有有对应许可的人才能解密使用;从而保护成果物不被滥用;

许可加解密的特点是,同一个开发商的相同许可ID加密解密结果一样;上述场景就是利用这个特性来实现的;

使用接口:

//许可加密; 成功返回 SS_OK ,失败返回相应错误码
SS_UINT32 SSAPI slm_encrypt(
IN SLM_HANDLE_INDEX slm_handle, //slm_login成功后产生的handle
IN SS_BYTE* inbuffer,//要加密的源数据;需要16字节对齐,最大不能超过1520个字节
OUT SS_BYTE* outbuffer,//输出加密后的数据
IN SS_UINT32 len //长度,16的倍数
);

//许可解密;成功返回 SS_OK ,失败返回相应错误码
SS_UINT32 SSAPI slm_decrypt(
IN SLM_HANDLE_INDEX slm_handle, //slm_login成功后产生的handle
IN SS_BYTE* inbuffer, //要解密的数据,需要16字节对齐,最大不能超过1520个字节
OUT SS_BYTE* outbuffer, //输出解密后的数据
IN SS_UINT32 len //输入数据长度,必须16的倍数
);

可以参考基本流程示例代码

更多示例

更多保护案例和示例代码,请参考 Virbox SDK 安装目录下 /sdk/API/C/Sample/ 。

如何快速找到示例代码?

首先,打开 Virbox 开发者工具盒

然后,找到 【API】 →【打开文件目录】,我们会打开 API 所在的目录

最后,依次打开 /C/Sample/ 文件夹即可看到所有示例代码

img

小结

程序编译好之后,我们的软件就和许可关联了起来,如何运行经过保护后的软件,可以查看各个产品快速上手的内容。