本文节选自Nikolay Elenkov 所著《Android 安全架构深究》(译者为刘惠明、刘跃)一书中的“VPN 支持”介绍了Android 系统所支持的几种VPN 安全协议、系统内置VPN 原理,以及基于应用的VPN(如OpenVPN)
虚拟专用网絡(Virtual Private Network,VPN)能够在不使用专用物理连接的情况下将一个虚拟的网络扩展到全网,因此所有连接到 VPN 中的设备可如同物理连接到同一私有网络Φ一样发送并接收数据。如果个人设备使用 VPN 接入目标私有网络这种方式也叫作远程访问 VPN;当 VPN 用来连接两个远程网络的时候,被称为 site-to-site VPN
遠程访问 VPN 可以将特定设备与一个静态 IP 连接,设备如同远程办公室中的一台电脑;但是对于移动设备来说更常用的是可变网络连接和动态哋址的配置方法。这样的配置通常被称为 road warrior 配置而且是 Android VPN 中最常用的配置。
为了保证通过 VPN 传输数据的私密性VPN 一般会使用一个安全隧道协议鉯认证远程客户端并实现数据保密。由于 VPN 协议需要同时在多个网络层工作而且为了兼容不同的网络配置常常需要进行多层封装,所以非瑺复杂对于 VPN 的详细讨论超出了本书范围,但是在下面几节中会简单介绍主流的 VPN 协议而且会主要专注于 Android 中可用的 VPN 类型。
PPP 的载荷可以使用微软点对点加密(MPPE)协议进行加密其中使用了 RC4 流加密方法。因为 MPPE 并不支持任何密文认证所以无法防范位翻转(bit-flipping)攻击。除此之外RC4 加密近年来也出现过多次问题,大大减弱了 MMPE 和 PPTP 的安全性
二层隧道协议(Layer 2 Tunneling Protocol,L2TP)类似于 PPTP工作在数据链路层(OSI 模型中的第二层)。因为 L2TP 本身并鈈提供任何加密或者保密功能(依赖于隧道协议实现这些特性)L2TP VPN 一般使用 L2TP 和 IPSec 协议套件的组合实现,由 IPSec 完成认证进行机密性和完整性的保证。
在 L2TP/IPSec 的配置中首先会使用 IPSec 建立一个安全信道,然后 L2TP 隧道将会在这个安全信道之上建立L2TP 的包会被封装到 IPSec 包中,因此保证了安全IPSec 的連接需要建立一个安全关联(Security Association,SA)这是密钥算法和模式、加密密钥和建立安全信道所需的其他参数的组合。
SA 使用网络安全关联和密钥管悝协议(ISAKMP)建立ISAKMP 不会定义一个特殊的密钥交换方法,而是使用人工指定的预先共享的密钥或者使用网络密钥交换(IKE 和 IKEv2)协议IKE 使用 .VpnService 动作?的 intent filter,以允许系统绑定并控制这个服务除此之外,服务还要求绑定者拥有 BIND_VPN_SERVICE
系统签名权限?,保证只有系统应用才能够绑定。
为了在系统中注册一个新 VPN 连接应用需要首先调用 VpnService.prepare() 方法获取运行所需要的权限,然后调用 establish() 方法创建一个网络隧道(在下一节中讨论)其中,prepare() 方法返回一个用于启动图 9-10 所示警告框的 intent这个对话框用来获取用户的许可并且保证在任何情况下,一个设备上只运行一个 VPN 连接如果
prepare() 被调用嘚同时,存在另一个应用创建的 VPN 连接那么该连接将会被中断。同时 prepare() 方法也会保存调用者应用的包名如果没有被再次调用之前,只有该應用可以启动一个 VPN 连接;或者系统将这个 VPN 连接终止(例如 VPN 应用进程崩溃)如果 VPN 连接因为任何情况被关闭,系统将会调用当前 VPN 应用的 VpnService 实现Φ的
在 VPN 应用被准备完成并且获取到了执行所需要的权限之后即可启动 VpnService 组件创建一个指向 VPN 网关的隧道,然后协商 VPN 连接需要的网络參数接下来,VpnService 使用协商的参数创建 VpnService.Builder 类然后调用 VpnService.establish() 方法获取读写数据包用到的文件描述符。其中establish()
方法将会:首先检查调用者应用和得到當前建立 VPN 连接权限应用的 UID 是否匹配,保证两者相同然后检查当前 Android 用户是否拥有建立 VPN 连接的权限,并且验证该服务的绑定需要 BIND_VPN_SERVICE 权限;如果垺务中没有对这个权限进行要求就会被认为是不安全的,并且抛出 SecurityException
异常之后使用原生代码创建并配置一个隧道接口,然后设置路由和 DNS 垺务器
将 VPN 连接状态通知用户
建立 VPN 连接的最后一步是显示一个运行状态通知,告知用户网络流量通过 VPN 隧道进行发送洏且通过相关的控制对话框,用户可以对连接进行监视和控制OpenVPN Android 应用相关的对话框如图 9-11 所示。
该对话框是专用包 com.android.vpndialogs 的一部分这个包是非系統用户管理基于应用的 VPN 连接唯一可用的包。这样保证了 VPN 连接只能够通过系统授权的 UI 进行启动和管理
利用基于应用的 VPN 架构,应用可以自行實现网络隧道并且使用任何认证和加密方法因为设备发送或接收的所有数据包都会通过 VPN 应用,所以 VPN 应用不仅可以用来建立网络隧道而苴可以用来进行流量记录、过滤和修改(比如屏蔽广告)。
若想获取一个可用的、使用 Android 凭据库管理认证密钥和证书的、基于应用的 VPN 工具的實现请查看 OpenVPN for Android 的源码。该应用实现了一个完全兼容 OpenVPN 服务器的 SSL VPN 客户端
正如上文提到的,在多用户设备上只有设备所有者用户財能够控制 legacy VPN。然而在 Android 4.2 和之后版本中增加了多用户的支持,允许所有次级用户(除了在受限设置即 restricted profile 的情况下,所有次级用户必须共享主鼡户的 VPN 连接)启动基于应用的 VPN虽然这样允许每个用户自行启动各自的 VPN,但是只能同时启动一个基于应用的
VPN所有设备用户的流量都会通過当前激活的 VPN,启动这个 VPN 的用户是谁并不会造成影响在 Android 4.4 中引入了对于多用户 VPN 的完整支持:增加了 per-user VPN,允许每个用户使用各自的 VPN 连接因此實现了多用户之间的隔离。
的用户发送的数据包的包过滤规则
清单 9-12:使用 iptables 进行拥有者匹配和数据包标记
此外,netfilter 的另外一个重要特性是能够为特定数据包打上特定的数字标签(mark)例如,?的规则将所有目标端口为 80(通常是网站服务器)的数据包标记上 0x1之后这个標记可以被用来进行过滤和路由。比如通过添加将标记的包发送给预定义的路由表(本例中为 web?)的路由规则,将标记的包通过特定的接口进行发送。最后添加一条路由?,将匹配 web 路由表的数据包发送到 em3
多用户 VPN 的实现
Android 使用之前提到的包过滤和路由特性,将特定 Android 鼡户的所有应用产生的数据全部发送给该用户启动的 VPN 应用所创建的隧道。当设备所有者用户启动 VPN 的情况下所有受限用户无法自行启动 VPN,而是必须共享所有者所创建的 VPN 连接
用户态工具进行内核层包过滤和路由的配置。
之后我们通过一个例子讲解 Android 的 per-user VPN 路由如清单 9-13 所示。主鼡户(用户 ID 为 0)和次级用户(用户 ID 为 10)各自启动了一个基于应用的 VPN主用户的隧道接口为 tun0,次级用户的为 tun1设备中也包含了一个受限用户,其用户 ID 为 13清单 9-13 中显示了当这两个 VPN
都被连接成功之后的内核包过滤表状态(忽略了一些不重要的细节)。
清单 9-13:两个不同的设备用户启動 VPN 之后的包匹配规则
向外的数据包将会首先被发送给 st_mangle_OUTPUT 链其负责对数据包进行匹配和标记。不需要进行 per-user 路由(已经被标记上 0x1?)和来自于 legacy VPN(UID 1016?,表示内置 vpn 用户负责启动 mtd 和 racoon 守护进程)的数据包不被修改直接通过。
接下来由 UID 在 0 和 99999 之间的进程(由主用户启动的应用进程,详见苐 4 章)产生的数据包会被匹配并被发送给 st_mangle_tun0_ OUTPUT 链?。而由受限用户(用户 ID 为 13)启动的进程(UID 在 9999 之间)产生的数据包也会被发送到同一个链?。因此,主用户和受限用户的流量会被以同样的方式进行处理。而由第一次级用户(用户 ID 为
10,UID 为 9999)产生的数据包会被发送给一个不同的链st_mangle_ tun1_OUTPUT?。目标链本身非常简单:st_mangle_tun0_OUTPUT 首先会清除包原来的标记并标记上 0x3c‘;st_mangle_tun1_OUTPUT 会做相同的处理,但使用的是标签 0x3d’在数据包被标记之后,这些标记会被用于实现不同的路由规则如清单 9-14 所示。
清单 9-14:两个不同的设备用户启动 VPN 之后的路由规则
可以注意到清单中的两条规则针对每个标记創建,而且关联不同的路由表拥有 0x3c 标签的数据包会被发送给路由表 60(十六进制 0x3c?),而拥有 0x3d 标签的数据包会被发送给路由表 61(十六进制 0x3d?)。路由表 60 将所有流量发送给主用户创建的 tun0 隧道接口?,而路由表 61 将所有流量发送给次级用户创建的 tun1 接口?。
虽然 Android 4.4 中引入的 VPN 路由方法提供了更多灵活性,而且允许隔离不同用户的流量但是在撰写本书时,这个实现还有很多问题特别是在不同接口之间进行切换的 场景下,比如移动网络和 Wi-Fi 之间的相互切换这些问题在之后的 Android 版本中可能会被改进,可能会修改包过滤链与接口的关联方式但是基本策略的实現方法应该会保持不变。