1. 目的/背景

此文档使用了secure boot 2.1方案的配置,如有其他版本的secure方案,本文档仅作参考,secure boot方案可在security MTK文档中查到,也可以咨询MTK,此文档适配于:MTK6739等平台。

2. 前述

Secure boot & Efuse方案和硬件强相关,在开始调试之前切记以下要点:

1.            确保所有的keys都是一次性生成的,keys、证书、hash一定要匹配,如果不确定,请全部重新生成一次

2.            生成keys之后谨慎保存,如无必要,尽量不做修改(尤其是root_key),修改之后,以前efuse的芯片便无法再使用。

3.            再所有方案double 验证OK之前,不要上传secure boot的开关宏,以免造成大面积芯片废弃

3. 可参考文档

Secure_2.1_Configuration_SOP.pdf

Efuse_self_blow_user_guide_v1.3.pdf

4. 详细配置方案

生成keys

利用openssl生成private key和publickey,并用pem_to_der.pyiaoben转换key的格式为pem

生成private key:

opensslgenrsa -out root_prvk.pem 2048

/*rsa是格式,2048是长度,目前在MTK都保持这样的格式*/

转换格式:

pythonpem_to_der.py root_prvk.pem root_prvk.der

生成public key

opensslrsa -in root_prvk.pem -pubout > root_pubk.pem

转换格式:

pythonpem_to_der.py root_pubk.pem root_pubk.der

生成img key,用来签名和校验img

openssl genrsa -out image_prvk.pem 2048

以上操作如下面演示:

产生oemkey.h和dakey.h

用der_extractor脚本将root key(root_pubk.der)导出生成oemkey.h,然后放到下面三个目录

生成命令:

./der_extractor root_pubk.der oemkey.h ANDROID_SBC

生成方式如图:

【DA】DA的生成交给Tool team,SW只需要提供oemkey.h给工具组即可

【PL】:$PL/custom/$project/inc/oemkey.h

【LK】:$LK/target/$project/inc/oemkey.h

同样的方式将root_pubk.der导出生成dakey.h

功能:dakey.h中包含的DA_PLpublic key public key用于preloader校验DA_PL.bin,所以在签名DA_PL.bin时需要拿这个key对应的private key对DA_Pluo签名

Note:值得注意的是,由于DA_PL下载权限很低,所以通常我们的DA都使用DA_BROM的方式去做下载,但是推荐此处同样配置OK。

dakey和oemkey都是用同样的pubk key生成的,所以生成出来的dakey和oemkey都是相同的,但是在dakey中需要修改部分内容,否则会导致编译报错:

Dakey.h生成后存放位置:

【PL】:$PL/custom/$project/int/dakey.h

Secure boot配置部分

Preloader配置:

vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/$project/$project.mk

MTK_SECURITY_SW_SUPPORT=yes

MTK_SEC_BOOT=

MTK_SEC_USBDL=

Note:上述两个BOOT和USBDL宏如果是SW root of  trust,意味着,硬件上不实现efuse,但sw上要强制打开secure boot和securitydownload,那么需要如下配置:

MTK_SEC_BOOT=ATTR_SBOOT_ENABLE

MTK_SEC_USBDL=ATTR_SUSBFL_ENABLE

如果是hw root of trust,意味着会烧写efuse,并在efuse后打开secure boot和secure download,需要如下配置(默认)

MTK_SEC_BOOT=ATTR_SBOOT_ONLY_ENABLE_ON_SHIP

MTK_SEC_USBDL=ATTR_SUSBDL_ONLY_ENABLE_ON_SHIP

vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/$project_name/cust_bldr.mk

MTK_EFUSE_WRITER_SUPPORT :=yes/*注意定义的格式*/

MTK_EFUSE_WRITER_RESERVE_CODESIZE := yes

CFG_USB_AUTO_DETECT := 1 //注意这个宏,此宏定义生效,下载阶段强制枚举BROM端口,DA和AUTH将在这里校验,如果该宏没有定义,会导致端口错误

~/vendor/mediatek/proprietary/bootable/bootloader/preloader/platform/mtxxxx/src/security/inc/sec_efuse.h

#define EFUSE_BLOW_KEY1                 0x90b1e2f6   //此为MTK默认值,建议修改定制

