应该不会的Andr​​oid的AccountManager商店的OAuth令牌对每个应用程序/ UID依据? [英] Shouldn't Android AccountManager Store OAuth Tokens on a Per-App/UID Basis?

查看:337
本文介绍了应该不会的Andr​​oid的AccountManager商店的OAuth令牌对每个应用程序/ UID依据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Android的出现的AccountManager来获取与不同的UID的应用程序相同的缓存身份验证令牌 - 这是安全的?它似乎并不兼容的OAuth2,因为不应该访问令牌被不同客户之间的共享。

背景/上下文

我建立一个Android应用程序,它使用OAuth2以REST API请求的认证/授权到我的服务器,这是一个了OAuth2提供商。由于应用程序是官方的应用程序(相对于第三方应用程序),它被认为是一个值得信赖的OAuth2客户端,因此我使用用于获取的OAuth2令牌资源所有者密码流程:用户(资源所有者)输入自己的用户名/密码到应用程序,然后将其客户端ID和客户端密钥的用户凭据到我的服务器的OAuth2令牌端点,以换取一个访问令牌可用于调用API一起,还有一个长住使用的时候到期,以获得新的访问令牌的刷新令牌。的理由是,它更安全,以刷新令牌存储设备不是用户的密码上。

我利用<一个href=\"http://developer.android.com/reference/android/accounts/AccountManager.html\">AccountManager用于管理设备上的帐户和相关联的接入令牌。因为我提供我自己的OAuth2提供者,我通过扩展<一个创建自己的自定义帐户类型href=\"http://developer.android.com/reference/android/accounts/AbstractAccountAuthenticator.html\">AbstractAccountAuthenticator和其他必需的组件,如本Android开发指南解释并在SampleSyncAdapter示例项目演示。我能够成功地从我的应用程序中,从账户和同步Android设置屏幕添加我的自定义类型的账户和管理它们。

的问题

不过,我关心的是路的AccountManager缓存和问题,身份验证令牌 - 具体而言,对于给定的账户类型和令牌类型相同的身份验证令牌似乎是由该用户授予的任何应用程序访问访问。

要获得通过的AccountManager的身份验证令牌,必须调用<一个href=\"http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken%28android.accounts.Account,%20java.lang.String,%20android.os.Bundle,%20android.app.Activity,%20android.accounts.AccountManagerCallback%3Candroid.os.Bundle%3E,%20android.os.Handler%29\">AccountManager.getAuthToken(),传递,除其他事项外,帐户实例为其中以获得验证令牌和所需 authTokenType 。如果不存在指定的帐户和authTokenType一个身份验证令牌,如果用户授权访问(通过grant <一个href=\"http://1.bp.blogspot.com/-2H_e7jCKEyQ/UJdvyERleHI/AAAAAAAAJiE/3ce0q6Mguyk/s400/gls-gb-gant-screen.png\">\"Access请求屏幕)已经取得了身份验证令牌请求(在这种情况下,请求应用程序的UID不符合认证的UID的应用程序),则返回令牌。如果我的解释是缺乏,<一个HREF =htt​​p://nelenkov.blogspot.com/2012/11/android-online-account-management.html>这是很有帮助的博客条目解释得很清楚。在此基础上后,经过检查源<一个href=\"https://github.com/android/platform_frameworks_base/blob/master/core/java/android/accounts/AccountManager.java\">AccountManager和<一个href=\"https://github.com/android/platform_frameworks_base/blob/master/core/java/android/accounts/AccountManagerService.java\">AccountManagerService (一个内部类,它确实为的AccountManager繁重)为我自己,看来只有1身份验证令牌存储每个authTokenType /帐户组合。

所以,似乎如果一个恶意的应用程序知道帐户类型和authTokenType(S)使用我的认证,它可以调用AccountManager.getAuthToken()来获得访问我的应用程序的存储的OAuth2令牌,假定用户授权访问恶意应用程序。

对于我来说,问题是的AccountManager的默认缓存实现是建立在其上,如果我们层的OAuth2用户认证/授权情况下,它会考虑手机/设备是一个单一的OAuth2客户端的服务的范例/资源提供者。 然而,这对我来说很有意义的模式是,每个应用程序/ UID应被视为其自身的OAuth2客户端。:当我的OAuth2提供者发出一个访问令牌,它发出该特定应用程序的访问令牌已发送了正确的客户端ID和客户端密钥,而不是设备上的所有应用程序。例如,用户可能有我的两个官方应用程序(称为应用程序客户端A),以及使用我的API(称为应用程序客户端B)安装了许可的第三方应用程序。本作的官方客户端A,我的OAuth2提供者可能会发出超级类型/范围令牌授权访问公共和私营件我的API,而对于第三方客户端B,我的供应商可能会发出限制类/范围令牌只授予访问公共API调用。 这不应该是可能的应用程序客户端B获得的应用程序客户端的一个访问令牌,它在当前的AccountManager / AccountManagerService实施似乎允许。有关,即使用户授予权限,以客户端B客户端A的超令牌,但事实上,我的OAuth2提供商只用于该令牌授予客户端A

