Android Java更新Android KeyStore中的证书和私钥 [英] Android java updating certificate and private keys in Android KeyStore

查看:115
本文介绍了Android Java更新Android KeyStore中的证书和私钥的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用HTTPS客户端证书进行身份验证的系统,但是证书本身是根据以下过程生成的:

I have a system that uses HTTPS client certificates to authenticate, but the certificates themselves are generated, according to the following process:

  1. 客户端设备生成证书(包括公钥和私钥)
  2. 客户端设备将公钥发送到服务器,服务器对公钥进行签名,并将其作为签名证书返回
  3. 客户端以安全的方式存储证书,然后将其用作HTTPS客户端证书

我们已经将该系统在iOS上运行,并且我试图移植到android,但是由于Android的文档记录薄弱且安全的API令人困惑,因此遇到了很多问题.

We have this system working on iOS, and I'm trying to port across to android, but encountering a lot of problems with Android's poorly documented and confusing security API's.

我的代码大致如下:

生成证书

keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);

Date startDate = new Date();
Date endDate = new Date(startDate.getTime() + FORTY_YEARS_IN_MILLISECONDS);

KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
        .setAlias(alias)
        .setKeySize(2048)
        .setKeyType(KeyProperties.KEY_ALGORITHM_RSA)
        .setSubject(new X500Principal("CN=" + alias))
        .setSerialNumber(BigInteger.TEN)
        .setStartDate(startDate)
        .setEndDate(endDate)
        .build();

KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE);
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair(); // this will put a certificate and key pair in the keyStore.
dumpKeyStore(keyStore);

byte[] entireKey = keyPair.getPublic().getEncoded();
// chop off first 24 bytes; the java key pair generator puts an object ID of  1.2.840.113549.1.1.1 RSA (RSA_SIGN) before the key which gets mangled when the server signs and sends back the certificate
byte[] publicKeyBytes = Arrays.copyOfRange(entireKey, 24, entireKey.length);

dumpKeyStore 是一种实用程序方法,它迭代密钥库,调用 keyStore.getEntry 来获取每个条目,并仅记录事物.此时,它将报告具有给定别名的单个条目,并且其类型为 KeyStore.PrivateKeyEntry .它具有关联的证书和公共密钥,可以从 PrivateKeyEntry 中获取.

dumpKeyStore is a utility method which iterates the keystore, calls keyStore.getEntry to get each entry and and just logs things. At this point, it reports that there is a single entry with the given alias, and it is of type KeyStore.PrivateKeyEntry. It has an associated certificate and public key which can be retrived from the PrivateKeyEntry.

发送到服务器

publicKeyBytes 发送到服务器,服务器将其作为新的签名x509证书的公钥,该证书将在响应中发送回去.我没有输入代码,这只是基本的网络.根据我的判断,返回的证书已加载并且看起来还不错.

publicKeyBytes is sent to the server, which puts it as the public key for a new, signed x509 certificate, which is sent back in the response. I haven't put code in, it's just basic networking. The returned certificate loads and looks fine from what I can tell.

保存和关联证书

我正在尝试使用相同的别名将其放入keyStore,因此(理论上)它可以与早期的正确私钥相关联.到目前为止,我的代码是这样的:

I'm trying to put it into the keyStore with the same alias, so it (in theory) can be associated with the correct private key from earlier. My code thus far is like this:

KeyStore keyStore;
try {
    keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
    keyStore.load(null);
}catch (IOException | NoSuchAlgorithmException | CertificateException e) {
    Log.wtf(TAG, e);
    throw new FatalError(TAG, e);
}

CertificateFactory certificateFactory;
try {
    certificateFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
    Log.wtf(TAG, e);
    throw new FatalError(TAG, e);
}

Certificate cert = certificateFactory.generateCertificate(new ByteArrayInputStream(certificateFromServer));

