【问题标题】:how to install CA certificate programmatically on Android without user interaction如何在没有用户交互的情况下在 Android 上以编程方式安装 CA 证书
【发布时间】:2012-07-24 07:46:25
【问题描述】:

我正在尝试在不提示用户的情况下安装证书。我知道这不是好的做法,但这正是 PM 想要的。

使用KeyChain.createInstallIntent(),我可以通过调用startActivity让Android启动证书安装对话框。但是,当我将意图传递给sendBroadcast 时,什么也没有发生。也许出于安全原因平台不支持此功能?

String CERT_FILE = Environment.getExternalStorageDirectory() + "/test/IAT.crt";
Intent intent = KeyChain.createInstallIntent();
try {
    FileInputStream certIs = new FileInputStream(CERT_FILE);
    byte [] cert = new byte[(int)certFile.length()];
    certIs.read(cert);
    X509Certificate x509 = X509Certificate.getInstance(cert);
    intent.putExtra(KeyChain.EXTRA_CERTIFICATE, x509.getEncoded()); 
    intent.putExtra(KeyChain.EXTRA_NAME, "IAT Cert");
    EapActivity.this.startActivityForResult(intent, 0);  // this works but shows UI
    EapActivity.this.sendBroadcast(intent);  // this doesn't install cert
} catch (IOException e) {

【问题讨论】:

  • 没有接收器在监听 Intent - 只是系统中的一个活动,并且有充分的理由 - 允许任何恶意随机应用程序静默安装根 CA 将是一个巨大的安全漏洞。

标签: android android-intent x509certificate android-wifi


【解决方案1】:

使用KeyChain.createInstallIntent(),我可以通过调用startActivity让Android启动证书安装对话框。但是,当我将意图传递给 sendBroadcast 时,什么也没有发生。

您将传递给startActivity()Intent 对象很少会与sendBroadcast() 一起使用。它们是Intent系统准消息总线的独立通道。

【讨论】:

  • 而且,有没有人知道如何检测证书安装意图? (在用户输入证书密码和名称之后?)我没有找到检查我的 CA 安全证书是否正确安装的奇怪方法。甚至在用户输入密码提取证书之前,当我请求 KeyStore 时,证书已经存在,所以我不知道用户是否完成了安装。
【解决方案2】:

如果您有系统权限,您只能静默安装证书。显示确认对话框是故意的,因为信任证书可能会产生严重后果——Android 可以愉快地打开网络钓鱼站点而不会发出警告等。也就是说,ICS/JB 中的对话框非常糟糕——它不会告诉你什么您正在安装的证书以及谁颁发的证书,只是它是 CA 证书,这很明显。

因此,要么使用公共 KeyChain API 并使用 startActivity() 来获取确认对话框,要么在将设备处理给用户之前预先配置设备。

更新:在 Android 4.4 中,DevicePolicyManager 有一个隐藏的 API (installCaCert),允许您静默安装证书。您需要MANAGE_CA_CERTIFICATES 权限,即signature|system,因此对于用户安装的应用程序仍然不可行。

【讨论】:

  • 它还强制您使用数字 PIN 码或锁定屏幕的解锁图案来保护您的设备,这在用户安全方面有点烦人但可以理解。我猜他们想确保安装证书的人也将是设备的所有者。
  • 类似的东西,但是如果没有密码/PIN 开头,您可以将其设置为您想要的任何内容。然后使用密码/PIN 导出主密钥以加密私钥。证书也用它加密,这不是绝对必要的。
  • installCaCert 已在 SDK 21 中可见,并且显然可供设备管理员使用。
  • @RupertRawnsley AFAIK,仅当您的设备管理员也是设备所有者(“超级管理员”)时才有效
  • 设备所有者是我的新人。通过 NFC 访问系统 - 可能出现什么问题? ;-)
【解决方案3】:

对于非系统应用程序开发人员 - 简单的答案是没有用户交互就无法完成。

对于系统应用程序开发人员,我找到了以下解决方案,注意您必须使用系统用户 ID 运行应用程序并使用系统密钥对应用程序进行签名,否则服务将拒绝您安装证书的尝试。

第 1 步 - 创建界面

在您的项目中创建一个新包:android.security,然后将IKeyChainService.aidl 复制到此包中。

第 2 步 - 绑定到服务并安装证书

