Используйте аппаратные ключи в SSLContext - PullRequest
2 голосов
/ 15 апреля 2019

Я бы хотел использовать ключи с аппаратной поддержкой для взаимной TLS на стороне клиента на моем Android. Ключ должен быть разблокирован с помощью биометрии.

Я нашел, как генерировать аппаратные пары ключей на Android:

KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyGenerator.initialize(
    new KeyGenParameterSpec.Builder(myAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
        .setUserAuthenticationRequired(true)
        .build());
keyGenerator.generateKeyPair();

и как разблокировать закрытый ключ с аппаратной поддержкой с помощью отпечатка пальца:

FingerprintManager fingerprintManager = (FingerprintManager) this.getSystemService(Context.FINGERPRINT_SERVICE);
PrivateKey key  = (PrivateKey) keyStore.getKey(myAlias, null);
Cipher cipher = Cipher.getInstance(cipherAlgorithm, "AndroidKeyStore");
cipher.init(Cipher.DECRYPT_MODE, key);
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, authenticationCallback, null);

Я также могу настроить свой HttpClient для использования клиентских сертификатов:

// I have loaded the PrivateKey privateKey and Certificate certificate from PEM files
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null);
final char pseudoSecretPassword[] = ("##" + System.currentTimeMillis()).toCharArray();
keyStore.setKeyEntry(
    PKIModule.DEFAULT_KEYSTORE_ALIAS,
    privateKey,
    pseudoSecretPassword,
    new Certificate[] {certificate}
);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, pseudoSecretPassword);
KeyManager[] keyManagers = kmf.getKeyManagers();
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagers, trustManagers, new SecureRandom());
OkHttpClient newClient = new OkHttpClient.Builder()
    .sslSocketFactory(sslContext.getSocketFactory())
    .build();

Однако я не нашел способа напрямую разблокировать закрытый ключ с аппаратной поддержкой для использования в KeyManager, используемом SSLContext, потому что механизм разблокировки работает с криптообъектами, а не с закрытыми ключами.

Как настроить совместную разблокировку биометрического ключа и клиентские сертификаты TLS на Android?

Обновление

После точек @pedrofb я обновил свой код для генерации пары ключей с KeyProperties.PURPOSE_SIGN и KeyProperties.DIGEST_NONE. Я подписал пару ключей клиента с помощью CA, который был импортирован в хранилище доверенных сертификатов сервера. А также создание клиентского KeyManager на основе AndroidKeyStore:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keyStore, null);
KeyManager[] keyManagers = factory.getKeyManagers();
sslContext.init(keyManagers, trustManagers, new SecureRandom());

Однако это не с

W/CryptoUpcalls: Preferred provider doesn't support key:
W/System.err: java.security.InvalidKeyException: Keystore operation failed
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1256)
        at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1281)
        at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
        at android.security.keystore.AndroidKeyStoreSignatureSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreSignatureSpiBase.java:219)
        at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineInitSign(AndroidKeyStoreSignatureSpiBase.java:99)
        at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineInitSign(AndroidKeyStoreSignatureSpiBase.java:77)
        at java.security.Signature$Delegate.init(Signature.java:1357)
        at java.security.Signature$Delegate.chooseProvider(Signature.java:1310)
        at java.security.Signature$Delegate.engineInitSign(Signature.java:1385)
        at java.security.Signature.initSign(Signature.java:679)
        at com.android.org.conscrypt.CryptoUpcalls.rawSignDigestWithPrivateKey(CryptoUpcalls.java:88)
        at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
        at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:383)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:231)
        at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
        at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
        at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:107)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:87)
        at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:221)
        at okhttp3.RealCall.execute(RealCall.java:81)
        at com.jemmic.secuchat.biometriccrypto.MainActivity.testMutualTLS(MainActivity.java:402)
        at com.jemmic.secuchat.biometriccrypto.MainActivity.access$300(MainActivity.java:87)
        at com.jemmic.secuchat.biometriccrypto.MainActivity$TestMutualTlsTask.doInBackground(MainActivity.java:315)
        at com.jemmic.secuchat.biometriccrypto.MainActivity$TestMutualTlsTask.doInBackground(MainActivity.java:311)
        at android.os.AsyncTask$2.call(AsyncTask.java:333)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
W/System.err: Caused by: android.security.KeyStoreException: Incompatible padding mode
        at android.security.KeyStore.getKeyStoreException(KeyStore.java:1159)
        ... 43 more
W/CryptoUpcalls: Could not find provider for algorithm: NONEwithRSA

1 Ответ

0 голосов
/ 15 апреля 2019

Некоторые соображения:

  • Ключ не имеет аппаратной поддержки , если устройство не имеет аппаратной поддержки.Вы можете проверить, хранится ли ключ на защищенном оборудовании, с помощью KeyInfo.isInsideSecurityHardware().

  • Для TLS требуется цифровая подпись, но ваш ключ создан для целей шифрования .Вам нужно изменить KeyProperties.PURPOSE_ENCRYPT на KeyProperties.PURPOSE_SIGN

  • FingerprintManager инкапсулирует использование объекта Signature, но не расширяет java.security.KeyStore требуется по умолчанию KeyManager

TLS требует непосредственного управления закрытым ключом, так как протокол TLS выполняет подпись для части общих данных во время рукопожатия с помощью определенного алгоритма.Чтобы использовать FingerprintManager, базовый поставщик криптографии должен поддерживать его напрямую.

Я полагаю, что вы можете получить тот же результат, выполнив следующее:

1 - разблокируйте нужный ключ с помощью отпечатка пальца

2 - предоставьте AndroidKeyStore значение KeyManagerFactory

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null,null);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, pseudoSecretPassword);

Это решение, я думаю, могло бы работать, но вам нужно будет связать сертификат с закрытым ключом, который соответствует списку принятых CA, которые сервер отправляет , поэтому вам придетсявыдать сертификат с использованием открытого ключа и сохранить его в хранилище ключей Android, связанном с закрытым ключом.

Вы не упомянули, собираетесь ли вы использовать решение этого стиля, довольно сложное

Если вы не собираетесь использовать сертификаты, вы можете написать свой KeyManager длявосстановить права PrivateKey во время рукопожатия TLS.Посмотрите на мой ответ здесь, он довольно похож на ваш вариант использования, но с использованием AndroidKeyChain вместо AndroidKeyStore

Запрос с автоматическим или пользовательским выбором соответствующего сертификата клиента

...