最新文章
-
MQTT协议分析
什么是MQTT MQTT是基于TCP/IP网络协议栈构建的异步通信消息协议,基于发布-订阅模式进行传输。实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中, MQTT协议中有三种身份: 发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。 其中消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者,客户端之间的通信完全是空间解耦的。如上图,客户端C如果要接收客户端A的信息,先向broker subscibe想要接收的主题,当Client A发布消息是发送给broker,broker接收到后判断此前Client C订阅了该主题信息,那么就会转发给Client C。因此发布消息/订阅消息与发布者和订阅者是完全解耦的,这种耦合度有下面三个维度: 空间解耦:发布者与订阅者并不知道对方的存在。 时间解藕:发布者与订阅者并不一定需要同时运行。 同步 Synchronization 解藕:两个组件的操作比如发布和订阅都不会在发布或者接收过程中产生中断。 MQTT传输的消息分为: 主题(Topic) 和 负载(payload)两部分: Topic: 为消息的类型,订阅者订阅(Subscribe)主题后,就会收到目标主题的消息内容(payload) payload: 消息的内容,发布者要发布的数据内容和订阅者要接收的数据内容。 MQTT协议传输 主题 MQTT传输中,topic是broker为每个client过滤消息用的,主要用字符串来标识。使用目录分层的结构来进行表示,broker收到消息后会将该主题下的消息转发给所有订阅该主题(Topic)的设备。以“/”为分隔符区分不同的层级,包含通配符“+” 或 “#”的主题又称为主题过滤器(Topic Filters);,不含通配符的成为主题名(Topic Names) ,示例如下: sensor/10/temperature sensor/+/temperature $SYS/broker/metrics/packets/received $SYS/broker/metrics/# '+' : 表示通配一个层级, 例如a/+,匹配a/x, a/y MQTT数据包格式 如上图,MQTT的包格式有3部分组成,fixed header,variable header,playload,下面依次进行展开。 fixed header fixed header又分为MQTT control packet type 和每种packet type对应的flag组成,Packet type主要有以下,其决定了mqtt实际传输的方法。 packet types flag 在fixed header的bit[3]~bit[0]主要存储的是各个packet type对应的标志,如上图只有PUBLISH type才有意义,其他的都是保留。 DUP:设置为1,表明这个数据包是一条重复的消息;否则该数据包就是第一次发布的消息。 QOS:用于发布的传输质量,bit[2]bit[1]=00,只发一次不保证成功;01最少发一次,收不到ACK会一直重传,接收者可能会重复接受到数据;10只有一次,接收者不会重复收到数据。 PUBLISH数据包不得将两个QoS位都设置为1。如果服务器或客户端收到PUBLISH两个QoS位都设置为1 的数据包必须关闭网络连接。 RETAIN:如果RETAIN 标志设置为1,则在客户端向服务器发送的PUBLISH数据包中,服务器必须存储应用程序消息及其QoS,以便可以将其传递给订阅与其主题名称匹配的未来订阅者。 下面针对QOS质量再进行说明。使用QoS 0可能丢失消息,使用QoS 1可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。 QOS 0 : QoS 0是最低的QoS 等级,消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。当我们使用 QoS 0 传递消息时,消息的可靠性完全依赖于底层的TCP协议。而TCP只能保证在连接稳定不关闭的情况下消息的可靠到达,一旦出现连接关闭、重置,仍有可能丢失当前处于网络链路或操作系统底层缓冲区中的消息。这也是 QoS 0 消息最主要的丢失场景。 QOS 1: 是加入了应答与重传机制,发送方只有在收到接收方的PUBACK报文以后,才能认为消息投递成功。在此之前,发送方需要存储该PUBLISH 报文以便下次重传。缺点就是Broker若从发布方收到了重复的PUBLISH报文,而在将这些报文转发给订阅方的过程中,再次发生重传,这将导致订阅方最终收到更多的重复消息。 QOS 2:解决QoS 0、1 消息可能丢失或者重复的问题,但它也带来了最复杂的交互流程和最高的开销。每一次的QoS 2 消息投递,都要求发送方与接收方进行至少两次请求/响应流程。QOS2是针对接受者不能同时收到重复报的场景需求。 步骤1:发送方(发布者或broker)存储并发布QoS2的报文,然后需要接收方(broker或订阅者)回复PUBREC报文。这里与QoS1流程基本一直,只是回复报文从PUBACK 变成了PUBREC。 步骤2:当发送方收到PUBREC报文后,表示对端已经收到了发送的报文,发送方将不能再重传这个报文,发送方所以此时可以删除本地存储的PUBLISH报文;接着发送一个PUBREL报文通知对端自己准备将本次使用的Packet ID标记为可用了。 步骤3:当接收方收到PUBREL报文后,回复PUBCOMP报文表示自己也准备好将当前的Packet ID用于新的消息了。 4)当发送方收到PUBCOMP报文,这一次的QoS 2 消息传输就算正式完成了。在这之后,发送方可以再次使用当前的 Packet ID 发送新的消息,而接收方再次收到使用这个 Packet ID 的PUBLISH报文时,也会将它视为一个全新的消息。 variable header 可变长度头部有些报文包含,有些报文则不包含。实际上可变长度的头部可以理解为实际的具体数据,因为不同的type对应的payload不同,相同的type也会有不同的数据可以区分,比如connect类型需要包含不少的格式信息。 协议版本 :号值表示客户端的版本。 clean sesion:MQTT客户端向服务器发起CONNECT请求时,可以通过’Clean Session’标志设置会话。‘Clean Session’设置为0,表示创建一个持久会话,在客户端断开连接时,会话仍然保持并保存离线消息,直到会话超时注销。‘Clean Session’设置为1,表示创建一个新的临时会话,在客户端断开时,会话自动销毁 Will Flag : 遗言标志位 Will QoS: 遗言的消息质量 Will Retain: 遗言的保持状态 Keep Alive timer(心跳时长):client与broker交互PINGREQ和PINGRESP。 payload 一些MQTT控制数据包包含有效载荷作为数据包的最后部分。对于 PUBLISH 数据包,这是应用程序消息。上图列出了需要有效载荷的控制数据包。 MQTT基本交互 CONNECT ClientId:每个客户端连接到broker的标识,具有唯一性。如果客户端与服务端不需要保持长连接状态,可以为空。 CleanSession:client告诉broker是否需要建立持久会话,在持久会话 (CleanSession = false)中,broker会存储client的所有订阅。如果会话不是持久的(CleanSession = true),那么 broker则不会为client存储任何内容并且会清除先前持久会话中的所有信息。 Username/Password :MQTT会发送username和password进行client认证和授权。如果此信息没有经过加密或者hash,那么密码将会以纯文本的形式发送。因此建议username 和 password 要经过加密安全传输。 LastWillxxx :表示遗愿,client在连接 broker的时候将会设立一个遗愿,这个遗愿会保存在 broker中,当 client 因为非正常原因与 broker断开连接时,broker会将遗愿发送给订阅了这个 topic(订阅遗愿的 topic)的client。 keepAlive:client在连接建立时与 broker 通信保活的时间间隔,通常以秒为单位。这个时间指的是client 与 broker 在不发送消息下所能承受的最大时长。client与broker交互PINGREQ和PINGRESP来保持连接。 CONNACK 当broker收到CONNECT消息后,需要发送CONNACK消息进行响应。CONNACK消息包括两部分内容。 PUBLISH 当MQTT client在连接上broker以后,就可以发布信息了。MQTT使用的是基于topic主题的过滤,每条消息都应该包含一个topic,broke 可以使用topic将消息发送给感兴趣 client。除此之外每条消息还会包含一个负载(Payload),Payload 中包含要以字节形式发送的数据。MQTT 是数据无关性的,也就是说数据是由发布者 - publisher 决定要发送的是 XML 、JSON 还是二进制数据、文本数据。 MQTT 中的 PUBLISH 消息结构如下。 Packet Identifier:这个PacketId标识在 client 和 broker 之间唯一的消息标识。packetId 仅与大于零的 Qos 级别相关。 TopicName:主题名称是一个简单的字符串,/ 代表着分层结构。 Qos:这个数字表示的是服务质量水平,服务质量水平有三个等级:0、1 和 2,服务级别决定了消息到达 client 或者 broker 的保证类型,来决定消息是否丢失。 RetainFlag:这个标志表示 broker 将最近收到的一条 RETAIN 标志位为true的消息保存在服务器端(内存或者文件)。 MQTT 服务器只会为每一个 Topic保存最近收到的一条RETAIN标志位为true的消息。如果MQTT 服务器上已经为某个 Topic 保存了一条 Retained 消息,当客户端再次发布一条新的 Retained 消息时,那么服务器上原来的那条消息会被覆盖。 Payload:这个是每条消息的实际内容。MQTT 是数据无关性的。可以发送任何文本、图像、加密数据以及二进制数据。 Dupflag:这个标志表示该消息是重复的并且由于预期的 client 或者 broker 没有确认所以重新发送了一次。这个标志仅仅与 Qos 大于 0 相关。 当 client 向 broker 发送消息时,broker 会读取消息,根据 Qos 的级别进行消息确认,然后处理消息。处理消息其实就是确定哪些 subscriber 订阅了 topic 并将消息发送给他们。 最初发布消息的 client 只关心将 PUBLISH 消息发送给 broker,一旦 broker 收到 PUBLISH 消息,broker 就有责任将其传递给所有 subscriber。发布消息的 client 不会知道是否有人对发布的消息感兴趣,同时也不知道多少 client 从 broker 收到了消息。 SUBSCRIBE client 会向 broker 发送 SUBSCRIBE 消息来接收有关感兴趣的 topic,这个 SUBSCRIBE 消息非常简单,它包含了一个唯一的数据包标识和一个订阅列表。 Packet Identifier:这个 PacketId 和上面的 PacketId 一样,都表示消息的唯一标识符。 ListOfSubscriptions:SUBSCRIBE 消息可以包含一个client的多个订阅,每个订阅都会由一个 topic和一个 Qos构成。 client在向broker发送 SUBSCRIBE 消息后,broker会向 client发送 SUBACK 确认消息。 Packet Identifier :这个数据包标识符和SUBSCRIBE中的相同。 ReturnCode:broker 为每个接收到的 SUBSCRIBE 消息的 topic/Qos 对发送一个返回码。例如,如果 SUBSCRIBE 消息有五个订阅消息,则SUBACK消息包含五个返回码作为响应。 到现在我们已经探讨过了三种消息类型,发布 - 订阅 - 确认消息,这三种消息的示意图如下。 参考文献: https://www.emqx.com/zh/blog/introduction-to-mqtt-qos https://blog.csdn.net/jackwmj12/article/details/129163012 https://www.cnblogs.com/cxuanBlog/p/14917187.html http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html -
SSL/TLS协议分析
什么是TLS 1994: SSL 1.0 NetScape公司提出SSL第一版,未公开。 1995: SLL 2.0 公开发布了第二版,与2011年弃用。 1996: SSL 3.0 第三版得到大规模应,于2015年弃用。 1999: TLS 1.0 RFC2246,被IETF纳入标准化,没太大改动,改名TLS。 2006: TLS 1.1 RFC4346,修复bug,增加参数。 2008: TLS 1.2 RFC,更多扩展和算法该节。 2018: TLS 1.3 减少时延,完全前向安全。 传输层安全性协议(英语:Transport Layer Security,缩写:TLS),前身称为安全套接层(英语:Secure Sockets Layer,缩写:SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。TLS(Transport Layer Security)是计算机网络通信用于安全加密的协议,HTTP+TLS后后即HTTPS。 TLS介于网络传输层和应用层之间,TLS主要用于数据加密过程,保证数据的安全传输。TLS是一套混合加密系统,使用了对称加密和非对称加密两种方式。非对称加密相对对称加密更安全,但是其复杂的加解密会使得通信效率降低,为了解决这种场景。先使用“非对称加密”的方式传输用于数据的“对称加密密钥”,以保证双方的对称加密秘钥是安全传输的,后续就可以直接使用对称加密秘钥进行传输了。关于非对称加密和对称加密接下来简单说明一下。 对称加密与非对称加密 对称加密 对称加密双方使用相同的秘钥进行加解密,秘钥被称为共享秘钥和对称秘钥。常见的对称加密算法有AES、DES、3DES等等,下面简要说明一下常用的AES算法。 AES加解密 AES是秘钥长度有128/192/256,其秘钥长度用于指定将明文转换为密文所需要的变化轮数,如当秘钥长度128位是,轮数是10;秘钥长度为192位时,轮数为12;秘钥长度为256时,轮数为14。 由于AES算法单次只能加解密固定长度的分组数据,如AES 单次只能加解密128位数据,而实际场景中的AES加解密长度并不是128位的整数倍,为了解决这个问题,使用AES可以使用分组密码模式配合消息填充的方法来解决。 分组密码模式 ECB模式 将明文进行分组加密,加密结果为密文分组,最后一个明文分组需要必须要填充为128位。 CBC模式 依旧是对明文进行分组加密,最后一个分组需要填充满128位。每一组明文在加密前都与前面的密文分组进行异或操作。由于第一个明文分组前没有密文分组,所以需要准备一个与密文分组长度相等的比特序列来代替密文分组,这个比特序列被称作初始化向量,简称IV。 CTR模式 CTR模式使用用于分组长度相同的计数值参与运算,通过对逐次累加的计数器进行加密来生成密钥流,通过加密计数器得到的密钥流与明文分组进行异或运算,得到密文分组。若明文长度不是分组长度的整数倍,假设最后一个明文分组N的 长度为L位,那么最后一个明文分组N只需与计数器N加密结果的左侧 L位异或,获得的密文分组N的长度也是N位。这种算法结构使得CTR 模式不需要对明文进行填充。 分组明文的填充 使用ECB/CBC模式,当加密明文不是分组密码长度的整数倍是,通常需要对明文进行填充,常用的填充方案是PKCS7。 以AES-CBC算法为例,若分组的长度是16字节,当加密明文是28字节是,则需要在明文末尾填充4字节,使其达到分组长度的整数倍;若待加密数据恰好是16字节,需要在明文后面额外填充16字节,并将其全部填充为16。 Mbedtls AES示例 uint8_t key[16] = { 0x06, 0xa9, 0x21, 0x40, 0x36, 0xb8, 0xa1, 0x5b, 0x51, 0x2e, 0x03, 0xd5, 0x34, 0x12, 0x00, 0x06 }; uint8_t iv[16] = { 0x3d, 0xaf, 0xba, 0x42, 0x9d, 0x9e, 0xb4, 0x30, 0xb4, 0x22, 0xda, 0x80, 0x2c, 0x9f, 0xac, 0x41 }; int cipher(int type) { size_t len; int olen = 0; uint8_t buf[64]; mbedtls_cipher_context_t ctx; const mbedtls_cipher_info_t *info; mbedtls_cipher_init(&ctx); info = mbedtls_cipher_info_from_type(type); //获取加密模式 mbedtls_cipher_setup(&ctx, info); //设置cipher结构体,内部是赋值的过程 mbedtls_cipher_setkey(&ctx, key, sizeof(key)*8, MBEDTLS_ENCRYPT); //设置密钥 mbedtls_cipher_set_iv(&ctx, iv, sizeof(iv)); //设置IV,CBC/CTR都需要IV,第一组的异或 mbedtls_cipher_update(&ctx, ptx, strlen(ptx), buf, &len);//更新cipher olen += len; mbedtls_cipher_finish(&ctx, buf + len, &len);//cipher完成 olen += len; mbedtls_cipher_free(&ctx); return 0; } int main(void) { cipher(MBEDTLS_CIPHER_AES_128_CBC); cipher(MBEDTLS_CIPHER_AES_128_CTR); return 0; } 非对称加密 上一小节说了对称加密算法,其特点就是加密速度快,效率高,但是其缺点就是在于密钥的传输存在安全性问题,因为大部分通信都是通过网络来进行传输的,密钥容易在传输过程中被窃取,一旦获得密钥,那么后面的加密就毫无意义了。为了解决这种问题,非对称加密就产生了,非对称加密的特点就是把密钥进行分离,分成公钥和私钥两个部分。公钥是传输的双方公有的密钥,用于数据的加密,而私钥用于解密,双方的私钥不一样,各自保管。通过公钥加密、各自的私钥解密这样即使公钥被泄露,也不用担心,没有私钥是无法解密的。常见的非对称加密算法有RSA,DSA,ECC等。下面重点简要介绍RSA加密算法。 RSA 1) Bob按照RSA算法标准生成密钥对,这个密钥对包含公钥和私钥。 2) Bob将公钥发送给Alice,私钥则自己进行保存起来。 3) Alice收到Bob的公钥后,使用该公钥加密明文,接着发送给Bob。 4) Bob接收到Alice使用公钥加密的密文后,使用自己的私钥进行解密得到明文,解密正确后则后续Bob就使用这套密钥。 mbedtls RSA示例 size_t olen = 0; uint8_t out[2048/8]; mbedtls_rsa_context ctx; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; const char *pers = "simple_rsa"; const char *msg = "Hello, World!"; mbedtls_platform_set_printf(printf); mbedtls_platform_set_snprintf(snprintf); mbedtls_entropy_init(&entropy); //初始化熵结构体 mbedtls_ctr_drbg_init(&ctr_drbg);//初始化随机数结构体 mbedtls_rsa_init(&ctx, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256); //初始化RSA结构体 mbedtls_entropy_add_source(&entropy, entropy_source, NULL, MBEDTLS_ENTROPY_MAX_GATHER, MBEDTLS_ENTROPY_SOURCE_STRONG); //添加熵源接口,设置熵源属性 mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const uint8_t *) pers, strlen(pers)); //根据个性化字符串更新种子 mbedtls_rsa_gen_key(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, 2048, 65537);//RSA生成密钥对 mbedtls_rsa_pkcs1_encrypt(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, strlen(msg), msg, out);//RSA加密操作,通过指定公钥进行加密 mbedtls_rsa_pkcs1_decrypt(&ctx, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, &olen, out, out, sizeof(out));//RSA解密操作,通过制定参数私钥解密 out[olen] = 0; memcmp(out, msg, olen); mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_entropy_free(&entropy); mbedtls_rsa_free(&ctx); return 0; 数字证书 使用非对称加密的好处就是,通过公钥加密、私钥解密,私钥是各自私有这样相比于对称加密就更安全了。一般情况下,公钥需要通过网络进行传输,而且公钥都是公开的。假设有这么一个场景,A和B在首次建立通信连接的时候,A发送给B公钥,但是在发送公钥的过程中被C劫持了,换成了C自己的公钥发送给B,那么B就误认为C的公钥是A发送的,那么后续C就可以作为中间人获取到通信内容,传输链路A->C-B。 为了解决这种在初次建立连接是被中间人中继的问题,因为非对称加密公私钥可以分离,所以可以找个大家信得过的机构来专门颁发公钥,这个机构颁发的就是数字证书,相当于就是身份证。让A和B的公钥是值得信赖的,不要让B误认为公钥是C的公钥。 签发证书的机构被称为 CA( Certificate Authority),理论上每个人都可以成为CA,因为每个人都可以自己签发证书,但是只有极少数的权威CA颁发的证书才会被承认。 一般来说数字证书可以按照安全程度分为以下三类: EV:EV证书(Extended Validation Certificate)是一种根据一系列特定标准颁发的X.509电子证书,根据要求,在颁发证书之前,证书颁发机构(CA)必须验证申请者的身份。不同机构根据证书标准发行的扩展验证证书并无太大差异,但是有时候根据一些具体的要求,特定机构发行的证书可以被特定的软件识别 OV:OV证书(Organization Validation SSL),指需要验证网站所有单位的真实身份的标准型SSL证书,此类证书不仅能够起到网站信息加密的作用,而且能向用户证明网站的真实身份 DV:DV证书(Domain Validation SSL),指需要验证域名的有效性。该类证书只提供基本的加密保障,不能提供域名所有者的信息 TLS协议 TLS协议可以分为记录层和握手层 记录层:负责对数据进行加密、压缩、分段,并保证数据的完整性和安全传输。 握手层:负责建立安全通信,完成密钥交换、身份认证以及协商加密算法等,确保双方通信的安全性。 握手层有3个协议,握手协议(Handshake Protocol)、更换加密规约协议(Change Cipher Spec Protocol)、告警协议(Alert Protocol)。 TLSv1.2 握手过程 步骤1:客户端通过明文的方式发送Client Hello 消息到服务器,消息中主要包含了客户端支持的ciphersuites, TLS 版本信息和客户端随机数。 步骤2:服务器接收到消息后,明文发送一个Server Hello给客户端,包括自己支持的ciphersuites, TLS 版本,自己的数字证书(证书中包含了公钥)和服务器端生成的随机数。在包的交互上,证书、随机数等可能是不包含在Server Hello一个包中单独进行发送,示服务器具体的行为。下图是Server Hello, 当客户端需要对服务器的身份进行验证时,服务器端发送 Certificate消息。该消息中包含证书清单,证书清单是一组X.509 v3证书列表。证书列表包含服务器证书、中间证书和根证书。通常情况下服务器并不会发送根证书,这就需要客户端提前导入根证书。通过Certificate消息,客户端将获得服务器的公钥,并通过根证书中的公钥验证服务器公钥的合法 性。下图是服务器证书Ceriticate,证书中包含了公钥。 如果服务器没有证书或者服务器的证书仅用来签名(如DSS证书、签名RSA证书),或者使用的是FORTEZZA KEA密钥交换算法,那么就需要发送Server Key Exchange。服务器会在 server Certificate 消息之后发送 Server Key Exchange 消息。 服务器Hello阶段结束后,一般会附上一条简单的Server Hello Done表示结束。 步骤3:客户端开始验证数字证书,可能会不断往上追溯 CA,直到一个可信任CA。验证证书合法之后,从证书中读取公钥信息。之后生成一个pre-master key(用来生成后续的对称秘钥),接着使用证书中的公钥来对pre-master key进行加密,然后发送给服务器。该过程是非对称加密传输。服务器接收到客户端发送过来的非对称加密的密文,使用自己的私钥进行解密,获得了pre-master key。注意此时是非对称加密传输,这样服务器就获得了后续对称加密的密钥。 经过1)2)3)不走,服务器和客户端有了3组数据,分别是客户端的随机数、服务器的随机数和pre-master key。其中由于客户端的随机数和服务器的随机数都是使用明文传输,所以这两个数字是有被暴露的风险的,但是由于pre-master key是使用非对称加密传输,十分安全,所以将这三者结合,使用之前协商好的特定的算法就可以生成一个密钥,这个密钥称为shared secert。也就是之后用来对称加密的密钥。 步骤4:客户端在计算出对称加密的密钥之后,使用该密钥进行对称加密通信,告知服务器之后都使用该密钥进行对称加密。注意此时是对称加密传输服务器接收到密文后,使用之前计算出的密钥来进行对称解密,解密成功之后,再使用该密钥进行对称加密通信。告知客户端密钥确认无误,可以使用该密钥进行通信。 至此,整个TLS的握手过程完整,之后就可以开始对称加密的通信了。总结一下SSL/TLS协议的基本过程,前两步又称为\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"握手阶段\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"(handshake),是SSL/TLS加密通信的基础。 通过CA体系交换公钥 使用非对称加密算法,交换用于对称加密的密钥 有效数据使用对称加密算法,进行密文传输 TLSv1.3握手过程 待补充。 TLS应用编程 创建连接 int ssl_transport_connect(NetworkContext_t* net_ctx, const char* host, uint16_t port, const char* cacert) { const char* pers = "ssl_client"; int ret; Address resolved_addr; mbedtls_ssl_init(&net_ctx->ssl); //初始化ssl结构体 mbedtls_ssl_config_init(&net_ctx->conf);//初始化ssl配置结构体 // mbedtls_x509_crt_init(&net_ctx->cacert);//初始化X.509证书结构体 mbedtls_ctr_drbg_init(&net_ctx->ctr_drbg);//初始化随机数结构体 mbedtls_entropy_init(&net_ctx->entropy);//初始化熵结构体 //初始化个性化字符串更新种子 if ((ret = mbedtls_ctr_drbg_seed(&net_ctx->ctr_drbg, mbedtls_entropy_func, &net_ctx->entropy, (const unsigned char*)pers, strlen(pers))) != 0) { return -1; } //加载ssl默认配置选项,可以指定端类型、传输协议等参数。 if ((ret = mbedtls_ssl_config_defaults(&net_ctx->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { LOGE("ssl config error: -0x%x", (unsigned int)-ret); return -1; } //配置认真方式,配置项包括 // VERIFY_NONE:不对证书进行验证。 // VERIFY_OPTIONAL:对证书进行验证,即使证书验证失败,继续完成握手操作 // VERIFY_REQUIRED: 对证书进行验证,而且要求证书必须通过验证,否则总之握手过程 mbedtls_ssl_conf_authmode(&net_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL); /* XXX: not sure if this is needed ret = mbedtls_x509_crt_parse(&net_ctx->cacert, (const unsigned char *) cacert, strlen(cacert) + 1); if (ret < 0) { LOGE("ssl parse error: -0x%x", (unsigned int) -ret); } mbedtls_ssl_conf_ca_chain(&net_ctx->conf, &net_ctx->cacert, NULL); */ //设置随机数生成器回调接口 mbedtls_ssl_conf_rng(&net_ctx->conf, mbedtls_ctr_drbg_random, &net_ctx->ctr_drbg); //通过配置选项完成ssl的设置 if ((ret = mbedtls_ssl_setup(&net_ctx->ssl, &net_ctx->conf)) != 0) { LOGE("ssl setup error: -0x%x", (unsigned int)-ret); return -1; } //配置ssl hostname if ((ret = mbedtls_ssl_set_hostname(&net_ctx->ssl, host)) != 0) { LOGE("ssl set hostname error: -0x%x", (unsigned int)-ret); return -1; } //创建socket,配置地址和端口,发起tcp连接 memset(&resolved_addr, 0, sizeof(resolved_addr)); tcp_socket_open(&net_ctx->tcp_socket, AF_INET); ports_resolve_addr(host, &resolved_addr); addr_set_port(&resolved_addr, port); if ((ret = tcp_socket_connect(&net_ctx->tcp_socket, &resolved_addr) < 0)) { return -1; } //配置 SSL 连接的接收超时时间 mbedtls_ssl_conf_read_timeout(&net_ctx->conf, SSL_RECV_TIMEOUT); //设置 SSL 连接的 BIO(输入输出)接口,即定义 SSL/TLS 连接使用的网络 I/O 操作函数。 //&net_ctx->ssl:这是指向 mbedtls_ssl_context 结构体的指针,表示 SSL/TLS 会话上下文。 //&net_ctx->tcp_socket:这是指向 TCP 套接字的指针,表示底层的网络连接(通常是一个 TCP 套接字,用于在网络中传输加密数据)。 //ssl_transport_mbedtls_send:这是一个自定义的发送数据函数,用于通过网络连接发送加密后的数据。mbedtls_ssl_write 函数最终会调用这个发送函数,将数据从应用层发送到网络层。 //ssl_transport_mbedtls_recv_timeout:这是一个自定义的接收数据函数,用于在设置的超时限制下从网络接收数据。它会被 mbedtls_ssl_read 调用,接收经过加密的 SSL/TLS 数据并解密。 mbedtls_ssl_set_bio(&net_ctx->ssl, &net_ctx->tcp_socket, ssl_transport_mbedlts_send, NULL, ssl_transport_mbedtls_recv_timeout); LOGI("start to handshake"); //执行handshake握手 while ((ret = mbedtls_ssl_handshake(&net_ctx->ssl)) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { LOGE("ssl handshake error: -0x%x", (unsigned int)-ret); } } LOGI("handshake success"); return 0; } mbedtls_ssl_set_bio传入的函数。 static int ssl_transport_mbedtls_recv_timeout(void* ctx, unsigned char* buf, size_t len, uint32_t timeout) { int ret; fd_set read_fds; struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&read_fds); FD_SET(((TcpSocket*)ctx)->fd, &read_fds); ret = select(((TcpSocket*)ctx)->fd + 1, &read_fds, NULL, NULL, &tv); if (ret < 0) { return -1; } else if (ret == 0) { // timeout } else { if (FD_ISSET(((TcpSocket*)ctx)->fd, &read_fds)) { ret = tcp_socket_recv((TcpSocket*)ctx, buf, len); } } return ret; } static int ssl_transport_mbedlts_send(void* ctx, const uint8_t* buf, size_t len) { return tcp_socket_send((TcpSocket*)ctx, buf, len); } 收发数据 int32_t ssl_transport_recv(NetworkContext_t* net_ctx, void* buf, size_t len) { int ret; memset(buf, 0, len); ret = mbedtls_ssl_read(&net_ctx->ssl, buf, len); return ret; } int32_t ssl_transport_send(NetworkContext_t* net_ctx, const void* buf, size_t len) { int ret; while ((ret = mbedtls_ssl_write(&net_ctx->ssl, buf, len)) <= 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { LOGE("ssl write error: -0x%x", (unsigned int)-ret); } } return ret; } 断开连接 void ssl_transport_disconnect(NetworkContext_t* net_ctx) { mbedtls_ssl_config_free(&net_ctx->conf); // mbedtls_x509_crt_free(&net_ctx->cacert); mbedtls_ctr_drbg_free(&net_ctx->ctr_drbg); mbedtls_entropy_free(&net_ctx->entropy); mbedtls_ssl_free(&net_ctx->ssl); tcp_socket_close(&net_ctx->tcp_socket); } 参考: 1. 《密码技术与物联网安全》 2. https://tinychen.com/20200602-encryption-intro/ -
密码保护:移动检测代码示例分析
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
密码保护:RTSP视频传输示例代码分析
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
密码保护:拍照示例代码分析
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
密码保护:SSL证书更新
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
Linux系统编译生成镜像流程
编译内核 清除内核 命令 ${MAKE} O= mrproper 示例 make CROSS_COMPILE=riscv32-unknown-linux- ARCH=riscv -j16 O=kernel/build KERNEL_SRC=kernel/linux-5.4 INSTALL_MOD_PATH=kernel/staging O= mrproper 配置defconfig 命令 defconfig:${MAKE} defconfig KBUILD_DEFCONFIG=${LICHEE_KERN_DEFCONF_RELATIVE} 示例 make CROSS_COMPILE=nds32le-linux-glibc-v5d/bin/riscv32-unknown-linux- ARCH=riscv -j16 O=kernel/build KERNEL_SRC=kernel/linux-5.4 INSTALL_MOD_PATH=kernel/staging defconfig KBUILD_DEFCONFIG=linux-5.4/xxx_defconfig 编译 ${MAKE} $MAKE_ARGS 示例 MAKE_ARGS+=' INSTALL_HDR_PATH=kernel/build/user_headers headers_install' $COMP_TYPE = Image.bz2/Image.lz4 根据LICHEE_COMPRESS是bzip2,gzip等。这里的rv选择的是Image.gz MAKE_ARGS+$COMP_TYPE make CROSS_COMPILE=nds32le-linux-glibc-v5d/bin/riscv32-unknown-linux- ARCH=riscv -j16 O=kernel/build KERNEL_SRC=kernel/linux-5.4 INSTALL_MOD_PATH=kernel/staging modules all INSTALL_HDR_PATH=kernel/build/user_headers headers_install Image.gz 安装modules 命令 ${MAKE} modules_install 示例 make CROSS_COMPILE=nds32le-linux-glibc-v5d/bin/riscv32-unknown-linux- ARCH=riscv -j16 O=kernel/build KERNEL_SRC=kernel/linux-5.4 INSTALL_MOD_PATH=kernel/staging modules_install 额外编译 单独额外编译的模块 make CROSS_COMPILE=nds32le-linux-glibc-v5d/bin/riscv32-unknown-linux- ARCH=riscv -j16 O=kernel/build KERNEL_SRC=kernel/linux-5.4 INSTALL_MOD_PATH=kernel/staging -C kernel/linux-5.4/xxx/modules/nand M=kernel/linux-5.4/xxx/modules/nand -j1 设备树 cpp \ -Wp,-MD,${dep}/.${outname}.d.pre.tmp \ -nostdinc \ -I ${LICHEE_KERN_DIR}/include \ -I ${LICHEE_KERN_DIR}/bsp/include \ -I ${die_dtsi_path} \ -I ${chip_dtsi_path} \ -undef \ -D__DTS__ \ -x assembler-with-cpp \ -o ${dep}/.${outname}.dts.tmp \ ${dtsfile} $DTC \ -O dtb \ -o ${outpath}/${outname} \ -W no-unit_address_vs_reg \ -W no-unit_address_format \ -W no-unique_unit_address \ -W no-graph_child_address \ -W no-simple_bus_reg \ -b 0 \ -@ \ -i ${LICHEE_CHIP_CONFIG_DIR}/configs/default/${LICHEE_KERN_VER} \ -d ${dep}/.${outname}.d.dtc.tmp ${dep}/.${outname}.dts.tmp 示例 cpp -Wp,-MD,kernel/staging/dts_dep/.xxx.dtb.d.pre.tmp -nostdinc -I kernel/linux-5.4/include -I kernel/linux-5.4/bsp/include -I bsp/configs/linux-5.4 -I configs/default -undef -D__DTS__ -x assembler-with-cpp -o kernel/staging/dts_dep/.xxx.dtb.dts.tmp configs/perf2/board.dts scripts/dtc/dtc -O dtb -o kernel/staging/xxx.dtb -W no-unit_address_vs_reg -W no-unit_address_format -W no-unique_unit_address -W no-graph_child_address -W no-simple_bus_reg -b 0 -@ -i configs/default/linux-5.4 -d kernel/staging/dts_dep/.xxx.dtb.d.dtc.tmp kernel/staging/dts_dep/.xxx.dtb.dts.tmp 将内核与dtb打包生成img ${MKBOOTIMG} --kernel ${BIMAGE} \ $(check_whether_use_ramdisk && echo "--ramdisk $RAMDISK") \ --board ${CHIP}_${LICHEE_ARCH} \ --base ${BASE} \ --kernel_offset ${KERNEL_OFFSET} \ $(check_whether_use_ramdisk && echo "--ramdisk_offset ${RAMDISK_OFFSET}") \ --dtb ${DTB} \ --dtb_offset ${DTB_OFFSET} \ --header_version 2 \ -o $STAGING_DIR/${IMAGE_NAME} 示例 tools/pack/pctools/linux/android/mkbootimg --kernel kernel/staging/Image.gz --board xxx300i_riscv32 --base 0x80000000 --kernel_offset 0x0 --dtb kernel/staging/sunxi.dtb --dtb_offset 10485760 --header_version 2 -o kernel/staging/boot.img 根文件系统 制作根文件系统 mksquashfs4 [rootfs-dir] [img-name] -noappend:不将squashfs选项传递给内核 -root-owned:设置root目录为root所有 -comp xz:使用xz压缩算法 -b 256k:设置块大小为256KB -p '/dev d 755 0 0' 和 -p '/dev/console c 600 0 0 5 1':设置文件系统的权限和所有权。 -processors 1:使用一个处理器进行压缩。 使用dd of生成rootfs.img dd if=root.squashfs of=openwrt/rootfs.img bs=128k conv=sync 编译uboot 略 打包整个镜像 命令: dragonxx imagexx.cfg partitionxx.fex 使用打包工具dragonxx进行打包,其中image.cfg和partition.fex中的内容为要打包的文件。 -
密码保护:随记
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
I2C协议总结
简介 以下是 I2C 总线的一些特性: 只需要两条线;串行数据线(SDA)和串行时钟线(SCL)。 连接到总线的每个设备都可以通过唯一的地址进行软件寻址简单的控制器/目标关系始终存在;控制器可以作为控制器发射器或控制器接收器。 它是真正的多控制器总线,包括冲突检测和仲裁,以防止如果两个或多个控制器同时发起数据传输,则会导致数据损坏。 串行面向8位的双向数据传输速度可达 100 kbit/s 标准模式,快速模式高达 400 kbit/s,快速模式Plus高达1Mbit/s,或高速模式下高达3.4 Mbit/s。 串行8位、单向数据传输在超快速模式下高达5 Mbit/s 片上滤波可抑制总线数据线上的尖峰,以保持数据完整性。 同一总线上可连接的IC 数量仅受最大总线电容。在某些条件下可以允许更大的电容。 串行数据 (SDA) 和串行时钟 (SCL) 两条线在连接到总线的设备。每个设备都由唯一的地址识别(无论是是微控制器、LCD 驱动器、存储器或键盘接口),并且可以作为发送器或接收器运行,具体取决于设备的功能。 LCD驱动器可能只是接收器,而存储器既可以接收也可以传输数据。此外发射器和接收器,当设备也可以被视为控制器或目标时执行数据传输。控制器是发起数据的设备总线上的传输并生成时钟信号以允许该传输。 传输协议 Start:数据传输的开始信号,由主机产生; Slave address:主机产生用于标识从设备的地址,bit7~bit1; R/W:主机产生,W(write)为主机向从机写数据,R(read)为主机向从机读数据,bit0; ACK:主机写数据从机收到拉低回复表示收到数据,主机读数据收到后拉低回复表示收到数据。ACK对应的是NACK,表示没有确认。 Data:发送的数据,以字节为单位,每8bit数据,从设备回一个ACK信号; Stop:数据传输的结束信号,由主机产生。 开始和停止标志 所有事务均以 START (S) 开始,并以 STOP (P) 终止(如上图图)。当 SCL为高电平时,SDA 线上的高电平到低电平的转换定义为启动信号。当SCL为高电平时,SDA线上的低电平到高电平转换定义了停止信号。 启动和停止条件始终由控制器生成。在 START 条件之后,总线被认为是繁忙的。在 STOP 条件后的某个时间,总线被认为再次空闲。 如果生成重复的 START (Sr) 而不是 STOP 条件,则总线保持忙碌状态。在这方面,START (S) 和重复START (Sr)条件在功能上是相同的。因此,对于本文档的其余部分,S符号用作通用术语来表示 START 和重复 START 条件,除非 Sr特别相关。如果连接到总线的设备包含必要的接口硬件,则可以轻松检测启动和停止条件。然而,没有此类接口的微控制器必须在每个时钟周期对 SDA 线进行至少两次采样才能感测转换。 数据格式 SDA 线上的每个字节必须是八位长。传输的字节数次数不受限制,但是每个字节后面必须跟一个ACK。数据首先传输最高有效位 (MSB),如果一个目标在执行完某些操作之前,无法接收或传输另一个完整的数据字节其他功能,例如服务内部中断,它可以保持时钟线SCL低电平强制控制器进入等待状态。当目标达到时,数据传输继续准备好另一个字节的数据并释放时钟线SCL。 ACK与NACK ack发生在每个字节传输之后,ACK允许接收器向发送器发出信号,表明已成功接收该字节并且可以发送下一个字节。控制器生成所有时钟脉冲,包括确认第九个时钟脉冲。确认信号定义如下:发送器在ACK时钟脉冲期间释放SDA线,以便接收器可以将 SDA 线拉低,并且在此时钟脉冲的高电平期间保持稳定的低电平(如上图),还必须考虑设置和保持时间。当SDA在此第九个时钟脉冲期间保持高电平时,这被定义为未确认信号NACK。然后,控制器可以生成 STOP 条件以中止传输,或生成重复的 START 条件以开始新的传输。有五种情况会导致生成 NACK: 总线上没有接收器具有传输的地址,因此没有设备可以响应确认。 接收器无法接收或发送,因为它正在执行某些实时功能,尚未准备好开始与控制器通信。 在传输过程中,接收器收到它无法理解的数据或命令。 在传输过程中,接收器无法再接收任何数据字节。 控制器接收器必须向目标发射器发出传输结束信号。 时钟同步(两个主设备) 两个控制器可以同时在空闲总线上开始传输,并且必须有一种方法来决定哪个控制器控制总线并完成传输。这是通过时钟同步和仲裁完成的。在单控制器系统中,不需要时钟同步和仲裁。(这里的控制器指的是主机)。 时钟同步是使用 I2C 接口到 SCL 线的有线“与”的方式连接来执行的。这意味着 SCL 线上从高到低的转换会导致相关控制器开始计算其低电平周期,并且一旦控制器时钟变为低电平,它就会将 SCL 线保持在该状态,直到达到时钟高电平状态(见图 7)。但是,如果另一个时钟仍处于其低电平周期内,则该时钟从低电平到高电平的转换可能不会改变 SCL 线的状态。因此,具有最长低电平周期的控制器将 SCL 线保持在低电平。具有较短低电平周期的控制器在此期间进入高电平等待状态。 当所有相关控制器都已计数完其低电平周期时,时钟线被释放并变为高电平。然后控制器时钟和 SCL 线的状态之间没有差异,并且所有控制器都开始计数其高电平周期。第一个完成其高电平周期的控制器将再次将 SCL 线拉低。这样,就生成了一个同步的 SCL 时钟,其低电平周期由时钟低电平周期最长的控制器决定,其高电平周期由时钟高电平周期最短的控制器决定。 仲裁 仲裁与同步类似,是指仅当系统中使用多个控制器时才需要的协议部分。目标设备(从设备)不参与仲裁程序。仅当总线空闲时,控制器才可以启动传输,两个控制器可以在 START 条件的最小保持时间 (tHD;STA) 内生成 START 条件,从而在总线上产生有效的 START 条件。然后需要仲裁来确定哪个控制器将完成其传输。 仲裁逐位进行。在每一位期间,当 SCL为高电平时,每个控制器检查 SDA 电平是否与其发送的电平匹配。此过程可能需要许多位。只要传输相同,两个控制器实际上可以完成整个事务而不会出错。控制器第一次尝试发送高电平,但检测到 SDA 电平为低电平时,控制器知道它已失去仲裁并关闭其 SDA 输出驱动器。另一个控制器继续完成其事务。 仲裁过程中不会丢失任何信息。失去仲裁的控制器可以生成时钟脉冲,直到失去仲裁的字节结束,并且 必须在总线空闲时重新启动其事务。如果控制器还包含目标功能,并且在寻址阶段失去仲裁,则获胜的控制器可能正在尝试寻址它。因此,失败的控制器必须立即切换到其目标模式。 下图显示了两个控制器的仲裁程序。可能涉及更多,具体取决于连接到总线的控制器数量。一旦生成 DATA1 的控制器的内部数据电平与 SDA 线上的实际电平之间出现差异,DATA1输出就会关闭。这不会影响获胜控制器发起的数据传输。 由于 I2C 总线的控制完全取决于竞争控制器发送的地址和数据,因此总线上没有中央控制器,也没有任何优先级顺序。 如果当一个控制器发送重复的 START 或 STOP 条件而另一个控制器仍在发送数据时,仲裁程序仍在进行中,则会出现未定义的情况。换句话说,以下组合会导致未定义的情况: 控制器 1 发送重复的 START 条件,控制器 2 发送数据位。 控制器 1 发送 STOP 条件,控制器 2 发送数据位。 控制器 1 发送重复的 START 条件,控制器 2 发送 STOP 条件。 小结: 所谓仲裁就是多个主设备该咋发咋发,但是了发了同时要进行检测,所发送的电平与自己发送的电平是否一致,如果不一直那就让别人先发。 读写位 参考: 《I2C-bus specification and user manual》 -
系统死机排查思路
反汇编现场寄存器 kasan watchpoint: 针对固定位置 栈溢出检测: cache:检查地址对齐、长度对齐(少/多刷,少/多无效)。 现象一般是watchpoint抓不到,软件复位无效,如USB驱动与cpu会同时操作内存。 硬件: 排除个体差异,存在电压、电流等不稳定,或者芯片不良等异常或内存、flash异常。