Activity 给出了如何安装 CA 证书的示例:

public class KeyChainTest extends Activity {

    private final Object mServiceLock = new Object();
    private IKeyChainService mService;
    private boolean mIsBoundService =false;

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override public void onServiceConnected(ComponentName name, 
                                                    IBinder service) {
            synchronized (mServiceLock) {
                mService = IKeyChainService.Stub.asInterface(service);
                mServiceLock.notifyAll();
                try {

                    byte[] result = YOUR_CA_CERT_AS_BYTE_ARRAY

                    //The next line actually installs the certificate
                    mService.installCaCertificate(result);

                } catch (Exception e) {
                    //EXception handling goes here
                }
            }
        }

        @Override public void onServiceDisconnected(ComponentName name) {
            synchronized (mServiceLock) {
                mService = null;
            }
        }
    };

    private void bindService() {
        mIsBoundService = bindService(new Intent(IKeyChainService.class.getName()),
                mServiceConnection,
                Context.BIND_AUTO_CREATE);
    }

    private void unbindServices() {
        if (mIsBoundService) {
            unbindService(mServiceConnection);
            mIsBoundService = false;
        }
    }

    @Override public void onDestroy () {
        unbindServices();
    }


    @Override
    protected void onStart() {
        super.onStart();
        // Bind to KeyChainService
        bindService();
    }
}

我希望这对某人有所帮助 - 我花了很长时间才解决这个问题 :)