#define EFUSE_BLOW_KEY2                 0x73d5a8c9

该项和input.xml中的magic key对应,在input.xml中详细解释配置方法

Lk配置

vendor/mediatek/proprietary/bootable/bootloader/lk/project/$project.mk

MTK_SECURITY_SW_SUPPORT=yes

MTK_EFUSE_WRITER_SUPPORT :=yes

Note:此处有两个宏是可选的,如需支持fastbootunlock的命令,可以将此处打开,但目前和MTK沟通,此功能开发尚未完全,需要自行定制,建议在调试该功能时先关闭。否则通过fastboot刷机会有风险。

MTK_SEC_FASTBOOT_UNLOCK_SUPPORT

MTK_SEC_FASTBOOT_UNLOCK_KEY_SUPPORT

 

Kernel配置:

根据project选项defconfig配置,需要确定是用arm32还是arm64

kernel-4.x/arch/arm/configs/

在所有项目的defcofnig和def_debug_cofnig中添加:

MTK_SECURITY_SW_SUPPORT=yes

DEVICE配置

Device/mediatek/$project/ProjectConfig.mk

MTK_EFUSE_WRITER_SUPPORT=yes

Preloader签名:

Build cert_chain格式的preloader是在build的过程中sign的,那么签名的配置是使用cert_chain签名格式:

1.将生成的root key(包括root_prvk.pem   ,  img_prvk.pem)cp到以下路径:

~/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/key/

配置宏配置sign preloader的pem路径:

Pl_content.ini 中配置imgkey路径:

Note:上述两个ini中sw_ver版本号需要一致

Pl_gfh_config_cert_chain.ini配置

注意:里面的version等其他信息,请不要随意更改

 

使用python形式的sign tool配置

先确定该路径:~/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/cfg下的PBP_BY_SUPPORT文件是否存在,如果遇到不存在的,请重新同步或者向MTK索要

Padding type的配置

确定同样路径下:

/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/cfg/

的PADDING_TYPE.ini中是否有设置:

Cert_chain   /*代表生成preloader的格式是以cert_chain格式的*/

Resign preloader,secure 2.1支持在build的过程中去签名preloader,也支持对已签名的镜像进行re-sign

签名流程检测:

产生cert1和cert2

用security2.1生成的root_prvk.pem和image_prvk.pem产生cert1和cert2_key

检查SecureGen.py脚本中几个关键点,如果没有需要自己添加:

/vendor/mediatek/proprietary/scripts/sign-image_v2/SecureGen.py

def genCert1(binList)

....

if ( isMD == 0):

if(argDict["root_key_padding"]):

sh_command= "python "+ resign_tool_path+ " type=cert1 privk="+argDict["cert1_key_path"] +"pubk="+cert2_pubk_path+" ver="+str(img_ver)+"group="+str(img_group)+" root_key_padding="+str(argDict["root_key_padding"])

else:

sh_command= "python "+ resign_tool_path+ " type=cert1 privk="+argDict["cert1_key_path"] +"pubk="+cert2_pubk_path+" ver="+str(img_ver)+" group="+str(img_group)

tmp_out_path= out_cert1_path

else:

mdBin =os.path.join(out, part_name+".img")

if(os.path.isfile(mdBin) ):

printmdBin+" exist"

if(argDict["root_key_padding"]):

sh_command= "python "+ resign_tool_path+ " type=cert1mdimg="+mdBin+" privk="+argDict["cert1_key_path"]+" pubk="+cert2_pubk_path+" ver="+str(img_ver)+"group="+str(img_group)+" root_key_padding="+str(argDict["root_key_padding"])

else:

sh_command= "python "+ resign_tool_path+ " type=cert1md img="+mdBin+"privk="+argDict["cert1_key_path"] +"pubk="+cert2_pubk_path+" ver="+str(img_ver)+"group="+str(img_group)

tmp_out_path= out_cert1md_path

else:

printmdBin+" Not exist"

print"Bypassmd image cert1 Gen!"

执行以下命令

./ vendor/mediatek/proprietary/scripts/sign-image_v2/SecureGen.py  mt67xx cert1_key_path=${KEY_PATH}/root_prvk.pem cert2_key_path=${KEY_PATH}/img_prvk.pem root_key_padding=pss  | tee securegen.log

