Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Choose client certificate from KeyChain #50669

Closed
mohachouch opened this issue Dec 9, 2022 · 21 comments
Closed

[Android] Choose client certificate from KeyChain #50669

mohachouch opened this issue Dec 9, 2022 · 21 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-io os-android P2 A bug or feature request we're likely to work on triaged Issue has been triaged by sub team

Comments

@mohachouch
Copy link

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

@lrhn lrhn added os-android area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. labels Dec 12, 2022
@mraleph
Copy link
Member

mraleph commented Dec 12, 2022

/cc @brianquinlan

@brianquinlan
Copy link
Contributor

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(). 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
Copy link
Author

mohachouch commented Dec 13, 2022

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
Copy link
Author

mohachouch commented Dec 13, 2022

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
Copy link
Contributor

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
Copy link
Author

mohachouch commented Dec 13, 2022

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
Copy link
Contributor

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

@mohachouch
Copy link
Author

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

@brianquinlan brianquinlan added library-io area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io-triaged P2 A bug or feature request we're likely to work on and removed area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. labels Dec 13, 2022
@mohachouch
Copy link
Author

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

@brianquinlan
Copy link
Contributor

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
Copy link
Member

mraleph commented Dec 14, 2022

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
Copy link
Contributor

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

@mohachouch
Copy link
Author

mohachouch commented Dec 14, 2022

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

I will share an sample un GitHub tomorow

@mraleph
Copy link
Member

mraleph commented Dec 15, 2022

@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
Copy link
Author

mohachouch commented Dec 15, 2022

Yeah @mraleph

We can add an AndroidSecurityContext

  class AndroidSecurityContext extends SecurityContext {
    Future<String> askAliasFromKeyStore()
    void setClientCertificateFromKeyStore(String keyStoreAlias);
  }

@brianquinlan brianquinlan added the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Dec 15, 2022
@mohachouch
Copy link
Author

mohachouch commented Dec 15, 2022

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

@github-actions github-actions bot removed the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Dec 15, 2022
@brianquinlan
Copy link
Contributor

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
Copy link
Author

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 😉

@a-siva a-siva added triaged Issue has been triaged by sub team and removed library-io-triaged labels Dec 20, 2022
@brianquinlan
Copy link
Contributor

Hey @mohachouch,

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

@mohachouch
Copy link
Author

mohachouch commented Jan 10, 2023

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

@lrhn lrhn added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. and removed area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. labels Apr 21, 2024
@a-siva
Copy link
Contributor

a-siva commented Oct 30, 2024

Closing issue here and will track this feature in dart-lang/http#1237

@a-siva a-siva closed this as completed Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-io os-android P2 A bug or feature request we're likely to work on triaged Issue has been triaged by sub team
Projects
None yet
Development

No branches or pull requests

5 participants