21、安全与权限:AAOS 安全模型、系统签名、SELinux 策略、权限管理

说实话,做车机系统开发,安全这块是绕不过去的坎。我刚开始接触 AAOS 时,觉得不就是个 Android 吗?后来才发现,车上的安全要求比手机严格得多。你想想看,手机死机了重启就行,车机要是被攻破了,那可是人命关天的事。

这一章,我就带你捋一遍 AAOS 的安全体系。说白了,就是搞清楚三件事:谁可以做什么、谁可以访问什么、出了问题怎么追责

21.1 AAOS 安全模型:分层隔离

AAOS 的安全模型,核心思想就是隔离。我习惯把它想象成一栋楼——不同楼层住着不同的人,楼层之间装了铁门,没钥匙进不去。

AAOS 安全分层(从底层到上层):

  • 硬件层:TrustZone、硬件密钥、安全存储
  • 内核层:Linux 内核安全、SELinux 强制访问控制
  • 框架层:Android 权限模型、系统签名
  • 应用层:应用沙箱、进程隔离

每一层都依赖下一层提供安全保障。比如,应用层的权限检查,最终要依赖内核层的 SELinux 策略来强制执行。我在项目中遇到过一个问题:明明应用声明了权限,但运行时还是被拒绝。查了半天,发现是 SELinux 策略没放行——这就是分层隔离的典型体现。

应用层:应用沙箱、进程隔离、权限检查 框架层:Android 权限模型、系统签名、PackageManager 内核层:Linux 安全模块、SELinux 强制访问控制 硬件层:TrustZone、硬件密钥、安全启动 依赖关系:上层依赖下层提供安全保障

21.2 系统签名:车机的身份证

系统签名,说白了就是给应用发一张「身份证」。只有持有正确签名的应用,才能被系统信任。

AAOS 里有两类签名:

签名类型 用途 谁可以使用
平台签名(platform) 系统级应用、系统服务 OEM 厂商
媒体签名(media) 多媒体相关应用 OEM 或授权第三方
共享签名(shared) 同一开发者的多个应用 应用开发者
测试签名(testkey) 开发调试阶段 开发者

注意:千万不要把测试签名用在量产车上!我曾经见过一个团队,因为赶工期,直接用 testkey 签了系统应用。结果车上市后,第三方应用可以轻易伪造系统签名——那场面,简直是灾难。

生成系统签名的命令,我一般这么用:

# 生成平台签名密钥
openssl genrsa -out platform.pem 2048
openssl req -new -x509 -key platform.pem -out platform.x509.pem -days 10000

# 使用 signapk.jar 签名
java -jar signapk.jar platform.x509.pem platform.pem \
    input.apk output.apk

嗯,这里要注意:密钥文件一定要妥善保管。我习惯把它们放在硬件安全模块(HSM)里,而不是直接放在构建服务器上。你想想看,密钥泄露了,整个车机系统就相当于裸奔了。

21.3 SELinux 策略:最后的防线

SELinux,全称是 Security-Enhanced Linux。说白了,它就是内核里的一把锁——即使应用拿到了 root 权限,SELinux 照样能拦住它。

AAOS 里 SELinux 的策略文件,通常放在 system/sepolicy/ 目录下。我刚开始看这些文件时,头都大了——全是 .te 后缀的文件,语法还很奇怪。

举个例子,假设我们要给一个车载导航应用放行 GPS 权限:

# 文件:navigation_app.te

# 定义类型
type navigation_app, domain;
type navigation_app_exec, exec_type, file_type;

# 初始化规则
init_daemon_domain(navigation_app)

# 允许访问 GPS 设备
allow navigation_app gps_device:chr_file rw_file_perms;

# 允许读取位置服务
allow navigation_app location_service:service_manager find;

这里的关键是 allow 语句。它的格式是:

allow 源类型 目标类型:类别 权限集合;

我个人的习惯是:先写一个宽松的策略,然后逐步收紧。因为一开始你根本不知道应用需要哪些权限。我曾经有一次,把策略写得太严了,结果导航应用死活拿不到 GPS 数据。排查了一整天,最后发现是少了一条 allow 规则。

