润小云解读鸿蒙OS系列(四):authmanager

一. 简介

设备之间互联是基于系统的IoT设备(如AI音箱、智能家居、智能穿戴等设备)与IoT主控设备(手机、平板等)间建立点对点的信任关系,并在具备信任关系的设备间,搭建安全的连接通道,实现用户数据端到端加密传输。

IoT主控设备和IoT设备建立点对点信任关系的过程,实际上是相互交换IoT设备的身份标识的过程。

authmanager是openharmony为设备提供认证机制的模块,模块内的处理流程为

二.HiChan(设备互联安全

为了实现用户数据在设备互联场景下,在各个设备之间的安全流转,实现用户数据的安全传输。设备之间的信任关系指的是本文档中涉及IoT主控设备和IoT设备之间建立的可信关系。

注:图片摘自Openharmony官网


三.加密框架mbedtls

AES-GCM加密算法:AES是一种对称加密算法,GCM是对该对称加密采用Counter模式,并带有GMAC消息认证码。AES-GCM算法是带认证和加密的算法,同时可以对给定的原文,生成加密数据和认证码。

首先调用int mbedtls_gcm_setkey(mbedtls_gcm_context *ctx, mbedtls_cipher_id_t cipher, const unsigned char *key, unsigned int keybits),该函数将GCM上下文与密码算法和密钥相关联。

加密调用:int mbedtls_gcm_crypt_and_tag(mbedtls_gcm_context *ctx, int mode, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char* output, size_t tag_len, unsigned char *tag),加密后的数据以出参形式传出。

解密调用:int mbedtls_gcm_auth_decrypt(mbedtls_gcm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *tag,size_t tag_len, const unsigned char *input, unsigned char *output),解密后的数据以出参形式传出。

四.流程详解与关键代码数据结构:

1初始化代码流程如下图所示:

该部分主要由discover 模块调用BusManager 函数开始,若flag = 1,则进行StartBus 函数的执行,它的实现主要是调用了StartListener 函数及StartSession 函数,实现对设备进行认证及加解密的过程。

  1. 关键数据结构

注册回调函数的全局变量g_baseLister

typedef struct {

OnConnectEventProc onConnectEvent;

OnDataEventProc onDataEvent;

} BaseListener;

  1. 关键接口StartListener

StartListener 该函数有两个参数,第一个参数是BaseListener *callback,即回调函数,第二个参数是const char *ip,也就是一个IP;该函数主要是创建了一个WaitProcess 的线程,该线程用于对g_maxFd 所代表的文件描述符利用select 函数进行监控,若返回值大于0,则调用ProcessAuthData 函数。该函数完成对建立的链接的数据进行收发,并对收到的数据进行处理的工作,两个处理事件的函数分别是onConnectEvent和onDataEvent,即前面注册的两个回调函数。其中onConnectEvent 函数主要是为新建立连接的设备创建AuthConnNode 类型的节点,并将其加入链表中;onDataEvent 函数是对AuthConnNode节点中的数据成员进行内存分配及对数据进行处理。

  1. StartSession

StartSession 该函数只有一个参数,即const char *ip,也就是一个IP,和StartListener函数中的IP 是一样的。该函数是为全局变量g_sessionMgr 申请空间及初始化,然后根据所给的参数创建socket 文件描述符并监听,之后通过调用StartSelectLoop 函数创建SelectSessionLoop 的线程,该线程将socket 文件描述符加入集合,并调用select 函数进行监控,若函数的返回值大于0,则调用ProcessData 函数,该函数有两个分支,若socket 未创建session则为其创建session;若已创建session,则处理其数据部分。


2.工作流程(消息处理流程)

2.1工作流程图如下

该部分主要有两个对外接口,一个是连接管理,另一个是数据处理。

数据处理流程图如下

该部分为收到数据后的整体处理流程,包括数据接收、包头解析和包数据处理。通过调用FindAuthConnByFd接口获取到连接结构AuthConn后,用AuthConnRecv来接收数据,通过ProcessPackets对收到的包处理,在ParsePacketHead中对包头解析,OnDataReceived中对数据处理,MODULE_AUTH_SDK类型包经过hichain处理流程,处理过程中调用回调函数 AuthOnTransmit, AuthGetProtocolParams, AuthSetSessionKey, AuthSetServiceResult, AuthConfirmReceiveRequest,其他包都会通过cJSON_Parse函数对数据解析,最后都会为MODULE_TRUST_ENGINE包和MODULE_CONNECTION包构造一个reply包发送给对端验证设备信息,对于验证IP的请求包在回包之后会将设备置为ONLINE状态,在所有数据处理结束之后关闭该连接。

2.2关键数据结构

2.2.1双向链表

typedef struct List {

struct List *prev;

struct List *next;

} List;

2.2.2保存所有连接的链表头

static List *g_fdMap = NULL;

2.2.3包类型定义

#define MODULE_NONE 0

#define MODULE_TRUST_ENGINE 1

#define MODULE_HICHAIN 2

#define MODULE_AUTH_SDK 3

#define MODULE_HICHAIN_SYNC 4

#define MODULE_CONNECTION 5

#define MODULE_SESSION 6

#define MODULE_SMART_COMM 7

#define MODULE_AUTH_CHANNEL 8

#define MODULE_AUTH_MSG 9

2.2.4数据缓冲区定义

typedef struct DataBuffer {

char *buf;

int size;

int used;

} DataBuffer;

2.2.5连接定义,一个AuthConn表示一个真实连接

typedef struct AuthConn {

int fd;

char authId[MAX_AUTH_ID_LEN];

char deviceId[MAX_DEV_ID_LEN];

char deviceIp[MAX_DEV_IP_LEN];

int busVersion;

int authPort;

int sessionPort;

int authState;

int onlineState;

DataBuffer db;

} AuthConn;

2.2.6连接管理节点,用双向链表的方式保存所有连接

typedef struct AuthConnNode {

List head;

AuthConn *aconn;

} AuthConnNode;

2.2.7数据包头结构

typedef struct {

int module;

int flags;

long long seq;

int dataLen;

} Packet;

2.3关键接口

2.3.1 ProcessConnectEvent

负责连接建立和管理


连接建立:

在链表g_fdMap中根据fd查找AuthConn,找不到则说明该fd没有被使用,然后为AuthConn分配内存,并且填充fd和ip数据到新建连接的AuthConn中,为AuthConnNode分配空间后将AuthConnNode和AuthConn绑定,最后将AuthConnNode追加在g_fdMap链表尾部后表示连接完成。

连接管理:

用一个g_fdMap为头指针的双向链表来保存所有的连接节点AuthConnNode,每一个AuthConnNode结构体都有自己的AuthConn,表示一个真正的连接,g_fdMap中最多可以保存32个连接,超过32个时建立连接会失败。

2.3.2 ProcessDataEvent

负责从fd对应的连接中接收数据包,并且根据包的module不同来分别处理

首先根据fd找到对应的连接AuthConn,如果该连接还没有分配缓冲区,则为其创建一个1536字节大小的接收buf并清空,然后通过AuthConnRecv函数接收数据,其底层也是调用大家所熟悉的socket编程接口recv去接收数据,将接收的数据保存在刚才为AuthConn分配的buf中,并且更新表示当前缓冲区使用属性的变量used。然后调用ProcessPackets函数对接收的数据包解析并处理,该函数中对包头解析,然后根据包头中获取的数据长度来对数据部分进行处理。

2.3.3 ParsePacketHead

解析包头

对收到的数据包头进行校验,magic number正确后,获取包的module、seq、flags、datalen信息并保存在Packet里面,后续对该包处理。

2.3.4 OnDataReceived

对收到的数据进行处理

数据处理主要依赖包头中pkt->module这个值来区分不同类型的包,包的类型共有10种,除了MODULE_AUTH_SDK包通过AuthInterfaceOnDataReceived处理外,其余都会通过DecryptMessage函数进行处理,包处理结束之后会调用cJSON_Parse来获取数据,最后会对MODULE_TRUST_ENGINE包和MODULE_CONNECTION包构造一个新的reply包,将本地的设备信息封装成cJSON格式的msg后发回对端。

3. 最终调用封装加密发送流程

3.1工作流程图如下

3.2关键数据结构

3.2.1 SessionKey

在hichain处理流程中获取到的key被保存在SessionKey中,在最后发送之前获取sessionkey打包到要发送的buf中。

typedef struct SessionKey {

char key[AUTH_SESSION_KEY_LEN];

int index;

int fd;

} SessionKey;

3.2.2 AuthSession

每当收到新的连接且类型是MODULE_AUTH_SDK时,创建一个AuthSession,并保存到全局数组g_authSessionMap中,下次再进行连接会话时从全局数据中找AuthSession

typedef struct AuthSession {

int isUsed;

long long seqId;

uint32_t sessionId;

AuthConn *conn;

} AuthSession;

3.3关键接口

3.3.1 AuthConnPackBytes

打包与加密接口,首先打包关键信息identifier,module等到传输buf中,判断isCipherText,是否需要加密,判断标准为module是否为MODULE_CONNECTION,MODULE_SESSION MODULE_SMART_COMM

3.3.2 AuthConnPackBytes

获取sessionkey,打包入要传输的buf中

3.3.3 EncryptTransData

实际调用mbedtls加密框架,进行加密的接口

3.3.4 AuthConnSend

调用send函数,把打包好的buf通过套接字fd发送出去。

五 . 结束

当初步建立信任关系的IoT主控设备与IoT设备间在进行通信时,双方首先完成信任关系绑定,然后基于存储在本地的对端身份公钥相互进行认证;在每次通信时完成双向身份认证以及会话密钥协商,之后设备使用此会话密钥来解密双方设备间的传输通道。

原文链接:,转发请注明来源!