以上KEY_PATH=指存放root_prvk.pem和img_prvk.prm的路径

说明:

请在codebase的根目录下使用该脚本,包括签名脚本,否则无法加载到环境参数

根据cert1_key_path的key,生成所有的img的cert1到

/vendor/mediatek/proprietary/custom/security/cert_config/cert1

根据cert2­_key_path的key,生成所有的image的cert2到:

/vendor/mediatek/proprietary/custom/security/cert_config/cert2_key/

执行完secureGen.py后,请检查上面目录文件是否更新

建议保留gen的log,以防doubleconfirm时没有参照依据。

产生签名的image

Cert2和cert2的key正确产生之后,就可以执行签名脚本,产生xx-verified.bin/img了,执行如下命令进行签名:

./vendor/mediatek/proprietary/scripts/sign-image_v2/SignFlow.py $platform $project | tee sign.log

或者

vendor/mediatek/proprietary/scripts/sign-image/sign_image.sh

build DA

提供oemkey.h生成DA_BR.bin,详细生成DA的情况在window下进行,详细请参考文档

SignDA的配置

Security2.1的DA需要使用python脚本进行签名

Key的配置:

所有对DA签名,生成auth和secrtfile的keys都放在:

~/vendor/mediatek/proprietary/scripts/secure_chip_tools/keys/下,如下:

drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:30 hsm/

drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:30 pbp/

drwxr-xr-x 2 keli cdgroup 4096 Dec 19 17:29 resignda/————对应DA

drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:33 sctrlcert/————对应secrtfile

drwxr-xr-x 2 keli cdgroup 4096 Dec 21 09:56 toolaut—————对应auth

而针对auth,da等的配置文件都存放于:

vendor/mediatek/proprietary/scripts/secure_chip_tools/settings/

drwxr-xr-x 6 keli cdgroup 4096 Dec 19 11:30 ./

drwxr-xr-x 8 keli cdgroup 4096 Dec 20 15:41 ../

drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:30 pbp/

drwxr-xr-x 2 keli cdgroup 4096 Dec 21 09:50 resignda/————对应DA

drwxr-xr-x 2 keli cdgroup 4096 Dec 20 15:13 sctrlcert/————对应secrtfile

drwxr-xr-x 2 keli cdgroup 4096 Dec 21 09:50 toolauth/—————对应auth

DA的keys:

其中da_prvk.pem和epp_prvk.pem都是RSA2048格式

-rw-r--r-- 1 keli cdgroup 1679 Nov 28 17:11 da_prvk.pem

-rw-r--r-- 1 keli cdgroup 1679 Nov 28 17:11 epp_prvk.pem

-rw-r--r-- 1 keli cdgroup  451 Nov 3009:32 PUBKda_prvk.pem

其中da_prvk可以openssl生成一份,也可以直接使用root_prvk去 rename。

签名DA_BR时,da_prvk.pem需要和auth中的DAA key匹配

签名DA_PL时要和dakey.h中的key匹配

Note:epp_prvk.pem可使用与da_prvk.pem一样的keys,epp_prvk,MTK说是为了兼容以前框架所用。

在settings的配置文件中:

bbchips_pss.ini中有如下配置需要double check

然后将需要签名的DA放在prebuilt/resignda/路径下,以psspadding的方式去执行DA的签名命令:

python resign_da.py  prebuilt/resignda/newMTK_AllInOne_DA.bin ¥platform settings/resignda/bbchips_pss.ini all out/resignda/newMTK_DA.bin-resign

Efuse.xml的配置

Note:

Security 2.1 EFUSE烧录的efuse.xml文件需要配置key-type位,key-type值有legacy和pss两种,如果不配置,默认是legacy。值得注意的是,efuxe.xml需要联系MTK获取,不要自行修改或者混用。

Note!!!:相同的一组SBC Key,key-type设置不同则生成的key hash也不同,因此烧入refuse后无法更正,请确保key-type的配置是否正确,如果不确定,可以与MTK ACS support确认。、

efuse.xml配置指导:

