mirror of
https://github.com/emqx/neuron.git
synced 2026-10-22 17:50:36 +08:00
Add license IO utilities in restful plugin
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* NEURON IIoT System for Industry 4.0
|
||||
* Copyright (C) 2020-2022 EMQ Technologies Co., Ltd All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**/
|
||||
|
||||
#define NEURON_LOG_LABEL "license"
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "neu_errcodes.h"
|
||||
#include "neu_log.h"
|
||||
|
||||
#include "license.h"
|
||||
|
||||
const char root_cert_pub_key[] = "-----BEGIN PUBLIC KEY-----\n\
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyJgH+BvEJIStYvyw1keQ\n\
|
||||
/ixVPJ4GGjlP7lTKnZL3mqZyPXQUEaLnRmcQ3/ra8xYQPcfMReynqmrYmT45/eD2\n\
|
||||
tK7rdXTzYfOWoU0cXNQMaQS7be1bLF4QrAEbJhEsgkjX9rP30mrzZCjRRnkQtWmi\n\
|
||||
4DNBU4qOl6Ee9aAS5aY+L7DW646J47Tyc7gAA4sdZw04KGBXnSyXzyBvPaf+QByO\n\
|
||||
rOXUxBcxehHN/Ox41/duYFXSR40U6lyp49NYJ6yEUVWSp4oxsrkcgqyegNKXdW1D\n\
|
||||
8oqJXzxalbh/RB8YYlX+Ae377gEBlLefPFdSEYDRN/ajs3UIeqde6i20lVyDPIjE\n\
|
||||
cQIDAQAB\n\
|
||||
-----END PUBLIC KEY-----";
|
||||
|
||||
static const uint32_t mon_yday[2][12] = {
|
||||
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
|
||||
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 },
|
||||
};
|
||||
|
||||
static int isleap(int year)
|
||||
{
|
||||
return (year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0);
|
||||
}
|
||||
|
||||
static uint64_t mktimestamp(struct tm dt)
|
||||
{
|
||||
uint64_t ret;
|
||||
int i = 0;
|
||||
|
||||
ret = (dt.tm_year - 1970) * 365 * 24 * 3600;
|
||||
ret +=
|
||||
(mon_yday[isleap(dt.tm_year)][dt.tm_mon] + dt.tm_mday - 1) * 24 * 3600;
|
||||
ret += dt.tm_hour * 3600 + dt.tm_min * 60 + dt.tm_sec;
|
||||
|
||||
for (i = 1970; i < dt.tm_year; i++) {
|
||||
if (isleap(i)) {
|
||||
ret += 24 * 3600;
|
||||
}
|
||||
}
|
||||
if (ret > 4107715199) { // 2100-02-29 23:59:59
|
||||
ret += 24 * 3600;
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static uint64_t get_asn1_ts(ASN1_TIME *time)
|
||||
{
|
||||
struct tm t;
|
||||
const char *str = (const char *) time->data;
|
||||
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
if (time->type == V_ASN1_UTCTIME) { /* two digit year */
|
||||
sscanf(str, "%2d%2d%2d%2d%2d%2dZ", &t.tm_year, &t.tm_mon, &t.tm_mday,
|
||||
&t.tm_hour, &t.tm_min, &t.tm_sec);
|
||||
t.tm_year += 2000;
|
||||
} else if (time->type == V_ASN1_GENERALIZEDTIME) { /* four digit year */
|
||||
sscanf(str, "%4d%2d%2d%2d%2d%2dZ", &t.tm_year, &t.tm_mon, &t.tm_mday,
|
||||
&t.tm_hour, &t.tm_min, &t.tm_sec);
|
||||
}
|
||||
|
||||
t.tm_mon -= 1;
|
||||
/* Note: we did not adjust the time based on time zone information */
|
||||
return mktimestamp(t);
|
||||
}
|
||||
|
||||
static int scan_bitvec(uint8_t vec[], size_t size, const char *hex)
|
||||
{
|
||||
if ('0' == *hex && 'x' == tolower(*(hex + 1))) {
|
||||
hex += 2;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
size_t len = strlen(hex);
|
||||
|
||||
while (i < len && i / 2 < size) {
|
||||
char c = hex[len - 1 - i];
|
||||
uint8_t v = 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
v = c - '0';
|
||||
} else if ('a' <= c && c <= 'f') {
|
||||
v = 10 + c - 'a';
|
||||
} else if ('A' <= c && c <= 'F') {
|
||||
v = 10 + c - 'A';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
vec[i / 2] += v << ((i & 0x01) * 4);
|
||||
++i;
|
||||
}
|
||||
|
||||
return (i == len) ? 0 : -1;
|
||||
}
|
||||
|
||||
void print_bitvec(char *s, size_t size, uint8_t vec[], size_t n)
|
||||
{
|
||||
size_t i = 0;
|
||||
for (size_t j = n; j-- > 0 && i < size;) {
|
||||
if (0 != j && 0 == vec[j]) {
|
||||
continue;
|
||||
}
|
||||
uint8_t h = (vec[j] & 0xF0) >> 4;
|
||||
uint8_t l = vec[j] & 0x0F;
|
||||
s[i++] = (h < 10) ? ('0' + h) : ('A' + h - 10);
|
||||
if (i < size) {
|
||||
s[i++] = (l < 10) ? ('0' + l) : ('A' + l - 10);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < size) {
|
||||
s[i] = '\0';
|
||||
} else if (size > 0) {
|
||||
s[size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static const char *const g_license_type_str_map_[] = {
|
||||
[LICENSE_TYPE_TRIAL] = "trial",
|
||||
[LICENSE_TYPE_RETAIL] = "retail",
|
||||
[LICENSE_TYPE_SME] = "small-enterprise",
|
||||
};
|
||||
|
||||
static license_type_e scan_licese_type(const char *s)
|
||||
{
|
||||
license_type_e t = LICENSE_TYPE_MAX;
|
||||
for (int i = 0; i < LICENSE_TYPE_MAX; ++i) {
|
||||
if (0 == strcmp(s, g_license_type_str_map_[i])) {
|
||||
t = (license_type_e) i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static int extract_license(const char *license_fname, const char *pub_key,
|
||||
license_t *license)
|
||||
{
|
||||
BIO * certbio = NULL;
|
||||
BIO * keybio = NULL;
|
||||
X509 * cert = NULL;
|
||||
X509_NAME * subj = NULL;
|
||||
X509_NAME * issuer = NULL;
|
||||
X509_NAME_ENTRY *entry = NULL;
|
||||
EVP_PKEY * pkey = NULL;
|
||||
const STACK_OF(X509_EXTENSION) * ext_list;
|
||||
ASN1_OBJECT * obj;
|
||||
ASN1_STRING * str;
|
||||
ASN1_TIME * not_before, *not_after;
|
||||
X509_EXTENSION * ext;
|
||||
char buf[100];
|
||||
const unsigned char *value;
|
||||
int i;
|
||||
int rv = 0;
|
||||
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
struct stat statbuf;
|
||||
if (-1 == stat(license_fname, &statbuf)) {
|
||||
rv = NEU_ERR_LICENSE_NOT_FOUND;
|
||||
goto final;
|
||||
}
|
||||
|
||||
/* load emqx certificate */
|
||||
certbio = BIO_new(BIO_s_file());
|
||||
if (0 == BIO_read_filename(certbio, license_fname)) {
|
||||
rv = NEU_ERR_LICENSE_INVALID;
|
||||
goto final;
|
||||
}
|
||||
if (!(cert = PEM_read_bio_X509(certbio, NULL, NULL, NULL))) {
|
||||
log_warn("loading certificate into memory");
|
||||
rv = NEU_ERR_LICENSE_INVALID;
|
||||
goto final;
|
||||
}
|
||||
|
||||
/* load public key */
|
||||
keybio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(keybio, pub_key);
|
||||
if (!(pkey = PEM_read_bio_PUBKEY(keybio, NULL, NULL, NULL))) {
|
||||
log_warn("loading pubkey into memory");
|
||||
rv = NEU_ERR_EINTERNAL;
|
||||
goto final;
|
||||
}
|
||||
|
||||
/* Verify the certificates */
|
||||
if (X509_verify(cert, pkey) == 0) {
|
||||
log_warn("certification verification failure");
|
||||
rv = NEU_ERR_LICENSE_INVALID;
|
||||
goto final;
|
||||
}
|
||||
|
||||
not_before = X509_get_notBefore(cert);
|
||||
not_after = X509_get_notAfter(cert);
|
||||
license->since_ = get_asn1_ts(not_before);
|
||||
license->until_ = get_asn1_ts(not_after);
|
||||
log_info("certificate expire date: %s", not_after->data);
|
||||
|
||||
subj = X509_get_subject_name(cert);
|
||||
for (i = 0; i < X509_NAME_entry_count(subj); i++) {
|
||||
entry = X509_NAME_get_entry(subj, i);
|
||||
str = X509_NAME_ENTRY_get_data(entry);
|
||||
value = ASN1_STRING_get0_data(str);
|
||||
log_info("certificate subject: %s", value);
|
||||
}
|
||||
|
||||
issuer = X509_get_issuer_name(cert);
|
||||
for (i = 0; i < X509_NAME_entry_count(issuer); i++) {
|
||||
entry = X509_NAME_get_entry(issuer, i);
|
||||
str = X509_NAME_ENTRY_get_data(entry);
|
||||
value = ASN1_STRING_get0_data(str);
|
||||
log_info("certificate issuer: %s", value);
|
||||
}
|
||||
|
||||
/* extract the certificate's extensions */
|
||||
|
||||
ext_list = X509_get0_extensions(cert);
|
||||
if (sk_X509_EXTENSION_num(ext_list) <= 0) {
|
||||
rv = NEU_ERR_LICENSE_INVALID;
|
||||
goto final;
|
||||
}
|
||||
|
||||
for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) {
|
||||
ext = sk_X509_EXTENSION_value(ext_list, i);
|
||||
obj = X509_EXTENSION_get_object(ext);
|
||||
str = X509_EXTENSION_get_data(ext);
|
||||
OBJ_obj2txt(buf, sizeof buf, obj, 1);
|
||||
value = ASN1_STRING_get0_data(str);
|
||||
log_info("certificate OID: %s => %s", buf, &value[2]);
|
||||
|
||||
const char *val = (char *) &value[2];
|
||||
|
||||
if (0 == strcmp(buf, LICENSE_KEY_TYPE)) {
|
||||
license->type_ = scan_licese_type(val);
|
||||
if (LICENSE_TYPE_MAX == license->type_) {
|
||||
log_error("invalid license type:`%s`", val);
|
||||
rv = NEU_ERR_LICENSE_INVALID;
|
||||
}
|
||||
} else if (0 == strcmp(buf, LICENSE_KEY_MAX_NODES)) {
|
||||
license->max_nodes_ = strtoul(val, NULL, 0);
|
||||
} else if (0 == strcmp(buf, LICENSE_KEY_MAX_NODE_TAGS)) {
|
||||
license->max_node_tags_ = strtoul(val, NULL, 0);
|
||||
} else if (0 == strcmp(buf, LICENSE_KEY_PLUGIN_FLAG)) {
|
||||
if (0 !=
|
||||
scan_bitvec(license->plugin_flag_,
|
||||
sizeof(license->plugin_flag_), val)) {
|
||||
log_error("fail to scan license plugin flag:`%s`", val);
|
||||
rv = NEU_ERR_LICENSE_INVALID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_info("certification verification success");
|
||||
|
||||
final:
|
||||
if (pkey)
|
||||
EVP_PKEY_free(pkey);
|
||||
if (cert)
|
||||
X509_free(cert);
|
||||
if (certbio)
|
||||
BIO_free_all(certbio);
|
||||
if (keybio)
|
||||
BIO_free_all(keybio);
|
||||
return rv;
|
||||
}
|
||||
|
||||
const char *license_type_str(license_type_e t)
|
||||
{
|
||||
return g_license_type_str_map_[t];
|
||||
}
|
||||
|
||||
void license_print(license_t *license)
|
||||
{
|
||||
struct tm tm = {};
|
||||
char start[16] = {};
|
||||
char end[16] = {};
|
||||
time_t since = license->since_;
|
||||
time_t until = license->until_;
|
||||
const char *fmt = "%Y%m%dT%H%M%S";
|
||||
|
||||
gmtime_r(&since, &tm);
|
||||
strftime(start, sizeof(start), fmt, &tm);
|
||||
gmtime_r(&until, &tm);
|
||||
strftime(end, sizeof(end), fmt, &tm);
|
||||
|
||||
char flag[sizeof(license->plugin_flag_) * 2 + 1] = {};
|
||||
print_bitvec(flag, sizeof(flag), license->plugin_flag_,
|
||||
sizeof(license->plugin_flag_));
|
||||
|
||||
log_info("license `%s` for %s, from %s to %s, max_nodes:%" PRIu32
|
||||
", max_node_tags:%" PRIu32 ", flag:0x%s",
|
||||
license->fname_, license_type_str(license->type_), start, end,
|
||||
license->max_nodes_, license->max_node_tags_, flag);
|
||||
}
|
||||
|
||||
void license_init(license_t *license)
|
||||
{
|
||||
// no associate license file
|
||||
// no plugins enabled
|
||||
// expired license period
|
||||
memset(license, 0, sizeof(*license));
|
||||
license->since_ = 1;
|
||||
}
|
||||
|
||||
void license_fini(license_t *license)
|
||||
{
|
||||
free(license->fname_);
|
||||
}
|
||||
|
||||
license_t *license_new()
|
||||
{
|
||||
license_t *license = malloc(sizeof(*license));
|
||||
if (NULL == license) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
license_init(license);
|
||||
|
||||
return license;
|
||||
}
|
||||
|
||||
void license_free(license_t *license)
|
||||
{
|
||||
if (NULL == license) {
|
||||
return;
|
||||
}
|
||||
|
||||
license_fini(license);
|
||||
free(license);
|
||||
}
|
||||
|
||||
int license_read(license_t *license, const char *fname)
|
||||
{
|
||||
if (NULL == license || NULL == fname) {
|
||||
return NEU_ERR_EINVAL;
|
||||
}
|
||||
|
||||
license_t lic;
|
||||
license_init(&lic);
|
||||
|
||||
lic.fname_ = strdup(fname);
|
||||
if (NULL == lic.fname_) {
|
||||
return NEU_ERR_ENOMEM;
|
||||
}
|
||||
|
||||
int rv = extract_license(fname, root_cert_pub_key, &lic);
|
||||
if (0 != rv) {
|
||||
license_fini(&lic);
|
||||
return rv;
|
||||
}
|
||||
|
||||
license_print(&lic);
|
||||
|
||||
free(license->fname_);
|
||||
*license = lic;
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* NEURON IIoT System for Industry 4.0
|
||||
* Copyright (C) 2020-2022 EMQ Technologies Co., Ltd All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**/
|
||||
|
||||
#ifndef LICENSE_H
|
||||
#define LICENSE_H
|
||||
|
||||
#define LICENSE_DIR "cert"
|
||||
#define LICENSE_FNAME "neuron.lic"
|
||||
|
||||
// License type
|
||||
// Type: string
|
||||
// - "trial"
|
||||
// - "retail"
|
||||
// - "small-enterprise"
|
||||
#define LICENSE_KEY_TYPE "1.3.6.1.4.1.52509.10"
|
||||
|
||||
// Max number of nodes.
|
||||
// Type: integer, non-negative
|
||||
#define LICENSE_KEY_MAX_NODES "1.3.6.1.4.1.52509.11"
|
||||
|
||||
// Max number of tags per node.
|
||||
// Type: integer, non-negative
|
||||
#define LICENSE_KEY_MAX_NODE_TAGS "1.3.6.1.4.1.52509.12"
|
||||
|
||||
// Bit flag indicating which plugins are enabled.
|
||||
// Type: integer
|
||||
//
|
||||
// MSB LSB
|
||||
// ..., 63, 62, ... 3, 2, 1, 0
|
||||
// ^----many others--------------^ ^ ^ ^
|
||||
// | | |
|
||||
// opcua----------------------------+ | |
|
||||
// modbus_rtu--------------------------+ |
|
||||
// modbus_plus_tcp------------------------+
|
||||
//
|
||||
#define LICENSE_KEY_PLUGIN_FLAG "1.3.6.1.4.1.52509.13"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "neu_errcodes.h"
|
||||
|
||||
typedef enum {
|
||||
LICENSE_TYPE_TRIAL = 0, // trial
|
||||
LICENSE_TYPE_RETAIL = 1, // retail
|
||||
LICENSE_TYPE_SME = 2, // small enterprise
|
||||
LICENSE_TYPE_MAX,
|
||||
} license_type_e;
|
||||
|
||||
const char *license_type_str(license_type_e t);
|
||||
|
||||
typedef enum {
|
||||
PLUGIN_BIT_MODBUS_PLUS_TCP = 0,
|
||||
PLUGIN_BIT_MODBUS_RTU = 1,
|
||||
PLUGIN_BIT_OPCUA = 2,
|
||||
PLUGIN_BIT_S7COMM = 3,
|
||||
} plugin_bit_e;
|
||||
|
||||
// Representing a single license.
|
||||
typedef struct {
|
||||
license_type_e type_; // license type
|
||||
char * fname_; // license file name
|
||||
uint64_t since_; // not before
|
||||
uint64_t until_; // not after
|
||||
uint32_t max_nodes_; // max number of nodes
|
||||
uint32_t max_node_tags_; // max number of tags per node
|
||||
uint8_t plugin_flag_[200 / 8]; // plugin enable/disable bits
|
||||
} license_t;
|
||||
|
||||
// Initialize to be expired with no file.
|
||||
void license_init(license_t *license);
|
||||
void license_fini(license_t *license);
|
||||
|
||||
// Create new expired license associated with no file.
|
||||
license_t *license_new();
|
||||
void license_free(license_t *license);
|
||||
// Read from license file.
|
||||
int license_read(license_t *license, const char *license_fname);
|
||||
|
||||
// Get associated license file name.
|
||||
static inline const char *license_get_file_name(const license_t *license)
|
||||
{
|
||||
return license->fname_;
|
||||
}
|
||||
|
||||
// Get license expiry date.
|
||||
static inline uint64_t license_get_expiry_timestamp(const license_t *license)
|
||||
{
|
||||
return license->until_;
|
||||
}
|
||||
|
||||
// Check if the license is expired according to the current time.
|
||||
static inline bool license_is_expired(const license_t *license)
|
||||
{
|
||||
uint64_t now = time(NULL);
|
||||
return now < license->since_ || license->until_ < now;
|
||||
}
|
||||
|
||||
static inline uint64_t license_get_max_nodes(const license_t *license)
|
||||
{
|
||||
return license->max_nodes_;
|
||||
}
|
||||
|
||||
static inline uint64_t license_get_max_node_tags(const license_t *license)
|
||||
{
|
||||
return license->max_node_tags_;
|
||||
}
|
||||
|
||||
static inline bool license_is_plugin_enabled(const license_t *license,
|
||||
plugin_bit_e bit)
|
||||
{
|
||||
return license->plugin_flag_[bit >> 3] & (1 << (bit & 0x07));
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user