From 285d049dafca3579f60d4fd0716312740b752bc2 Mon Sep 17 00:00:00 2001 From: eeff <33566201+eeff@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:04:14 +0800 Subject: [PATCH] Add license IO utilities in restful plugin --- plugins/restful/license.c | 396 ++++++++++++++++++++++++++++++++++++++ plugins/restful/license.h | 140 ++++++++++++++ 2 files changed, 536 insertions(+) create mode 100644 plugins/restful/license.c create mode 100644 plugins/restful/license.h diff --git a/plugins/restful/license.c b/plugins/restful/license.c new file mode 100644 index 00000000..31eeed94 --- /dev/null +++ b/plugins/restful/license.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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; +} diff --git a/plugins/restful/license.h b/plugins/restful/license.h new file mode 100644 index 00000000..e38fb104 --- /dev/null +++ b/plugins/restful/license.h @@ -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 +#include +#include + +#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