第2章 mbedtls入门
2.1 本章主要内容
本章将首先介绍如何在Linux平台通过CMake工具安装mbedtls。CMake是一种近几年较为流行的C/C++应用程序构建工具,比Makefile脚本CMake工具简单且易学。mbedtls提供多个实用的命令行工具,例如SSL客户端与服务端工具、X.509证书生成工具等。除了这些工具之外,mbedtls还包括多个动态或静态链接库——mbedtls、mbedx509和mbedcrypto,通过这些扩展库可实现各种各样的物联网安全功能。虽然在Linux平台使用mbedtls已经具有足够的代表性,但本章还将介绍如何在Zephyr平台使用mbedtls。Zephyr是由Linux软件基金会主持开发的新一代物联网操作系统,Zephyr已经支持多种嵌入式平台,并包含了网络与安全套件,mbedtls就是Zephyr操作系统支持的安全套件。在Zephyr操作系统的帮助下,基于mbedtls的物联网安全应用可以运行于大多数嵌入式平台上。
2.2 mbedtls体系结构
mbedtls是一款采用Apache 2.0许可证协议开源软件加密库,使用标准C语言编写,采用独立的模块化设计,以大大降低模块之间的耦合。从功能上来看,mbedtls主要包括密码学算法、X.509证书、TLS/DTLS协议3个组成部分。mbedtls非常适合于嵌入式系统,在嵌入式系统中可作为OpenSSL的替代者。相比OpenSSL,mbedtls代码更加简洁,API简单、直观且易于理解。除此之外,mbedtls采用模块化设计,使用宏定义的方式将平台依赖代码进行隔离,若用户将mbedtls移植到新的平台运行,只需修改相关宏定义并添加平台依赖相关的代码即可。mbedtls体系结构如图2-1所示。本章剩余部分将介绍mbedtls的安装方法,以及如何构建mbedtls应用。
图2-1 mbedtls体系结构
2.3 Linux mbedtls安装
本节将介绍如何在Linux平台安装mbedtls。在mbedtls官方代码仓库中介绍了至少4种不同的安装方法:yotta、Make、CMake和Microsoft Visual Studio(Visual Studio 6或Visual Studio 2010)。本节主要介绍CMake安装方法,相较于其他方法,CMake构建工具更加简单易用。CMake工具是一个跨平台的安装(编译)工具,使用简单的脚本语句来描述编译与安装过程。CMake不能直接输出可执行文件,但是它能够输出各种各样的makefile脚本,然后再通过makefile脚本构建可执行文件。CMake的结构化文档名为CMakeLists.txt,一个CMake工程中总包含一个CMakeLists.txt。
2.3.1 安装CMake
在Linux正确构建mbedtls开发环境之前,需要在Linux中安装合适版本的CMake工具。在Debian/Ubuntu系统中可通过apt-get工具从中心软件仓库中获取并安装CMake。在控制台中输入以下指令便可完成CMake的安装。
$ sudo apt-get update $ sudo apt-get install cmake
但中心软件仓库中的CMake版本一般较低,可能无法满足需求。若遇到版本问题时可前往CMake官网下载合适版本。下面以v3.8.2版本为例,说明如何安装较新版本的CMake工具。本节把CMake工具安装至{用户目录}/opt/cmake路径下,为了能够正确使用CMake工具,还需要把CMake工具的具体安装路径写入环境变量中。安装CMake的具体过程如下:
# 新建CMake安装文件夹 $ mkdir –p $HOME/opt/cmake && cd $HOME/opt/cmake # 通过wget指令获取cmake-v3.8.2版本安装包 $ wget https://cmake.org/files/v3.8/cmake-3.8.2-Linux-x86_64.sh # 执行CMake安装过程 $ yes | sh cmake-3.8.2-Linux-x86_64.sh | cat # 在.bashrc文件最后增加一行 $ echo "export PATH=$PWD/cmake-3.8.2-Linux-x86_64/bin:\$PATH" >> $HOME/.bashrc # 环境变量生效 $ source ~/.bashrc
CMake工具安装完成之后,可通过查看当前版本编号的方式验证该工具是否正确安装。在控制台中输入“cmake–version”命令即可验证当前版本编号。如果CMake安装正确,可获得类似以下输出:
# 查看cmake版本信息 $ cmake –version # 输出内容 cmake version 3.8.2 CMake suite maintained and supported by Kitware (kitware.com/cmake).
2.3.2 使用CMake安装mbedtls
完成CMake工具安装之后可从GitHub获取mbedtls的最新源代码。与其他优秀的开源软件一样,mbedtls同样托管于GitHub——https://github.com/ARMmbed/mbedtls.git。截至目前,mbedtls已经推出多个版本,本书以mbedtls-2.12.0版本为例说明mbedtls的安装过程。
1.获取mbedtls源代码
本节中mbedtls的源代码将被克隆至{用户目录}/repo/mbedtls文件夹。
# 新建repo文件夹 $ mkdir -p ~/repo # 克隆mbedtls源代码 $ git clone https://github.com/ARMmbed/mbedtls.git
2.切换到某个发布分支
由于mbedtls仍处于持续更新阶段,已知Bug被修复,新的特性与功能被不断添加到master分支中,建议在实际使用时以某个发布版本为主。本节以mbedtls-2.12.0分支为例,通过checkout命令切换至mbedtls-2.12.0分支。
$ git checkout -b mbedtls-2.12.0 origin/mbedtls-2.12.0
通过checkout命令可检验出本地或远程分支,以上指令将检验出远程仓库名为mbedtls-2.12.0分支,并在本地也创建一个同名分支mbedtls-2.12.0。
3.重要文件与目录说明
mbedtls源代码中包含多个重要文件或目录,其中README.md文档详细描述了mbedtls的安装步骤,programs目录包含了多组示例代码和应用工具。mbedtls源代码中各文件或目录的主要功能如表2-1所示。
表2-1 mbedtls重要文件与目录
4.编译并安装mbedtls
完成分支切换之后,再通过cmake命令生成makefile脚本,并通过make install命令安装mbedlts。
# 进入mbedlts源代码目录 $ cd $HOME/repo/mbedtls # 生成makefile文件,启用生成动态链接库选项 $ cmake -DUSE_SHARED_MBEDTLS_LIBRARY=On . # 编译并安装 $ make $ sudo make install $ sudo ldconfig
1)-DUSE_SHARED_MBEDTLS_LIBRARY=On参数用于配置动态链接库选项,把USE_SHARED_MBEDTLS_LIBRARY设置为On时,最终将编译获得mbedtls、mbedx509和mbedcrypto扩展库。默认情况下这些扩展库将被安装至/usr/local/lib目录中。
2)使用cmake命令时,请不要忘记cmake指令最后的“.”,该“.”用于指定CMakeLists.txt的位置。观察mbedtls源代码目录可以发现,CMakeLists.txt文件位于mbedtls源代码根目录下。
3)执行make install命令之后,多个mbedtls工具将会被安装到/usr/local/bin/目录中,这些工具包括:hello、dh_client、dh_server、rsa_sign和rsa_verify等。
5.设置环境变量
在用户目录.bashrc文件末尾增加MBEDTLS_BASE参数,修改完成后在控制台执行source$HOME/.bashrc,该指令可使新增的环境变量立即生效。
$ echo "export MBEDTLS_BASE=<mbedtls 源代码仓库安装路径>" >> $HOME/.bashrc
$ source $HOME/.bashrc
6.必要的验证工作
安装完成之后可使用某个mbedtls工具验证其是否安装成功。在控制台中输入hello将获得以下输出。
$ hello MD5('Hello, world!') = 6cd3556deb0da54bca060b4c39479839
mbedtls自带的hello工具只是一个验证性工具,该工具把字符串“Hello,world!”输入到MD5算法中,并把计算结果输出到控制台中。MD5算法是一种常见的单向散列算法,除了MD5算法之外,mbedtls还支持SHA系列单向散列算法。hello工具的具体实现可参考{mbedtls代码仓库}\programs\hash目录中的hello.c。由于篇幅限制,代码清单2-1中的hello.c内容略有删减。
代码清单2-1 hello.c
#include <stdio.h> #include "mbedtls/config.h" #include "mbedtls/md5.h" #define mbedtls_printf printf int main( void ) { int i; unsigned char digest[16]; char str[] = "Hello, world!"; mbedtls_printf( "\n MD5('%s') = ", str ); mbedtls_md5( (unsigned char *) str, 13, digest ); for( i = 0; i < 16; i++ ) mbedtls_printf( "%02x", digest[i] ); mbedtls_printf( "\n" ); return( 0 ); }
hello工具使用CMake工具构建。在hello.c同级目录中包含一个CMakeLists.txt文件,CMakeLists.txt文件的主要内容如下:
add_executable(hello hello.c) target_link_libraries(hello mbedtls)
在CMake构建规则中,add_executable函数用于指定可执行文件名称,并添加相应的源文件,此处可执行文件名为hello,相应的源文件为hello.c。另外,target_link_libraries函数用于添加相应的库文件,此处库文件为libmbedtls.so。
2.4 Linux mbedtls示例
本节将通过两个示例说明如何在Linux平台构建mbedtls编写示例。第1个示例通过一个Base64示例说明如何使用CMake工具构建mbedtls应用。第2个示例将遍历mbedtls的所支持的安全套件,除了遍历安全套件之外,该示例还将通过修改mbedtls配置文件的方法,裁剪不必要的安全套件。
注意:由于篇幅限制,示例代码中只给出部分内容,具体示例代码可在本书代码仓库中查看,本章示例位于02_start/linux文件夹下。
2.4.1 Base64示例
Base64算法是一种基于64个字符的编码算法,它是一种以任意8位字节序列组合的描述形式,这种描述形式不易被人直接识别。Base64算法是一种可以把非ASCII编码数据转化为ASCII编码数据的方法。经过Base64编码之后的数据长度会比原始数据长度增加1/3。除此之外Base64编码算法还包括填充规则,编码之后的输出结果总是4字节的整数倍。与Base64编码算法类似的算法还有Base32编码算法和Base16编码算法,这些算法的详细说明可参考标准文件“RFC 4648 The Base16,Base32,and Base64 Data Encodings”。在该标准文件中,给出了3组Base64编码与解码样本数据,这些样本数据如表2-2所示。
表2-2 Base64编码与解码样本数据
虽然经过Base64编码之后的结果不能被直接识别,但是Base64并不是一种加密/解密算法,Base64仅仅是一种编码算法,它输出的结果并没有任何“保密性”。mbedtls中也包括Base64的具体实现,下面通过一个示例说明如何在Linux平台编写一个简单的mbedtls示例。
1.示例代码
示例的测试样本来自rfc3548,被编码的数据为一个字符数组,编码的结果为字符串形式的“FPucA9l+”。在输入数据中0xfb、0x9c、0xd9和0x7e并不能通过ASCII编码表示,但是输出结果却可通过ASCII编码表示。Base64示例如代码清单2-2所示。
代码清单2-2 Base64示例代码
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "mbedtls/base64.h"
#define mbedtls_printf printf
// 省略部分中间代码
int main(void)
{
size_t len;
uint8_t rst[512];
len = sizeof(msg);
dump_buf("\n base64 message: ", msg, len);
mbedtls_base64_encode(rst, sizeof(rst), &len, msg, len);
mbedtls_printf(" base64 encode : %s\n", rst);
mbedtls_base64_decode(rst, sizeof(rst), &len, rst, len);
dump_buf(" base64 decode : ", rst, len);
printf("\n");
return 0;
}
示例中所使用接口的具体描述如表2-3所示。
表2-3 Base64示例相关接口描述
2.编写CMakeLists.txt
为了在Linux平台上构建一个可执行程序,还需要编写一个CMakeLists.txt文件,具体内容如代码清单2-3所示。
代码清单2-3 CMakeLists.txt文件
cmake_minimum_required(VERSION 3.8.2)① project("Base64")② include_directories(./ $ENV{MBEDTLS_BASE}/include)③ aux_source_directory($ENV{MBEDTLS_BASE}/library MBEDTLS_SOURCES)④ set(SOURCES ⑤ ${CMAKE_CURRENT_LIST_DIR}/base64.c ${MBEDTLS_SOURCES}) add_executable(base64 ${SOURCES})⑥
1)设置CMake最低版本限制;
2)设置CMake工程名称为Base64;
3)通过include_directories函数指定mbedtls头文件路径,此处mbedtls头文件路径位于环境变量$ENV{MBEDTLS_BASE}中;
4)添加mbedtls源文件到MBEDTLS_SOURCES变量中,此处通过aux_source_directory函数找出mbedtls library中所有C文件,并把这些C文件路径输出至MBEDTLS_SOURCES变量中;
5)通过set函数定义一个名为SOURCES的变量,该变量包含所有mbedtls源文件以及示例代码base64.c;
6)定义可执行文件名为base64,该可执行文件依赖SOURCES变量。
3.编译与执行
编译与执行过程如下:
# 进入示例所在路径 $ cd 02_start/linux/base64 # 新建一个build文件夹,用于保存临时文件 $ mkdir –p build & cd build # 生成makefile文件 $ cmake .. $ make # 执行示例 $ ./base64 base64 message: 14 fb 9c 03 d9 7e base64 encode : FPucA9l+ base64 decode : 14 fb 9c 03 d9 7e
从输出的结果可以看出,一组不能被ASCII编码的数据被转化为可以完全被ASCII编码的字符串“FPucA9l+”。
2.4.2 遍历mbedtls安全套件
虽然Base64示例相对简单,但是已经展现了使用mbedtls构建应用的基本步骤。经过一个简单的示例之后,我们适当提高难度实现一个更为复杂的示例,在这个示例中将遍历mbedtls所支持的安全套件。mbedtls支持很多常用的安全套件,但对于物联网嵌入式终端来说,过多的安全套件将会占用更多的资源,另外有些安全套件由于种种历史原因也不会在物联网领域流行。mbedtls可通过配置文件裁剪一些不必要的安全套件,这样可大大缩小mbedtls所占用的代码空间。在mbedtls中可通过mbedtls_ssl_list_ciphersuites函数遍历所有用于TLS/DTLS通信的安全套件。
例如,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384安全套件如图2-2所示。该密码套件具体含义如下:
·密钥协商算法ECDHE
·身份认证算法ECDSA
·对称加密算法AES_256
·消息认证算法GCM
·伪随机数算法SHA384
图2-2 安全套件名称构成方式
虽然安全套件名称不能表示所有参数,但是可以指示其中的重要参数。当前已经有超过300种安全套件被官方定义,可访问IANA的TLS官方页面获得完整列表。
1.示例代码
遍历mbedtls安全套件的代码,如代码清单2-4所示。
代码清单2-4 遍历mbedtls安全套件示例
#include <stdio.h> #include "mbedtls/ssl.h" int main( void ) { int index = 1; const int *list; const char *name; mbedtls_printf("\n Available Ciphersuite:\n\n"); list = mbedtls_ssl_list_ciphersuites();① for(; *list; list++) { name = mbedtls_ssl_get_ciphersuite_name(*list);② mbedtls_printf(" [%03d] %s\n", index++, name); } mbedtls_printf("\n"); return 0; }
1)mbedtls_ssl_list_ciphersuites将返回全局数组supported_ciphersuites,该数组定义了所有被mbedtls支持的安全套件;
2)获得安全套件名称,例如TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384或TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384。
2.编写CMakeLists.txt
为了在Linux平台上构建一个可执行程序,还需要编写一个CMakeLists.txt文件,这个CMakeLists.txt构建文件与上一个Base64示例的构建文件非常相似,只是可执行文件名称和依赖C文件名称发生了变化,具体内容如代码清单2-5所示。
代码清单2-5 CMakeLists.txt文件
cmake_minimum_required(VERSION 3.8.2)① project("Ciphersuite-list")② include_directories(./ $ENV{MBEDTLS_BASE}/include)③ aux_source_directory($ENV{MBEDTLS_BASE}/library MBEDTLS_SOURCES)④ set(SOURCES⑤ ${CMAKE_CURRENT_LIST_DIR}/ciphersuite-list.c ${MBEDTLS_SOURCES}) add_executable(ciphersuite-list ${SOURCES})⑥
1)设置CMake最低版本限制;
2)设置CMake工程名称为Ciphersuite-list;
3)设置mbedtls头文件路径;
4)添加mbedtls源文件输出到MBEDTLS_SOURCES变量中;
5)通过set函数定义一个名为SOURCES的变量,该变量包含mbedtls源文件以及示例代码ciphersuite-list.c;
6)定义可执行文件名为ciphersuite-list,该可执行文件依赖SOURCES变量。
3.编译与执行
行编译与执行过程如下:
# 进入示例所在路径 $ cd 02_start/linux/ciphersuite-list # 新建一个build文件夹,用于保存临时文件 $ mkdir –p build & cd build # 生成makefile文件 $ cmake .. $ make # 执行示例 $ ./ciphersuite-list Available Ciphersuite: [001] TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384 [002] TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 [003] TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 [004] TLS-ECDHE-ECDSA-WITH-AES-256-CCM [005] TLS-DHE-RSA-WITH-AES-256-CCM [006] TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384 [007] TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 [008] TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 [009] TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA [010] TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA // 省略部分内容 [125] TLS-PSK-WITH-AES-128-CBC-SHA256 [126] TLS-PSK-WITH-AES-128-CBC-SHA [127] TLS-PSK-WITH-CAMELLIA-128-GCM-SHA256 [128] TLS-PSK-WITH-CAMELLIA-128-CBC-SHA256 [129] TLS-PSK-WITH-AES-128-CCM-8 [130] TLS-PSK-WITH-3DES-EDE-CBC-SHA
从输出结果可以看出,默认配置下的mbedtls支持130种安全套件。虽然mbedtls所支持的安全套件仅是IANA组织规定的一部分,但是在嵌入式物联网应用中,这些安全套件还是显得有些“臃肿”。
4.替换mbedtls配置文件
mbedtls可通过修改配置文件的方式进行裁剪,默认的配置文件位于{mbedtls代码仓库}/include/mbedtls/config.h文件中。虽然config.h文件中对于每一个参数均有详细的辅助说明,但是参数之间存在一定的依赖关系,所以从头编写一个配置文件往往需要不少实践经验。mbedtls提供了几种参考配置,这些参考配置详见configs文件夹。此处会使用config-mini-tls1_1.h文件作为配置文件,配置文件中启用了MBEDTLS_KEY_EXCHANGE_RSA_ENABLED宏定义,从后面的执行结果可以看出,列表中只保留了与RSA密钥协商相关的密码套件。config-mini-tls1_1.h的具体内容如代码清单2-6所示,该文件中相关配置含义和使用方法将在后续的章中逐个分析。
代码清单2-6 config-mini-tls1_1.h
#ifndef MBEDTLS_CONFIG_H #define MBEDTLS_CONFIG_H /* System support */ #define MBEDTLS_HAVE_ASM #define MBEDTLS_HAVE_TIME /* mbed TLS feature support */ #define MBEDTLS_CIPHER_MODE_CBC #define MBEDTLS_PKCS1_V15 #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED #define MBEDTLS_SSL_PROTO_TLS1_1 /* mbedtls组件 */ #define MBEDTLS_AES_C #define MBEDTLS_ASN1_PARSE_C #define MBEDTLS_ASN1_WRITE_C // 省略部分内容
5.再次编译执行
在编译过程中,通过CFLAG参数指定配置文件查找路径和自定义配置文件,本示例中配置文件位于{mbedtls代码仓库}/configs目录中,配置文件宏定义DMBEDTLS_CONFIG_FILE被赋值为<config-mini-tls1_1.h>。
# 进入示例所在路径 $ cd 02_start/linux/ciphersuite-list # 进入build目录,删除编译文件 $ cd build && rm -rf * # 生成makefile文件 $ CFLAGS="-I$MBEDTLS_BASE/configs -DMBEDTLS_CONFIG_FILE='<config-mini-tls1_1.h>'" cmake .. $ make # 执行示例 $ ./ciphersuite-list Available Ciphersuite: [001]TLS-RSA-WITH-AES-256-CBC-SHA256 [002]TLS-RSA-WITH-AES-256-CBC-SHA [003]TLS-RSA-WITH-AES-128-CBC-SHA256 [004]TLS-RSA-WITH-AES-128-CBC-SHA [005]TLS-RSA-WITH-3DES-EDE-CBC-SHA
再次执行之后我们将发现,ciphersuite-list的输出内容产生了明显变化,此时mbedtls支持的安全套件仅有5个。mbedtls的模块化设计使用户可以根据实际需要裁剪安全组件,以达到最优配置。
2.5 Zephyr OS简介
Zephyr OS是由Linux基金会托管的开源协作项目,其目标是构建一个针对资源受限设备的小型、可裁剪的实时操作系统(RTOS)。Zephyr OS系统架构如图2-3所示。
图2-3 Zephyr OS系统架构
Zephyr OS采用模块化设计,支持多种主流硬件架构,如ARC架构、ARM架构、X86架构等,开发人员可以很容易地根据需求定制一个最优的解决方案。除此之外,Zephyr OS相比其他开源物联网系统具有很多优点,如表2-4所示。
表2-4 Zephyr OS优点
2.6 Zephyr开发环境搭建
本节以Ubuntu 1604 Desktop为例,说明如何在Linux环境下构建Zephyr开发环境。构建Zephyr开发环境前,需在Ubuntu中正确安装Python3和CMake等工具。构建Zephyr开发环境的步骤较多,详细内容可参考Zephyr OS入门手册。
1.安装依赖包
为了搭建Zephyr开发环境,需要在Ubuntu中安装必要的依赖包。
# 更新软件源 $ sudo apt-get update $ sudo apt-get upgrade # 安装依赖包 $ sudo apt-get install --no-install-recommends git cmake ninja-build gperf \ ccache doxygen dfu-util device-tree-compiler \ python3-ply python3-pip python3-setuptools python3-wheel xz-utils file \ make gcc-multilib autoconf automake libtool librsvg2-bin \ texlive-latex-base texlive-latex-extra latexmk texlive-fonts-recommended
2.获取Zephyr源代码
把Zephyr代码克隆到用户目录中,再切换到zephyr-v1.13.0分支,本书所有章节的示例均基于该分支。
$ mkdir -p repo $ cd repo $ git clone https://github.com/zephyrproject-rtos/zephyr $ git checkout zephyr-v1.13.0
3.安装必要的Python3依赖工具
# 进入zephyr源代码目录 $ cd zephyr # 通过pip3工具安装其他依赖项 $ pip3 install --user -r scripts/requirements.txt
4.安装Zephyr SDK
Zephyr SDK包括相关硬平台所依赖的编译、下载和调试等工具。本文中使用的SDK版本为0.9.3。安装过程中控制台将出现Zephyr SDK安装路径的提示,建议把Zephyr SDK安装到{用户目录}\opt\zephyr-sdk文件夹中。
# 通过wget下载Zephyr SDK $ wget https://github.com/zephyrproject-rtos/meta-zephyr-sdk/releases/download/0.9.3/zephyr-sdk-0.9.3-setup.run # 安装Zephyr SDK $ chmod +x zephyr-sdk-0.9.3-setup.run $ ./zephyr-sdk-0.9.3-setup.run # 输出内容 Verifying archive integrity... All good. Uncompressing SDK for Zephyr 100% Enter target directory for SDK (default: /opt/zephyr-sdk/): {用户目录}/opt/zephyr-sdk Installing SDK to {用户目录}/opt/zephyr-sdk The directory {用户目录}/opt/zephyr-sdk/sysroots will be removed! [*] Installing x86 tools... [*] Installing arm tools... [*] Installing arc tools... [*] Installing iamcu tools... [*] Installing mips tools... [*] Installing nios2 tools... [*] Installing xtensa tools... [*] Installing riscv32 tools... [*] Installing additional host tools... Success installing SDK. SDK is ready to be used.
5.设置环境变量
在用户目录中的.bashrc文件末尾增加ZEPHYR_TOOLCHAIN_VARIANT、ZEPHYR_SDK_INSTALL_DIR和ZEPHYR_BASE等参数。修改完成后在控制台执行source$HOME/.bashrc,该指令可使新增的环境变量立即生效。
$ echo "export ZEPHYR_TOOLCHAIN_VARIANT=zephyr" >> $HOME/.bashrc $ echo "export ZEPHYR_SDK_INSTALL_DIR=<zephyr-sdk 安装路径>" >> $HOME/.bashrc $ echo "export ZEPHYR_BASE=<zephyr 源代码仓库安装路径>" >> $HOME/.bashrc $ source $HOME/.bashrc
2.7 Zephyr硬件平台选择
Zephyr操作系统支持多款ARM平台,本书与Zephyr有关的mbedtls示例均运行在Nucleo F429ZI平台。相较于Linux平台,在类似Nucleo F429ZI这样的嵌入式平台上运行mbedtls应用时需要考虑更多的限制,这些限制包括mbedtls应用所需的内存和栈空间等,这些额外的消耗将限制mbedlts的发挥空间。
2.7.1 资源介绍
Nucleo F429ZI是一款基于ARM Cortex-M4内核的STM32F4系列开发板,板载MCU为STM32F429ZI,STM32F429ZI主频可达180MHz,并具有2MB内部Flash和256KB RAM,还具有以太网和真随机数生成器等功能。真随机数生成器是物联网应用的关键部分。Zephyr提供的标准驱动中包括熵源(entropy)驱动,并可通过sys_rand32_get获得一个4字节大小的真随机数。在下一节的随机数示例中,将使用STM32F429ZI的真随机数生成器生成4字节随机数。该开发板主要资源情况如表2-5所示,外观如图2-4所示。
表2-5 Nucleo F429ZI板载资源情况
图2-4 Nucleo STM32F429ZI开发板外观
2.7.2 Ubuntu中安装STLink工具
为了把编译得到的固件下载至Nucleo F429ZI开发板中,需要在Ubuntu中正确安装STLink工具。具体的安装步骤如下:
# 安装依赖库 $ sudo apt-get install libusb-1.0 # 克隆STLink工具 $ git clone https://github.com/texane/stlink # 编译 $ make release # 安装 $ cd Release $ sudo make install
安装STLink工具后,若Ubuntu主机与Nucleo STM32F429ZI板载的STLink相连,STLink工具将会在Ubuntu主机中虚拟一个串口设备,该串口设备的名称为ttyACM0或ttyACM1。Zephyr平台Nucleo STM32F429ZI相关示例中,控制台输出内容将会重定向到ttyACM0设备中,波特率为115200。调试Zephyr示例时,可借助minicom工具查看STM32F429ZI的输出日志。
2.8 Zephyr应用示例开发
完成Zephyr开发环境的构建工作之后,本节通过一个随机数示例说明开发Zephyr应用的基本步骤。随机数是物联网应用的重要组成部分,公钥密码、数字签名和TLS/DTLS部分均与随机数相关。Zephyr应用示例结构如下:
# 进入示例路径 $ cd 02_start/zephyr/random # 查看示例文件结构 $ tree -L 2├── CMakeLists.txt├── prj.conf └── src└── main.c
随机数示例相关文件及其描述如表2-6所示。
表2-6 Zephyr随机数示例相关文件描述
2.8.1 编写CMakeLists.txt
此处CMakeLists.txt文件是一个非常好用的Zephyr模板文件,只需简单的修改便可适配其他的Zephyr应用。该文件的具体内容如下:
cmake_minimum_required(VERSION 3.8.2) include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) project(NONE) FILE(GLOB app_sources src/*.c) target_sources(app PRIVATE ${app_sources})
在CMakeLists.txt文件中,通过include指令引入一个构建脚本boilerplate.cmake。为了能够正确地找到boilerplate.cmake脚本,CMake构建脚本通过环境变量中的ZEPHYR_BASE变量获取Zephyr源代码的安装路径,只有ZEPHYR_BASE环境变量设置正确才可以顺利载入boilerplate.cmake脚本。再通过FILE命令把src文件夹中的所有C文件复制到CMake内部变量app_sources中。最后,通过target_sources指令引入依赖的C文件,构建名为app的可执行固件。boilerplate.cmake较为复杂,它包含了很多CMake技巧,但其具体内容已经超出了本书的讨论范围。
2.8.2 编写prj.conf
每个Zephyr应用都包含一个conf配置文件,可通过该配置文件启用或禁用某些系统功能。为了启用随机数驱动和控制台打印功能,需要在prj.conf文件中设置CONFIG_ENTROPY_GENERATOR和CONFIG_STDOUT_CONSOLE。本节中的prj.conf具体内容如下:
CONFIG_ENTROPY_GENERATOR=y CONFIG_STDOUT_CONSOLE=y
2.8.3 编写main.c
随机数应用示例使用sys_rand32_get接口获取随机数,该接口位于random/rand32.h头文件中。示例代码会以1秒为周期,获取并生成4字节随机数,如代码清单2-7所示。
代码清单2-7 main.c用于生成4字节随机数
#include <zephyr.h> #include <random/rand32.h> #include <stdio.h> void main(void) { printf("\n %s board random:\n", CONFIG_BOARD); while (1) { printf(" 0x%08x\n", sys_rand32_get()); k_sleep(1000); } }
2.8.4 编译与运行
在使用cmake指令时,需通过-DBOARD参数指定硬件平台,本书中所有的示例均可以运行在native_posix平台和nucleo_f429zi平台,前者比较适合作为应用示例快速实现和验证的开发环境,而后者则为实际的嵌入式硬件平台,需要更加关注内存资源的使用情况。下面分别对两种硬件平台的编译与执行过程进行介绍。
1.native_posix平台
# 进入示例代码文件夹 $ cd 02_start/zephyr/random # 新建一个build文件夹,用于存放临时文件 $ mkdir -p build && cd build # 通过cmake指令生成native_posix平台makefile文件 $ cmake -DBOARD=native_posix .. # 编译与执行 $ make && make run native_posix board random: 0x098a2adf 0x698f8e7b 0x324be164 0x3218c6a9 0x11c8915c 0x13127769 # 省略之后内容
2.nucleo_f429zi平台
与native_posix平台不同的是,nucleo_f429zi平台示例编译完成后需要通过make flash命令将生成的固件下载至目标板中。应用程序将运行结果输出至串口控制台,所以应用程序下载至开发板运行之前,需新建终端并通过minicom工具打开指定串口。操作指令如下:
# 请根据实际情况修改串口名称
sudo minicom -b 115200 -D /dev/ttyACM0
编译与运行过程如下:
# 进入示例代码文件夹 $ cd 02_start/zephyr/random # 新建一个build文件夹,用于存放临时文件 $ mkdir -p build && cd build # 通过cmake指令生成nucleo_f429zi平台makefile文件 $ cmake -DBOARD=nucleo_f429zi .. # 编译 $ make # 执行下载 $ make flash # 串口控制台输出 nucleo_f429zi board random: 0x098a2adf 0x698f8e7b 0x324be164 0x3218c6a9 0x11c8915c # 省略之后内容
2.9 Zephyr mbedtls示例
上一节已经说明了如何构建Zephyr应用,本节将在此基础上增加mbedtls相关的内容。在2.8节介绍了编写CMakeLists.txt和prj.conf的方法,这些方法在本节依然适用。与前面的示例不同,为了构建mbedtls相关示例,本节将修改prj.conf文件,并在该文件中增加mbedtls的编译选项。另外本节还将增加一个名为mbedtls_config.h的mbedtls配置文件。本节示例中的CMakeLists.txt、prj.conf和mbedtls_config.h文件都是不错的模板文件,后续章的示例都将以这些文件为基础。
2.9.1 Base64示例
让我们再次回到mbedtls的讨论中,之前的Zephyr示例中并没有mbedtls部分的内容,下面通过一个Base64示例说明如何在Zephyr环境下中使用mbedtls。与大多数Zephyr示例相似,此处包括CMakeLists.txt、prj.conf和src/main.c,除此之外还包括mbedtls_config.h配置文件。该示例目录结构如下,各文件描述如表2-7所示。
# 进入示例路径 $ cd 02_start/zephyr/base64 # 查看示例结构 $ tree -L 2├── CMakeLists.txt├── prj.conf└── src ├── main.c └── mbedtls_config.h
表2-7 Zephyr应用示例相关文件描述
1.示例代码
代码清单2-8与之前的Linux平台实现代码几乎相同,示例描述及接口描述在这里不做重复介绍。
代码清单2-8 main.c Base64编码与解码
#include <zephyr.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "mbedtls/base64.h"
#include "mbedtls/platform.h"
// 省略部分中间代码
int main(void)
{
size_t len;
uint8_t rst[512];
mbedtls_platform_set_printf(printf);
len = sizeof(msg);
dump_buf("\n base64 message: ", msg, len);
mbedtls_base64_encode(rst, sizeof(rst), &len, msg, len);
mbedtls_printf(" base64 encode : %s\n", rst);
mbedtls_base64_decode(rst, sizeof(rst), &len, rst, len);
dump_buf(" base64 decode : ", rst, len);
printf("\n");
return 0;
}
2.编写mbedtls_config.h
该示例中增加一个名为mbedtls_config.h的mbedtls配置文件,该文件是一个典型的模板文件,在其他示例中将被反复使用。该模板文件可分为以下3部分。
·Zephyr系统支持:保证mbedtls在Zephyr操作系统中正确运行,该部分一般保持不变,本书的其他示例也将沿用该部分定义;
·mbedtls组件:此处增加了Base64支持,建议按需增加;
·mbedtls配置文件检查:一般情况下需引入mbedtls/check_config.h,用于检查mbedtls配置参数之间的依赖关系,见代码清单2-9。该部分一般保持不变。
代码清单2-9 mbedtls_config.h
#define MBEDTLS_PLATFORM_C #define MBEDTLS_PLATFORM_MEMORY #define MBEDTLS_MEMORY_BUFFER_ALLOC_C #define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS #define MBEDTLS_PLATFORM_EXIT_ALT #define MBEDTLS_NO_PLATFORM_ENTROPY #define MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES #define MBEDTLS_PLATFORM_PRINTF_ALT #define MBEDTLS_BASE64_C
1)MBEDTLS_PLATFORM_C启用平台抽象接口,使能该参数后,用户可重新定义calloc/free等接口。
2)MBEDTLS_PLATFORM_MEMORY启用内存分配接口,使能该参数后用户可以自己实现calloc/free接口,并通过宏定义替换或通过接口设置方式进行替换。
3)MBEDTLS_MEMORY_BUFFER_ALLOC_C启用mbedtls自带的内存分配接口,该参数适用于那些没有动态内存分配功能的嵌入式平台。
4)MBEDTLS_PLATFORM_NO_STD_FUNCTIONS不使用标准库函数,如calloc/free等接口。
5)MBEDTLS_PLATFORM_EXIT_ALT使能exit接口替换,使能后允许平台设置exit接口。
6)MBEDTLS_NO_PLATFORM_ENTROPY不使用内置的熵源,开启该宏定义后需要通过接口添加自定义熵源接口。
7)MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES取消默认熵源功能,用户可通过接口添加自定义熵源接口。
8)MBEDTLS_PLATFORM_PRINTF_ALT使能printf接口替换,使能后允许用户使用mbedtls_platform_set_printf接口设置自定义printf函数。
9)MBEDTLS_BASE64_C启用Base64功能。
3.编写prj.conf
为了在Zephyr应用中集成mbedtls,需要在prj.conf配置文件中启用CONFIG_MBEDTLS和CONFIG_MBEDTLS_BUILTIN。默认情况下,Zephyr使用的mbedtls配置文件为{zephyr代码仓库}/ext/lib/crypto/mbedtls/configs config-mini-tls1_2.h,此处示例使用了自定义的配置文件mbedtls_config.h,所以还需要在prj.conf中定义CONFIG_MBEDTLS_CFG_FILE为mbedtls_config.h。此处的prj.conf文件也是一个典型的模板文件,其他章的示例将会在此模板文件上进行修改。
CONFIG_STDOUT_CONSOLE=y CONFIG_MBEDTLS=y CONFIG_MBEDTLS_BUILTIN=y CONFIG_MBEDTLS_CFG_FILE="mbedtls_config.h"
4.编写CMakeLists.txt文件
CMakeLists.txt和之前的构建文件大致相同,由于启用了mbedtls,并指定了自定义的mbedtls配置文件,所以还需要通过CMake的target_include_directories指令引入mbedtls配置文件所在的路径,否则在编译过程中将提示无法找到mbedtls_config.h。
cmake_minimum_required(VERSION 3.8.2) include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) project(NONE) if (CONFIG_MBEDTLS) target_include_directories(mbedTLS INTERFACE ${PROJECT_SOURCE_DIR}/src) endif() target_sources(app PRIVATE src/main.c)
5.编译与运行
基础示例默认会运行在necluo_f429zi平台,若需要运行在仿真平台只需将-DBOARD参数指定为native_posix即可,具体过程可回顾2.8.4节。编译过程完成时控制台将输出Flash和RAM的消耗情况,此时Base64示例仅消耗STM32F429ZI约14KB Flash空间和约4KB RAM空间。在后续章的示例中,我们还将关注mbedtls应用的Flash和RAM消耗情况。
应用程序将把运行结果输出至串口控制台,所以应用程序下载至开发板运行之前需新建终端,并通过minicom工具打开指定串口。操作指令如下:
# 请根据实际情况修改串口名称
$ sudo minicom -b 115200 -D /dev/ttyACM0
编译与运行过程如下:
# 进入示例代码文件夹 $ cd 02_start/zephyr/base64 # 新建一个build目录,用于存放临时文件 $ mkdir -p build && cd build # 通过cmake指令生成nucleo_f429zi平台makefile文件 $ cmake -DBOARD=nucleo_f429zi .. # 编译并查看资源消耗情况 $ make Memory region Used Size Region Size %age Used FLASH: 14400 B 2 MB 0.69% CCM: 0 GB 64 KB 0.00% SRAM: 4356 B 256 KB 1.66% IDT_LIST: 200 B 2 KB 9.77% # 下载到开发板运行 $ make flash # 串口控制台输出 base64 message: 14 fb 9c 03 d9 7e base64 encode : FPucA9l+ base64 decode : 14 fb 9c 03 d9 7e
2.9.2 大数运算示例
完成Base64示例之后,本节继续在Zephyr平台运行一个大数运算示例。大数运算是密码学中常用的计算手段之一,是公钥密码和数字签名算法的基础。所谓大数运算,就是运算过程的参数或结果超过了计算机编程语言中基本数据类型所表示的范围,例如C语言中64位无符号整数的表示范围为0~18446744073709551615。虽然64位无符号整数的表示范围已经很大,但在密码学范畴这种类型的整数依然不能满足需求。mbedtls支持大数运算,大数运算的具体实现详见{mbedtls代码仓库}/library/bignum.c。下面通过大数乘法运算、大数模指数运算和大数模逆运算这3个示例说明bignum相关接口的使用方法。在本示例中共有A、E和N三组参数参与运算,先计算一组大数乘法X=A*E,再计算一组大数模指数运算X=A^E mod,最后计算一组大数模逆运算。本节示例相关参数和计算结果如图2-5所示。
与本章其他示例相似,本节示例也包括main.c、mbedtls_config.h、prj.conf和CMakeLists.txt等文件。
1.示例代码
在示例代码中的大数运算中,大数乘法运算、大数模指数运算与实数域中的概念非常相似,但是模逆运算和实数域中的倒数运算存在很大差异。在实数域中,2与0.5的乘积为1,则称2的倒数为0.5,在有限域(此处的模逆运算)中,2 X 8 mod 17=1,则称2关于模17的逆元为8。总之,实数域中倒数的概念与乘积为1有关,而有限域中逆元的概念与余数为1有关。示例代码如代码清单2-10所示。
图2-5 大数运算示例
代码清单2-10 bignum示例代码
#include <zephyr.h> #include <string.h> #include <stdio.h> #include "mbedtls/bignum.h" #include "mbedtls/platform.h" static void dump_buf(char *buf, size_t len) { for (int i = 0; i < len; i++) { mbedtls_printf("%c%s", buf[i], (i + 1) % 32 ? "" : "\n\t"); } mbedtls_printf("\n"); } int main(void) { size_t olen; char buf[256]; mbedtls_mpi A, E, N, X; mbedtls_platform_set_printf(printf); mbedtls_mpi_init(&A); mbedtls_mpi_init(&E); mbedtls_mpi_init(&N); mbedtls_mpi_init(&X); mbedtls_mpi_read_string(&A, 16, "EFE021C2645FD1DC586E69184AF4A31E" \ "D5F53E93B5F123FA41680867BA110131" \ "944FE7952E2517337780CB0DB80E61AA" \ "E7C8DDC6C5C6AADEB34EB38A2F40D5E6" ); mbedtls_mpi_read_string(&E, 16, "B2E7EFD37075B9F03FF989C7C5051C20" \ "34D2A323810251127E7BF8625A4F49A5" \ "F3E27F4DA8BD59C47D6DAABA4C8127BD" \ "5B5C25763222FEFCCFC38B832366C29E" ); mbedtls_mpi_read_string(&N, 16, "0066A198186C18C10B2F5ED9B522752A" \ "9830B69916E535C8F047518A889A43A5" \ "94B6BED27A168D31D4A52F88925AA8F5" ); mbedtls_mpi_mul_mpi(&X, &A, &N); mbedtls_mpi_write_string(&X, 16, buf, 256, &olen); mbedtls_printf("\n X = A * N = \n\t"); dump_buf(buf, olen); mbedtls_mpi_exp_mod(&X, &A, &E, &N, NULL); mbedtls_mpi_write_string(&X, 16, buf, 256, &olen); mbedtls_printf("\n X = A^E mode N = \n\t"); dump_buf(buf, olen); mbedtls_mpi_inv_mod( &X, &A, &N); mbedtls_mpi_write_string(&X, 16, buf, 256, &olen); mbedtls_printf("\n X = A^-1 mod N = \n\t"); dump_buf(buf, olen); mbedtls_mpi_free(&A); mbedtls_mpi_free(&E); mbedtls_mpi_free(&N); mbedtls_mpi_free(&X); return 0; }
示例代码中相关接口描述如表2-8所示。
表2-8 大数运算示例相关接口描述
2.编写mbedtls_config.h
为了使mbedtls支持大数运算,需要在配置文件中增加MBEDTLS_BIGNUM_C定义。mbedtls_config.h的其他部分与上一节介绍的模板文件相同。
3.编写prj.conf
由于大数运算需要消耗更大的栈空间,建议把MAIN_STACK_SIZE设置为4096字节或更大值,默认情况下MAIN_STACK_SIZE的大小为仅1024字节。另外大数运算过程中还会使用动态内存分配接口,可以在配置文件中定义mbedtls栈大小,示例中将mbedtls的堆大小设置为4096字节。prj.conf配置文件内容如下:
CONFIG_STDOUT_CONSOLE=y CONFIG_MAIN_STACK_SIZE=4096 CONFIG_MBEDTLS=y CONFIG_MBEDTLS_BUILTIN=y CONFIG_MBEDTLS_ENABLE_HEAP=y CONFIG_MBEDTLS_HEAP_SIZE=4096 CONFIG_MBEDTLS_CFG_FILE="mbedtls_config.h"
4.编写CMakeLists.txt
CMakeLists.txt文件内容与上一节完全相同。
5.编译与运行
基础示例默认会运行在necluo_f429zi平台,若需要运行在仿真平台只需将-DBOARD参数指定为native_posix即可,具体过程可回顾2.8.4节。编译过程完成后,控制台将输出Flash空间和RAM空间的消耗情况。由于增加了mbedtls自定义栈空间,栈空间占4KB,另外还增加了4KB的Zephyr主线程栈空间,所以大数运算的内存消耗增加至11KB左右,约占STM32F429ZI整个RAM空间的4.4%。
应用程序将把运行结果输出至串口控制台,所以应用程序下载至开发板运行之前需新建终端,并通过minicom工具打开指定串口。操作指令如下:
# 请根据实际情况修改串口名称
$ sudo minicom -b 115200 -D /dev/ttyACM0
编译与运行过程如下:
# 进入示例代码文件夹 $ cd 02_start/zephyr/bignum # 新建一个build目录,用于存放临时文件 $ mkdir -p build && cd build # 通过cmake指令生成nucleo_f429zi平台makefile文件 $ cmake -DBOARD=nucleo_f429zi .. # 编译并查看资源消耗情况 $ make Memory region Used Size Region Size %age Used FLASH: 22760 B 2 MB 1.09% CCM: 0 GB 64 KB 0.00% SRAM: 11548 B 256 KB 4.41% IDT_LIST: 200 B 2 KB 9.77% # 下载到开发板运行 $ make flash # 串口控制台输出 X = A * N = 602AB7ECA597A3D6B56FF9829A5E8B85 9E857EA95A03512E2BAE7391688D264A A5663B0341DB9CCFD2C4C5F421FEC814 8001B72E848A38CAE1C65F78E56ABDEF E12D3C039B8A02D6BE593F0BBBDA56F1 ECF677152EF804370C1A305CAF3B5BF1 30879B56C61DE584A0F53A2447A51E X = A^E mode N = 36E139AEA55215609D2816998ED020BB BD96C37890F65171D948E9BC7CBAA4D9 325D24D6A3C12710F10A09FA08AB87 X = A^-1 mod N = 3A0AAEDD7E784FC07D8F9EC6E3BFD5C3 DBA76456363A10869622EAC2DD84ECC5 B8A74DAC4D09E03B5E0BE779F2DF61
2.10 本章小结
本章介绍了Linux和Zephyr环境下开发mbedtls应用的一般步骤。本章通过一个Base64示例说明开发mbedtls应用的具体步骤,这些步骤包括源代码编写、CMake构建脚本编写和编译与执行等。无论开发Linux应用还是开发Zephyr应用,CMake都是一种重要的构建工具。在遍历mbedtls安全套件示例中,我们可通过修改mbedtls配置文件的方式对其进行裁剪。相较于Linux平台,为了在Zephyr中启用mbedtls组件,还需要在配置文件prj.conf中增加CONFIG_MBEDTLS和CONFIG_MBEDTLS_BUILTIN选项。本章还引入了一个nucleo_f429zi平台的硬件随机数示例,硬件随机数生成器是物联网安全应用的基础。
请务必注意本章中提到的3个模板文件——CMakeLists.txt、prj.conf和mbedtls_conf.h,这些模板文件经过简单的修改之后便可适用于其他章节的示例。但由于篇幅关系,这些模板文件中的通用内容将不再重复介绍。