区块链杂记

celr: celer network 代币,属于l2技术,主打链下链上结合,发挥链下方案的性能优势
ren: republic protocol 代币,主打私密撮合交易
rune: THOR chain的代币,主打快速结算
dydx:基于ERC20的dydx exchange平台的token,主打deFi和低手续费

docsify maxLevel subMaxLevel参数说明

maxLevel,subMaxLevel是docsify中的两个重要的参数,用来控制整个文档站左侧导航菜单的层级。

作用

maxLevel:控制着左侧的菜单的总层级.
subMaxLevel:表示由文档内容中的标题自动生成菜单的级别,从第一级起算。例如subMaxLevel取2,表示一共两级,所以实际只显示第二级(##),因为第一级默认跟外层的入口同级,显示外层的名字。

数值关系

maxLevel>=1
subMaxLevel>=1 && subMaxLevel<maxLevel

source code illustration of TLS support in esp32

嵌入式下实现TLS支持原理

我们知道要实现https,MQTT等协议时,要求通讯安全,客户端就必须实现tls证书的支持。

但是日常我们打来电脑和手机浏览器访问https网站,好像并不需要关注tls的问题?

答案是因为浏览器厂商已经帮助我们兼容好了,但当你要通过嵌入式IOT设备或者单片机实现https访问的时候,你就需要处理TLS证书的问题了。

因为ESP32的开源代码比较清晰简洁,所以我们今天的讲解,以ESP32为硬件平台。其他硬件也是类似的原理,你只要实现自己的过程,替换底层的socket接口就可以实现类似的效果。

此文通过对关键代码的详细讲解,说明在ESP32下支持TLS证书的实现原理和过程。以此原理和过程作为参考,用户也可以实现其他嵌入式硬件和单片机的TLS证书支持,因为ESP的证书的内容和代码都是源码可见的,在弄懂了后就可以很好的移植和仿制。

ESP32 HTTPS demo

官方的例子见 \examples\protocols\https_request,举例了三种支持TLS的方式:

    https_get_request_using_crt_bundle();
    https_get_request_using_cacert_buf();
    https_get_request_using_global_ca_store();

第一种是cert bundle方式,这个是今天我们重点的讲解。后两种就是使用用户指定的证书的方式,代码非常简单,就不展开了,今天主要介绍 cert bundle的方式。把cert bundle方式弄清楚了,后面两种也就清楚了,因为就是等于是cert bundle 方式的简化,简化了证书的创建,匹配和校验过程。

cert bundle的优劣

  • 优点:cert bundle的优势是不需要用户关注证书和手动下载证书,自动实现全球几乎所有TLS根证书的支持。
  • 缺点:因为内置一百多种证书,所以占用空间要大写,经过实际代码测试验证,大概大了60K左右。

ESP32 x509 Certificate Bundle

esp32 针对TLS的场景提供了x509 Certificate Bundle的实现支持,其可以简单理解为 x509证书集合。

关于x509 Certificate Bundle的详细介绍,可以参看:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_crt_bundle.html

原文已经讲的非常清楚了,我们就不赘述了,今天只重点关注源码细节。

ESP32 TLS实现过程简述

  1. cert bundle 反序列化
  2. 创建空的证书
  3. 在https的访问回调中,根据cert name,查找对应的证书。
  4. 校验证书合法性
  5. 校验通过后,建立链接,进行基于tls的读写过程。
  6. 关闭链接,释放tls对象

源码分析

上面简述了整个过程,接下来我们将针对上面的步骤,对关键源码进行解析说明。

cert bundle 反序列化

外层调用:

if (s_crt_bundle.crts == NULL) {
        ret = esp_crt_bundle_init(x509_crt_imported_bundle_bin_start);
    }

参数 x509_crt_imported_bundle_bin_start 对应的值为 asm("_binary_x509_crt_bundle_start")

这个asm对象表示一段汇编代码,作用是返回x509对象数据地址,这个对象是通过python工具将cacrt_all.pem打包成二进制文件的,具体汇编的内容,可以自己参见 build目录下的 x509_crt_bundle.S汇编文件。

里层函数实现,解析注释:

static esp_err_t esp_crt_bundle_init(const uint8_t* x509_bundle)
{
    // 根据包头的两个字节获取证书个数
    s_crt_bundle.num_certs = (x509_bundle[0] << 8) | x509_bundle[1];
    s_crt_bundle.crts      = calloc(s_crt_bundle.num_certs, sizeof(x509_bundle));

    if (s_crt_bundle.crts == NULL) {
        ESP_LOGE(TAG, "Unable to allocate memory for bundle");
        return ESP_ERR_NO_MEM;
    }

    const uint8_t* cur_crt;
    cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET;

    ESP_LOGW(TAG, "cert num:%d", s_crt_bundle.num_certs);

    // 根据个数一个一个的来偏移获取每个证书的内容
    for (int i = 0; i < s_crt_bundle.num_certs; i++) {
        s_crt_bundle.crts[i] = cur_crt;
        // 每个证书前面4个字节是长度信息
        size_t name_len = cur_crt[0] << 8 | cur_crt[1];
        size_t key_len  = cur_crt[2] << 8 | cur_crt[3];
        ESP_LOGW(TAG, "cert name len:%d,key len:%d", name_len, key_len);
        // 根据长度进行每个证书的偏移
        cur_crt = cur_crt + CRT_HEADER_OFFSET + name_len + key_len;
    }

    return ESP_OK;
}

这段代码的作用就是 将入参的cert bundle 地址内存,根据数据结构定义{BUNDLE_HEADER_OFFSET,{name_len,key_len, name,key}}反序列化成cert结构数组对象 s_crt_bundle。

截止本文撰写日期,esp32 实现的这个x509 cert bundle 支持135个根证书,几乎支持了全球所有的证书。

创建空的证书

mbedtls_x509_crt_init(&s_dummy_crt);
mbedtls_ssl_conf_ca_chain(ssl_conf, &s_dummy_crt, NULL);

这个空证书的作用主要是承载访问服务器时获取的服务器证书特征,这个特征会通过回调的方式传给 mbedtls_ssl_conf_verify 函数参数。

查找和校验证书

代码如下,解析见注释:

int esp_crt_verify_callback(void* buf, mbedtls_x509_crt* crt, int depth, uint32_t* flags)
{
    mbedtls_x509_crt* child = crt;

    /* It's OK for a trusted cert to have a weak signature hash alg.
       as we already trust this certificate */
    uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD);

    if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
        return 0;
    }

    if (s_crt_bundle.crts == NULL) {
        ESP_LOGE(TAG, "No certificates in bundle");
        return MBEDTLS_ERR_X509_FATAL_ERROR;
    }

    ESP_LOGD(TAG, "%d certificates in bundle", s_crt_bundle.num_certs);

    size_t name_len = 0;
    const uint8_t* crt_name;

    // start 和 end 是证书数组的index。这里实现的是二分查找算法,说明cert bundle     
    // 数组的名字是增加了排序特征的,具体细节,需要查看python的打包工具的实现代码。
    bool crt_found = false;
    int start      = 0;
    int end        = s_crt_bundle.num_certs - 1;
    int middle     = (end - start) / 2;

    /* Look for the certificate using binary search on subject name */
    while (start <= end) {
        name_len = s_crt_bundle.crts[middle][0] << 8 | s_crt_bundle.crts[middle][1];
        crt_name = s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET;

        int cmp_res = memcmp(child->issuer_raw.p, crt_name, name_len);
        if (cmp_res == 0) {
            crt_found = true;
            break;
        } else if (cmp_res < 0) {
            end = middle - 1;
        } else {
            start = middle + 1;
        }
        middle = (start + end) / 2;
    }
    // 二分查找结束

    // 校验证书合法性
    int ret = MBEDTLS_ERR_X509_FATAL_ERROR;
    if (crt_found) {
        size_t key_len = s_crt_bundle.crts[middle][2] << 8 | s_crt_bundle.crts[middle][3];
        ret = esp_crt_check_signature(child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, key_len);
    }

    if (ret == 0) {
        ESP_LOGI(TAG, "Certificate validated");
        *flags = 0;
        return 0;
    }

    ESP_LOGE(TAG, "Failed to verify certificate");
    return MBEDTLS_ERR_X509_FATAL_ERROR;
}

