原理
RPMB
操作这节是在协议的6.6.22
,他的作用归纳如下
- 提供一种验签访问
RPMB
的方法。这项功能是把一些数据存放到特殊的区域,要访问这个区域需要通过签名认证。这个认证由第一次写入eMMC
的一个加密秘钥来提供。 - 验证秘钥是在读写访问过程,使用一段认证消息字段,用于保护读写访问模式下的安全区域数据,可以防止重放攻击。
- 提供次数统计寄存器和加随机数混杂的方式,用于防范攻击者使用重发攻击的方式。
访问RPMB分区的数据格式
数据格式如下,高位在前,其实总线上的数据帧应该是512bit + 1bit(start) + 2Byte(crc16) + 1bit(end)
接下来从低到高,依次解读每个pos
的内容
Req/Resp
Request/Response Type
请求和回复类型
长度:2字节
方向:request方向(eMMC到内存),Response方向(从内存到eMMC)
描述:定义请求和应答的类型。请求跟应答有如下这些类型
Key/MAC
Authentication Key / Message Authentication Code
,密钥/身份认证码
长度:32字节 (256bit)
方向:请求(Key、MAC)/应答(MAC)
描述:密钥或者还是身份认证码,取决于类型标志,也即前面的Req/Resp
字段
Result
Operation result
操作结果
长度:2字节
方向:应答 (device给到host的)
描述:包括写次数和成功读写RPMB的信息。
RPMB操作结果数据结构体:
操作结果,也即bit[6:0]
的类型有如下这些,带0x008x
结果的,是因为write counter
达到最大值了,才会带0x008x
Write counter
写次数
长度:4字节
方向:请求和应答
描述:这个值表示身份验证的成功次数和主机进行配置的写请求次数
Data Address
数据地址
长度:2字节
方向:请求和应答
描述:读写RPMB的偏移地址,起始地址是0x00,以256字节对齐。此时CMD18和CMD25中的地址参数会被忽略 。
Nonce
随机数字段
长度:16字节
方向:请求和应答
描述:主机创建的随机数,在请求中带有这个,应用于RPMB验签
Data
数据
长度:256字节
方向:请求和应答
描述:要写的数据
Block count
块个数
长度:2字节
方向:请求和应答
描述:请求读写的块(这里的块个数是半个sector,也就是256字节)个数,这个值和CMD23的参数一样
(没看懂,那RPMB读写的时候,用的是哪种CMD23)
RPMB分区的存储映射
这部分主要是RPMB分区内部的结构,分为三部分
Authentication Key
32字节,只能写一次,不能被擦除,也不能被读取。
Write Counter
4字节,只读。所有验证数据请求和配置的次数统计。初始值是0,一直递增。该值不能重置,当到达最大值0xffffffff后,就不会继续累加,然后操作结果字段的第七比特位会置一。 如果溢出了,会体现在RPMB帧中的Result的Operation Result中,前面有提到。
Data
数据区,可读写,不可擦除。操作单位是256B。
MAC的计算
身份验证码(MAC)是通过HMAC SHA-256
计算出来的。这个算法需要输入一个key和一个信息。计算的结果是一个256bit
长的字段,也就是身份验证码(MAC)。其实就是哈希算法的一种罢了。
RPMB中用来计算哈希的是[283:0]
,不包括start bit
,end bit
,crc16
那如果这个时候,host要写RPMB分区0x10地址的两个block(RPMB的block的大小是256B),MAC是怎么计算的呢?
- host要写两个block,就要发出两个数据帧
- MAC计算的输入
- 两个数据帧的[283:0]区域作为一个message
- host这边自己的key(可能是对的key也能是错的key,毕竟这是写操作嘛)
- MAC计算的结果则放入最后的一个帧
RPMB操作方法
进入RPMB
则是切换partion_acess
,然后读写则是跟通常一样发送CMD23/CMD18/CMD25
。下面列举几种情况。
写入KEY
写KEY是通过CMD25
,在CMD25
之前要先通CMD23
的参数第31bit来设置block count
为1,来表明是RPMB编程?
内核代码中有对应的代码设置bit31
的操作
秘钥信息本身是通过数据包传递的,包的大小是512字节,包括了请求类型信息和秘钥。请求类型0x001
表示是秘钥。秘钥(KEY)则是在[315:284]
的位置
CRC数据以后DAT0会被eMMC拉低 ,代表eMMC处于busy状态。这时候,通过CMD13可以查看CMD25的执行状态。 读结果寄存器(Result register)来查看是否成功写入KEY。 可以去协议中看看这部分,这里就不赘述了。
读count值
读counter值是通过CMD25来实现的。请求类型值是0x0002,来表明请求读取计数值。
counter值本身实在读数据的数据包里面,也即是bit[11:8]。数据包大小是512字节。包含应答类型、nonce、counter值,MAC值和请求结果。如果读失败了,那么结果值是0x6。如果是其他的错误发生,那么结果值就是0x01。如果counter值已经过期了,结果值的bit7会设置为1 。
写数据
写入RPMB数据是通过多块写命令(CMD25)来完成的。在CMD25命令之前要先通过CMD23来设置要写的块数量。
注意!块的单位是256字节。 通过配置EXT_CSD[166]设置写方式。 block数量是所有的256大小块数量,请求类型0x003表示写数据。
- eMMC接收到数据之后,首先检查counter值是否有效,如果counter值无效,那么就返回0x85的错误,数据将被丢弃。
- 然后检查地址,如果地址不对,那么就返回0x4的错误。如果是多块写,而数据地址没有对齐,那么也是0x4的错误,数据也不会写入。
- 接着检查MAC是否正确。如果eMMC计算的MAC和数据包中的MAC不匹配,那么返回0x2的错误。
- 最后对比counter值是否和eMMC存储的一致,不一致返回错误0x3
- 如果一切正常,那么数据写入RPMB分区,eMMC的counter值会加一。
- 如果写失败返回0x5,其他错误返回0x1 。
读数据
测试操作过程
之前在qemu的文章中有提到uboot下怎么操作,这里就展示内核下是怎么操作的,以下操作均在板子上实现的
- 先创建32字节的key,并写入key,key写完后最好重启下(看有的资料说要重启下)
# echo 'Authkeymustbe32byteslength_0000' > keyfile.txt
# mmc rpmb write-key /dev/mmcblk0rpmb keyfile.txt
# ls -lh keyfile.txt
-rw-r--r-- 1 root root 32 Jan 1 00:00 keyfile.txt
#
- 创建一个256字节的数据,就是准备要写入RPMB分区的数据
dd if=/dev/random of=data.txt bs=1 count=256
# hexdump -C data.txt
00000000 81 f9 0e 65 c0 ec 25 53 1a d4 f3 af 6d 95 30 f3 |...e..%S....m.0.|
00000010 35 6e 2a 5f d1 db 9c 68 9b 57 7d eb 71 da 39 96 |5n*_...h.W}.q.9.|
00000020 55 bf 85 df d8 b2 31 ad 43 97 7c bb 03 88 0e 07 |U.....1.C.|.....|
00000030 d5 dd 95 5d bb 7f dd bd 40 90 5f a0 4d 84 02 19 |...]....@._.M...|
00000040 71 c5 de 35 fd 26 3d b3 4e 5a 79 86 ba 55 54 1f |q..5.&=.NZy..UT.|
00000050 75 85 2a b7 e0 f6 2b b9 2a 4e 27 02 72 cc 4a de |u.*...+.*N'.r.J.|
00000060 8d ea fa dc 11 be e6 55 41 e6 6c a7 c0 ea 97 d5 |.......UA.l.....|
00000070 f5 31 de 11 03 bf 40 7f 81 c2 91 d6 da c9 90 35 |.1....@........5|
00000080 a0 5b fa e0 7e d3 6b d5 ac 65 db 89 d2 97 5b 8a |.[..~.k..e....[.|
00000090 1d dd 0f 1d d3 27 d0 84 97 ff 16 01 87 c5 63 86 |.....'........c.|
000000a0 16 b7 f2 1c d4 a5 26 3f 74 70 6b 65 82 0e 07 4d |......&?tpke...M|
000000b0 4d 42 3a ec fc 27 fe f7 9d d8 13 ae d7 54 9c 5e |MB:..'.......T.^|
000000c0 89 db 89 8f 5f b2 3d ac df c0 7c 0d 87 53 43 7e |...._.=...|..SC~|
000000d0 61 b1 4a e4 03 47 a0 06 96 39 9a f1 63 29 a4 b8 |a.J..G...9..c)..|
000000e0 d2 ea 08 b3 c4 dc 33 b1 b9 ba 9a 57 d9 04 44 59 |......3....W..DY|
000000f0 ac 5d 8f 9a f1 b1 65 72 a7 b3 a4 a7 bc b4 62 a8 |.]....er......b.|
00000100
#
- 用正确的key写进去,一切正常
mmc rpmb write-block /dev/mmcblk0rpmb 0 data.txt keyfile.txt
- 搞个不对的key写进去,会提示写失败
echo 'Authkeymustbe32byteslength_1234' > wrongkey.txt
mmc rpmb write-block /dev/mmcblk0rpmb 0 data.txt wrongkey.txt
- 不用AuthKey读取RPMB数据, 虽然能读出数据,但其实并不能保证这个数据是否被篡改过的
mmc rpmb read-block /dev/mmcblk0rpmb 0 1 out.txt
- 用正确的AuthKey读取RPMB数据,这个结果能读到数据,并且保证这个数据没有被修改过,而不是攻击者伪造的数据
mmc rpmb read-block /dev/mmcblk0rpmb 0 1 usekey.txt keyfile.txt
- 用错误的AuthKey读取RPMB数据,提示
RPMB MAC missmatch
的错误,无法读取到数据
# mmc rpmb read-block /dev/mmcblk0rpmb 0 1 no.txt wrongkey.txt
RPMB MAC missmatch
#
- 读取counter值
总结:
- 上面所做的所有操作,必须在可信任的环境下执行,也就是
TEE
环境,保护AuthKey不被泄露 - 由于RPMB的读操作没有AuthKey也能返回的,因此,存入RPMB的数据最好是经过加密的
代办事项
可以在optee/ATF
中去尝试访问RPMB,反正就是在一个安全的环境下去做
参考链接
想法
是不是以后设计一种加密的算法或者流程,可以参考这个呢?