【讨论】:

  • 这在我们的应用程序共享 UID 之前是不可能的。 final String actualPackage = getPackageManager().getNameForUid(getCallingUid()); if (!expectedPackage.equals(actualPackage)) { throw new IllegalStateException(actualPackage); } 验证调用者。你能解释一下在不共享 UID 的情况下你是如何管理的吗?
  • @Pankaj - 我们在系统应用程序的 Android Manifest 的 sharedUserId 属性中使用 sysytem uid,也许这就是为什么我不需要像您那样以编程方式进行操作。
  • E AndroidRuntime: java.lang.NoSuchMethodError: No interface method installCaCertificate([B)Ljava/lang/String; in class Landroid/se curity/IKeyChainService; or its super classes (declaration of 'android.security.IKeyChainService' appears in /system/framework/framework.jar)
  • 通过查看DevicePolicyManagerIKeyChainServiceKeychainConnection keychainConnection = KeyChain.bind(context); keychainConnection.getService() 获得。对于 System App 开发者,不想乱搞DevicePolicyManager,我们可以通过反射获取IKeyChainService。无论如何,我尝试并成功安装了第三方 cacert,但安全设置屏幕显示了一个“三角形”,警告用户以某种方式安装了第三方 cacert。似乎Android系统知道它不是来自“标准方式”,虽然它不影响用例..
  • 我无法让第一步工作。我已经在 /src/main/aidl/android.security 中导入了 IKeyChainService.aidl。我必须评论第 30 行 UnsupportedAppUsage。但是由于 couldn't find import for class android.content.pm.StringParceledListSlice,该项目也不会构建。当我创建这些时使用parcelable StringParceledListSlice; 等手动缺少类。然后编译仍然失败:error: package android.security.keymaster does not exist public int attestKey(..android.security.keymaster.KeymasterCertificateChain c.. 欢迎所有帮助!
【解决方案4】:

只有系统用户应用程序可以静默安装 CA 证书。不过,在 Lollipop 上,Google 通过 DevicePolicyManager 引入了静默证书管理 API,但您必须是 Android-for-Work 配置文件所有者或设备所有者。

【讨论】:

    【解决方案5】:

    如果你有root权限,你可以将证书文件复制到/data/misc/user/0/cacerts-added/

    【讨论】:

    • 嗨。我尝试这样做,起初它似乎可以工作,但是当我需要配置的应用程序尝试连接到其服务器时,由于证书问题而失败。除了将证书复制到该文件夹​​之外,还有其他事情要做吗?
    • @SebastiánRodriguez 您使用的是哪个版本?
    • 什么版本?在我提出问题之后,我成功地安装了证书,并将其复制到/data/misc/user/0/cacerts-added/,其名称显然是唯一的,并且由 Android 内部管理。我还必须 chmod 和 chown 复制的证书。
    【解决方案6】:

    根据@ospider 的回答,我设法以这种方式成功安装了证书:

    adb shell mkdir -p /data/misc/user/0/cacerts-added
    adb push certificate.cer /data/misc/user/0/cacerts-added/807e3b02.0
    
    # Maybe these two lines are not strictly necessary...
    adb shell chmod 644 /data/misc/user/0/cacerts-added/807e3b02.0
    adb shell chown system:system /data/misc/user/0/cacerts-added/807e3b02.0
    

    我通过手动安装我想要自动化的证书并查看 Android 如何保存它(adb shell ls -l /data/misc/user/0/cacerts-added/)获得了复制文件的名称(807e3b02.0


    希望对您有所帮助。

    问候。

    【讨论】:

      【解决方案7】:

      这个帖子已经有点过时了,但是因为我偶然发现了同样的问题并且找不到任何适用于 Android O 或更高版本的“开箱即用”解决方案,我想我会分享我的想法和在尝试将证书(CA 和其他证书)安装到 Android 受信任的凭据“用户”存储时,这对我来说效果很好:

      // Supply context, e.g. from "Context context = getApplicationContext();"
      // String fileName points to the file holding the certificate to be installed. pem/der/pfx tested.
      RandomAccessFile file = new RandomAccessFile(fileName, "r");
      byte[] certificateBytes = new byte[(int)file.length()];
      file.read(certificateBytes);
      
      Class<?> keyChainConnectionClass = Objects.requireNonNull(context.getClassLoader()).loadClass("android.security.KeyChain$KeyChainConnection");
      Class<?> iKeyChainServiceClass  = Objects.requireNonNull(context.getClassLoader()).loadClass("android.security.IKeyChainService");
      
      Method keyChainBindMethod = KeyChain.class.getMethod("bind", Context.class);
      Method keyChainConnectionGetServiceMethod = keyChainConnectionClass.getMethod("getService");
      Object keyChainConnectionObject = keyChainBindMethod.invoke(null, context);
      Object iKeyChainServiceObject = keyChainConnectionGetServiceMethod.invoke(keyChainConnectionObject);
      
      Method installCaCertificate = iKeyChainServiceClass.getDeclaredMethod("installCaCertificate", byte[].class);
      installCaCertificate.invoke(iKeyChainServiceObject, certificateBytes);
      

      请注意,如果您想以这种方式静默安装证书,您的应用需要是系统应用,即它需要具有

      android:sharedUserId="android.uid.system"
      

      在它的清单中声明。

      干杯!

      【讨论】:

      • 这给了我一个错误,即“java.lang.NoSuchMethodException: android.security.KeyChain.bind [class android.content.Context]” 知道我该怎么做吗?跨度>
      • 您使用的是哪个 Android 版本,@vidulaJ?任何最近的 Android 版本中的 KeyChain 类肯定都应该包含“bind”方法,参见官方来源:android.googlesource.com/platform/frameworks/base.git/+/master/… 在撰写本文时,“bind”方法在第 879 行和源文件中定义。即使我们回到 Jelly Bean (Android 4.1),该方法仍然存在,请参见此处的第 410 行:android.googlesource.com/platform/frameworks/base/+/refs/heads/…
      • 我在 Android 10 中运行此代码,即使我尝试直接从 KeyChain 或 KeyChainConnection 声明对象的方法。但即使这样做,public static KeyChainConnection bind(@NonNull Context context) 也会抛出 InterruptedException,无法访问。想不出我的代码有什么问题。
      • 另外,我在官方文档中找不到'bind'方法。 developer.android.com/reference/kotlin/android/security/…
      • 当然“bind”不在官方文档中,它被有意标记为“@hide”标志,以防止开发人员访问此方法。此类方法仅供 Android 操作系统内部使用,但您可以从我在 2020 年 7 月 22 日发布的代码示例中看到,您可以使用反射 API 来访问这些方法,提供您的应用具有特权系统级访问权限,即需要使用目标设备的平台密钥进行签名,否则您无法在应用的清单中声明所需的 android:sharedUserId。
      猜你喜欢
      • 2014-12-16
      • 1970-01-01
      • 2017-03-20
      • 1970-01-01
      • 2014-11-17
      • 2012-02-19
      • 2018-10-31
      • 1970-01-01
      相关资源
      最近更新 更多