<?xml version="1.0" encoding="UTF-8" ?>

<flashtool-config version="2.0">

<general>

<chip-name>MT6779</chip-name> //平台信息,对应项目的xml该项应该是匹配的,如果不匹配请确认xml来源

....

preloader_¥project.bin //在此preloader中校验

...

<connection

type="BromUSB" //下载端口需要对应

...

<efuse>

      <magic-key                            /*需要和efuse.h中定义的key匹配,注意倒叙

        key1="f6e2b190"

        key2="c9a8d573" />

/*

比如在code中的值如下

4#defineEFUSE_BLOW_KEY1            0x90b1e2f6

5#defineEFUSE_BLOW_KEY2            0x73d5a8c9

 

则efuse.xml中必须为

key1="f6e2b190"

key2="c9a8d573"/>

*/

...

Disable_DBGPORT_LOCK="false"

  Enable_SW_JTAG_CON="true"

Enable_ACC="false"

Enable_ACK="false"

      Enable_SLA="true"//校验AUTH文件的SLA

        Enable_DAA="true"//校验DA,项目初调试,可以先开启DA,验证DA通过后,再开启AUTH验证,这样有助于定位问题

        Enable_SBC="true"//校验SBC

Disable_JTAG="false" />

      <sbc-pub-key>//security2.1preloader默认是cert_chain格式,对应此项配置是key_type为pss,pub-key-e必须设置为010001,如不确定,向MTK咨询

           <key-type>pss</key-type>

        <pub-key-e>010001</pub-key-e>

<pub-key-n>//security 2.1,此处的keys值可以直接从root中获取,这里不需要配置

        </pub-key-n>

</sbc-pub-key>

<common-lock

com_ctrl_lock="false"  //ctrl定制位锁

usb_id_lock="false"/>

<secure-lock

sec_msc_lock="false"

sec_attr_lock="false"

ackey_lock="false"

sbc_pubk_hash_lock="false" /> //efuse hash的锁,字面理解,但有个疑问,既然是EFUSE,应该写入后熔断,为什么需要上锁呢?脱了裤子放个屁?

...

<sec_msc

 md1_sbc_en="true"/>  //区别于SecureBoot和AVB,这是MTK自己的modem签名验证功能

<c_ctrlm

disable_self_blow="false" /> //是否允许自烧写

Auth生成:

Efuse烧写了DAA or SLA,在下载的时候,需要用到auth file和scertfile,需要使用python脚本生成

Auth key配置:

Auth的keys

Auth的配置改动和MTK文档不同,auth处请详细参考该文档

da_pubk.pem*//da_prvk和sla_prvk:这里找了下逻辑,最终会根据prvk去拿pubk,所以可以直接用公钥,而prvk可以保存起来不开放。

epp_prvk.pem*//epp_prvk:和MTK确认过,是为了兼容之前的架构,可以不管,不会用到

root_pubk.pem

sla_pubk.pem //这个由OEM同一使用,开发一般只能拿到pubk

在auth的settings配置中,先确定toolath_key.ini中

确认rootkey的路径是否OK

确认tooauth_gfh_config_pss.ini配置:

注意:

此处于上图差异部分:

sla_pad_type与开启SLA功能客制化的sla_challenge.dll 使用的pad type一直,daa_pad_type_pad_type与sign DA使用的padding type一致

Auth生成命令:

python toolauth.py -i settings/toolauth/toolauth_key.ini  -gsettings/toolauth/toolauth_gfh_config_pss.ini out/toolauth/authXX.auth

结束检查:

一切配置OK,请做最后的double confirm(针对MTK6799,6767,6758,6739等新平台)

检查SBC_PUBK_HASH

Key config:

preloader/custom/$peoject/security/chip_config/s/key/pl_key.ini

检查自己的keys:

确定该路径下的prvk是自己本次配置的,如果不是,请覆盖root_prvk.pem

对于efuseself-blow,这些SBC_PUBK_HASH是由这里去获取,而不是配置在input.xml中

以上是security2.1 secure boot完全配置方案

Note:

在刷机前要确定efuse.img是否选中,并且分区中是否有对应的分区表信息

Effuse手机,需要电池电压高于3.7V(可以参考自己项目电池电量)

