前言:
这是一篇关于Android安全的翻译,这是原文地址。这篇文章主要讲了通过 KeyChain
安装和使用私有证书的方法。证书相关处理在 Android 应用开发中并不常见,但是在企业应用中是不可或缺的。
我会一段一段地复制原文,然后在下面给出翻译,如有图片,我会重新保存再上传,以免出现“图片无法显示的情况”,因为原文不能直接访问。
原文:
Using the ICS KeyChain API
November 29, 2011
译文:
使用 ICS (冰激凌三明治安卓4.0系统代号) KeyChain 应用编程接口
2011年11月29日
原文:
Update: Sample app code is now available on github.
译文:
更新:示例应用代码现在在 github 上。
原文:
The recently released Android 4.0 (Ice Cream Sandwich, ICS) introduces a new, unified UI for both tablets and handsets, lots of 'people-centric' communication and sharing features and other convenient improvements such as a better camera app and the much-hyped face unlock. Since everyone is talking about those, we will have a look at some of the less-user visible, but nonetheless important security-related improvements.
译文:
最近发布的 Android 4.0 (冰激凌三明治, ICS) 介绍了一个全新的,统一平板和手机的 UI 。很多'以人为中心'的交互和共享特性,以及其它方便的改进,例如更好的相机应用,过度炒作的人脸解锁。由于每个人都在讨论这些,我们要看看那些用户很少看见,但却是重要的安全相关的改进。
原文:
Android is often said to be missing crucial security features to be seriously accepted in the corporate world, which has long been the domain of RIM's BlackBerry. Two of those missing features were the ability to control the system's trusted CA certificates and offer a centralized secure credential storage. Since many companies use private PKI's, the ability to install trusted certificates system-wide is essential for using corporate services secured by those PKI's. Until now, the only way to use those was to embed the needed CA certificates in each application and create custom TrustStores to be able to connect using SSL. A system-wide credential storage has actually been available for a while, but it was only usable by the built-in VPN and WiFi (EAP) clients. One could install a private key/certificate pair using the Settings app, but there was no public API to access the installed keys from applications. ICS offers SDK API's for both trusted certificate management and the secure credential storage via the KeyChain class. We will have a look at how it is used in the following sections.
译文:
Android 经常被说成缺失了重要的安全特性,并且被企业界严肃地接受了,长久以来这是 RIM (译注:加拿大RIM公司)黑莓的领域。两个缺失的特性是:控制系统的受信任 CA 证书的能力,提供一个中心化的证书存储。由于很多公司使用私有的 PKI(Public Key Infrastructure 公钥基础设施),在系统层面安装信任证书对于使用受 PKI 保护的企业服务是必不可少的。直到现在,使用这些所需 CA 证书的唯一途径是嵌入到每个应用中,并且创建自定义的 TrustStores,用 SSL 连接。一个系统层面可用的证书存储已经存在一段时间了,但它只对内置的 VPN 和 WiFi (EAP) 客户端可用。人们可以通过“设置”应用安装私有的凭据/证书对 (key/certificate) 。但是没有通过应用实现安装的公开 API 。通过 KeyChain
类,ICS 给信任证书管理和安全证书存储提供了 SDK API。我们在下面的部分要看看它如何使用。
原文:
The KeyChain
class is deceptively simple: it offers only 4 public static methods, but those are sufficient to do most certificate-related tasks. Let's first see how one would install a private key/certificate pair and use those to sign and verify some data. The KeyChain
API lets you install a private key/certificate pair bundled in a PKCS#12 file. Instead of offering an API to directly install the key and certificate, KeyChain
provides a factory method, createInstallIntent()
that returns a system intent to parse and install keys/certificates (that is actually the same intent offered by the Settings app in previous versions). To install a PKCS#12 file, you have to read it to a binary array, store it under the EXTRA_PKCS12
key in the intent's extras, and start the associated activity:
译文:
KeyChain
类看似简单,它提供了4个公开的静态方法,但这些方法对大多数证书相关工作来说足够了,首先让我们看看如何安装一个私有的凭据/证书对,并用它签名和验证一些数据。KeyChain
API 让你安装一个包装在 PKCS#12 文件中的私有的 key/certificate 对。而不是提供一个 API 来直接安装私钥和证书,KeyChain
提供了一个工厂方法 createInstallIntent()
,它返回一个系统 intent 来解析和安装 keys/certificates , (它实际上和以前版本中设置应用提供的 intent 是相同的)。要安装一个 PKCS#12 文件,你必须把它读进一个二进制数组,把它存储在 intent extras
的 EXTRA_PKCS12
字段中,然后启动相关的 activity
:
Intent intent = KeyChain.createInstallIntent();
byte[] p12 = readFile("keystore-test.pfx");
intent.putExtra(KeyChain.EXTRA_PKCS12, p12);
startActivity(intent);
原文:
This will prompt you for the PKCS#12 password in order to extract and parse the key and certificate. If the password is correct, you will be prompted for a 'certificate name' as shown in the screenshot below. If the PKCS#12 has a friendly name attribute it will be shown as the default, if not you will just get a long hexadecimal hash string. The string you enter here is the key/certificate alias you will use to access those later via the KeyChain API. You will be prompted to set a lock screen PIN or password to protect the credential storage if you haven't already set one.
译文:
为了解压并解析凭据和证书,这会提示你输入 PKCS#12 的密码,如果密码正确,会提示输入‘证书名称’,像下面的截屏展示的一样。如果 PKCS#12 有一个友好的名称属性,它默认会被显示,如果没有,你会得到一个很长的十六进制 hash 串。你在这儿输入的字符串是key/certificate的别名,在后面你要用它们通过 KeyChain
API 访问 key/certificate。如果你还没有设置过,会提示你设置一个锁屏 PIN (Personal Identification Number,个人识别密码),或者密码来保护证书存储,
原文:
To use a private key stored in the system credential storage, you need to call KeyChain.choosePrivateKeyAlias() and provide a callback implementation that receives the selected alias:
译文:
要用系统证书存储中的一个私有的密钥库,你需要调用 KeyChain.choosePrivateKeyAlias()
并且提供一个回调实现,它接收已选的别名:
public class KeystoreTest extends Activity implements OnClickListener,
KeyChainAliasCallback {
@Override
public void onClick(View v) {
KeyChain.choosePrivateKeyAlias(this, this,
new String[] { "RSA" }, null, null, -1, null);
}
@Override
public void alias(final String alias) {
Log.d(TAG, "Thread: " + Thread.currentThread().getName());
Log.d(TAG, "selected alias: " + alias);
}
}
原文:
The first parameter is the current context, the second -- the callback to invoke, and the third and forth specify the acceptable keys (RSA, DSA or null for any) and acceptable certificate issuers for the certificate matching the private key (Edit: it turns out both keyTypes and issuers are currently unused, so just pass null). The next two parameters are the host and port number of the server requesting a certificate, and the last one is the alias to preselect. We leave all but the key type as unspecified (null or -1) here to be able to select from all available certificates. One thing to note here is that the alias() callback will not be called on the main thread, so you shouldn't try to directly manipulate the UI (it is called on a binder thread). Using the key requires user authorization, so Android will display a key selection dialog which also serves to allow access to the selected key.
译文:
第一个参数是当前的 context
, 第二个是回调,第三第四是可接受的密钥类型(RSA, DSA,或者 null 表示所有)和可接受的证书发行人,为了让证书匹配私钥。(修订:发现 keyTypes
和 issuers
当前都没有用,所以只传了 null)接下来的两个参数是请求证书的服务器主机和端口号,最后一个是预选的别名。除了密钥类型,这里其它的都不去指定(null 或者 -1),这样可以选择所有可用的证书。需要注意的是 alias()
回调不会在主线程调用,因此你不应该尝试直接操作 UI (它在一个 binder
线程调用)。使用密钥需要用户授权,因此 Android 会显示一个私钥选择对话框,这个对话框还用来同意访问已选的私钥。
原文:
In order to get a reference to a private key, you need to call the KeyChain.getPrivateKey() method passing the key alias name received in the previous step. This doesn't seem to be documented but if you try to call this method on the main thread you will get an exception saying that this may 'lead to a deadlock'. Here we call it on a background thread using AsyncTask (which is almost always the right thing to do when dealing with potentially time-consuming I/O operations).
译文:
为了得到一个私钥的引用,你需要调用 KeyChain.getPrivateKey()
方法,传递前一步接收到的私钥别名。看起来没有文档说明,但当你尝试在主线程调用这个方法,你会得到一个异常,说这可能会 ‘导致死锁’。这里我们在一个后台线程调用它,用 AsyncTask
(当处理潜在的耗时 I/O 操作,这几乎总是正确的)。
new AsyncTask<Void, Void, Boolean>() {
private Exception error;
@Override
protected Boolean doInBackground(Void... arg) {
try {
PrivateKey pk = KeyChain.getPrivateKey(ctx,
alias);
X509Certificate[] chain = KeyChain.getCertificateChain(ctx,
alias);
byte[] data = "foobar".getBytes("ASCII");
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initSign(pk);
sig.update(data);
byte[] signed = sig.sign();
PublicKey pubk = chain[0].getPublicKey();
sig.initVerify(pubk);
sig.update(data);
boolean valid = sig.verify(signed);
Log.d(TAG, "signature is valid: " + valid);
return valid;
} catch (Exception e) {
e.printStackTrace();
error = e;
return null;
}
}
@Override
protected void onPostExecute(Boolean valid) {
if (error != null) {
Toast.makeText(ctx, "Error: " + error.getMessage(),
Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(ctx, "Signature is valid: " + valid,
Toast.LENGTH_SHORT).show();
}
}.execute();
原文:
We first get the private key and certificate chain using the key alias and then create and verify a signature to check if the key is actually usable. Since we are using a self-signed certificate the 'chain' consists of a single entry, but for a certificate signed by a CA you will need to find the actual end entity certificate in the returned array.
译文:
首先我们利用密钥别名得到私钥和证书链(certificate chain),然后创建并且验证一个签名(signature),来检查密钥是否真正可用。由于我们使用一个自定义的证书,所以 chain
由一个单独的实体组成。但对于一个 CA 签署的证书,你需要在返回的数组中查找到真正的证书实体。
原文:
Installing a CA certificate is not very different from installing a PKCS#12 file: you load the certificate in a byte array and pass it as an extra to the install intent.
译文:
安装一个 CA 证书和安装一个 PKCS#12 文件差别不大,把证书加载到一个字节数组,作为一个 extra
传递到安装的 intent
。
Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert);
startActivity(intent);
原文:
Android will parse the certificate, and if it's Basic Constraints extension is set to CA:TRUE it will consider it a CA certificate and import it into the user trust store. You will need to authenticate to import the certificate, but the funny thing is that the import dialog does not show neither the certificate DN, nor its hash value. The user has no way of knowing what they are importing, until it's done. Very few people will bother to actually check, so this could be a potential security threat: malicious applications might trick people into installing rogue certificates. Here's how the import dialog looks:
译文:
Android 会解析证书,如果它的基本约束扩展(Basic Constraints extension)被设置成 CA:TRUE
,它会被当成是一个 CA 证书,并导入到用户信任存储。你需要授权导入证书,但有趣的是,导入对话框既不显示证书 DN (Distinct Name 独特的名称),也不显示它的哈希值。用户没有办法知道他们在导入什么,直到导入完成。很少有人会真正地费心检查,因此这会是一个潜在的安全威胁:恶意的应用可能会欺骗用户安装流氓证书,导入对话框看起来是这样的:
原文:
After the certificate is imported, it will show up in the 'Trusted credentials' screen's 'User' tab (Settings->Security->Trusted credentials). Tapping the certificate entry displays a details dialog, where you can (finally!) check the subject, issuer, validity period, serial number and SHA-1/SHA-256 fingerprints. You can also remove the certificate by pressing the 'Remove' button (scroll down to display it).
译文:
证书导入以后,它会在 Trusted credentials
页面的 User
tab 中显示(设置->安全->信任的证书)。点击证书条目,显示一个详情对话框,这里你能(终于!)检查主题、发行者、有效期限、序列号和 SHA-1/SHA-256 指纹。你也可以通过点击 'Remove' 按钮删除证书(下滑显示)。
原文:
While you can delete individual CA certificates, there is no way to delete individual keys and user certificates. You can delete all by using the 'Clear credentials' option in the Credential storage section of the security settings. Another thing to note is that, as long as you have keys in the credential storage, you cannot remove the screen lock, since it is used to protect access to the keystore. In previous Android versions, there was a separate 'credential storage password', but it seems in ICS they decided to simplify things by using the screen lock password to protect credential storage as well.
译文:
虽然你能删除个人的 CA 证书,但没有办法删除个人的密钥和用户证书。你可以使用安全-设置-证书存储
区域的 '清除证书' 选项删除所有的证书。另一个要注意的事是一旦你有私钥在证书存储中,你就不能删除屏幕锁,由于它被用来保护 keystore
的访问。在之前的 Android 版本,有一个单独的'证书存储密码'('credential storage password'),但好像在 ICS 他们决定简化,用锁屏密码保护证书存储。
原文:
The newly introduced KeyChain API lets you install and access private keys in a centralized and secure credential storage, as well as add system-wide trusted certificates. It doesn't provide low-level access to the underlying keystore, utilizing the Android intent dispatching mechanism instead to call a system activity that does the actual work. The CA certificate install dialog is missing a crucial feature (displaying details about the certificate), but all in all, providing the access to the system keystore service is a step in the right direction.
译文:
新引入的 KeyChain
API 让你在一个中心化的安全证书存储中安装和访问私钥,同样添加系统层面的受信任证书。它没有提供 keystore 之下的下层访问,利用 Android intent
的分发机制代替调用做实际工作的系统 Activity
。CA 证书安装对话框缺少了一个关键特性(显示证书详情),但总得来说,提供系统 keystore 服务的访问是朝着正确方向的一个进步。
原文:
That wraps the first part of our Android keystore introduction. In the next part we will look into all that is hidden behind the KeyChain
facade, and try to give some details about the underlying implementation.
译文:
这覆盖了我们的 Android keystore 介绍的第一部分。在下一部分我们要调查 KeyChain
后面的一切,并且尝试给出一些关于底层的实现。