代码很简单,注释中已经说了,就是一个二分查找过程。

校验证书合法性

相关代码在上面的部分已经注释过了,就是esp_crt_check_signature函数。校验的细节,感兴趣的朋友可以自己查看此函数源码。

TLS读写过程

写请求过程:

do {
        ret = esp_tls_conn_write(tls, REQUEST + written_bytes, sizeof(REQUEST) - written_bytes);
        if (ret >= 0) {
            ESP_LOGI(TAG, "%d bytes written", ret);
            written_bytes += ret;
        } else if (ret != ESP_TLS_ERR_SSL_WANT_READ && ret != ESP_TLS_ERR_SSL_WANT_WRITE) {
            ESP_LOGE(TAG, "esp_tls_conn_write  returned: [0x%02X](%s)", ret, esp_err_to_name(ret));
            goto exit;
        }
    } while (written_bytes < sizeof(REQUEST));

esp_tls_conn_write根据长度来循环写。

读响应过程:

    do {
        len = sizeof(buf) - 1;
        bzero(buf, sizeof(buf));
        ret = esp_tls_conn_read(tls, (char*) buf, len);

        if (ret == ESP_TLS_ERR_SSL_WANT_WRITE || ret == ESP_TLS_ERR_SSL_WANT_READ) {
            continue;
        }

        if (ret < 0) {
            ESP_LOGE(TAG, "esp_tls_conn_read  returned [-0x%02X](%s)", -ret, esp_err_to_name(ret));
            break;
        }

        if (ret == 0) {
            ESP_LOGI(TAG, "connection closed");
            break;
        }

        len = ret;
        ESP_LOGD(TAG, "%d bytes read", len);
        /* Print response directly to stdout as it is read */
        for (int i = 0; i < len; i++) {
            putchar(buf[i]);
        }
        putchar('\n'); // JSON output doesn't have a newline at end
    } while (1);

