`

Best Practices for Security & Privacy

 
阅读更多

              

Best Practices for Security & Privacy

        本文讨论如何保证你的app的数据安全      

Security Tips

        如何执行各种任务,以及如何保证你的app的数据和用户的数据安全。             

Security with HTTPS and SSL

        如何确保你的app执行网络操作是安全的。       

Developing for Enterprise

        对于企业级app,如何实现设备管理策略。——Enhancing Security with Device Management Policies

      

      Security Tips

        Android有内置于操作系统的安全功能,这显著降低了app安全问题的频率和影响。Anroid系统如此设计,因此,你能基于默认的操作系统和文件权限开发你的app,而不必太多的考虑你的app的安全问题。

        帮助你构建安全的app的一些核心的安全功能包括:

  • The Android Application Sandbox,Android Application 沙箱能把你的app的数据和代码执行与其他的app隔离开来。
  • application framework提供了通用安全功能的强大而稳定的实现,例如加密、权限和安全的IPC(进程间通信)。
  • 像ASLR、NX、Propolice、safe_iop、OpenBSD dlmalloc、OpenBSD calloc和Linux mmap_min_addr等这些技术来降低安全风险和通用的内存管理错误。
  • 加密文件系统防止设备上的数据丢失或被盗。
  • 用户授权机制以限制对系统功能和用户数据的访问。
  • 应用程序定义的权限来控制基于每个app上的application数据。

       然而,熟悉本文给出的Android安全实践和建议也是十分重要。遵循下面的实践建议作为平时的编码习惯将减少无意中引入的对你的用户产生不利影响的安全问题。

      Storing Data

       Android系统上的app共同的最受关注的安全问题你的app保存在设备上的数据是否能被其他app访问。在Android设备上保存数据有如下三种方式:

      Using internal storage

        默认地,你在内部存储器(internal storage)产生的文件仅仅能被你的app访问。这种保护机制是由Android实现的,对几乎所有的application都是有效的。

        一般地,对于IPC文件,你应该不使用MODE_WORLD_WRITEABLE 或者 MODE_WORLD_READABLE 模式。因为它们并没有提供特殊应用对数据访问的限制的能力,也没有提供对数据格式进行控制。如果你想和其他的app进程分享数据,你可以考虑使用content provider,其对其他的app提供了读和写权限,并且能基于特定情况进行动态授权。

       为了对敏感数据进行额外的保护,你可以选择使用一个你的app不能直接访问的key加密本地文件。例如,key能放在KeyStore 里,并且有密码保护,而密码不保存在设备上。由于能监视用户输入密码,这并没有从根本上保护数据,但是当设备丢失时,对没有文件系统加密(file system encryption)时,数据能被保护。   

      Using external storage

       产生在外部存储( external storage)上的文件,例如SD卡,是全局可读写的。因为外部存储能被用户移除,也能被任何app修改,你不应该使用外部存储存储敏感信息。

       像任何来自于不可信任源的数据时,当处理来自于外部存储的数据时你应该执行输入验证(perform input validation )。我们强烈地建议在动态加载之前你不要在外部存储上存储可执行文件或者class文件。如果你确要从外部存储检索可执行文件,在动态加载之前文件应该被签名和密码验证。      

       Using content providers

        Content providers提供结构化存储机制。其能限制和保护你自己的app,或者对其他的app提供可访问接口。如果你不想要其他的app访问你的ContentProvider,在app的manifest文件里声明为android:exported=false 。反之,设置android:exported属性为“true”以允许其他app访问存储数据。

       当产生一个能被其他app访问的ContentProvider时,你能在功能清单文件里指定一个读和写的权限(permission ),或者分开指定读和写的权限。我们建议你限定权限到那些要求完成手边任务的权限上。记住:比起打破已有用户、把用户逼走,为了为用户添加新的功能在以后添加权限对用户来说通常更容易更可接受。

        如果你仅仅在你自己的apps之间通过content provider共享数据,使用android:protectionLevel属性设置

成"signature"保护是更好的。签名权限不需要用户确认,因此当app通过content provider访问被相同的key signed的数据时它们提供了更好的用户体验,更好的访问控制。

        content provider也能提供更小粒度的访问控制,通过声明android:grantUriPermissions属性,并使用在intent对象里的FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION标志来做访问控制。这些权限范围通过 <grant-uri-permission element>能更近一步限定。

        当访问congtent provider时,使用参数化的查询方法例如query()update()delete()以避免来自不信任源的潜在的SQL注入。注意如果selection参数在提交它给方法之前通过和用户数据拼接而构建时,使用参数化方法是不够的。

 

 

        对于写权限不要有一种错误的安全感。Consider that the write permission allows SQL statements which make it possible for some data to be confirmed using creative WHERE clauses and parsing the results。例如,一个黑客可能在通话记录里试探某个电话号码是否存在。只要这个电话号码已存在,他可以通过修改一行来探测。

       如果content provider数据具有可预测的结构,写权限可能等同于提供了读和写权限。   

     Using Permissions

       因为Android的“沙箱”机制隔离把app彼此隔离开来,app必须明确地共享资源和数据。android通过权限声明来做到这一点。app需要这些权限来获取额外的不被基本的sandbox提供的能力,包括访问设备特性,例如照相机。

 

      Requesting Permissions

      我们推荐你的app最下化声明权限的个数,这样能提高用户对你的pp的可接受度,并且使你的app减少黑客袭击。最小化权限声明、不使用敏感权限能减少无意中滥用这些权限的风险。

      如果能设计你的app确保功能实现的情况下不需要任何权限,这是最完美的。例如, rather than requesting access to device information to create a unique identifier, create a GUID for your application (see the section about Handling User Data). Or, rather than using external storage (which requires permission), store data on the internal storage.

      除了使用<permissions>节点请求权限,你也能使用<permissions>来保护IPC(进程间通信)。IPC是安全敏感的,并且会暴露给其他的app,例如像ContentProvider。一般地,相比于用户确定权限声明,我们推荐使用其他的访问控制,因为权限可能使用户混淆和迷惑。例如,对于属于同一个开放者的一组apps之间,在IPC通信间考虑使用signature protection level权限控制。

       不要泄漏权限保护的数据。这通常发生在通过IPC暴露你的app数据时。IPC通信机制一定要求你声明和获取特定的权限,但是不要请求任何客户端的IPC接口的权限。关于更多潜在的影响详情,以及这种类型问题的频率在 USENIX: http://www.cs.be rkeley.edu/~afelt/felt_usenixsec2011.pdf发表在上的论文里有讨论。  

       Creating Permissions

        一般地,你应该努力定义尽可能少的权限来满足你的安全需求。大多数app都不需要产生新的自定义的权限,因为系统定义的权限几乎覆盖了所有的情况。大多数情况下,进行访问检查和权限控制使用已存在的权限。

        如果你必须产生一个新权限,首先考虑使用 "signature" protection level是否能满足你的访问控制要求。签名权限对用户来说是透明的,仅仅在当app执行许可检查时,同一个开放者的相同签名的apps才被允许访问。

       如果你使用"dangerous" protection level产生一个权限,有许多较复杂的地方需要考虑:

  • 权限必须要求有一个字符串,该字符串简明地告诉用户需要的安全权限,用户决定是否继续。
  • 该权限字符串必须国际化以满足不同的语言。
  • 用户可能被权限迷惑或者觉得是潜在风险而选择不安装app
  • 当权限的产生者还没有安装时app就请求了权限。

         如上这些需考虑的地方给作为开发者的你展示了显著的非技术挑战,同时也让用户感到困惑,这即是为什么我们不鼓励"dangerous" permission level的使用。     

     Using Networking

       网络事务根本上是安全的风险,因为它涉及发送用户私有数据。人们正日益意识到移动设备的隐私问题,特别地当设备执行网络操作时。因此,你的app以最优的方式在任何时候都能保护用户的数据安全是十分重要的。     

      Using IP Networking

        在Android上的网络相比于其他的Linux环境来说,并没有特别的不同。最关键点是确保对于敏感的数据使用合适的协议,例如HttpsURLConnection 用于安全的web流。只要服务器端支持HTTPS,比起HTTP我们更倾向使用HTTPS。因为移动设备频繁的链接到网络,而这些网络又是不安全的,例如公共的Wi-Fi热点。

       有身份验证的,socket层加密的通信使用SSLSocket类可以很容易的实现。考虑到Android设备通过Wi-Fi链接到不安全的无线网络的频率,强烈的推荐所有有网络请求的apps使用安全的网络链接。

       我们看到有些app使用localhost网络端口处理敏感的IPC。我们不鼓励这种方式,因为这些接口也能被设备上的其他app访问。因此,你应该使用Android IPC机制,该机制能保证身份验证,例如 Service比起使用回环,更糟糕的方式是使用绑定到的INADDR_ANY,因为那么你的app可能从任何地方接收请求。

      此外,一个老生常谈的常见的问题是确保您不信任来自HTTP或其他不安全的协议下载的数据。这包括在WebView 里的输入验证和来自HTTP的任何响应。    

     Using Telephony Networking

      SMS协议主要设计用于user-to-user通信,对想要传输数据的app不是很合适。由于SMS的局限,我们强烈地推荐使用 Google Cloud Messaging(GCM)和IP网络来从一个web服务器发送数据消息到用户设备上的app。

        注意SMS在网络和设备上既不是加密的也不是很强身份验证的。特别地,任何SMS接收者应该遇到过恶意用户可能向你的app发送SMS——不要依赖不可靠的SMS数据执行敏感命令。还有,你应该知道SMS可能在网路上遭到篡改和拦截。Android设备上,SMS消息通过广播意图来发送,因此SMS可能被声明了READ_SMS 权限的app捕获和读取。

      Performing Input Validation

        缺乏输入验证是影响app的最通用的安全问题之一,不管你的app运行在什么平台上都存在这个问题。Android已做了平台级措施来减少app输入验证问题的暴露,你应该尽可能的使用这些特性和策略。还要注意选择类型安全的预研也能减少输入验证问题的可能性。

        如果你使用native代码,任何来自于文件、网络和IPC的数据都有引入安全问题的风险。最通用的问题有 buffer overflowsuse after freeoff-by-one errors。Android提供了许多技术—例如ASLR和DEP—减少产生这些错误的可能性,但它们并没有解决这些突出问题。你能通过认真的处理指针和管理缓冲区来避免这些问题。

       还有,像JavaScript和SQL这些基于字符串的语言由于转义字符和 script injection也可能遭遇输入验证问题。

       如果你向SQL数据库或者content provider提交了数据查询请求,SQL注入可能是一个问题。解决该问题最后的方式是使用参数化查询,这在上面章节讲content providers时有所讨论。限定权限为只读或者只写权限也能减少潜在的SQL注入相关的安全问题。

      如果你不能使用上面的安全相关建议,我们强烈地推荐使用格式良好的结构化数据,并且与期望格式数据的一致性检查。然而,字符黑名单或者字符替换可能是有效的策略,这些技术在实践中被证明是错误的,应该尽可能避免。

 

      Handling User Data

       一般地,保护用户数据安全的最好方式是尽可能少的使用访问用户敏感和个人数据的API。如果你不得不使用用户数据,如果能避免存储或者传递个人数据信息,就不要存储和传递用户数据。最后,考虑是否可以使用哈希或者不可逆的数据形式来实现你的app的业务逻辑。例如,你可以使用一个email地址哈希后作为主键,而不传输或者存储email地址。这减少了不慎泄漏数据的几率,这也减少了黑客袭击你的app的机会。

        如果你的app需要使用到个人信息例如密码或者用户名,记住某些法律许可或者条款需要您提供隐私政策来解释你使用和存储这些数据。因此遵循最小化访问用户数据的安全实践可能是最简单的措施和规则。

       你也要考虑你的app是否无意中给其他部分暴露了个人信息,例如给第三方广告或者服务组件。如果你不知道一个组件或者服务为什么要求个人数据,不要提供给它提供个人数据。一般地,减少对个人信息的访问将减少这方面潜在的问题。

        如果必须要访问敏感数据,评估信息是否必须要上传到服务器,或者是否可以在客户端执行这些操作。考虑使用敏感数据的代码仅在客户端执行以避免传输用户数据。

        还有,确保你不要无意中由于过份的IPC许可、全局可写的文件或者网络socket而暴露你的用户数据给设备上的其他app。泄漏权限保护数据将在Requesting Permissions部分专门讨论。

       如果GUID被要求,产生一个大的,唯一的数存储它。不要使用电话标识符例如电话号码或者IMEI,因为这些数据可能会暴露个人信息。这个主题更多的信息将在 Android Developer Blog.里讨论。

      注意在Android上写设备端log时,logs是共享资源,对有READ_LOGS 权限的app是可访问的。虽然phone日志数据是临时的,并且在重启时删除,不合适的打印用户信息日志也可能无意中泄漏用户数据给其他的app。        

       Using WebView

        因为WebView消费web内容,这些内容可能包括HTML和JavaScript,不合适的使用可能引入通用的web安全问题,像ross-site-scripting (JavaScript注入)。Android有许多机制减少这些潜在问题的范围,如通过限制WebView的某些功能的使用以最小化满足你的app的功能。

        如果你的app不需要在WebView里直接使用JavaScript,不要调用setJavaScriptEnabled()。一些实例代码使用这个方法,你可能会在这些代码上借鉴或者修改这些代码以为你的app所用,如果你的app不需要,移除该方法的调用。默认地,WebView并不执行JavaScript,因此跨站脚本攻击是不可能的。

        使用addJavaScriptInterface() 方法时要特别注意,因为它允许JavaScript调用app里的普通的java操作方法。如果你使用它,暴露addJavaScriptInterface()给仅仅所有的输入是可信的web页。如果不可信的输入被允许,不可信的JavaScript可能能调用你的app里的Android方法。一般地,我们推荐仅仅addJavaScriptInterface() 暴露给包含在的app的APK里的JavaScript。

        如果你的app在WebView里访问敏感数据,你可能需要使用clearCache() 方法删除存储在本地的任何文件。服务器端头字段例如no-cache也能被用于标识app不应该缓存特别的内容。     

        Handling Credentials

        一般地,我们推荐最小化请求用户证书的频率,最好不要使用——为了使钓鱼攻击更明显,更不容易成功。推荐使用一个用户授权token并刷新它。

        不管什么情况下,用户名和密码都不要存储在设备上。相反,使用由用户提供的用户名和密码,执行初始认证,然后用一个短暂的,特定服务的授权令牌。

        能被多个app使用和访问的Service应该能被AccountManager.访问和使用。如果可能,使用AccountManager类调用云端Service,并且不要在设备上存储密码。

        使用AccountManager检索一个Account后,CREATOR在传给任何证书之前,这样你不可能传递证书给错误的app。

        如果证书仅仅被你产生的app使用,那么你能验证app通过AccountManager使用checkSignature()方法。另外,如果仅仅一个app将使用该证书,你能使用 KeyStore存储该证书。    

      Using Cryptography

        除了提供数据隔离,支持全文件系统加密和提供安全通信渠道,Android提供了各种的算法通过加密保护数据。

        一般地,尽可能使用高水平的已存在的能满足你的业务关切的实现框架。如果你需要从一个你知道的位置安全地检索一个文件,一个简单的HTPPS URI能满足你的需求,而不需要你熟知密码学。如果你需要一个安全通道,可以考虑使用HttpsURLConnection 或者SSLSocket,而不是自己写协议。

 

        如果你发现你确实需要自己实现协议,我们强烈地推荐你不要使用自己实现的加密算法。使用存在的加密算法例如AES或者RSA,这些算法实现在Cipher 类里有提供。

        使用一个安全的随机数产生器,SecureRandom,去初始化加密keys,KeyGenerator。使用非安全随机数产生器产生的key显著地消弱了加密算法的强度,可能产生离线袭击。

        如果你需要存储一个key以重复使用,使用像KeyStore这样的机制,它提供了长期存储和检索加密key的机制。     

       Using Interprocess Communication

       一些app企图使用像网络套接字和共享文件这样的传统的Linux技术来实现IPC。我们不推荐你这样做,我们强烈地推荐你使用Android系统提供的功能实现IPC,例如 IntentBinder或者Messenger配合着Service或者BroadcastReceiver使用。Android IPC机制允许你验证链接到你的IPC的app的一致性,并且设置每一个IPC机制的安全策略。

        一些安全元素能在IPC机制间共享。如果你的IPC机制是不打算给其他的app使用,在组件的manifest元素里设置 android:exported 属性为"false",例如对于<service>元素。这对于在同一个UID里的由多个进程组成的app是很有用的,或者你写到最后,你发现你事实上你不需要通过IPC暴露功能给其他的app,你又不想重写代码,这样你只需简单的在manifest里设置该属性为false。

      如果你的IPC想要被其他的app访问,你能使用<permission>元素作为一个安全策略。如果你的IPC用于你的使用相同key签名的各个app之间,更偏向于使用android:protectionLevel来实现"signature"级权限。  

      Using intents

       在Android里意图是更好的异步IPC机制。取决于你的app需求,你能使用sendBroadcast()或者sendOrderedBroadcast()或者一个显示意图来启动一个专门的app组件。

        注意有序广播可能被一个接收者消费和终结,因此它们可能不被投递到所有的apps。如果你需要投递一个确保到某个接收者的意图,那么你必须使用一个声明了接收者的显式意图。

        意图发送者能指定接收者要有一个非空权限,并在方法调用里验证该权限。仅仅有该权限的app能收到该意图。如果在一个广播意图里的数据是敏感的,你应该考虑使用一个权限来保证恶意app不能注册和接收没有相应权限的广播。这些情况下,你能考虑直接调用接收者,而不是发起一个广播。

        注:意图过滤器不应该考虑作为安全特性——组件能通过显式意图来调用,可能没有符合意图过滤器的数据。你应该在你的意图接收者里执行输入验证来确定对于被调用的广播接收者、service或者activity来说是合适的数据格式。     

        Using services

        Service 常常被用于提供功能给其他的app使用。每一个service类必须在manifest文件里有相应的声明。

        默认地,services不要exported,不能被其他的app调用。然而,如果你给你的Servcie声明添加任何意图过滤器,那么默认是exported。因此,最好清晰地声明android:exported属性来明确serivce的行为是你想要的。Services也能使用android:permission属性来保护。如此做,其他的app将需要在它们的自己的manifest里声明相应的<uses-permission>元素来确保能开启、停止或者绑定service。

        Service能使用权限来保护调用自己的IPC调用,在执行调用实现之前调用checkCallingPermission() 方法来实现。我们推荐在manifest使用声明的权限,因为这些不容易被检测到。    

        Using binder and messenger interfaces

       在Android里使用Binder 或者Messenger是更好的RPC风格的IPC机制。它们提供了定义良好的接口来保证接入点间的相互认真,如果需要。

        我们强烈里推荐设计不需要做权限检查业务的接口。Binder 或者Messenger对象不需要在app的manifest文件里进行声明。因此你不能直接在这些对象上使用声明权限。这些对象继承了实现自己的Service或者activity在manifest里声明的权限。如果你实现了一个要求认证和访问控制的接口,这些控制必须明晰地在 Binder或者Messenger通过代码实现。

        如果提供不需要访问控制的接口,使用checkCallingPermission()验证调用着是否有所需的权限。在访问一个service之前这是特别重要的,因为你的app标识可能传递给其他的app接口。如果调用一个service提供的接口,如果你没有访问被给service的权限,bindService()回调可能失败。如果调用你自己的app提供的接口,可以使用 clearCallingIdentity()满足app内部的安全检查。

        关于在Service里执行IPC的更多信息,参见Bound Services

       Using broadcast receivers

        BroadcastReceiver 处理Intent初始化的异步请求。

        默认地,广播接收着是exported的,并且能被其他的app调用。如果你的BroadcastReceiver 是打算给其他的app使用,你可能需要在manifest文件的<receiver> 元素中使用安全权限。这将阻止没有相应权限的app给 BroadcastReceiver发送意图      

       Dynamically Loading Code

        我们强烈地不建议加载你的app APK之外的代码。这样做由于代码注入和代码捕获显著地增加了app受威胁的风险。这也增加了app版本管理和测试的复杂性。最后,这也使得验证app行为变得不可能,因为这在一些环境里可能被禁止。

        如果你的app确实要动态加载代码,记住关于动态加载代码最重要的方式是它必须和app APK运行有相同的安全权限。用户基于你的标识和描述决定是否安装你的app,这些给用户的知会表明了你提供了运行在app上的代码,包括动态加载的代码。

        动态加载代码最主要的安全风险是代码需要来自于可信的源。如果模块直接包括在你的APK里,那么它们不能被其他的app修正。不管这些代码是native库还是使用DexClassLoader加载的类,这都是对的。我们已看到过一些app企图从不安全的地方加载代码的实例,例如通过非加密的协议从网络下载代码或者从外部存储这样全局可写的地方。这些地方可能导致某些人通过网络篡改传输的数据,或者用户设备上的其他app修改存储在设备上的数据。  

      Security in a Virtual Machine

        Dalvik是Android的运行时虚拟机(VM)。Dalvik是专门为Android设计的,但一些其他虚拟机对代码安全的关注也用到了Android上。一般地,你自己不需要关注与虚拟机相关的安全问题。你的app运行在一个安全的沙箱环境里,因此系统里其他的进程不能访问你的代码或者私有数据。

       如果你对深挖虚拟机安全这方法的知识感兴趣,我们推挤你自学这方面一些已存在的文档。较好的两个文档资源如下:

       本文主要关注于Android于其他的VM环境相比有那些特有的特性和不同点。对于在其他的环境有VM编程经验的开发着来说,写Android App有两点主要不同的地方值得关注。

  •  一些虚拟机,例如JVM或.net运行时,扮演安全边界的角色,从底层操作系统功能隔离代码。Android上,Dalvik VM不是一个安全边界——app 沙箱在OS级上实现,因此Dalvik能和同一个app的native代码进行互操作,而没有任何安全限制。
  • 考虑到移动设备上有限的存储空间,普遍地,开发者可能想要构建模块app,并使用动态类加载。当这样做的时候,要考虑安全因素—你从哪检索你的app逻辑代码和你本地哪儿存储。不要从未验证的源处使用动态代码,例如不安全的网络源或者外部存储,因为代码可能由于恶意的行为而被修改。

       Security in Native Code

        一般地,我们鼓励开发者使用Android SDK来进行app开发。而不是使用Android NDK来开发native代码。使用native代码开放的app将更加复杂,移植性差,并且更可能产生通用的内存崩溃错误例如缓存溢出。

        Android使用Linux内核来实现的,如果你想要使用native代码,熟悉使用Linux开发安全实践是特别有用的。Linux安全开发实践超出本文所讲范围,但最受欢迎的相关资源是“Secure Programming for Linux and Unix HOWTO”,参见http://www.dwheeler.com/secure-programs

        Android和Linux环境之间最重要的不同点是Application Sandbox。在Android上,所有的app都运行在Application沙箱里,包括那些native代码。与Linux类比,开发者可以简单地认为是如同给每个app一个有有限权限的独一无二的UID。更详细的讨论参见 Android Security Overview,你也应该对app权限机制比较熟悉,即使你编写native代码。

 

      Security with HTTPS and SSL

         安全的Sockets层(Secure Sockets Layer :SSL)——现在技术上被成为Transport Layer Security (TLS)——是为了在客户端和服务器端加进行加密通信的通用的构建模块。app不正确的使用SSL可能导致通过网络传输的app数据被恶意拦截。为了确保这不发生在你的app上,本文主要讲述了使用安全网络协议可能碰到的常见的陷阱,并表明了对使用Public-Key Infrastructure (PKI)更大的关切。     

      Concepts

        一个典型的SSL使用图景是服务器端有通过公共的key和私有key进行匹配的验证。作为一个SSL客户端和服务器端进行握手的一部分,服务器端提供给客户端使用 public-key cryptography进行签名验证的私有key。

        然而,任何人都能产生属于自己的证书和私有key。因此一个简单的握手除了能证明服务器知道与公钥相匹配的私钥外并不能证明任何其他的事情。解决该问题的一种方法是让客户端有一个或者多个它信任的证书集合。如果该证书不在该集合里,服务器将是不可信的。

       这种简单方式有几个小缺点。随着时间的推移服务器应该能升级到更强的key("key rotation"),用新的公钥取代证书里的原有的公钥。 不幸地,这时由于服务器端配置的改变,客户端必须进行升级。这将是有问题的,如果服务器不在应用开发者的控制下,例如,可能是一个第三方web Service。这种方法也有问题,如果app需要能和任意服务器例如web浏览器或者email app进行交互。

        为了解决这些问题,服务器配置有从称为数字证书机构(Certificate Authorities (CAs))发行的证书。宿主平台包含了可信的CA列表。像Android 4.2(Jelly Bean),Android当前包含了超过100个CAs,这些CAs将会在每次发布时被更新。与Server端相似,一个CA有一个证书和私钥。当发布一个CA给服务器的时候,CA使用它的私钥签名服务器证书。然后客户端能验证服务器的CA发布的数字证书。

        然而,当解决一些问题的同时,使用CAs又引入另一个问题。因为CA给许多服务器颁发数字证书,你仍然需要一些措施确保你正和你想要的服务器进行交互。为了解决这个问题,由CA颁发的数字证书通过一个特定的名称如gmail.com或者一组主机通配符例如*.google.com来识别服务器。

        下面的例子将使得这些概念更具体点。下面的命令行片段,openssl工具的s_client 命令寻找通配符服务器数字证书信息。它指定443端口因为这是HTTPS协议默认使用的端口。该命令发送openssl s_client的输出给openssl x509。openssl x509命令根据 X.509 standard格式化数字证书信息。特别地,该命令请求主题,该主题包含了服务器名信息和标识CA的发布者信息。

        

$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
         你能看到数字证书是由RapidSSL CA机构颁发给匹配*.wikipedia.org的服务器的。

 

        假设你有一个Web服务器,该服务器有由知名CA机构颁发的数字证书。你能使用下面的简单代码进行安全请求:

        

URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
         是的,真的就这么简单。如果你想要改成HTTP请求,你能转换成HttpURLConnection类。关于HttpURLConnection的Android文档有更多的例子,这些例子演示了如何处理请求和响应头、提交数据、管理cookies、使用代理和缓存响应等。但是就验证数字证书和主机名的更多详情,Android框架提供了相应的APIs来帮你处理这些问题。了解上面这些可能就能满足你想要的。那即是说,下面是一些其他的考虑。         

        Common Problems Verifying Server Certificates

          假设你没有接收到返回的内容,而是扔出如下异常:
       
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
        下面是产生该异常的可能的原因,包括:
         接下来的部分讨论当你链接到安全服务器时如何解决这些问题。

        Unknown certificate authority

        这种情况下,因为你的CA不被系统信任SSLHandshakeException 异常发生。也可能是因为你有新的CA机构颁发的数字证书,而该CA又不被Android系统接收和信任,或者你正运行没有CA认证的旧版本的app。亦或你是用的CA是一个不知名的机构,因为它有可能不是public的,而是像政府、公司或者教育机构等组织颁发的给它们自己使用的私有数字证书。
        幸运的是,你能让 HttpsURLConnection 信任一组CAs集合。该过程可能有点费解,因此下面的例子从InputStream接收一个特定的CA,使用它产生一个KeyStore,该keyStore被用于产生和初始化TrustManagerTrustManager是系统用于验证来自于服务器的数字证书的管理类——从KeyStore里产生一个或者多个CAs的ke——这些仅仅是被 TrustManager信任的CAs。
        考虑到新的TrustManager,下面例子初始化了一个新的SSLContext ,该SSLContext 提供一个SSLSocketFactory,你能使用该SSLSocketFactory来重写来自于 HttpsURLConnection的默认的 SSLSocketFactory 。这种链接方式将使用你的CAs进行数字证书验证。
        下面是完全使用华盛顿大学的CA的例子:
        
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
    ca = cf.generateCertificate(caInput);
    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
    caInput.close();
}

// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
         使用知道你的CAs的定制的TrustManager,系统能验证来自于可信发布机构的你的服务器的数字证书。
         注:~~~~~~
           ~~~~~~
        ~~~~~~~
          

      Developing for Enterprise

         本课你将学习到开发企业级app将用到的APIs和技术。

        Lessons

        本课里,你将学习到如何产生一个有安全意识的app,通过增强设备管理策略来管理对内容的访问。

      Enhancing Security with Device Management Policies

         从Android 2.2(API level 8)开始,Android通过设备管理APIs提供了系统级设备管理能力。
        本节你将学到如何产生一个有安全意识的app,通过增强设备管理策略来管理对内容的访问。特别地,app能配置成在展示受限内容给用户之前能确保它有足够强度的锁屏密码。     

      Define and Declare Your Policy

        首先,你需要定义各种策略来在功能级别上的支持。策略可能覆盖锁屏密码增强、过期超时和加密等。
        你必须声明所选择的策略集,该策略集将被app执行。可以在res/xml/device_admin.xml文件里声明。Android manifest文件也应该应用该声明的策略集。
        每一个声明的策略都与DevicePolicyManager里一些相关的设备管理方法相对应(等于最小的密码长度和最小的大写字符个数就是这样的两个例子)。如果app企图调用与在XML文件里声明的策略相对应的方法时,这将导致一个运行时的SecurityException 。如果app企图管理各种策略,其他的权限,例如 force-lock,是有效的。像后续你将看到的,作为设备管理员激活进程的一部分,声明的策略列表在系统屏幕上展示给用户。
       下面片段声明了密码限制策略,在 res/xml/device_admin.xml里:
        
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-policies>
        <limit-password />
    </uses-policies>
</device-admin>
         在Android manifest文件里引用该策略声明文件:
        
<receiver android:name=".Policy$PolicyAdmin"
    android:permission="android.permission.BIND_DEVICE_ADMIN">
    <meta-data android:name="android.app.device_admin"
        android:resource="@xml/device_admin" />
    <intent-filter>
        <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
    </intent-filter>
</receiver>   

      Create a Device Administration Receiver

        产生一个设备管理广播接收者,该广播接收者将接收与你声明支持的策略相关的事件通知。app能选择性重写回调方法。
       在该例子app里,设备管理 ,当设备管理器由用户停止的时候,配置的策略将从shared preference里删除。你应该考虑实现与你的用例相关的业务逻辑。例如,app可能会采取一些行动如删除设备上的敏感数据,禁用远程同步,同时提醒管理员等来减轻安全风险。
        为了广播接收者能工作,确保在Android的manifest文件里像上面的片段描述的那样进行了注册。
        
public static class PolicyAdmin extends DeviceAdminReceiver {

    @Override
    public void onDisabled(Context context, Intent intent) {
        // Called when the app is about to be deactivated as a device administrator.
        // Deletes previously stored password policy.
        super.onDisabled(context, intent);
        SharedPreferences prefs = context.getSharedPreferences(APP_PREF, Activity.MODE_PRIVATE);
        prefs.edit().clear().commit();
    }
}

        Activate the Device Administrator

         执行任何策略之前,用户需要手动的激活app作为一个设备管理者。下面的代码片段描述了如何触发系统设置页面(settings activity)。在系统设置页面用户能激活你的app。包含解释为什么你的app需要请求成为设备管理员的文本,并将其高亮展示给用户是一个好的做法,通过在intent里指定extra字段值为EXTRA_ADD_EXPLANATION 
        
if (!mPolicy.isAdminActive()) {

    Intent activateDeviceAdminIntent =
        new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);

    activateDeviceAdminIntent.putExtra(
        DevicePolicyManager.EXTRA_DEVICE_ADMIN,
        mPolicy.getPolicyAdmin());

    // It is good practice to include the optional explanation text to
    // explain to user why the application is requesting to be a device
    // administrator. The system will display this message on the activation
    // screen.
    activateDeviceAdminIntent.putExtra(
        DevicePolicyManager.EXTRA_ADD_EXPLANATION,
        getResources().getString(R.string.device_admin_activation_message));

    startActivityForResult(activateDeviceAdminIntent,
        REQ_ACTIVATE_DEVICE_ADMIN);
}
        如果用户选择“Activate”,app变成设备管理员,能开始策略配置和执行。
        app也需要能处理设置返回的情形。这种情况下,用户可能通过点击取消按钮、Back键或者Home键来终止激活过程。
        因此,在策略设置activity的onResume()方法里需要有再次评估条件和是否展示设备管理员激活选项给用户的逻辑。       

        Implement the Device Policy Controller

        设备管理员被成功的激活后,app使用请求的策略来配置设备策略管理器。记住Android每次新的发版都将加入新的策略。当你的app使用新的策略而又需要支持旧的Android平台版本时,执行版本检查是必要的。例如,密码大写字符最小个数限制策略在API 11(Honeycomb)及以上的版本才有效。下面的代码表明了如何在运行时进行版本检查。
       
DevicePolicyManager mDPM = (DevicePolicyManager)
        context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName mPolicyAdmin = new ComponentName(context, PolicyAdmin.class);
...
mDPM.setPasswordQuality(mPolicyAdmin, PASSWORD_QUALITY_VALUES[mPasswordQuality]);
mDPM.setPasswordMinimumLength(mPolicyAdmin, mPasswordLength);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    mDPM.setPasswordMinimumUpperCase(mPolicyAdmin, mPasswordMinUpperCase);
}
        在这点上,app能执行策略。然而app不能使用和访问实际的锁屏密码,通过设备策略管理器API,它能决定现有的密码是否满足所需的策略。如果已有的锁屏密码不能满足要求,设备管理API不会自动的采取纠正措施。明晰地发起Setting app里的系统密码改变屏幕是app的职责。例如:
        
if (!mDPM.isActivePasswordSufficient()) {
    ...
    // Triggers password change screen in Settings.
    Intent intent =
        new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
    startActivity(intent);
}
        正常地,用户能从None、Pattern、PIN(numeric)或者密码(字符数字)等这些锁机制中选择一个有效的锁机制。当配置密码策略后,那些比策略中定义的更弱的密码类型将被禁用。例如,如果数字密码安全性策略被配置,用户仅仅能选择PIN(数字的)或者Password(字母数字的)的密码。
        一旦设备通过设置了锁屏密码来进行安全保护,app允许访问安全的内容。
        
if (!mDPM.isAdminActive(..)) {
    // Activates device administrator.
    ...
} else if (!mDPM.isActivePasswordSufficient()) {
    // Launches password set-up screen in Settings.
    ...
} else {
    // Grants access to secure content.
    ...
    startActivity(new Intent(context, SecureActivity.class));
}
 

 

   

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics