Используйте аппаратные ключи в SSLContext

1

Я хотел бы использовать аппаратные ключи для клиентского взаимного 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
Теги:
security
keystore
sslcontext

1 ответ

0

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

  • Ключ не имеет аппаратного обеспечения, если устройство не имеет аппаратной поддержки. Вы можете проверить, хранится ли ключ на безопасном оборудовании, с помощью 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

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

  • 0
    Хорошие KeyInfo.isInsideSecurityHardware() о необходимости проверки KeyInfo.isInsideSecurityHardware() и необходимости KeyProperties.PURPOSE_ENCRYPT и KeyProperties.PURPOSE_SIGN . Кроме .setDigests(KeyProperties.DIGEST_NONE, ...) может потребоваться .setDigests(KeyProperties.DIGEST_NONE, ...) ( github.com/google/conscrypt/issues/497 ).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню