bauerj / paperless_app

An Android/iOS app for Paperless
https://play.google.com/store/apps/details?id=eu.bauerj.paperless_app
GNU General Public License v3.0
527 stars 46 forks source link

Self-Signed Certificates in OS Trust Store are Ignored (Not Properly Validating Certificate Chain) #84

Open hydrian opened 2 years ago

hydrian commented 2 years ago

Describe the bug Android App error produces when trying to connect with a trusted cert chain.

To Reproduce Steps to reproduce the behavior:

  1. Install Paperless app from Google Play store
  2. Enter Paperless-NG HTTPS url
  3. See error Screenshot_20220509-105715

Expected behavior No errors about an insecure connection.

Screenshots Here is my cert chain Screenshot_20220509-105747

Screenshot_20220509-110604

Screenshot_20220509-110014

If you look at the fingerprint in the error, it is complaining about the Intermediate Root CA SHA-1 hash. I suspect the cert validation is not following the cert chain properly.

Additional information Paperless App Version: e.g. 0.1.2 (Google Play Stable) Do you use Paperless NG as a backend: Yes

bauerj commented 2 years ago

Hey,

I suspect this has nothing to do with your certificate chain but with the way Flutter makes network connections. TLS is implemented in Dart instead of using the native OS API. This means that Flutter will not use the OS certificate store to validate certificates. More details can be found in https://github.com/bauerj/paperless_app/issues/19

We could only implement this work-around with the red alert dialog that you see to allow self-signed certificates to be used server-side.

Until we have this properly fixed, I'll leave this issue open.

bauerj commented 2 years ago

By the way, what I wrote above is only what we could piece together about this. It may be possible that this just needs a configuration change on our side but everything we tried failed.

hydrian commented 2 years ago

I'll take a deeper look. I'm a junior Flutter dev, so I'll see what options we have.

hydrian commented 2 years ago

https://github.com/flutter/flutter/issues/41781#issuecomment-702948726

https://developer.android.com/training/articles/security-config

hydrian commented 2 years ago

I haven't been able to test this yet since I don't have a build environment configured yet, but here is example of what looks like needs to be configured. https://github.com/bauerj/paperless_app/compare/master...hydrian:master

SpiderD555 commented 2 years ago

I am not a developer, but what @qcasey created in his branch was a step in the right direction, where this solution goes few steps further until a success. Can one of you smart guys take a look there ? I am setting new paperless server, so I may test it if someone wishes to port the solution to paperless app.

qcasey commented 2 years ago

At a glance, that solution does look promising

bauerj commented 2 years ago

I haven't been able to test this yet since I don't have a build environment configured yet, but here is example of what looks like needs to be configured. master...hydrian:master

This looks exactly like what @qcasey did in 388055b6745041e9908bf0335d0d85380d68f709. Please feel free to test this again, maybe we just did something wrong or there was a different bug preventing success.

where this solution goes few steps further until a success

After a quick look, this seems to hard-code a trusted root CA into the app which is kind of the opposite of what we want to achieve here. Please correct my if I'm wrong.

qcasey commented 2 years ago

After a quick look, this seems to hard-code a trusted root CA into the app which is kind of the opposite of what we want to achieve here. Please correct my if I'm wrong.

Yeah you're right, I missed that part; it's been a while :smile: It's essentially what I was describing here I suppose: https://github.com/bauerj/paperless_app/issues/19#issuecomment-754239437

The last option I've seen is to manually include your root CA certificate in assets/ and pubspec, then set up Dio to compare bad certificates to that fingerprint. This requires building paperless_app yourself of course, but is the most complete and secure solution.

SpiderD555 commented 2 years ago

Please correct me if I am wrong (I am not a Dart expert), but shouldn't it be possible to feed the app a trusted root certificate using a security context function setTrustedCertificates ? I see a file parameter, so shouldn't it be possible to make a function to read the root certificate file first (let the user choose it from disk), store it somewhere in application data, and use it subsequently in https connection calls ?

bauerj commented 2 years ago

Yes, sure, but isn't this essentially what where doing right now? I mean we currently only store the cert fingerprint instead of the entire certificate but functionally it should be equivalent. I think from a UX-view the current method is better since no files have to be copied and opened.

hydrian commented 2 years ago

Having to import a trusted CA per an app sucks, especially if your user base is on the larger size.

hydrian commented 2 years ago

I think there may be another issue going on here too. When I enter my system trusted paperless-NG URL, I get the error. But if press 'NO' or force close/stop the app, when I relaunch the app, it goes directly to the login screen with no errors.

It obvious that app is saving the URL regardless of the untrusted or not status. Not sure if the automatically going to the login screen is because the TLS chain is trusted and the error is the false positive or if fingerprint is just being saved regardless of status and then let through the second time.

hydrian commented 2 years ago

I got a debugger on it last night but got tired. I'll try again tonight.

johnstef99 commented 3 weeks ago

I had a similar problem. I wanted to use an SSL proxy like Burp or Proxyman to intercept HTTPS requests from my Flutter app. The first issue was that Flutter doesn't use the system's proxy settings. I solved that by using the http_proxy plugin. However, I noticed that the http_proxy plugin was allowing any certificate by using:

client.badCertificateCallback =(X509Certificate cert, String host, int port) => true;

I thought "ok lets remove that" since I installed to my device the custom certificate generated by Proxyman my certificate will be trusted and it will not get marked as bad certificate. My assumption though was correct only for iOS. It looks like the HttpClient on iOS is automatically trusting the user installed certificates, but on android that wasn't the case.

So I ended up creating an android specific plugin flutter_user_certificates_android that gets the user installed CA certificates from the devices and I modified the http_proxy plugin so that it uses flutter_user_certificates_android to trust the certificates for any HttpClient (modified http_proxy -> flutter_system_proxy)