// find the existing certificate, copy it's private key out, then replace the certificate with the one from the server but keeping the private key
try {
    KeyStore.PrivateKeyEntry existingPrivateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);

    KeyStore.PrivateKeyEntry newEntry = new KeyStore.PrivateKeyEntry(existingPrivateKeyEntry.getPrivateKey(), new Certificate[]{ cert });
    keyStore.setEntry(alias, newEntry, null);
} catch (Exception e) {
    Log.wtf(TAG, e);
    throw new FatalError(TAG, e);
}
dumpKeyStore(keyStore);

在这一点上,最终的dumpKeyStore指示存在具有正确别名的条目,但是在尝试调用 keyStore.getEntry

At this point, the final dumpKeyStore indicates that there is an entry with the correct alias, however it gets a "NoSuchAlgorithmException: Unknown key entry" exception thrown when it tries to call keyStore.getEntry

我要执行的操作(替换证书但保留私钥)是否可能在android中进行?如果是这样,我该怎么办?看来这真的没有用

Is what I'm trying to do (replace a certificate but keep the private key) possible in android? If so, how might I do it? It seems like this isn't really working

谢谢

猎户座

推荐答案

事实证明,我做错了事.您不需要替换或修改KeyStore中的证书,只需在初始化 HttpsURLConnection 使用的 SSLContext 时使用自定义的 KeyManager code>和KeyManager可以选择您想要的任何证书或私钥.

As it turns out, I was doing the wrong thing. You don't need to replace or modify the certificates in the KeyStore, you just need to use a custom KeyManager when you initialize the SSLContext used by HttpsURLConnection and the KeyManager can pick whichever certificates or private keys you'd like.

这大大简化了KeyStore管理.现在是我的情况

This simplifies the KeyStore management greatly. My scenario is now

  1. 使用 KeyPairGenerator (别名为 X
  2. )生成公用/专用密钥对.
  3. 将公共密钥发送到服务器,该服务器从该公共密钥生成新的签名证书,然后将其发送回
  4. 使用别名为 X-Signed
  5. setCertificateEntry 将此签名证书放入KeyStore中.
  1. Generate a public/private key pair using KeyPairGenerator with an alias of X
  2. Send the public key to the server which generates a new signed certificate from that public key, and and sends it back
  3. Put this signed certificate in the KeyStore using setCertificateEntry with an alias of X-Signed

当我建立 HttpsURLConnection 时,它是这样的:

When I establish the HttpsURLConnection, it goes like this:

KeyStore androidKeyStore = KeyStore.getInstance(LocalKeyStore.ANDROID_KEYSTORE);
androidKeyStore.load(null);

X509Certificate signedClientCertificate = (X509Certificate)androidKeyStore.getCertificate("X-Signed");
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)androidKeyStore.getEntry("X", null);

X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
        return clientCertificateAlias;
    }
    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
        return null; // different if you're validating the server's cert
    }
    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return new X509Certificate[] { signedClientCertificate };
    }
    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return new String[]{ "X" };
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return null; // different if you're validating server's cert
    }
    @Override
    public PrivateKey getPrivateKey(String alias) {
        if(alias != clientCertificateAlias) {
            Log.e(TAG, String.format("X509ExtendedKeyManager is asking for privateKey with unknown alias %s. Expecting it to ask for %s", alias, clientCertificateAlias));
            return null;
        }
        return privateKeyEntry.getPrivateKey();
    }
};

X509TrustManager trustServerCertificates = new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // do nothing, this method doesn't get called
    }
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) 
        // code to validate server's cert in here
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null; // any issuer
    }
};

m_sslContext = SSLContext.getInstance("TLS");
m_sslContext.init(new KeyManager[]{ keyManager }, new TrustManager[] { trustServerCertificates }, null);

// later on

conn = (HttpURLConnection)url.openConnection();
SSLContext sslContext = m_sslContext;

if(conn instanceof HttpsURLConnection && sslContext != null) {
    ((HttpsURLConnection)conn).setSSLSocketFactory(sslContext.getSocketFactory());
}

这对我来说很好,并且我可以继续使用 AndroidKeyStore 及其按应用程序隐私和硬件支持的存储方式

This is working well for me, and I can keep using AndroidKeyStore with it's per-app privacy and hardware-backed storage

这篇关于Android Java更新Android KeyStore中的证书和私钥的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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