我俯瞰的东西吗?我相信身份验证令牌应该每个应用程序/ UID的基础上发行(每个应用程序是一个不同的客户端)合理的/实际的,或者是身份验证令牌,每个设备(每个设备是一个客户端)标准/接受练?

还是有在我的周围的AccountManager / AccountManagerService 的code /安全性限制的理解存在某些缺陷,使得此漏洞实际上并不存在?我测试过的的AccountManager 和我的自定义身份验证和我的测试客户端应用程序B,它有一个不同的包范围和UID,是上述客户端A /客户端B方案能够通过传递,在相同的 authTokenType (在此期间,我与访问请求提示补助屏幕来获得身份验证令牌,我的服务器已经为我的测试客户端应用程序中印发,这是我认可的,因为我是一个用户,因此毫无章法)...

可能的解决方案

。 秘密authTokenType 结果
为了获得验证令牌,该 authTokenType 必须知道;如若 authTokenType 被视为一个类型的客户端秘密,这样对于给定的秘密令牌类型发出的令牌可以由只有授权客户端,它知道应用程序来获得秘密令牌类型?这似乎并不十分安全;一个根深蒂固的设备上,这将是可能的检查系统的<$ C $ authtokens 表的 auth_token_type 列C>帐户数据库并检查存储与我的令牌authTokenType值。因此,秘密的身份验证令牌在我的应用程序的所有安装(和设备上使用的任何授权的第三方应用程序)使用的类型将被暴露在一个中心位置。至少有OAuth2用户的客户端ID /秘密,即使他们必须与应用程序打包,他们有s不同的客户端应用程序之间的$ P $垫出来,一些尝试,可向混淆它们(这是比没有好),以帮助劝阻那些谁也解包/编译应用程序。

乙。定制身份验证令牌结果
根据文档的<一个href=\"http://developer.android.com/reference/android/accounts/AccountManager.html#KEY_CALLER_UID\">AccountManager.KEY_CALLER_UID和<一个href=\"http://developer.android.com/reference/android/accounts/AuthenticatorDescription.html#customTokens\">AuthenticatorDescription.customTokens,和 AccountManagerService 源$ C ​​$ C我前面提到的,我应该可以指定我的自定义帐户类型使用自定义标记和自旋我自己的令牌缓存/存储实现在我的自定义身份验证,其中我能得到调用应用程序的UID,以便存储/每个UID的基础上获取身份验证令牌。基本上,我会喜欢的默认实现的 authtokens 表中,除了会有一个额外的 UID 列,以便令牌是唯一索引的UID,帐户和身份验证令牌类型(而不是仅仅解释和验证令牌型)。这似乎是比使用秘密authTokenTypes一个更安全的解决方案,因为这将涉及使用相同的 authTokenTypes 在我的应用程序/认证的所有安装,而从UID的变化系统 - 到系统,并且不能很容易被欺骗。除了获得编写和管理自己的令牌缓存机制的快乐开销,有什么缺点是那里在安全方面这种做法?它是大材小用?难道我真的保护什么,还是我失去了一些东西,这样即使有地方这样的实现,它仍然是很容易的一个恶意的应用程序客户端,以获得使用其他应用程序客户端的验证令牌的的AccountManager authTokenType (S)这是不能保证的秘密(假设说恶意应用程序不知道的OAuth2客户端密钥,因此无法直接获得新鲜的象征,但只是希望得到一个已经代表授权的应用程序客户端的缓存在的AccountManager )?

℃。发送客户端ID /秘密W /令牌的OAuth2 结果
我可以用 AccountManagerService 的默认标记存储实现坚持和接受对我的应用程序的身份验证令牌未经授权访问的可能性,但我可能会迫使API请求始终包括OAuth2用户端ID和客户端密钥,除了访问令牌,并验证服务器端的应用程序是该令牌首先颁发的授权客户端。不过,我想避免这种情况,因为 A)据我所知,在的OAuth2规范并不要求对受保护资源的请求客户端身份验证 - 只需要访问令牌,和 B)我想避免对每个请求认证所述客户机的额外开销。


  

这是不一般的情况下,可能的(所有的服务器获取在一个协议一系列消息 - 不能判定中生成的消息的code)所示。 - <一个href=\"http://stackoverflow.com/questions/14437096/shouldnt-android-accountmanager-store-oauth-tokens-on-a-per-app-uid-basis/14468836#14468836\">Michael


