You need to enable JavaScript to run this app.
导航

在线授权说明

最近更新时间2022.02.28 19:42:10

首次发布时间2022.02.25 17:16:51

本文将描述在线授权基本概念和操作流程,读完本文您将对在线授权机制有全面的了解,并且学会如何顺利的通过在线授权机制使用我们的软件。

什么是软件证书

软件证书 是软件使用权的凭证,一个合法的软件证书有以下特性:

  1. 授权到功能
    一个证书对应着一组功能的授权,软件可以激活证书授权范围内的所有功能。
  2. 授权到时间
    一个证书具备有效时间,只有在有效期内的证书才能激活软件。
  3. 授权到独立设备
    为了防止证书泄漏带来的损失,每台独立设备都拥有其独一无二的证书,一台设备的证书无法在另一台设备使用。
  4. 按业务划分
    同一业务下的证书,都拥有相同的功能、有效期等属性。

软件证书一般以文件的形式存在设备文件系统中。

什么是在线授权

在线授权是一种软件证书的网络分发方式,通过这个机制,设备可以快速、安全的通过网络获取软件证书。

为什么要在线授权

在线授权不是唯一的证书分发方式。例如,您依然可以通过联系商务人员以人工审核+电子邮件的方式获取软件证书。相较人工途径,在线证书可以更高效的完成授权流程,在自动化的同时保证安全性。

在线授权流程

下图展示了一次在线授权流程
image

  1. 从SDK 获取authMsg

    //bef_effect_ai_auth_msg.h
    bef_effect_ai_get_auth_msg( char** buf, int* len)
    
  2. 构造消息体,发送请求到字节跳动在线授权服务
    URL:https://cv-tob.bytedance.com/v1/api/sdk/tob_license/getlicense
    HTTP 方法:POST
    内容格式:JSON
    消息示例:

    POST /v1/api/sdk/tob_license/getlicense HTTP/1.1
    Host: cv-tob.bytedance.com
    Content-Type: application/json
    
    {
        "key": <key>,
        "authMsg": <authMsg>,
        "nonce": <nonce>,
        "timestamp": <timestamp>,
        "digest": <digest>
    }
    

    字段解释:
    key :业务标识,对应用户开通的业务类型。
    authMsg :验证消息,由SDK 产生的设备相关标识。
    nonce :随机数。
    timestamp :unix 时间戳(从1970/1/1 到当前的秒数)
    digest :数字签名,见以下描述。

    为了保障消息的权威性和完整性,这里使用HMAC_SHA256 作为签名算法

    digest = HMAC_SHA256(<secret>, key + nonce + timestamp + aughMsg)
    

    secret :业务密钥,创建业务时候同key 一起获得。
    + :为字符串拼接操作("a" + "b" = "ab")

  3. 授权服务器返回响应消息
    消息示例:

    //Success
    {
        'data': <base64 encoded license>,
        'digest': <digest>,
        'status_code': 0
    }
    
    //Failure
    {
        'error': <error message>,
        'status_code': <code>
    }
    

    若请求成功,则data 为base64 编码的证书内容(要获取证书内容,需base64 解码)
    digest = HMAC_SHA256(<secret>, data) 其中secret 同步骤2。
    若请求失败,则error 为错误提示,code 为对应错误码,请参考常见错误或联系我们。

  4. 将获取的证书设置到SDK 以激活相关模块

代码示例

Python

def getLicense(key, secret, authMsg):
    payload = {
        "key": key,
        "authMsg": authMsg,
        "nonce": random.randint(0, 999999999),
        "timestamp": int(time.time())
    }
    payload['digest'] = crypto.HMAC_SHA256(secret, bytes(key + str(payload['nonce']) + str(payload['timestamp']) + authMsg).encode('utf-8'))

    url = "https://cv-tob.bytedance.com/v1/api/sdk/tob_license/getlicense"
    headers = {
        'Content-Type': "application/json",
        'cache-control': "no-cache"
    }

    response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
    resp_json=json.loads(response.text)
    print("{}".format(resp_json['data']))
    raw = base64.b64decode(resp_json['data'])
    print('{}'.format(raw))

    a = resp_json['data']
    b = resp_json['digest']
    h = crypto.HMAC_SHA256(secret,a)
    if h == b:
        print("crypto.HMAC_SHA256 match")
    print('getLicense !')

c++

#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <curl/curl.h>
    
//获取HMAC_SHA256 数字签名 
std::vector<uint8_t> 
HMAC_SHA256(const std::vector<uint8_t>& key
           ,const std::vector<uint8_t>& value)
{
    unsigned int len = SHA256_DIGEST_LENGTH;
    unsigned char hash[SHA256_DIGEST_LENGTH];
    size_t keyLen = key.size();
    size_t valueLen = value.size();

    HMAC_CTX *hmac = HMAC_CTX_new();

    HMAC_Init_ex(hmac, (unsigned char*)key.data(), keyLen, EVP_sha256(), NULL);
    HMAC_Update(hmac, (unsigned char*)value.data(), valueLen);
    HMAC_Final(hmac, hash, &len);
    HMAC_CTX_free(hmac);

    return std::vector<uint8_t>((uint8_t*)hash,(uint8_t*)hash+SHA256_DIGEST_LENGTH);
}

//将二进制内容转换为大写十六进制字符串
std::string hexitize(const std::vector<unsigned char>& input, const char* const digits = "0123456789ABCDEF")
{
    std::ostringstream output;

    for (unsigned char gap = 0, beg = input[gap]; gap < input.size(); beg = input[++gap]) {
        output << digits[beg >> 4] << digits[beg & 15];
    }

    return output.str();
}
    
    
//使用CURL 进行请求
int request_license(
    const char* key, 
    const char* secret, 
    const char* authMsg, 
    char** license, 
    int* size) {

    CURL *curl;
    CURLcode res;
    curl = curl_easy_init();
    std::string readBuffer;

    auto payload_str = _get_request_content(key, secret, authMsg);

    if(curl) {
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
        curl_easy_setopt(curl, CURLOPT_URL, "https://cv-tob.bytedance.com/v1/api/sdk/tob_license/getlicense");
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload_str.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
        res = curl_easy_perform(curl);
    }
    curl_easy_cleanup(curl);

    if (readBuffer.length() > 0) {
        auto ret = json::parse(readBuffer);
        int code = ret["status_code"];
        if (code != 0) {
            std::string msg = ret["error"];
            *license = new char[msg.length()+1];
            memcpy(*license, msg.c_str(), msg.length()+1);
            *size = msg.length();
            return code;
        }

        std::string lic = ret["data"];
        std::string digest = ret["digest"];

        //verify digest
        auto re_hash = HMAC_SHA256(
            Misc::concat(secret, ""),
            Misc::concat(lic, "")
        );
        if (hexitize(re_hash).compare(digest) == 0) {
            vector<uint8_t> lic_b = base64_decode(lic);
            *license = new char[lic_b.size()];
            memcpy(*license, lic_b.data(), lic_b.size());
            *size = lic_b.size();
            return code;
        }

        //digest verified failure, corrupted message
        const char m[] = "Detecting data corruption, please reissue the request.";
        *license = new char[strlen(m)+1];
        *size = strlen(m);
        memcpy(*license, m, (*size)+1);
        return -1;
    }


    *license = nullptr;
    *size = 0;
    return res;  
}

常见错误

详见错误码表