jellyfin / jellyfin-android

Android Client for Jellyfin
https://jellyfin.org
GNU General Public License v2.0
1.46k stars 240 forks source link

Adding Client Certificate Choice Feature for TLS Authentication in Jellyfin Android App #1222

Open vavan11 opened 9 months ago

vavan11 commented 9 months ago

Description: I've configured a LoadBalancer with TLS Authentication in front of my Jellyfin server. This setup works perfectly in Google Chrome, where it prompts me to choose a client certificate for authentication. However, when using the Jellyfin Android app, it fails to offer the same functionality. It appears the app currently lacks the capability to ask users to select a client certificate, a key component for successful TLS Authentication.

bestrocker221 commented 9 months ago

+1 same here. It would be a great, great feature to allow the android client to fetch the client certificate from Android. This way mTLS would work and it would make private Jellyfin instances even more awesome.

nakato commented 9 months ago

I'd like to see this feature as well, as using a VPN has become problematic due to some smart-home apps breaking when a VPN is present.

I used to setup WireGuard with its own RFC1918 network for mobile devices to get seamless access to my internal services while local and roaming, but some apps fail to work when a VPN is enabled. The Phillips NutriU app, which controls kitchen appliances, such as an air fryer, is one such app and the failure feels like an explicit check to me as I can toggle the VPN back on once the app is open and it will continue to work as expected. I'm guessing it is an attempt to mitigate the risk of someone burning their house down while not at home and it's been poorly implemented as it's blocking appliances on the local (WiFi) network as well.

While toggling the VPN is a minor annoyance for me, it's problematic for family, as I configure the VPN and they never touch it. With the smart appliance issue, I've had to disable the VPNs and move services to mTLS where possible. An interesting side effect of this has been I haven't had to fix any devices recently as there isn't a VPN to somehow get disabled.

While Mutual TLS can be used to identify a client and provide AuthN, I don't think the original submitter is asking for this, and I know this is not a feature I care about.

For example, the usage flow would look something like this.

  1. Server administrator provisions Jellyfin instance behind Reverse Proxy, such as Nginx, with Mutual TLS configured. Nginx for example: ssl_client_certificate /store/secrets/nginx/client_certs/ca.crt;
  2. Server administrator generates client key/certificate bundle as user_cert.p12.
  3. User manually provisions user_cert.p12 into Android "User certificates" store.
  4. User opens Jellyfin application, adds the sever and hits connect.
  5. User is presented with the android system certificate selector to select a certificate and hits "Ok".
  6. User is presented with jellyfin login if not already logged in.

The chromecast button being removed would be beneficial as well, as that would not work with such a configuration.

honkphluxx commented 7 months ago

Just a heads-up: I am not a contributer to this project (yet?), but I did pick up this task as a weekend project some weeks ago.

It isnt as straight forward as I had hoped. The Android client currently uses 3 different components to connect, each of which coming with a separate HTTP implementation/abstraction. Because of this and other reasons, this feature isnt really a natural fit for the apps architecture. So, once I (or whoever else) get this working with the current app, there might also be discussions, acceptance issues, etc. That just said to manage expectations early.

components that connect to the server are:

1) Main server communication, implemented in jellyfin-sdk-kotlin is based on ktor. 2) WebView fragment for the content browsing UI 3) Exoplayer currently preferring CronetDataSource as HTTP implementation

Thinking of the UI flow, there are 2 approaches: Either, you can ask the user for a key/certificate, latest at ssl handshake time. Or you have a keystore populated with certificates and Java's crypto picks the right key automatically, based on the root cert shown by the server.

We want to access the key/cert through Android KeyChain api. This is the only api to access keys the user has added through the system settings dialogs. The KeyChain API strictly requires to ask the user for a key, through a dialog. This is seen as permission grant: Once the user has acknowledged the apps permission to use that key, it can read the key at any time in the future.

Ktor (Main server connection) uses common http implementations like OkHTTP. Crypto is done the standard Java way, create a KeyStore holding the keys, setup SslSocketFactory and let Java decide to use keys when needed. I got this working already.

Changes on the WebView fragment are even more straight forward (hypothetically..): WebView has callback OnReceivedClientCertRequest, which is called in the moment the server needs a certificate. This callback is a perfect fit for the KeyChain API. Adding client cert support to a pure webview client requires literally only to implement that callback. You can find an implementation easily. However (and this is my current blocker), once I messed with the SslSocketFactory in jellyfin-sdk-kotlin, WebView somehow tries to use that instead of calling OnReceivedClientCertRequest - and fails. I did successfully run the WebView UI with client cert, but I needed to cheat my way through the server probing with jellyfin-sdk-kotlin. Once I have the "proper" code there, WebView is broken again..

The Exoplayer part is another story. I found a comment from Google somewhere that you need to implement your own DataSource for client auth. I currently have a changed version of DefaultDataSource.java lying around, but I did not yet come to test it..

Thats my summary of the current state. I do have some changes for jellyfin-sdk-kotlin online already here: https://github.com/honkphluxx/jellyfin-sdk-kotlin/tree/client-cert However, thats all WIP right now. My plan is, to get the whole thing into a PoC state, and then approach the community for real feedback.

There will be plenty of points to discuss. A big one is UX: Ideally, the apps UI should not change for existing users. The server indicates that it wants a client cert, we should be able to tap into this signal and react accordingly. However, this would only be a simple thing when the WebView api is used. As a matter of fact, the app starts server probing through jellyfin-sdk-kotlin. There, we have no chance (AFAIK) to tap into the SSL handshake. So, we either have to decide upfront to use client certs (UX change), or we try once normally, then re-try with certs in case of an error. I have looked into the latter briefly, and I saw nginx returning 500 when the cert is missing. 500 is a rather unspecific status, it would be quite a gamble to take this as an indication that we need a cert.

For now, I added a big button on the connect screen, to select a cert.

Another issue is compatibility with existing features. The app tries to make use of all the nice modern features Android has to offer. Like Cronet e.g.: To my knowledgge, neither the Exoplayer CronetDataSource, nor Cronet itself supports client certs. We need to go back to traditional http implementations here. Also, Chromecast will never work with client certs.

And finally, having a Java file in a Kotlin project, copied from Google, initially released under bsd license, does not really improve code cleanliness.

honkphluxx commented 7 months ago

Reconsidering.. once we are there, we either need another component just to tap into the SSL handshake, or make client related settings reachable from beginning.

REZ2O commented 1 week ago

Reconsidering.. once we are there, we either need another component just to tap into the SSL handshake, or make client related settings reachable from beginning.

Any progress on your side?😃