调试技巧:使用 adb shell dmesg | grep avc 查看 SELinux 拒绝日志。如果看到类似 avc: denied 的信息,那就是策略没放行。

21.4 权限管理:谁可以做什么

Android 的权限模型,在 AAOS 里被进一步强化了。车机上的权限,不光要管「能不能用」,还要管「什么时候能用」。

举个例子,一个音乐应用想访问麦克风。在手机上,用户同意就行。但在车上,你还要考虑:

  • 车辆是否在行驶中?
  • 驾驶员是否在通话?
  • 是否有紧急事件(如碰撞预警)正在播报?

AAOS 引入了一个叫 CarPermissionManager 的东西,专门处理这类场景。我写过一个示例:

// 检查是否有权限使用麦克风
CarPermissionManager carPermissionManager = 
    Car.createCar(this).getCarManager(CarPermissionManager.class);

if (carPermissionManager != null) {
    int result = carPermissionManager.checkPermission(
        "android.car.permission.CAR_MICROPHONE",
        getPackageName()
    );
    
    if (result == PackageManager.PERMISSION_GRANTED) {
        // 可以访问麦克风
        startRecording();
    } else {
        // 权限被拒绝,可能是车辆状态不允许
        showPermissionDeniedDialog();
    }
}

这里有个坑:不要假设权限一旦授予就永远有效。车机的权限状态可能会因为车辆状态变化而动态调整。比如,车辆从「驻车」切换到「行驶」时,某些权限会被自动撤销。

21.5 实战:配置一个安全的车机应用

好了,理论说完了,咱们来点实际的。假设我们要开发一个车载诊断应用,需要访问车辆的 OBD 数据。安全配置应该怎么做?

第一步,在 AndroidManifest.xml 中声明权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.obdapp">

    <!-- 系统级权限,需要平台签名 -->
    <uses-permission android:name="android.car.permission.CAR_DIAGNOSTIC" />

    <application
        android:sharedUserId="android.uid.system"
        ...>
        ...
    </application>
</manifest>

第二步,编写 SELinux 策略:

# obd_app.te
type obd_app, domain;
type obd_app_exec, exec_type, file_type;

init_daemon_domain(obd_app)

# 允许访问诊断服务
allow obd_app car_diagnostic_service:service_manager find;
allow obd_app car_diagnostic_service:binder call;

# 允许读取车辆网络数据
allow obd_app vehicle_network_device:chr_file rw_file_perms;

第三步,使用平台签名对应用进行签名:

java -jar signapk.jar platform.x509.pem platform.pem \
    obd_app_unsigned.apk obd_app_signed.apk

避坑指南:我曾经犯过一个错误——在开发阶段用了 testkey 签名,然后直接刷到测试车上。结果 SELinux 策略死活不生效,因为 testkey 签名的应用被系统当作「第三方应用」处理,根本不会加载对应的策略文件。记住:测试也要用 platform 签名,除非你只是想验证功能逻辑。

21.6 安全审计:出了问题怎么查

安全配置做得再好,也难免出问题。关键是出了问题能快速定位。

我常用的审计手段:

  1. 查看 logcat:搜索 PermissionSecurityException 等关键词
  2. 查看 dmesg:搜索 avc: denied 定位 SELinux 拒绝
  3. 使用 adb shell dumpsys:查看权限授予状态
  4. 检查签名:用 jarsigner -verify -verbose -certs app.apk 确认签名是否正确

举个例子,有一次我发现某个系统应用无法启动,logcat 里报 SecurityException。我第一反应是权限问题,但检查了 AndroidManifest 发现权限声明没问题。后来用 dmesg | grep avc 一看,原来是 SELinux 策略里少了一条 allow 规则。加上之后,问题就解决了。

嗯,安全这东西,说白了就是「防患于未然」。配置的时候多花点心思,出问题的时候就能少掉点头发。


公众号:蓝海资料掘金营,微信 deep3321