但同样可以说,在此期间,客户首次颁发访问令牌OAuth2流程初始客户端身份验证。唯一的区别在于,代替验证对刚才的令牌请求,对受保护资源的请求也将用同样的方式进行验证。 (请注意,客户端应用程序将能够通过 AccountManager.getAuthToken的 loginOptions 参数(),其中我的自定义验证器只会传递给我的资源提供者,占了OAuth2协议)。


关键问题


  1. 是否确实有可能一个应用程序来获得另一个应用程序的的authToken 作为用相同的authTokenType调用AccountManager.getAuthToken()的帐户?

  2. 如果这是可能的,这是一个有效的/实用的安全性的OAuth2范围内的关注?


      

    您可以永远依靠给来自用户的用户剩余的秘密身份验证令牌......所以这是合理的为Android忽略这个安全通过隐藏的目标,它的设计 - <一个href=\"http://stackoverflow.com/questions/14437096/shouldnt-android-accountmanager-store-oauth-tokens-on-a-per-app-uid-basis/14468836#14468836\">Michael


    BUT - 我不关心让未经我同意的身份验证令牌用户(资源所有者);我担心的未授权客户的(应用)。如果用户希望将自己的受保护资源的攻击者,那么他就可以敲自己出。我是说,用户安装我的客户端应用程序,并在不知不觉中,一个冒名顶替的客户端应用程序,能够获得我的应用程序的身份验证令牌,只是因为它传入正确的authTokenType它不应该是可能的,用户是懒得/不知道/赶到检查访问请求屏幕。这个比喻可能有点过于简单,但我不含糊的安全,我的安装Facebook应用程序无法读取我的Gmail应用程序,这是我(用户)不同生根我的手机和检查缓存中的电子邮件考虑内容自己。


      

    要接受(提供Android系统)访问请求的应用程序使用您的令牌所需的用户......鉴于此,Android的解决方案似乎确定 - 应用程序无法使用默默用户的身份验证,而不问 - <一个href=\"http://stackoverflow.com/questions/14437096/shouldnt-android-accountmanager-store-oauth-tokens-on-a-per-app-uid-basis/14468836#14468836\">Michael


    BUT - 这也是问题的授权的 - 我的官方客户端发出的身份验证令牌的关键是一组受保护的资源,而客户端和只有客户的授权。我想,人们可以说,因为用户是那些受保护资源的拥有者,如果他接受来自第三方客户端(无论是sactioned合作伙伴的应用程序或一些钓鱼者)的访问请求,那么他有效授权三阶方客户端,使得访问这些资源的请求。但我有问题,这一点:


    • 一般用户是不是安全意识,足以能够胜任做出这个决定。我不认为我们应该完全取决于用户的判断力挖掘拒绝Android的访问请求屏幕prevent甚至粗钓鱼企图的。当用户与访问请求psented $ P $,我的认证可能是超级详细,枚举所有类型的敏感保护资源(只有我的客户应该能够访问)该用户将被授予他应该接受的请求,并且在大多数情况下,用户将仍然是太不知道和要接受。而在其他更复杂的网络钓鱼的尝试中,冒名顶替的应用程序只是要显得太官方,为用户即使在提高访问请求屏幕眉毛。或者,这里有一个较为生硬的例子 - 访问请求屏幕上,我的身份验证可以简单地说,的不接受这一要求如果您看到这个画面,一个恶意的应用程序正在试图获得访问您的帐户! 的希望,在这样的情况下,大多数用户将拒绝该请求。可是 - 为什么它甚至走到这一步?如果Android的只是保持隔离,他们发出的每个应用程序/ UID的范围身份验证令牌,那么这将是一个非问题。让我们简化 - 即使在我只有一个正式的客户端应用程序的情况下,因此,我的资源提供者甚至不担心发行代币等,第三方客户端,作为一个开发者,我应该说对的选择的AccountManager,不!锁闭这种身份验证令牌,这样只有我的应用程序可以访问。我能做到这一点,如果我沿着自定义标记的路线走,但即使在这种情况下,我不能够prevent从与访问请求屏幕psented第一个是$ P $用户。最起码,应该更好记载,AccountManager.getAuthToken()的默认实现将返回相同的身份验证令牌的所有请求的应用程序/ UID的。

    • 即使是Android的文档识别的OAuth2为行业标准认证($和p $ psumably授权)。该规范的OAuth2明确指出,访问令牌不能被客户端之间共享或以任何方式泄露。那么,为什么不默认的AccountManager实行/配置做起来很简单的客户端来获取最初是从服务其他客户机获得相同的缓存身份验证令牌?该范围内的AccountManager一个简单的解决将是下了他们最初从服务获得相同的应用程序/ UID只能重新使用缓存的令牌。如果没有本地缓存​​可用于给定的UID身份验证令牌,那么就应该从服务获得的。或者至少使这个为开发一个可配置的选项。

    • 在OAuth的3条腿的流量(这涉及到用户授权访问客户端),是不是应该是服务/资源提供者(而不是,比方说,OS),它到达:一种)验证客户端和 B)的如果客户端有效,present该许可访问请求的用户?好像Android是(错误地)篡夺在流这个角色。


      

    但是,用户可以明确地允许应用再使用一个previous验证到一服务,这是方便用户.-- <一href=\"http://stackoverflow.com/questions/14437096/shouldnt-android-accountmanager-store-oauth-tokens-on-a-per-app-uid-basis/14468836#14468836\">Michael


    BUT - 我不认为在便利权证的投资回报率的安全风险。在用户的密码被存储在用户的帐户的情况下,则确实,正在被购买的用户的唯一方便的是,而不是发送一个网页请求到我的服务,以获得实际被授权一个新的,独特的令牌请求的客户端,则返回未授权客户端本地缓存的令牌。因此,用户获得看到一个登录...进度对话框的一对夫妇少秒,用户的风险的轻微便利正在majorly由有他的资源被盗/滥用。


  3. 请记住,我将致力于为 A)使用的OAuth2协议,为确保我的API请求, B)提供我自己的OAuth2资源/身份验证提供者(而不是用说,谷歌或Facebook的身份验证),和 C)利用Android的的AccountManager来管理我的自定义帐户类型及其令牌(S),是的我的任何建议的解决方法有效吗? 这是最有意义的?我俯瞰任何优点/缺点吗?是我没有想到的还有值得选择呢?


      

    [使用方法]另类的客户不要有一个秘密的API,它试图只给一个官方客户端可以访问;人们会解决这个问题。确保您的所有面向公众的API是安全的不管(将来)客户端用户使用 - <一个href=\"http://stackoverflow.com/questions/14437096/shouldnt-android-accountmanager-store-oauth-tokens-on-a-per-app-uid-basis/14468836#14468836\">Michael


    BUT - 这难道不是败在首位的OAuth2采用的主要目的之一?什么是好,如果授权所有潜在authorizees将被授权对受保护资源的相同的范围?


  4. 有没有其他人觉得这是一个问题,如何做到围绕它你的工作吗我做了一些谷歌搜索的广泛试图找出如果其他人都感觉到这是一个安全问题/关注,但似乎涉及Android的的AccountManager和身份验证令牌大部分职位/的问题是关于如何使用谷歌帐户进行身份验证,而不是自定义帐户类型和的OAuth2提供商。此外,我找不到任何人,是concerend差不多的身份验证令牌存在的不同的应用程序使用的可能性,这使我怀疑这是否确实是可能/值得摆在首位的关注(见我的第一个2的关键问题上面的列出)。