并且开机后,开机过程不要关机,确保efuse流程能正常运行完成

5.    共基线secure boot开发策略

在共基线开发多个项目时,由于secure 2.1方案存在证书和密钥的路径是在平台路径下,没有区分项目,所以这块的策略需要处理:

1.      vendor/mediatek/proprietary/bootable/bootloader/preloader/platform/$platrm/src/security/inc/sec_efuse.h

该文件中定义了两个magic key,会在efuse.xml中对magickey进行匹配校验,所以对于这里的key需要做项目区分,当然也可以用同一个keys,这样efuse.xml中magickey也保持相同

2.      ~/vendor/mediatek/proprietary/custom/$project/security/cert_config下存放了cert和keys

共基线策略:

drwxr-xr-x 4 keli cdgroup 4096 Dec 27 16:32 XXX1/———新添加的项目X1文件夹,其中存放cert1和cert2_key

drwxr-xr-x 2 keli cdgroup 4096 Dec 21 11:07 cert1/——通用的,可保持默认

drwxr-xr-x 2 keli cdgroup 4096 Dec 21 11:07 cert2_key/

-rw-r--r-- 1 keli cdgroup  311 Dec 2111:07 img_list.txt——签名镜像信息,设置签名镜像列表

-rw-r--r-- 1 keli cdgroup  886 Dec 2111:07 img_ver.txt——版本信息,确定每个签名版本的一些配置情况,使用默认即可

drwxr-xr-x 4 keli cdgroup 4096 Dec 27 17:21 XXX2/-------新添加的项目X2文件夹,其中存放cert1和cert2_key

以上项目名注意与环境变量中的项目名对应

3.      签名脚本

Cert_config是在签名的时候调用,所以也需要修改签名时候获取的路径:

--- a/sign-image_v2/SignFlow.py

+++ b/sign-image_v2/SignFlow.py

@@ -41,9 +41,9 @@ def setPath():

if (project =="X1"):

cert1_dir = cert_dir+"/XXX1/cert1/"

cert2_key_dir = cert_dir+"/XXX1/cert2_key/"

elif (project =="X2"):

cert1_dir = cert_dir+"/XXX2/cert1/"

cert2_key_dir =cert_dir+"/XXX2/cert2_key/"

else:

cert1_dir =cert_dir+"/cert1/"

cert2_key_dir =cert_dir+"/cert2_key/"

6.    验证结果

Efuse后,需要验证手机是否已经置于efuse状态,验证方法如下:

1.      开机在lk界面时:左下角会显示logo:

Efuse blow:seccuesful

2.      开完成后,读取出efuse.img,查看里面内容,开头出现大量aaaaa内容即成功,如下:

怎么导出efuse分区:

在adbshell下/dev/block/platform/bootable/by-name/下,查找efuse对应的分区号:e.g. mmcblk0p17

然后remount后将分区导出

adb pull /dev/block/mmcblk0p17

然后查看分区数据:hd mmcblk0p17

00000000  aa aa aa aa aa aa aa aa  aa aa aa aa aa aa aa aa |................|

*

00000200  1c 00 00 00 28 01 0000  ff 02 00 00 ff 02 00 00  |....(...........|

00000210  ff 02 00 00 ff 02 0000  29 01 00 00 00 00 00 00  |........).......|

00000220  00 00 00 00 00 00 0000  00 00 00 00 00 00 00 00  |................|

*

00080000

@test

3.      将未签名的镜像刷入,手机无法开机,然后再将镜像签名刷入,可开机。说明已经efuse

