>客户端设备生成证书(包括公钥和私钥)
>客户端设备将公钥发送到服务器,该服务器对公钥进行签名,并将其作为签名证书返回
>客户端以安全的方式存储证书,然后将其用作HTTPS客户端证书
我们有这个系统在iOS上工作,我正在尝试移植到android,但遇到了很多问题,因为Android的文档记录不清,安全API混乱.
我的代码大致如下:
生成证书
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中重新获得.
发送到服务器
publicKeyBytes被发送到服务器,服务器将其作为新的签名x509证书的公钥,该证书将在响应中发回.我没有放入代码,它只是基本的网络.返回的证书加载,看起来很好.
保存并关联证书
我试图用相同的别名将它放入keyStore,因此它(理论上)可以与之前的正确私钥相关联.到目前为止我的代码是这样的:
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); } 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); } dumpKeyStore(keyStore);
此时,最终的dumpKeyStore指示存在具有正确别名的条目,但是当它尝试调用keyStore.getEntry时会引发“NoSuchAlgorithmException:UnkNown key entry”异常.
我想在android中做什么(替换证书但保留私钥)?如果是这样,我该怎么做?看起来这不是真的有效
谢谢
猎户座
解决方法
这极大地简化了KeyStore管理.我的情景现在
>使用KeyPairGenerator生成公钥/私钥对,别名为X.
>将公钥发送到服务器,该服务器从该公钥生成新的签名证书,然后将其发回
>使用带有别名X-Signed的setCertificateEntry将此签名证书放入KeyStore
当我建立HttpsURLConnection时,它是这样的:
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,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,它的每个应用程序隐私和硬件支持的存储