我AP preciate您的输入/指导!


在回应...

<一个href=\"http://stackoverflow.com/questions/14437096/shouldnt-android-accountmanager-store-oauth-tokens-on-a-per-app-uid-basis/14468836#14468836\">Michael's答 - 我认为主要困难我有你的答案是:


  1. 我仍然倾向于认为应用程序作为一个服务的独立的,不同的客户,而不是已授权用户/手机/设备本身是一大的客户端,因此令牌一个应用程序应该不会,默认情况下,转让给一个也没有。好像你可能会暗示,这是毫无意义考虑每个应用程序作为一个单独的客户端,因为这种可能性,


      

    用户可以运行一个根深蒂固的手机,并读取该令牌,获得访问你的私有API ... [或]如果用户的系统被攻破(攻击者就​​可以读出在这种情况下,该令牌)


    和因此,在事物的宏伟计划,我们应该考虑该设备是服务的客户端,因为我们不能保证设备本身的应用程序之间的安全。这是真的,如果系统本身受到了损害,那么就不可能有认证/授权请求从该设备发送到服务的保证。但同样可以说的说,TLS;传输的安全性是无关紧要的,如果端点本身不能确保。而对于绝大多数的Andr​​oid设备,这是不妥协的,我相信它是更安全的考虑每个应用程序客户端作为一个独特的端点,而不是通过共享相同的身份验证令牌混为一谈他们各成一体。


  2. 当presented与访问请求画面(类似于软件的用户许可协议,我们总是同意,并在安装前详细阅读),我不信任用户的判断来区分恶意/未经授权的客户端应用程序从一个不是。


解决方案

这是一个有效的/实用的安全威胁?


  

有关官方客户端A,我的OAuth2提供者可能会发出超级类型/范围令牌,该令牌授予公共和私营件我的API

访问

在一般情况下,你可以永远依靠从用户剩下的秘密的 的提供给用户的身份验证令牌。例如 - 用户可以运行一个根深蒂固的手机,并读取该令牌,获得访问你的私有API。如果同上用户的系统被攻破(攻击者就​​可以读出在这种情况下,标记)。