esp_tls_conn_read 循环读,直到链接关闭为止,跳出循环

释放 tls对象:

esp_tls_conn_delete(tls);

结束

好了,整个代码过程还是封装的非常干净和清晰的,如果你需要在自己的嵌入式系统中实现TLS证书过程,那么也可以仿照以上的过程,实现自己的bundle和匹配校验过程,并且可以根据实际需要定制和优化。

希望以上的分析,能帮你快速实现自己的TLS访问功能。

letsencrypt root certification expire illustration

letsencrypt 跟证书过期更换说明

见官方的声明 https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/

今年2021.9.30后,原有的DST RootCA X3就要过期了,改成ISRG Root X1。

对于浏览器用户基本不用担心,因为浏览器厂商自动做了支持。但是对于老旧的设备和一些嵌入式IOT设备等,就需要支持最新的证书,否则可能出现访问的问题。

use openssl client to download ssl certification

显示目标链中的所有证书:

openssl s_client -showcerts -connect www.xxx.com:443 </dev/null

当有多个子域名的时候,第一个不一定是想要的,所以自己根据CN来判断到底是哪组证书。然后自己截取 "-BEGIN CERTIFICATE-" "-END CERTIFICATE-" 之间的部分。

如果直接取第一个:

openssl s_client -showcerts -connect www.xxx.com:443 </dev/null |sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > test.cert

UNRAID下基于dnspod实现免费DDNS服务

UNRAID下基于dnspod实现免费DDNS服务

