FIDO协议系列引入了一个新的安全概念:应用层面,用于描述用户凭证的范围,以及支持应用程序隔离的可信计算基础如何做出访问控制的决定,决定哪些应用和网页源可以使用哪些密钥。
本文档描述了实施应用程序方面概念的动机和要求,以及它如何适用于FIDO协议。
1.概览
现代联网应用程序通常呈现用户可以与其进行交互的几种方式。本文档介绍了Application Facet的概念,以描述跨不同平台的单个逻辑应用程序的标识。例如,应用程序MyBank可能有一个Android App,IOS APP和一个可以从浏览器访问的Web app,这些是MyBank应用程序的所有类型。
与传统的用户名口令方式相比,FIDO架构提供了更简单、更强大的鉴别能力,通知避免了其他鉴别方案的许多不足。FIDO协议的核心是使用代表用户信任凭据的公私密钥对,Challenge和Response。
为了最小化经常遇到的关于隐私、"身份"概念的纠纷和可信第三方的必要性的问题,FIDO的密钥是严格限定范围的,在用户和依赖方间动态配置,仅可选择与服务器分配的用户名相关联。与这种方式形成对比的例子,例如TLS中使用传统的PKIX客户端证书,引入了可信的第三方,在具体实施中标识断言和密钥持有者加密凭证,缺少受众的监督,甚至可能在用户未知或未准许的情况下在协议握手的明文部分被发送。
尽管FIDO协议由于诸多原因是更可取的,但是也引来了一些挑战:
什么样的网页源和本地应用能组成一个单一逻辑应用,他们怎样进行可靠地识别?
在每个网页浏览器和应用都访问相同目标实体提供的服务时,如何避免用户重复在设备上为每个网页浏览器或应用注册新的密钥。
怎样做到如用户希望的那样,在不违反应用程序安全隔离和防护恶意代码的前提下进行注册密钥的共享操作。
用户如何在多个具有用户界面的FIDO可信设备计算机的设备上进行身份凭证的漫游?
1.1 动机
FIDO 概念性地给注册密钥设定了一个范围,用一个三元组(用户名,认证器, 依赖方)表示。但是依赖方由什么组成呢?对一个用户来说,在相同设备上, 通过一个或多个浏览器或者专门的应用程序访问同一依赖方的相同服务集是很 普遍的。依赖方可能需要用户执行一个复杂的过程来证明用户的身份并为其注 册一个新的 FIDO 密钥,但是用户不希望在相同的设备上每次打开浏览器或者 应用程序时,都需要重复这个复杂的身份验证过程。
1.2 避免 APP 钓鱼欺骗
FIDO 提供一种友好用户界面的认证方式来允许访问注册的密钥,例如输入一个 简单的 PIN 码、触摸一个设备或扫描指纹。如果用户在不同的依赖方中重复使 用相同的验证输入,应该不会有安全方面的问题,特别是当验证输入是生物识 别时,用户可能别无选择。
使用“应用商店”分发模式的现代操作系统经常向用户许诺“安全试用”任何应用 程序。操作系统把不同应用隔离开来,所以一个应用不能读取其他应用的数据 或者互相访问接口,而且需要获得用户明确的许可来存取共享的系统资源。
如果用户要下载一个恶意的游戏,这个游戏会让用户去激活他的 FIDO 认证器 来“保存进度”,但实际上却解锁他的银行凭证并且接管了他的账户,FIDO 就失 败了,因为网络钓鱼的风险仅从口令转移到了应用的下载。FIDO 通过保持对注 册密钥所代表的高价值共享状态的良好监管,从而不违反平台“安全试用”任何 应用程序的承诺。
1.3 与OAuth和OAuth2的比较
OAuth和OAuth2协议是用于服务器件的安全模型,假定每个应用实例可以发布并且保护"应用的秘密"。这种方法并不适用于应用商店的安全模型。虽然在其应用服务中提供OAuth类型的应用安全保障来试图仅允许授权的/官方的应用程序来连接是很常见的,这类"秘密"实际上对所有能够访问应用商店的程序来说是共享的,并且这些"秘密"可以简单的逆向工程复原出来。
相反的,FIDO提出类型的概念从一开始就是为了"应用商店"模型。它依赖于客户端平台的隔离特性,来保证用户在行为良好的"可信组"注册的密钥只停留在该可信组内部,及时后来用户安装了恶意软件也不受影响,并且不需要将Secrect硬编码到共享包中。不管怎样,用户必须正确地选择哪个应用和浏览器来进行注册。如果应用要求用户在依赖方注册FIDO密钥用来非法或仿冒使用,应用商店策略可以删除这些应用。
1.4 非目标
Application Facet的概念并不是企图通过网络辨别出调用应用的是什么服务。应用程序身份的远程认证是一个明确的非目标。
如果一个未授权的应用能够让用户提供所有用来注册新的FIDO密钥的信息,依赖方并不能通过FIDO协议或者类型的概念来判别它是为授权的,或者拒绝这个应用执行FIDO操作。并且如果用户选择信任这样的应用,也会在本文档描述机制外的密钥共享访问。
Facet机制为通过Trusted Computing Base创建和读取密钥提供了一种保持其正确范围的方式,Trusted Computing Base实现了对恶意程序的隔离。用户凭证也可以在多个拥有友好界面的TCBs设备间漫游,如果每个设备都正确地实现了这种机制,凭证会保持在正确的范围内。然而,在TCBs与用户敌对的环境里安全并不能得到保证,例如在恶意代码以"root"级别权限运行的设备上。在不提供应用程序隔离但运行具有用户权限(例如传统桌面操作系统)的所有代码环境中,完整TCB(包括Web浏览器)可能会成功地强制实施Web起始的凭据范围,但不能有效强制规定应用程序的可用范围。
2.AppID和FacetID断言
当用户执行注册操作时,认证器会生成一个新的私钥,并将公钥发给依赖方。作为该操作的一部分,每个密钥都会与一个AppID相关联。AppID是一个URL,作为服务器发送的协议报文的一部分,并且指出了凭证的目标。默认情况下,凭证的受众被限制为与AppID同源。在某些情况下,依赖方希望在一个更大的范围内使用密钥。如果AppId URL采用https方案,FIDO客户端可能会把它作为一个TrustedFacetList进行解引用并处理。可信类型列表指定了范围和受众的限制,包括多个类型,例如与AppID源相同DNS区域的其他网页资源,或者URL指出了类似于移动引用这样的可信类型。
注释:
用户可能会在同一个认证器上为一个AppID注册多个密钥,例如有多账户的情况。这种注册可能会使用依赖方依赖方用户名或本地昵称来方便用户区分,但是也肯呢个不提供用户名或本地昵称来方便用户区分,但是也可能不提供用户名,例如在双因子认证的情况下,与密钥相关的用户账号可能通过带外方式与FIDO协议的其他模块进行通信。所有共享同一AppID的注册信息,也同时共享相同的受众限制。
2.1 AppID 和 FacetID 断言的处理规则
2.1.1 确定请求应用程序类型标识符(facetID)
如果是网页,FacetID必须是触发操作网页的网页源,可写为一个空路径的URI。忽略默认的端口和任何路径组件。
一个FacetID的例子如下所示:
https://login.mycorp.com/
如果是安卓系统,FacetID必须是一个来源于APK签名证书的Sha-1哈希值得URI。例如:
android:apk-key-hash:<sha1_hash-of-apk-signing-cert>
如果是IOS系统,facetID必须是应用程序的BundleID的URI
ios:bundle-id:<ios-bundle-id-of-app>
2.1.2 判断请求者的FacetID是否被AppID授权
1.如果AppID不是HTTPS URL,并且与请求者的FacetID相匹配,该操作可以继续进行而无需进行额外的处理。
2.如果AppID为null或值为空,客户端必须将AppID设备请求者的FacetID,该操作可以继续进行而无需进行额外的处理。
3.如果请求者的FacetID是https://的形式,源于AppID共享相同的主机(例如,一个应用程序的主页是https://fido.example.com/myApp,将AppID设置为https://fido.example.com/myApp),该操作可以继续进行而无需进行额外的处理。如果需要的话,该算法可异步今次那个来获取可信类型列表。
4.开始用HTTP GET方法来获取可信类型列表,地址必须用HTTPS URL来指定。
5.URL必须是可以匿名解引用的,也就是说HTTP GET不能包含cookies、鉴别、源或者引用报头,并且不能有TLS证书或者其他形式的证书。
6.必须将响应的MIME Content-Type(内容形式)设为"Application/fido.trusted-apps+json"。
7.当获取可信类型列表时,应该遵守HTTP响应中缓存的相关HTTP报头。
8.保存可信类型列表的服务器必须对所有客户端做出统一的响应。也就是说,不能根据任何凭证材料更改响应正文的内容。包括环境授权,例如请求中提供的源IP地址。
9.如果服务器返回一个HTTP重定向(状态码3xx),服务器也必须发送HTTP报头FIDO-AppID-Redirect-Authorized:true,并且客户端必须在重定向前验证这个Http报头。这么做防止了未授权方在目标域中对开发式重定向器的滥用。如果此项检查通过,从第4步重新开始算法。
10.可信类型列表可能含有无数项,但是客户截断或者拒绝处理大的响应。
11.从trustFacet数组的对象中,选择一个与协议报文version一致的对象。
12.Ids中URL方案必须识别出应用程序的身份(例如使用"apk:","ios:"或者其他相似方案)或者https:
13. Ids 中使用 https://方案的实体必须只能包含方案、主机和端口组件,以及可 选的尾部“/”。任何路径、查询字符串、用户名/口令,或者分块信息都必须 被丢弃。
14. 所有列出来的网页源必须包括 DNS 中相同最小特定私有标签范围内的主机 名,使用如下算法:
1)从 https://publicsuffix.org/list/effective_tld_names.dat 处(客户端可以缓 存该数据)或者平台中相同功能处获取公共 DNS 后缀列表。
2)在重定向前,提取出原始 AppID URL 中的主机部分。
3)最小特定私有标签是 AppID URL 中与公共后缀左边加上附加标签后的 样式相匹配的一部分。
4)对于可信类型列表中的每个网页源,DNS 中最小特定私有标签的计算 结果与 AppID URL 的匹配必须是不区分大小写的。不匹配的项必须被丢弃。
15.如果可信类型列表无法检索或无法通过以上规则完成解析,客户端必须终 止对请求的 FIDO 操作的处理。
16.在处理了正确 version 的 trustedFacets 项、并且移除了所有无效项之后,如 果请求者的 FacetID 与 ids 列表中的一项相匹配,那么这个操作可以进行。
2.1.3 TrustedFacet结构
AppID URL中的JSON资源由一个包含唯一成员trustedFacets的结构组成,trustedFacets是一个TrustedFacets结构的数组。
dictionary TrustedFacets{
Version version;
DOMString ids;
}
2.1.3.1 TrustedFacets结构成员
version 类型为Version 这组可信类型支持的协议版本。
ids类型为DOMString数组 一个URL数组,用来鉴别AppID所授权的类型(facets)
2.1.4 AppID示例1
{
"trustedFacets": [
{
"version": {
"major": 1,
"minor": 0
},
"ids": [
"https://register.example.com",
"https://fido.example.com",
"http://www.example.com",
"http://www.example-test.com",
"https://www.example.com:444"
]
}
]
}
在该策略中,"https://www.example.com"和"https://register.example.com"可以访问为这个AppID注册的密钥,但是"https://user1.example.com"则不可以。
2.1.5 AppID示例2
"hosting.example.com"是公共后缀,在"example.com"下运行并且为多家公司提供托管云服务。"https://companyA.hosting.examlpe.com/appID"作为一个AppID,位于该地址的资源主体包括:
{
"trustedFacets": [
{
"version": {
"major": 1,
"minor": 0
},
"ids": [
"https://register.example.com",
"https://fido.companyA.hosting.example.com",
"https://xyz.companyA.hosting.example.com",
"https://companyB.hosting.example.com"
]
}
]
}
在这个策略中,"https://fido.companyA.hodting.example.com"可以访问为这个AppID注册的密钥。但是"https://register.example.com"和"https://companyB.hosting.example.com"则不能,因为DNS名字和AppID的名字之间存在一个公共后缀。
2.1.6 获取本地Android本地应用的FacetID
下面的代码展示了FIDO客户端如何获取和构建一个调用的安卓本地应用的FacetID。
private String getFacetID(Context aContext, int callingUid) {
String packageNames[] = aContext.getPackageManager().getPackagesForUid(callingUid);
if (packageNames == null) {
return null;
}
try {
PackageInfo info =
aContext.getPackageManager().getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
byte[] cert = info.signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate c = (X509Certificate) cf.generateCertificate(input);
MessageDigest md = MessageDigest.getInstance("SHA1");
return "android:apk-key-hash:" + Base64.encodeToString(md.digest(c.getEncoded()),
Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
return null;
}
2,1,7 其他安全性性考虑
UAF协议支持传递FacetID给FIDO服务器和鉴别响应的计算结果中包含facetID。
信任了网页源类型,毫无疑问也就要信任这个实体名下所有的子域,因为网页用户代理不在这样的源之间提供屏障。所以,在AppID示例1中,虽然没有明确列出,但是https://foobar.register.example.com实际上仍然可以访问AppID"https://www.example.com/appID"注册的凭证,因为它实际上可以当作"https://register.example.com"。
这里所描述的控件的组件必须可靠的识别请求者,从而安全地执行这项机制。允许这种识别的平台进程间通信机制如果可用就应该使用。
执行上面描述的控件的组件不太可能验证TrustedFaceList中项目的完整性和意图。如果一个可信的类型会被恶意方破解或被作为混淆代理人,用户就有可能在恶意方的诱导下完成鉴别过程。
2.1.7.1 可信类型标识符通配符
可信类型标识是不支持通配符的。FacetID是唯一识别特定安全主体的URI,被信任与给定的注册凭证进行交互。通配符给主体的定义带来了模糊性,因为到底统配符是什么意思、统配符怎样扩展、在哪里出现等在不同的应用程序和协议中并没有统一的规范。对于表明应用程序身份的方案来说,并没有明确通配符是否在任何方式中都合适。对于网页源来说,它扩展了凭证的范围,有可能包含流氓或者缺陷的主机。
总体来说,这些模糊性可能导致在客户端的实现过程中,出现可被利用的身份检查行为的差异,并且会需要过度复杂和低效率的身份检查算法。