换句话说,有没有这样的东西作为一个私人的API,它是在同一时间,以任何身份验证的用户可访问的,所以这是合理的为Android忽略这个安全通过隐藏的目标,它的设计。


  

恶意应用程序...可能获得对我的应用程序的存储的OAuth2令牌


有关恶意应用程序的情况下,它开始听起来比较合理的,一个恶意的应用程序不应该能够使用客户端的令牌,因为我们预计Android的许可制度提供恶意应用程序隔离(提供的用户读/关心当他们安装了它,他们接受了权限)。然而,正如你说的,用户需要接受(提供Android系统)访问请求的应用程序来使用你的令牌。

鉴于此,Android的解决方案似乎确定 - 应用程序不能默默使用用户的认证没有要求,但用户可以明确允许应用程序重新使用previous身份验证服务,这方便了用户。

可能的解决方案审查


  

秘密authTokenType ......似乎不是很安全的。


同意 - 这只是一个通过隐藏的安全层;这听起来像任何希望分享您的身份验证的应用程序将不得不查找authTokenType是什么,无论如何,所以采用这种方法只是让有点为这个假设的应用程序开发更加尴尬。


  

发送客户端ID /秘密W /令牌的OAuth2 ... [来]验证服务器端的应用程序是合法客户端


这是不一般的情况下,可能的(所有的服务器获取在一个协议一系列消息 - 不能判定中生成的消息的code)所示。在这种特定的情况下,它可能会防止一个(非根)的更有限的威胁替代客户端/恶意应用程序 - 我不熟悉不够用的AccountManager评论(同上为您定制身份验证令牌的解决方案)。

建议

您描述的两个威胁 - 即用户不希望能够访问他们的帐户恶意应用程序,以及替代的客户,你(开发商)不希望使用API​​的部分。


  • 恶意应用:考虑您提供的服务是多么敏感,如果它不大于例如更敏感谷歌/ Twitter账户,仅仅依靠Android的保护(上安装的权限,访问请求屏幕)。如果是的的更敏感,考虑你使用Android的AccountManager的约束是否合适。为了坚决防止恶意利用其帐户的用户,尝试的危险动作(参考加入网上银行新的收件人的帐户详细信息)双因素认证。


  • 替代客户端:没有秘密的API,试图只给一个官方客户端可以访问;人们会解决这个问题。确保您的所有面向公众的API是安全的不管(将来)客户端用户使用。


Android's AccountManager appears to fetch the same cached auth token for apps with different UIDs - is this secure? It does not seem compatible with OAuth2, since access tokens are not supposed to be shared between different clients.

Background/Context

I am building an Android app which uses OAuth2 for authentication/authorization of REST API requests to my server, which is an OAuth2 provider. Since the app is the "official" app (as opposed to a 3rd-party app), it is considered a trusted OAuth2 client, so I am using the resource owner password flow for obtaining an OAuth2 token: the user (the resource owner) enters his username/password into the app, which then sends its client ID and client secret along with the user credentials to my server's OAuth2 token endpoint in exchange for an access token that can be used to make API calls, as well as a long-lived refresh token used to get new access tokens when they expire. The rationale is that it is more secure to store the refresh token on the device than the user's password.

I am utilizing AccountManager for managing the account and associated access token on the device. Since I am providing my own OAuth2 provider, I have created my own custom account type by extending AbstractAccountAuthenticator and other required components, as explained in this Android Dev Guide and demonstrated in the SampleSyncAdapter sample project. I am able to successfully add accounts of my custom type from within my app and manage them from the "Accounts and sync" Android settings screen.

The Issue

However, I am concerned with the way the AccountManager caches and issues auth tokens - specifically, that the same auth token for a given account type and token type seems to be accessible by any app to which the user has granted access.

To obtain an auth token through the AccountManager, one must invoke AccountManager.getAuthToken(), passing, among other things, the Account instance for which to obtain the auth token and the desired authTokenType. If an auth token exists for the specified account and authTokenType, and if the user grants access (via the grant "Access Request" screen) to the app which has made the auth token request (in such cases where the requesting app's UID does not match the authenticator's UID), then the token is returned. In case my explanation is lacking, this helpful blog entry explains it very clearly. Based on that post, and after examining the source of AccountManager and AccountManagerService (an internal class which does the heavy lifting for AccountManager) for myself, it appears that only 1 auth token is stored per authTokenType/account combo.

So, it seems feasible that if a malicious app knew the account type and authTokenType(s) used by my authenticator, it could invoke AccountManager.getAuthToken() to obtain access my app's stored OAuth2 token, assuming that the user grants access to the malicious app.