MTK Secure Boot 2.1详细配置方案-终极版相关推荐

  1. 【安装篇】- 基于 VMWARE Oracle Linux7.9 安装 Oracle19c RAC 详细配置方案

    作者 | yanwei 来源 | 墨天轮 https://www.modb.pro/db/95684 大家好,我是 JiekeXu,很高兴又和大家见面了,今天和大家一起来看看 Linux7.9 安装 ...

  2. 今日头条屏幕适配方案终极版正式发布!

    原文地址: juejin.im/post/5bce68- 以下是 骚年你的屏幕适配方式该升级了! 系列文章,欢迎转发以及分享: 骚年你的屏幕适配方式该升级了!(一)-今日头条适配方案 骚年你的屏幕适配 ...

  3. 全面屏适配方案,终极版,华为隐藏导航栏解决方案

    全面屏适配方案,终极版,华为隐藏导航栏解决方案 参考文章: (1)全面屏适配方案,终极版,华为隐藏导航栏解决方案 (2)https://www.cnblogs.com/lizhanqi/p/93371 ...

  4. 《转》Android 今日头条屏幕适配方案终极版正式发布!

    概述 Android系统发布十多年以来,关于Android的UI的适配一直是开发环节中最重要的问题,但是我看到还是有很多小伙伴对Android适配方案不了解. 刚好,近期准备对糗事百科Android客 ...

  5. android 最新头条适配,今日头条屏幕适配方案终极版正式发布!

    以下是 骚年你的屏幕适配方式该升级了! 系列文章,欢迎转发以及分享: 前言 我在前面两篇文章中详细介绍了 今日头条适配方案 和 SmallestWidth 限定符适配方案 的原理,并验证了它们的可行性 ...

  6. Vsftpd 虚拟用户配置参考---终极版

    Vsftpd 虚拟用户配置文档 一.主要文件列表 虚拟用户列表 account.txt 口令库文件 /etc/vsfptd/account.db PAM认证 /etc/pam.d/vs_1 主配置文件 ...

  7. MTK平台Android 安全中secure boot机制

    一.相关名词解释 1.公钥:通俗来说,公钥就是公开的密钥,是私钥拥有者公开的,公钥通常用于加密会话密钥.验证数字签名,或加密可以用相应的私钥解密的数据. 2.私钥:私有的钥匙,不会公开,私钥加密又称为 ...

  8. CloudStack+XenServer详细部署方案 交换机配置和服务器连线

    CloudStack+XenServer详细部署方案(2):交换机配置和服务器连线 本文将根据设计文档, 对交换机进行配置和服务器网络连线方式进行说明. Step1.交换机规划,  根据功能将交换机端 ...

  9. MTK Android 9.0(Android P) + kernel-4.9 默认关闭DM Verity 和secure boot 解决无法adb remount的问题

    在android P版本上执行adb remount会提示以下错误: remount of the / superblock failed: Permission denied remount fai ...

最新文章

  1. zookeeper安装教程(zookeeper3.4.5为例)
  2. 5分钟了解Zigbee的前世今生
  3. html中加入一个计时器,向html中的计时器添加毫秒
  4. android spi读写不通,Android-SPI学习笔记
  5. UI实用素材|电商购物类APP界面设计原则!
  6. ARM太贵,80多家科技巨头悄然站队开源芯片架构RISC-V
  7. nova.api.openstack.auth解析(ocata版本)
  8. flash实验中需添加的flash.c文件
  9. oracle卸载步骤图解,Oracle详细卸载步骤
  10. Excel 单元格自定义下拉菜单
  11. Java 数学三角函数正弦、余弦、正切以及反正弦、反余弦、反正切函数的使用
  12. 深度linux软件中心 qq,ubuntu上安装QQ(包括多个软件安装方法)
  13. 微信小程序小说搭建流程
  14. Ps 钢笔工具的使用
  15. 网站301转向代码大全
  16. 分布式链路追踪在字节跳动的实践
  17. 微软首席数字官亲述微软自己的数字化转型故事
  18. Win2003集群简介
  19. Android 硬件加速使用总结
  20. 商品、单品、SPU、SKU

热门文章

  1. mybatis之抛弃XML,拥抱注解
  2. 基于OpenCV的形状检测
  3. 怎样查询计算机登录记录,qq登陆记录,教您QQ如何查看登录历史记录
  4. 广告投放相关专业名词整理
  5. 免费将图片转换成网络链接
  6. Microsoft.VisualStudio.MinShell.Msi安装失败(2017与2019)
  7. 【Linux】【开发环境】【RHEL】开发环境搭建系列之十一——Linux系统下搭建基于vim的C/C++ IDE开发环境
  8. 以太坊智能合约开发2-Solidity语法学习
  9. GraphQL的探索之路 – 一种为你的API而生的查询语言 - 第314篇
  10. 测试用例-----听歌项目