dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.23k stars 1.57k forks source link

[Android] Choose client certificate from KeyChain #50669

Closed mohachouch closed 6 days ago

mohachouch commented 1 year ago

Hello,

We want to use flutter/dart for a large application in France.

The application must be able to retrieve the client certificate from the Android KeyChain.

Android side (Kotlin):

val certificateChain = KeyChain.getCertificateChain(mainActivity.applicationContext, certificateAlias)?.get(0);
val privateKey = KeyChain.getPrivateKey(mainActivity.applicationContext, certificateAlias);

Dart side: The security context only accepts an array of bytes.

We will begin migrating from Xamarin in January. Is it possible to do this? If not, we will have to go to another technology.

Thank you

mraleph commented 1 year ago

/cc @brianquinlan

brianquinlan commented 1 year ago

Hey @mohachouch,

Could you explain your use case here? If you were going to use a Kotlin API, what would you do with privateKey?

It is possible to get the byte representation of a java.security.Key with [Key.getEncoded()](https://developer.android.com/reference/java/security/Key#getEncoded()). Assuming that it is an X509 key then you should be able to load it into a SecurityContext with setTrustedCertificateBytes

Let me know if that is not what you are trying to do.

Cheers, Brian

mohachouch commented 1 year ago

Hey @brianquinlan,

Thanks for you help.

As part of our company, we have an Android tablet application that allows very secure operations.

For this, https exchanges are secured by a client certificate. The certificate is automatically sent to the tablets and I do not have direct access to the certificate.

Currently we use Xamarin, we have direct access to the native Http Client so we can easily pass the KeyStore certificate to the HTTP session but in flutter, I don't know how to do it.

val certificateChain = KeyChain.getCertificateChain(mainActivity.applicationContext, alias)?.get(0);
val privateKey = KeyChain.getPrivateKey(mainActivity.applicationContext, alias);

certificateChain .encoded is not null but not working BAD_PKCS12_DATA 
privateKey.encode is null 

I tried to pass the certificate to the security context, but I got a BAD_PKCS12_DATA error

I'm a bit lost and I don't know exactly how to retrieve the client certificate from Android KeyChain and how to pass it to the security context.

Thanks

mohachouch commented 1 year ago

I tried to solve the problem all day, the client authentication works fine when I add the test certificate in the assets, but impossible from the KeyChain. Unable to retrieve certificate and private key.

I'm starting to tell myself that it's not possible and that I'll have to use the native http client

Please help me, I have to present a proof of concept of the project to my superiors

brianquinlan commented 1 year ago

Hi @mohachouch,

What call fails: SecurityContext.useCertificateChain or SecurityContext.usePrivateKey?

useCertificateChain and usePrivateKey expect the data to be in PEM or PKCS12 format.

Certificate.encoded is ASN.1 DER format.

PEM is just base64 encoded DER with a header and footer.

If you can use the basic_utils package then a crypto function might help.

Like crlDerToPem (though that might put the wrong header and footer).

You can make your own converter based on the header/foots that you need i.e.

final privateKey = formatKeyString(base64.encode(bytes),
    X509Utils. BEGIN_PRIVATE_KEY, X509Utils.END_PRIVATE_KEY)
final certChain = formatKeyString(base64.encode(bytes),
    X509Utils.BEGIN_CERT, X509Utils.END_CERT)
mohachouch commented 1 year ago

Thank you @brianquinlan for the explanation of the different certficate formats.

For certficate retrieval on Kotlin I use this function

val certificateChain = KeyChain.getCertificateChain(mainActivity.applicationContext, alias)!!.get(0);

With the conversion to the PEM format, the certificate, it's OK, Thanks you

But, I still have a problem for the private key

For private key retrieval on Kotlin I use this function :

val privateKey = KeyChain.getPrivateKey(mainActivity.applicationContext, alias);

val encoded = privateKey.encoded

The problem is that the value of encoded is null.

The type of my private key is : AndroidKeyStoreRSAPrivateKey

The implementation in android is to return null for encoded

https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r25/keystore/java/android/security/keystore/AndroidKeyStoreKey.java

The key store documentation said : Once keys are in the keystore, you can use them for cryptographic operations, with the key material remaining non-exportable. https://developer.android.com/training/articles/keystore

Do you have an idea ?

Thanks

brianquinlan commented 1 year ago

@mohachouch - how do you use val certificateChain = KeyChain.getCertificateChain(mainActivity.applicationContext, alias)!!.get(0); in your current code?

mohachouch commented 1 year ago

I use platform channels to switch from Kotlin to flutter.

I don't think the Keychain can be accessed from flutter.

KeyChain.choosePrivateKeyAlias allows me to retrieve the alias certificate that the user has selected

KeyChain.choosePrivateKeyAlias

KeyChain.getCertificateChain allows me to retrieve the certificate

val certificateChain = KeyChain.getCertificateChain(mainActivity.applicationContext, alias)!!.get(0);

KeyChain.getPrivateKey allows me to retrieve the private key

val privateKey = KeyChain.getPrivateKey(mainActivity.applicationContext, alias);

But the problem is that it is of type AndroidKeyStoreRSAPrivateKey and the value of getEncoded() is null

So I feel like I couldn't extract the private key from the Android KeyChain to Flutter and I'll have to use the native http client

mohachouch commented 1 year ago

@brianquinlan Can you confirm that this is not possible with the current http client implementation? Thanks

brianquinlan commented 1 year ago

It is not possible using dart:io HttpClient but it might be possible using another dart HTTP client.

Could you tell me how you are using this in your existing Xamarin application? I'd like to understand what APIs you are using.

For example, are you using a custom SSLSocketFactory?

mraleph commented 1 year ago

Out of curiosity I poked a bit around in BoringSSL and Android to understand if this sort of thing is supported and how.

It seems that if we wanted to support this in our SecureSocket we would need to implement a BoringSSL ENGINE which can perform operations with opaque private keys. (example from AOSP source here). The operations have to be performed by talking to keystore service over IPC. Here is some example of how keystore client in C++ could look like.

This is all very poorly documented territory.

brianquinlan commented 1 year ago

@mraleph We'd also have to expose PrivateKey (or similar) as a Dart API.

mohachouch commented 1 year ago

Yes @brianquinlan, wa are using a custom SSLSocketFactory with native http client OkHttp

I will share an sample un GitHub tomorow

mraleph commented 1 year ago

@mraleph We'd also have to expose PrivateKey (or similar) as a Dart API.

Yeah, we would need some API changes. Maybe we don't explicitly need PrivateKey but something like

class SecurityContext {
  void setClientCertificateFromKeyStore(String keyStoreAlias);
}

Though this might not be all too portable.

mohachouch commented 1 year ago

Yeah @mraleph

We can add an AndroidSecurityContext

  class AndroidSecurityContext extends SecurityContext {
    Future<String> askAliasFromKeyStore()
    void setClientCertificateFromKeyStore(String keyStoreAlias);
  }
mohachouch commented 1 year ago

Hey @brianquinlan, here is an github gist of using the private key and certificate in Native/Koltin

https://gist.github.com/mohachouch/c079ca23568cc75c6a50c0f4f924f51d

I think the native implementation in dart can be very interesting. Flutter is starting to be used more and more in business and I have no doubt that other people will have the same need.

Thanks

brianquinlan commented 1 year ago

Hey @mohachouch,

As far as I know, there is nothing in the Dart ecosystem that will allow you to do this without writing a native plugin.

If you decide to take the plugin approach, it would probably end up looking like package:cronet_http but you'd wrap OkHttp instead of Cronet.

mohachouch commented 1 year ago

Hey @brianquinlan

I started the implementation with okhttp. I got a lot of inspiration from cronethttp (Thanks)

https://github.com/mohachouch/native_http_client

It's not production ready but it's work 😉

brianquinlan commented 1 year ago

Hey @mohachouch,

I see that your repo is now non-public. Did you decide on a different approach?

mohachouch commented 1 year ago

Hey @brianquinlan,

Oup's, I think now it's ok, the repo is public

We'll start by using this approach. But we really hope it will be supported by the Dart team. (Native client or dart client with client certificate from keychain)

Thanks

a-siva commented 6 days ago

Closing issue here and will track this feature in https://github.com/dart-lang/http/issues/1237