To me, the problem is that AccountManager's default caching implementation is built on a paradigm on which, if we were to layer an OAuth2 authentication/authorization context, it would consider the phone/device to be a single OAuth2 client for a service/resource provider. Whereas, the paradigm that makes sense to me is that each app/UID should be considered as its own OAuth2 client. When my OAuth2 provider issues an access token, it is issuing an access token for that particular app which has sent the correct client ID and client secret, not all apps on the device. For instance, the user might have both my official app (call it app Client A), and a "licensed" third-party app which uses my API (call it app Client B) installed. For the official Client A, my OAuth2 provider may issue a "super" type/scope token which grants access to both public and private pieces of my API, whereas for the third-party Client B, my provider may issue a "restricted" type/scope token which only grants access to the the public API calls. It should not be possible for app Client B to obtain app Client A's access token, which the current AccountManager/AccountManagerService implementation seems to allow. For, even if the user grants authorization to Client B for Client A's super token, the fact remains that my OAuth2 provider only intended to grant that token to Client A.

Am I overlooking something here? Is my belief that auth tokens to should be issued on a per-app/UID basis (each app being a distinct client) rational/practical, or are auth-tokens-per-device (each device being a client) the standard/accepted practice?

Or is there some flaw in my understanding of the code/security restrictions around AccountManager/AccountManagerService, such that this vulnerability does not actually exist? I've tested the above Client A/Client B scenario with the AccountManager and my custom authenticator, and my test client app B, which has a different package scope and UID, was able to obtain the auth token that my server had issued for my test client app A by passing-in the same authTokenType (during which I was prompted with "Access Request" grant screen, which I approved since I'm a user and therefore clueless)...

Possible Solutions

a. "Secret" authTokenType
In order to obtain the auth token, the authTokenType must be known; should the authTokenType be treated as a type of client secret, such that a token issued for a given secret token type may be obtained by only those "authorized" client apps which know the secret token type? This does not seem very secure; on a rooted device, it would be possible to examine the auth_token_type column of authtokens table in the system's accounts database and examine authTokenType values that are stored with my tokens. Thus, the "secret" auth token types used across all installations of my app (and any authorized third-party apps used on the device) will have been exposed in one central location. At least with OAuth2 client IDs/secrets, even if they must be packaged with the app, they are spread out among different client apps, and some attempt may be made to obfuscate them (which is better than nothing) to help discourage those who would unpackage/decompile the app.

b. Custom Auth Tokens
According to the docs for AccountManager.KEY_CALLER_UID and AuthenticatorDescription.customTokens, and the AccountManagerService source code I referenced earlier, I should be able to specify that my custom account type uses "custom tokens" and spin my own token caching/storage implementation within my custom authenticator, wherein I can obtain the UID of the calling app in order store/fetch auth tokens on a per-UID basis. Basically, I would have an authtokens table like the default implementation, except there would be an added uid column so that tokens are uniquely indexed on U̲I̲D̲, a̲c̲c̲o̲u̲n̲t̲, and A̲u̲t̲h̲ ̲T̲o̲k̲e̲n̲ ̲T̲y̲p̲e̲ (as opposed to just a̲c̲c̲o̲u̲n̲t̲ and A̲u̲t̲h̲ ̲T̲o̲k̲e̲n̲ ̲T̲y̲p̲e̲). This seems like a more secure solution than using "secret" authTokenTypes, since that would involve using the same authTokenTypes across all installations of my app/authenticator, whereas UIDs vary from system-to-system, and cannot be easily spoofed. Aside from the joyful overhead of getting to write and manage my own token caching mechanism, what downsides are there to this approach in terms of security? Is it overkill? Am I really protecting anything, or am I missing something such that even with such an implementation in place, it would still be easy enough for one malicious app client to obtain another app client's auth token using the AccountManager and authTokenType(s) which are not guaranteed to be secret (assuming that said malicious app does not know the OAuth2 client secret, and therefore cannot directly get a fresh token but could only hope to get one that was already cached in the AccountManager on behalf of the authorized app client)?

c. Send client ID/secret w/ OAuth2 token
I could stick with the AccountManagerService's default token storage implementation and accept the possibility of unauthorized access to my app's auth token, but I could force API requests to always include the OAuth2 client ID and client secret, in addition to the access token, and verify server-side that the app is the authorized client for which the token was issued in the first place. However, I would like to avoid this because A) AFAIK, the OAuth2 spec does not require client authentication for protected resource requests - only the access token is required, and B) I would like to avoid the additional overhead of authenticating the client on each request.

This isn't possible in the general case (all the server gets is a series of messages in a protocol - the code that generated those messages can't be determined). --Michael

But the same could be said of the initial client authentication in the OAuth2 flow during which the client is first issued the access token. The only difference is that instead of authenticating on just the token request, requests for protected resources would also be authenticated in the same way. (Note that the client app would be able to pass in its c̲l̲i̲e̲n̲t̲ ̲i̲d̲ and c̲l̲i̲e̲n̲t̲ ̲s̲e̲c̲r̲e̲t̲ through the loginOptions parameter of AccountManager.getAuthToken(), which my custom authenticator would just pass to my resource provider, per the OAuth2 protocol).


