/** * 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 "errcodes.h" #include "utils/asprintf.h" #include "utils/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 = ((uint64_t) 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, char **ts_str) { 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); } neu_asprintf(ts_str, "%4d-%02d-%02d %02d:%02d:%02d", 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 const char *const g_plugin_bit_str_map[] = { [PLUGIN_BIT_MODBUS_PLUS_TCP] = "modbus-plus-tcp", [PLUGIN_BIT_MODBUS_RTU] = "modbus-rtu", [PLUGIN_BIT_OPCUA] = "opcua", [PLUGIN_BIT_S7COMM] = "s7comm", [PLUGIN_BIT_FINS] = "fins", [PLUGIN_BIT_QNA3E] = "QnA3E", [PLUGIN_BIT_IEC60870_5_104] = "IEC60870-5-104", [PLUGIN_BIT_KNX] = "KNXnet/IP", [PLUGIN_BIT_NONA11] = "NONA11", [PLUGIN_BIT_DLT645_2007] = "DLT645-2007", [PLUGIN_BIT_BACNET] = "BACnet/IP", [PLUGIN_BIT_MAX] = NULL, }; 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))) { nlog_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))) { nlog_warn("loading pubkey into memory"); rv = NEU_ERR_EINTERNAL; goto final; } /* Verify the certificates */ if (X509_verify(cert, pkey) == 0) { nlog_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->since_str_); license->until_ = get_asn1_ts(not_after, &license->until_str_); nlog_info("certificate expire date: %s", not_after->data); if (NULL == license->since_str_ || NULL == license->until_str_) { nlog_warn("cannot strdup"); rv = NEU_ERR_EINTERNAL; goto final; } 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); nlog_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); nlog_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; } unsigned mark = 0; 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); nlog_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_) { nlog_error("invalid license type:`%s`", val); rv = NEU_ERR_LICENSE_INVALID; } mark |= 0x01; } else if (0 == strcmp(buf, LICENSE_KEY_MAX_NODES)) { license->max_nodes_ = strtoul(val, NULL, 0); mark |= 0x02; } else if (0 == strcmp(buf, LICENSE_KEY_MAX_NODE_TAGS)) { license->max_node_tags_ = strtoul(val, NULL, 0); mark |= 0x04; } else if (0 == strcmp(buf, LICENSE_KEY_PLUGIN_FLAG)) { if (0 != scan_bitvec(license->plugin_flag_, sizeof(license->plugin_flag_), val)) { nlog_error("fail to scan license plugin flag:`%s`", val); rv = NEU_ERR_LICENSE_INVALID; } mark |= 0x08; } else if (0 == strcmp(buf, LICENSE_KEY_TOKEN)) { license->token_ = strdup(val); mark |= 0x10; } } if (0x0F == (mark & 0x0F)) { nlog_info("certification verification success"); } else { rv = NEU_ERR_LICENSE_INVALID; nlog_error("missing license key fields, mark:%u", mark); } 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]; } const char *plugin_bit_str(plugin_bit_e b) { return g_plugin_bit_str_map[b]; } void license_print(license_t *license) { char flag[sizeof(license->plugin_flag_) * 2 + 1] = {}; print_bitvec(flag, sizeof(flag), license->plugin_flag_, sizeof(license->plugin_flag_)); nlog_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_), license->since_str_, license->until_str_, 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->since_str_); free(license->until_str_); free(license->fname_); free(license->token_); } 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 -1; } license_t lic; license_init(&lic); lic.fname_ = strdup(fname); if (NULL == lic.fname_) { return -1; } int rv = extract_license(fname, root_cert_pub_key, &lic); if (0 != rv) { license_fini(&lic); return rv; } license_print(&lic); free(license->since_str_); free(license->until_str_); free(license->fname_); free(license->token_); *license = lic; return rv; }