ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android 6.0定制:默认MTP+ADB模式与关闭RSA认证弹框实战

Android 6.0定制:默认MTP+ADB模式与关闭RSA认证弹框实战 做安卓整机或系统定制的兄弟应该都遇到过这个需求设备连上电脑直接进MTP文件管理模式同时不要每次都弹那个“允许USB调试吗”的RSA指纹确认框。尤其是机顶盒、工控板、POS机这类量产项目产线工位插上USB就要自动跑adb指令总不能每条产线都配个人坐在那里点屏幕。Android 6.0这个版本虽然老但在嵌入式设备里存量很大里面两个机制特别值得搞清楚USB默认功能由哪些配置决定以及RSA配对确认到底是谁在管。这篇就把我实际改过的路子完整过一遍覆盖编译期改配置、运行时改属性、源码层跳过确认三种方案顺带把踩过的坑一起列出来。1. 先搞清楚Android 6.0的USB模式由谁控制、RSA配对框怎么来的动手改配置之前必须先把机制捋清楚。Android 6.0的USB模式切换和adb安全认证是两条独立的链路但经常被人混在一起。USB模式决定了设备插上电脑后枚举成什么设备RSA配对框则只跟adb调试连接相关。两条链路都由系统属性驱动搞清楚属性名和读写时机改起来才不会再被系统改回去。1.1 Android 6.0的USB模式到底是谁在定Android 6.0里USB的当前工作模式由sys.usb.config这个属性直接控制持久化的配置则存在persist.sys.usb.config。系统服务里的UsbDeviceManager启动后会先读persist.sys.usb.config如果为空就退回使用config.xml里预编译的默认值也就是config_defaultUsbFunctionsAOSP默认一般是mtp。读取之后服务会把最终值写到sys.usb.configinit进程监听这个属性变化再由init.usb.rc里的对应触发规则去写内核的usb gadget节点最终内核枚举出对应的USB设备类型。所以“默认文件管理”这个需求本质上就是要让USB默认枚举成MTP设备。改法有三个层面修改frameworks/base/core/res/res/values/config.xml里config_defaultUsbFunctions的默认值通过PRODUCT_PROPERTY_OVERRIDES或PRODUCT_DEFAULT_PROPERTY_OVERRIDES在编译时预置persist.sys.usb.config设备首次开机后手动setprop persist.sys.usb.config mtp,adb再重启这三个层面优先级不同生效时机也不同后面第二章会展开讲怎么选。这里要先记住一个关键点persist.sys.usb.config一旦被设置过后续开机会一直优先生效config.xml里的默认值只在没有任何persist记录时才起作用。很多朋友改了config.xml发现设备重启后还是老样子多半就是data分区里已经残留了旧的persist配置。1.2 RSA配对确认框是哪个组件弹出来的RSA配对提示框是Android从4.2.2引入的安全认证机制官方叫ADB Authentication目的很简单防止有人用USB线随意连接设备就拿到adb权限。主机端第一次连设备时adb客户端会自动生成一对RSA密钥存放在电脑的用户目录下Linux和macOS是~/.android/adbkey和~/.android/adbkey.pubWindows则在C:\Users\用户名\.android\下。设备端adbd收到连接请求后如果发现这台主机的公钥不在白名单里就会把公钥和指纹信息丢给frameworks/base/services/usb/java/com/android/server/usb/UsbDebuggingManager.java。这个Manager再发一个广播拉起一个带指纹信息的系统对话框等你点“允许”或“拒绝”。点击允许后公钥会追加写入设备上的适配键文件比如常见的/data/misc/adb/adb_keys之后同一台电脑再次连接就不会再弹框了。关键就在这里Android 6.0中adbd是否强制走这套认证由一个只读属性控制就是ro.adb.secure。这个属性值为1时认证流程强制开启未知公钥必然弹框值为0时adbd会直接跳过签名校验等价于任何主机都能连自然也谈不上弹框。默认情况下eng版本和部分userdebug版本这个值是0user版本则固定为1。所以我们前面说的“不弹RSA配对提示框”最干净利落的做法就是让adbd根本不去检查认证也就是编译时把ro.adb.secure这个属性固定成0。2. 编译期一劳永逸改默认MTP和关闭RSA认证的完整做法如果你的设备镜像是由你这边出源码、出编译产物那最好的办法就是在编译期把所有配置一次性固化。这样产线拿到的镜像已经是“默认MTPadb免认证”的状态完全不需要人工干预。下面每一步都是我实际在项目里用过的写法照抄基本不会出问题。2.1 修改默认USB功能为MTPADB在Android 6.0的编译体系里给某个设备或产品配置默认属性最标准的位置是设备目录下的device.mk或者对应的BoardConfig.mk。要设置persist.sys.usb.config直接加一行PRODUCT_PROPERTY_OVERRIDES \ persist.sys.usb.configmtp,adb这里用PRODUCT_PROPERTY_OVERRIDES而不是PRODUCT_DEFAULT_PROPERTY_OVERRIDES原因是persist.sys.usb.config属于正常读写属性且它最终会写进/system/build.prop系统启动读属性时会自动加载。我见过有人把这个属性加进了PRODUCT_DEFAULT_PROPERTY_OVERRIDES结果属性确实能在系统起来后看到但UsbDeviceManager在某些平台上读取时机更早可能读不到persist记录而导致回退到默认的mtp所以稳妥起见还是按传统做法放到PRODUCT_PROPERTY_OVERRIDES。改完以后编译出来的镜像里/system/build.prop会出现一行persist.sys.usb.configmtp,adb。设备开机的第一次UsbDeviceManager读取到该配置会把它同步成当前sys.usb.configUSB枚举出来就是MTP和ADB的组合设备。如果你的平台是MTK或者高通还要确认一下内核里usb gadget的mtp,adb复合功能是否已经开启。多数参考设计默认都支持但有些裁剪过的内核只保留了mtp或adb单功能遇到这种情况编译期就要去内核配置里确认USB_CONFIGFS相关的项。2.2 用ro.adb.secure0干掉RSA确认框关闭RSA认证最简单的是在device.mk里设置默认属性PRODUCT_DEFAULT_PROPERTY_OVERRIDES \ ro.adb.secure0注意这里要用PRODUCT_DEFAULT_PROPERTY_OVERRIDES因为ro.adb.secure是只读属性必须放到default.prop里让init进程最早阶段就加载。/system/build.prop里的ro.属性虽然也能被读取但adbd在部分启动流程中读取属性的时间很早放进default.prop才能保证在任何进程需要它之前就已经存在。如果这里不做特殊处理AOSP的编译脚本会按版本类型自动给ro.adb.secure赋值。具体逻辑在build/core/main.mk附近大致是user版本默认给1eng和userdebug默认给0。你在device.mk里强制覆盖成0是为了保证即使最终产物是user版本也能跳过RSA校验。还有一个隐蔽点有些方案商会在公共的device.mk里再次覆盖这个属性或者通过ADDITIONAL_DEFAULT_PROPERTIES追加。如果你改完编译出来发现default.prop里还是1就去全代码仓库里搜一下ro.adb.secure看看有没有别的地方在赋值特别是build/target/product下那些公共mk文件。2.3 让配置持久生效的makefile写法细节实际项目里尤其是给多个型号共用同一个SDK的情况我倾向于把这两个配置单独抽一个mk片段比如usb_default.mk然后在各个产品的device.mk统一include# usb_default.mk PRODUCT_PROPERTY_OVERRIDES \ persist.sys.usb.configmtp,adb PRODUCT_DEFAULT_PROPERTY_OVERRIDES \ ro.adb.secure0这样出多款机型时只要维护这一个文件不用每个device.mk都重复写。改完务必做一步验证编完包后直接解压system镜像查看build.prop和根文件系统的default.prop确认两个属性都按预期写入了。我遇到过好多次改device.mk以为生效了结果因为编译缓存或公共mk覆盖打包出来的镜像根本没带上配置直到解包检查才发现。顺带说一句如果你不希望最终用户通过系统设置里的开发者选项再次切换USB模式可以在UsbDeviceManager里把模式切换的功能锁死或者直接不在系统UI里暴露USB用途选择。但从实际量产反馈来看大部分项目保留切换能力更稳妥毕竟售后调试时还能手工切回仅充电。安全性上的考量我会在最后一章一起说。3. 不改系统镜像运行时预置公钥和临时调模式的办法不是所有场景都能重新编镜像。如果你手上已经有一台跑着Android 6.0的量产机或者客户不想动固件只是想在调试阶段不再被RSA弹窗烦那可以走运行时方案。这个方案的核心思路是既然弹窗是因为公钥不在白名单里那就把当前电脑的公钥手动塞进白名单既然默认模式不是MTPADB那就直接改持久属性然后重启。3.1 预置RSA公钥白名单的具体操作设备必须已经处于可adb连接状态且你有权限写/data目录。首次正常连接后执行一次命令拿到公钥再把它投递到设备上# 本机查看adb公钥内容 cat ~/.android/adbkey.pub然后把公钥内容以单行形式写入设备适配键文件。正常情况下/data/misc/adb/这个目录只有在第一次弹框允许后才会生成所以路径可能要自己创建adb shell mkdir -p /data/misc/adb adb push ~/.android/adbkey.pub /data/misc/adb/adb_keys adb shell chown system:system /data/misc/adb/adb_keys adb shell chmod 600 /data/misc/adb/adb_keys这里有一个容易忽略的点设备上的adbd不同版本或不同平台读取白名单的路径可能略有差异。Android 6.0常见的适配键路径是/data/misc/adb/adb_keys但有的定制平台会去读/data/misc/adb/adb_keys注意没有s或/adb_keys。建议操作之前先插上设备弹一次框、点允许然后立即查看这个目录下生成了什么文件照着已有的文件名和格式来预置成功率最高。另外如果你有几十台同样型号的设备要批量处理与其一台台push不如直接在制作data镜像时把该文件打进/data/misc/adb/目录。量产烧录完data分区公钥就已经在了。要换工位电脑时重新生成密钥文件再打包data镜像即可。这里的局限也很明显只能免掉已知电脑的弹框如果后续来了台新电脑还是会弹。要做到“绝对不弹”除非全局关闭认证否则就得把可能用到的电脑公钥全部预置进去。对于封闭产线这个方案够用对于开放环境的消费类产品不太推荐。3.2 开机自动写入公钥的init脚本如果设备已经没法手动操作但又想把某台授权电脑的公钥固化进去可以做一个开机自启动脚本。把adbkey.pub放到系统分区一个固定路径比如/system/etc/adb_keys然后在init.rc或者你自己的rc脚本里增加一段on boot mkdir /data/misc/adb 0770 system system copy /system/etc/adb_keys /data/misc/adb/adb_keys chmod 600 /data/misc/adb/adb_keys chown system system /data/misc/adb/adb_keys这里用copy而不是把公钥直接放在/data原因也很实在/system分区有dm-verity保护拆改而/data可能被恢复出厂清掉。放在/system里每次开机自动复制即使数据被清空只要重新开机又能自动恢复白名单。需要注意Android 6.0的SELinux策略如果开启了Enforcingcopy命令可能被SELinux拦截需要检查init.rc所在域的file_contexts和te规则确保对/data/misc/adb目录有写权限。3.3 运行时改USB默认模式的正确姿势不想重新编镜像还想改默认USB模式可以分两步走。第一步先确认UsbDeviceManager当前选定的模式adb shell getprop persist.sys.usb.config adb shell getprop sys.usb.config正常情况下persist为空或与sys一致。第二步直接设置持久属性并重启adb root adb shell setprop persist.sys.usb.config mtp,adb adb reboot重启后系统会以MTPADB组合模式启动。要注意的是setprop设置persist.开头的属性内部会同步写入/data/property下的持久属性文件理论上不重启也能通过setprop sys.usb.config mtp,adb立刻切换但这个动态切换只对当前开机状态有效第二次重启就会失效。所以想让“默认”生效一定要设置persist属性并重启。运行时改属性这个方法对临时调试非常管用。比如产线里偶尔一批设备因为误操作被切到了仅充电模式一条setprop persist.sys.usb.config mtp,adb reboot就能批量纠正。不过只有在已经能连上adb的前提下才能这样操作如果设备当前USB已经断开了adb就需要想办法进recovery通过脚本改属性或者恢复出厂后重新走初始化脚本。4. 源码级改造保留RSA认证但免去人工点确认有的项目出于安全考虑不想直接关掉RSA认证但又希望设备连上没有授权过的电脑时能自动允许而不是等人去点屏幕。这种需求就要动系统源码了核心位置在frameworks/base/services/usb/java/com/android/server/usb/UsbDebuggingManager.java。这个类的职责很集中接收adbd转交的主机公钥、拉起系统确认对话框、处理后把公钥追加到白名单文件。我们要做的就是把“弹框等确认”这个环节跳过收到公钥直接放行。4.1 UsbDebuggingManager的类结构和关键入口在Android 6.0的代码里UsbDebuggingManager的内部通过一个Handler接收各种消息核心消息大致包括MESSAGE_ADB_ENABLED开启adb调试MESSAGE_ADB_DISABLED关闭adb调试MESSAGE_ADB_ALLOW_KEY用户点击了“允许”MESSAGE_ADB_DENY_KEY用户点击了“拒绝”adbd发现新公钥后会把公钥字符串和指纹信息传给UsbDebuggingManagerManager先做合法性检查随后调用一个方法去启动确认界面比如通过startActivity拉起一个带指纹的对话框。如果用户点允许Handler收到MESSAGE_ADB_ALLOW_KEY再调用内部的writeKey之类的函数把公钥写入白名单文件并通知adbd重新校验。要跳过弹框最简单的源码修改就是找到启动确认界面的位置直接调用“允许”分支会执行的写文件逻辑。大致思路如下private void confirmKey(String key, String fingerprints) { // 原代码弹出确认对话框 // startConfirmation(key, fingerprints); // 修改后直接写入白名单并通知adbd writeKey(key); }具体方法名不同版本略有差异但思路一致。改完重点看一下writeKey内部的实现它通常会先判断公钥是否已经在白名单里避免重复插入然后以追加方式写入/data/misc/adb/adb_keys紧接着更新文件权限最后给adbd发送“认证已通过”的响应。4.2 自动允许时的安全取舍和额外处理这种“自动允许所有公钥”的做法等于是把RSA认证流程保留在代码层面但把最后的人工确认变成了机器确认。好处是ro.adb.secure还是1系统属性层面看起来仍然是安全状态安全扫描时会认为认证功能是开启的坏处是任何能够物理接触USB口的人插上电脑就能直接进adb且无需任何授权和小范围关闭ro.adb.secure的实际风险差别不大。所以我在项目里一般不会做全自动允许而是做一个折中只自动允许预设的“可信指纹”公钥其他公钥还是走弹框。实现也不复杂在确认方法里把收到的fingerprints或公钥内容同一个预置白名单比对private void confirmKey(String key, String fingerprints) { if (mTrustedFingerprints.contains(fingerprints)) { writeKey(key); } else { startConfirmation(key, fingerprints); } }mTrustedFingerprints可以做成一个存放在/system/etc或/data/misc下的配置文件编译期预置启动时读取。这样既能省掉产线工位的弹框又不会把设备完全暴露给任意主机。这个模式在不少商业设备方案里都能看到比单纯设置ro.adb.secure0要细得多。改完UsbDebuggingManager.java记得同步检查权限和SELinux。writeKey要写/data/misc/adb/adb_keys如果这个路径没有生成过应用进程需要创建目录必须确保system_server或相关进程有在该目录下写入的权限。Android 6.0的SELinux策略如果打开了Enforcing可能还需要补充新的te规则否则会看到avc: denied日志而写入失败。5. 踩坑记录改完不生效、USB识别异常怎么办这部分都是我在多个项目里实际遇过的坑按出现频率从高到低排一下。很多问题其实跟代码本身无关纯粹是配置文件改错了地方或者被系统其他逻辑覆盖了。5.1 改完ro.adb.secure还是不生效问题出在哪优先查default.prop。很多时候你改了device.mk但最终的default.prop是在编译时由多个来源合并生成的PRODUCT_DEFAULT_PROPERTY_OVERRIDES只占其中一部分。build/core/main.mk会根据构建类型自动追加ro.adb.secure的内容如果你看到user版本镜像里始终是1在device.mk里加的可能被别人追加的属性覆盖了。解决办法有两个一是按前面说的搜索全仓库找到所有赋值点统一改成0二是在adbd源码里直接固定行为把auth_required初始值改成false。我自己更推荐第二种因为它在任何版本类型下都不会被属性干扰属于最终保险。位置在AOSP的system/core/adb/daemon/auth.cppadbd_auth_verify函数里有一行判断是否要求认证直接把判断条件去掉或改为恒真即可。缺点是这样改动比较侵入系统升级或代码合并时容易冲突。5.2 mtp和adb组合生效了但电脑上只看到一个usb设备有的电脑上弹出MTP设备却看不到adb端口或者反过来说明USB复合设备枚举不完整。这往往是驱动层面的事。Windows连接Android 6.0的mtp,adb组合设备需要分别装MTP驱动和adb驱动。MTP一般系统自带adb驱动则需要安装Google USB Driver或厂商提供的驱动否则设备管理器里会看到一个带感叹号的“Android Composite ADB Interface”。如果设备管理器显示ADB Interface有黄色感叹号先更新驱动指定到Android SDK的extras/google/usb_driver目录手动安装。装好驱动后再运行adb devices如果还是显示unauthorized在系统设置里关掉再打开“USB调试”重新触发一次公钥协商。另外确认一下你的USB线是不是只有充电的很多调试半天发现是线材问题换一根数据线立刻解决。5.3 首次连接不弹框但设备默认也不是MTP这种一般出现在同时改了默认模式和认证开关、但设备已经开过机的情况下。原因很简单persist.sys.usb.config在第一开机时已经被写入了别的值比如none或charging你后来加的persist.sys.usb.configmtp,adb只在全新data分区的机型上生效。解决方法是恢复出厂设置或者在已开机的设备上手动执行adb shell setprop persist.sys.usb.config mtp,adb adb reboot另外有些平台会在recovery或工厂模式下对persist.sys.usb.config做特殊处理比如MTK的meta模式、高通的9008端口都会临时切换USB功能。量产前最好在不同场景下都测一遍确认重启后还能回到你设定的默认值。5.4 关闭RSA认证后拔插USB第一次连接会闪一下但连不上Android 6.0的adbd关闭认证后理论上任意主机重连都不需要确认但偶尔第一次插上会看到设备短暂出现一下又消失。这通常不是认证的问题而是Windows侧adb驱动设备节点建立较慢或者USB枚举过程中adbd与系统服务之间的握手还没完成。可以先在设备上确认adb服务在跑adb shell getprop init.svc.adbd输出应该是running。如果状态正常把电脑端adb版本升级到1.0.36以上老版本adb客户端对于Android 6.0的某些握手流程兼容性并不好。再不行就禁用电脑的USB选择性暂停设置很多Windows笔记本在节能策略下会周期性断开空闲USB设备表现就是adb断断续续。我在实际项目里最终的推荐组合是量产镜像编译期固定ro.adb.secure0persist.sys.usb.config预置成mtp,adb如果项目对安全审计有硬性要求那就改成保留RSA认证通过预置可信公钥白名单的方式实现“首次连接自动放行指定电脑”。前一套适合产线和开发调试后一套适合交付给最终用户但仍需远程维护的设备。最终怎么选看你手里的产品定义是效率优先还是安全优先两边我都跑过稳定性都没问题。
返回列表