Key Questions

  1. Is it indeed possible for one app to obtain another app's authToken for an account by invoking AccountManager.getAuthToken() with the same authTokenType?
  2. If this is possible, is this a valid/practical security concern within an OAuth2 context?

    You could never rely on an auth token given to a user remaining secret from that user...so it's reasonable for Android to ignore this security by obscurity goal in its design --Michael

    BUT - I'm not concerned about the user (the resource owner) getting the auth token without my consent; I'm concerned about unauthorized clients (apps). If the user wants to be an attacker of his own protected resources, then he can knock himself out. I'm saying it should not be possible that a user installs my client app and, unwittingly, an "imposter" client app that is able to gain access to my app's auth token simply because it passed-in the correct authTokenType and the user was too lazy/unaware/rushed to examine the access request screen. This analogy may be a bit oversimplified, but I don't consider it "security by obscurity" that my installed Facebook app cannot read emails cached by my Gmail app, which is different from me (the user) rooting my phone and examining the cache contents myself.

    The user needed to accept an (Android system provided) access request for the app to use your token... Given that, the Android solution seems OK - apps can't silently use a user's authentication without asking --Michael

    BUT - This is also a problem of authorization - the auth token issued for my "official" client is the key to a set of protected resources for which that client and only that client is authorized. I suppose one could argue that since the user is the owner of those protected resources, if he accepts the access request from a third party client (be it a "sactioned" partner app or some phisher), then he is effectively authorizing the third-party client that made the request to access those resources. But I have issues with this:

    • The average user is not security-conscious enough to be able to competently make this decision. I don't believe we should depend solely on the user's judgment to tap "Deny" on Android's access request screen to prevent even a crude phishing attempt. When the user is presented with the access request, my authenticator could be super-detailed and enumerate all the types of sensitive protected resources (that only my client should be able to access) for which the user will be granting should he accept the request, and in most cases, the user will still be too unaware and is going to accept. And in other, more sophisticated phishing attempts, the "imposter" app is just going to look too "official" for the user to even raise an eyebrow at the access request screen. Or, here's a more blunt example - on the access request screen, my authenticator could simply say, "Do not accept this request! If you are seeing this screen, a malicious app is trying to gain access to your account!" Hopefully, in such a case, most users would deny the request. But - why should it even get that far? If Android simply kept auth tokens isolated to the scope of each app/UID for which they were issued, then this would be a non-issue. Let's simplify - even in the case where I have just one "official" client app, and therefore my resource provider does not even worry about issuing tokens to other, third-party clients, as a developer I should have the option of saying to the AccountManager, "No! Lock-down this auth token so that only my app has access." I can do this if I go along the "custom tokens" route, but even in that case, I would not be able to prevent the user from first being presented with the access request screen. At the very least, it should be better-documented that the default implementation of AccountManager.getAuthToken() will return the same auth token for all requesting apps/UIDs.
    • Even the Android docs recognize OAuth2 as the "industry standard" for authentication (and presumably authorization). The OAuth2 spec clearly states that access tokens are not to be shared between clients or divulged in any way. Why, then, does the default AccountManager implemenation/configuration make it so easy for a client to obtain the same cached auth token that was originally obtained from the service by another client? A simple fix within the AccountManager would be to only re-use cached tokens for the same app/UID under which the they were originally obtained from the service. If there is no locally cached auth token available for a given UID, then it should be obtained from the service. Or at least make this a configurable option for the developer.
    • In the OAuth 3-legged flow (which involves the user granting access to the client), isn't it supposed to be the service/resource provider (and not, say, the OS) which gets to A) authenticate the client and B) if the client is valid, present the user with the grant access request? Seems like Android is (incorrectly) usurping this role in the flow.

    But the user can explicitly allow apps to re-use a previous authentication to a service, which is convenient for the user.--Michael

    BUT - I don't think the ROI in convenience warrants the security risk. In cases where the user's password is being stored in the user's account, then really, the only convenience that is being bought for the user is that instead of sending a web request to my service to get a new, distinct token that is actually authorized for the requesting client, a locally cached token that is not authorized for the client is returned. So the user gains the slight convenience of seeing a "Signing In..." progress dialog for a couple of seconds fewer, at the risk of the user being majorly inconvenienced by having his resources stolen/misused.

  3. Keeping in mind that I am committed to A) using the OAuth2 protocol for securing my API requests, B) providing my own OAuth2 resource/authentication provider (as opposed to authenticating with say, Google or Facebook), and C) utilizing Android's AccountManager to manage my custom account type and its token(s), are any of my proposed solutions valid? Which makes the most sense? Am I overlooking any of the pros/cons? Are there worthwhile alternatives that I have not thought of?

    [Use] Alternative clients Don't have a secret API that attempts to only be accessible to an official client; people will get around this. Ensure all your public facing APIs are secure no matter what (future) client the user is using --Michael

    BUT - Doesn't this defeat one of the key purposes of using OAuth2 in the first place? What good is authorization if all potential authorizees would be authorized to the same scope of protected resources?

  4. Has anyone else felt this was an issue, and how did your work around it? I've done some extensive Googling to try to find if others have felt this to be a security issue/concern, but it seems that most posts/questions involving Android's AccountManager and auth tokens are about how to authenticate with a Google account, and not with a custom account type and OAuth2 provider. Moreover, I could not find anyone that was concerend about the possibility of the same auth token being used by different apps, which makes me wonder whether this is indeed a possibility/worthy of concern in the first place (see my first 2 "Key Questions" listed above).

I appreciate your input/guidance!


In Response to...

Michael's Answer - I think the major difficulties I have with your answer are:

  1. I am still inclined to think of apps as being separate, distinct clients of a service, as opposed to the user/phone/device itself being one "big" client, and therefore a token that has been authorized for one app should not, by default, be transferable to one that has not. It seems like you may be hinting that it is moot to consider each app as a distinct client because of the possibility that,

    the user could be running a rooted phone, and read off the token, gaining access to your private API... [or] if the user's system was compromised (the attacker could read off the token in this case)

    and that therefore, in the grand scheme of things, we should consider the device to be a client of the service since we cannot guarantee security between apps on the device itself. It's true if the system itself has been compromised, then there can be no guarantee of authenticating/authorizing requests being sent from that device to a service. But the same could be said, of say, TLS; transport security is irrelevant if the endpoints themselves cannot be secured. And for the vast majority of Android devices, which are not compromised, I believe it is more secure to consider each app client as a distinct endpoint, instead of lumping them all into one by sharing the same auth token.

  2. When presented with the "access request" screen (akin to the software user license agreement that we always read thoroughly before consenting and installing), I don't trust the user's judgment to distinguish a malicious/unauthorized client app from one that is not.

解决方案

Is this a valid/practical security concern?

For the official Client A, my OAuth2 provider may issue a "super" type/scope token which grants access to both public and private pieces of my API

In the general case, you could never rely on an auth token given to a user remaining secret from that user. For example - the user could be running a rooted phone, and read off the token, gaining access to your private API. Ditto if the user's system was compromised (the attacker could read off the token in this case).

Put another way, there's no such thing as a "private" API that is at the same time accessible to any authenticated user, so it's reasonable for Android to ignore this security by obscurity goal in its design.

a malicious app ... could obtain access to my app's stored OAuth2 token

For the malicious app case, it begins to sound more reasonable that a malicious app shouldn't be able to use the client's token, as we expect Android's permission system to provide isolation of malicious apps (provided the user read / cared about the permissions they accepted when they installed it). However, as you say the user needed to accept an (Android system provided) access request for the app to use your token.

Given that, the Android solution seems OK - apps can't silently use a user's authentication without asking, but the user can explicitly allow apps to re-use a previous authentication to a service, which is convenient for the user.

Possible Solutions Review

"Secret" authTokenType ... does not seem very secure

Agreed - it's just another layer of security through obscurity; it sounds like any app wishing to share your authentication would have had to look up what the authTokenType was anyway, so adopting this approach just makes it a bit more awkward for this hypothetical app developer.

Send client ID/secret w/ OAuth2 token ... [to] verify server-side that the app is the authorized client

This isn't possible in the general case (all the server gets is a series of messages in a protocol - the code that generated those messages can't be determined). In this specific instance, it might protect against the more limited threat of a (non-root) alternative client / malicious app - I'm not familiar enough with the AccountManager to comment (ditto for your custom auth tokens solutions).

Suggestion

You described two threats - malicious apps that a user doesn't want to have access to their account, and alternative clients that you (the developer) doesn't want using parts of the API.

  • Malicious apps: Consider how sensitive the service you are providing is, and if it's not more sensitive than e.g. Google / twitter accounts, just rely on Android's protections (permissions on install, Access Request screen). If it is more sensitive, consider whether your constraint of utilizing Android's AccountManager is appropriate. To strongly protect the user against malicious use of their account, try two factor authentication for dangerous actions (c.f. adding a new recipient's account details in online banking).

  • Alternative clients: don't have a secret API that attempts to only be accessible to an official client; people will get around this. Ensure all your public facing APIs are secure no matter what (future) client the user is using.

这篇关于应该不会的Andr​​oid的AccountManager商店的OAuth令牌对每个应用程序/ UID依据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