目标环境:unraid

玩nas的都知道,ddns是必不可少的服务,让你能在互联网访问家里服务器的所有服务。

之前用的是一个免费的ddns,但是一个月要手动激活确认一次,觉得很麻烦。其次测试发现ip的同步时效有问题,时间久了就导致服务地址不可用(中间做了proxy,目前还没有最终确认是nginx的问题,还是这个ip失效的问题,待换了此新方案后确认)。

补充:这问题应该是nginx的proxy_pass的dns缓存问题。

简单的搜了下,目前有宝塔,群晖等方案,但考虑到方案的通用性,还是决定直接用原生cron的方案,而实际操作下来,也非常方便。

dnspod 域名设置

  1. 创建或者导入已有的域名,添加子域名,设置默认的地址,比如127.0.0.1
  2. 注意ipv4 和ipv6的设置差别。 一个是A类型,一个是AAAA类型。

dnspod 获取token

  1. 登陆dnspod, 我的账号 - api秘钥 - DSPod Token。
  2. 根据要求填写,然后生成id和token,自己保存起来。

软件包使用

dnspod目前支持的工具包地址: https://github.com/rehiy/dnspod-shell

1. 编辑ddnspod.sh,分别修改/your_real_path/ardnspod、arToken和arDdnsCheck为真实信息
2. 运行ddnspod.sh,开启循环更新任务;建议将此脚本支持添加到计划任务;

unraid定时运行

crontab -e

在末尾添加:

# Run minute cron jobs at every minutes after the hour:
* * * * * /xxx/dnspod-shell/ddnspod.sh 1> /dev/null

路径改成自己实际的。

刷新域名

如果发现127.0.0.1变成实际的ip了,说明脚本和定时任务生效了。

android studio环境gradle配置

build.gradle的内容都是由系统配置自动生成的,如果有相关报错,先确认系统配置。

要点

  1. grandle配置。file -> project structure -> project。
    file
    dependecies的配置,目前看是grandle的插件来自动更新这部分内容的,需要确认grandle插件的正确安装:

    dependencies {
        classpath 'com.android.tools.build:gradle:4.2.2'
        classpath 'com.google.gms:google-services:4.3.3'
    }
  2. app 签名管理:file -> project structure -> Modules -> Signing configs

问题

  1. Execution failed for task ':processReleaseResources'
    解决:在build.gradle尾部添加:

    allprojects {
    repositories {
        google()
        jcenter()
    }
    }
  2. goole地图的支持:通过在dependencies添加实现:

    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    
    implementation 'com.google.android.gms:play-services-maps:12.0.1'
    implementation 'com.google.maps.android:android-maps-utils:0.5+'
    }

    具体版本,需要自己实际调整。

apns pem 推送证书生成

  1. Certificates, Identifiers & Profiles,下载 Apple Push Services文件,生成的是 apns.cer,将cer双击导入到苹果系统。

  2. 从钥匙串中导出p12文件,证书和证书的key,注意设置安全密码:
    file

  3. 通过命令行开始生成

    cert:
    openssl pkcs12 -clcerts -nokeys -out apns_cert.pem -in apns_cert.p12
    key:
    openssl pkcs12 -nocerts -out apns_cert_key.pem -in apns_cert_key.p12

    这个过程会提示你输入之前的文件的密码,以及新生成的文件的密码,一定要区分清楚,因为后面的程序调试是需要生成文件的密码的。

  4. 将两个pem合成一个

    cat apns_cert.pem apns_cert_key.pem > ck.pem
  5. 验证
    开发证书:
    openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert apns_cert.pem -key apns_cert_key.pem
    量产证书:
    openssl s_client -connect gateway.push.apple.com:2195 -cert apns_cert.pem -key apns_cert_key.pem
    如果验证成功,内容结尾显示如下:

    SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : DES-CBC3-SHA
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: xxx
    Start Time: 1627218051
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)

    Verify return code: 0 (ok),说明成功。