Skip to content

feat: add support for Android user-installed certificates#19830

Closed
jfly wants to merge 2 commits intoimmich-app:mainfrom
jfly:support-android-user-certs
Closed

feat: add support for Android user-installed certificates#19830
jfly wants to merge 2 commits intoimmich-app:mainfrom
jfly:support-android-user-certs

Conversation

@jfly
Copy link
Contributor

@jfly jfly commented Jul 9, 2025

Description

This is part of #15230.

Frustratingly, Dart/Flutter ignores user-installed certificates. Working around this requires rooting your Android device to install certificates as "system" certs, which isn't an option for everyone.

This is a known issue with Dart, see
dart-lang/sdk#50435 and
flutter/flutter#56607 for details.

I have read through
#15230 and #13555, and I understand that switching to
cronnet_http would also resolve this. While that may be the correct long-term approach, it looks like there are a lot of network codepaths in Immich, and as I know basically nothing about Dart, nor Flutter, nor Immich's codebase, I thought this would be a better short term approach.

This depends on my fork of
johnstef99/flutter_user_certificates_android, which I've sent a PR for here
johnstef99/flutter_user_certificates_android#2. If y'all don't like the supply chain implications of that, I'm happy to inline the implementation here instead.

How Has This Been Tested?

I have step-ca and DNS configured so that immich.mm resolves to an immich server with a certificate issued by my step-ca certificate authority. I've installed that certificate authority as a user-installed certificate on my Android phone.

  • With a fresh install of immich, I can configure https://immich.mm as a server
  • After logging in, I can view photos, upload photos and video, and play video.

Screenshots (if appropriate)

2025-07-08_18-06-21_pattern

The help text on this option is no longer true. I'd suggest that we deprecate it in favor of encouraging people to add certificates to their OS:

2025-07-08_17-59-03_pattern

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation if applicable
  • I have no unrelated changes in the PR.
  • I have confirmed that any new dependencies are strictly necessary. Strictly? No. See my offer above about to inline the implementation if that's preferred.
  • I have written tests for new code (if applicable)
  • I have followed naming conventions/patterns in the surrounding code
  • All code in src/services/ uses repositories implementations for database calls, filesystem operations, etc.
  • All code in src/repositories/ is pretty basic/simple and does not have any immich specific logic (that belongs in src/services/)

@bo0tzz bo0tzz changed the title Add support for Android user-installed certificates feat: add support for Android user-installed certificates Jul 9, 2025
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no way to make this opt-in, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, but everything I know about android development I learned in the last week while researching this issue.

@jfly jfly force-pushed the support-android-user-certs branch 2 times, most recently from 8546274 to 01f0a57 Compare July 9, 2025 18:21
@shenlong-tanwen
Copy link
Member

@jfly Hey, Thanks a lot for working on this. However, if we are to merge this in, rather than deprecating the existing "Allow Self signed certificates" toggle, we could just remove all the custom verification logic all together. We don't need a lot of different codepath to address the same issue. However, this would mean that we would also need to test this exhaustively to make sure it does not break anything from the current setup. So things like:

  • mTLS
  • Self-signed certificates
  • Custom CAs

For all cases, we'd need to test both the dart side of things and the native side of things. Most network calls are from the dart side. Video player, Asset download are made from native side and we should validate these as well. If we are to remove the self signed toggle, we've to make sure everything works on iOS as well.

You could just inline all the changes. We are migrating to using pigeon for all message passing between flutter and the platform side, so you can add an API to fetch the certificates and load it in the dart's SecurityContext. We might also need some documentation change to inform users about installing certificates to the OS cert store, rather than loading it in the app like they used to.

The core team currently has got it's hands full with working towards the stable release. I know this is a significant undertaking for your free time, but if you can get this properly tested after removing the existing toggle and adding in your changes, we can happily merge this in. If you need help setting up specific test scenarios or access to different certificate setups, let us know - we can coordinate with the community to help test various configurations.

@jfly
Copy link
Contributor Author

jfly commented Jul 21, 2025

I know this is a significant undertaking for your free time, but if you can get this properly tested after removing the existing toggle and adding in your changes, we can happily merge this in.

Sure, I'm happy to take this on. I've converted my understanding of your requirements into a checklist below. Please let me know if I'm missing anything:

  • Inline the changes (drop dependency on johnstef99/flutter_user_certificates_android)
    • Use pigeon for message passing between dart and kotlin.
  • Remove "Allow self-signed SSL certificates" checkbox
  • Docs? @shenlong-tanwen, please advise. IMO this is unnecessary, it's just fixing a bug and making things work the way users would expect. I do think a release note would be useful, is there a good place to add that?
  • Test on Android. Confirm you can view photos, upload photos and video, play video, and download assets.
  • Test on iOS. Confirm you can view photos, upload photos and video, play video, and download assets. @shenlong-tanwen, I will need help with this as I do not have access to an iOS device, nor development machine.
    • mTLS (aka "SSL Client Certificate")
    • Self-signed certificates
    • Custom CAs
  • Test upgrade migration from the older version with the setting on with a certificate to the new one without the toggle
    • Android
    • iOS

@shenlong-tanwen
Copy link
Member

Sure, I'm happy to take this on. I've converted my understanding of your requirements into a checklist below.

This is perfect! Thanks a lot for taking this on. As far the documentation, we can discuss this during the release of this PR. But yes, a callout in the release note might just do. It is also probably worth testing the migration from the older version with the setting on with a certificate to the new one without the toggle. Please feel free to ping me here or over discord if you need any help with the implementation or testing

@jfly jfly force-pushed the support-android-user-certs branch 2 times, most recently from 5182425 to c6419f1 Compare July 24, 2025 20:38
jfly added a commit to jfly/immich that referenced this pull request Jul 25, 2025
I ran into this while testing out
<immich-app#19830>. When I add, change,
or remove a client certificate under Immich's advanced settings, the
change wouldn't take effect until some mysterious point in the future.
For example:

1. Add a client certificate. It doesn't get used.
2. Remove certificate. *Now* the client certificate from step 1) is used.
3. Restart application. Now no client certificate is used.

This all boils down to some missing `await`s. The user would change the
cert, and we'd start asynchronously saving it to the store, and while
the save is still happening, [`HttpSSLOptions` pulls the "old" value out of
`SSLClientCertStoreVal`](https://github.com/immich-app/immich/blob/v1.136.0/mobile/lib/utils/http_ssl_options.dart#L30).

With the appropriate `await`s, this behaves much more sanely.
@jfly jfly force-pushed the support-android-user-certs branch from 0925f1e to 187a465 Compare July 25, 2025 01:28
jfly added a commit to jfly/immich that referenced this pull request Jul 25, 2025
I ran into this while testing out
<immich-app#19830>. When I add, change,
or remove a client certificate under Immich's advanced settings, the
change wouldn't take effect until some mysterious point in the future.
For example:

1. Add a client certificate. It doesn't get used.
2. Remove certificate. *Now* the client certificate from step 1) is used.
3. Restart application. Now no client certificate is used.

This all boils down to some missing `await`s. The user would change the
cert, and we'd start asynchronously saving it to the store, and while
the save is still happening, [`HttpSSLOptions` pulls the "old" value out of
`SSLClientCertStoreVal`](https://github.com/immich-app/immich/blob/v1.136.0/mobile/lib/utils/http_ssl_options.dart#L30).

With the appropriate `await`s, this behaves much more sanely.
@jfly jfly force-pushed the support-android-user-certs branch from 187a465 to 813adfc Compare July 25, 2025 01:30
jfly added a commit to jfly/immich that referenced this pull request Jul 25, 2025
I ran into this while testing out
<immich-app#19830>. When I add, change,
or remove a client certificate under Immich's advanced settings, the
change wouldn't take effect until some mysterious point in the future.
For example:

1. Add a client certificate. It doesn't get used.
2. Remove certificate. *Now* the client certificate from step 1) is used.
3. Restart application. Now no client certificate is used.

This all boils down to some missing `await`s. The user would change the
cert, and we'd start asynchronously saving it to the store, and while
the save is still happening, [`HttpSSLOptions` pulls the "old" value out of
`SSLClientCertStoreVal`](https://github.com/immich-app/immich/blob/v1.136.0/mobile/lib/utils/http_ssl_options.dart#L30).

With the appropriate `await`s, this behaves much more sanely.
jfly added a commit to jfly/immich that referenced this pull request Jul 25, 2025
I ran into this while testing out
<immich-app#19830>. When I add, change,
or remove a client certificate under Immich's advanced settings, the
change wouldn't take effect until some mysterious point in the future.
For example:

1. Add a client certificate. It doesn't get used.
2. Remove certificate. *Now* the client certificate from step 1) is used.
3. Restart application. Now no client certificate is used.

This all boils down to some missing `await`s. The user would change the
cert, and we'd start asynchronously saving it to the store, and while
the save is still happening, [`HttpSSLOptions` pulls the "old" value out of
`SSLClientCertStoreVal`](https://github.com/immich-app/immich/blob/v1.136.0/mobile/lib/utils/http_ssl_options.dart#L30).

With the appropriate `await`s, this behaves much more sanely.
alextran1502 pushed a commit that referenced this pull request Jul 25, 2025
I ran into this while testing out
<#19830>. When I add, change,
or remove a client certificate under Immich's advanced settings, the
change wouldn't take effect until some mysterious point in the future.
For example:

1. Add a client certificate. It doesn't get used.
2. Remove certificate. *Now* the client certificate from step 1) is used.
3. Restart application. Now no client certificate is used.

This all boils down to some missing `await`s. The user would change the
cert, and we'd start asynchronously saving it to the store, and while
the save is still happening, [`HttpSSLOptions` pulls the "old" value out of
`SSLClientCertStoreVal`](https://github.com/immich-app/immich/blob/v1.136.0/mobile/lib/utils/http_ssl_options.dart#L30).

With the appropriate `await`s, this behaves much more sanely.
@jfly jfly force-pushed the support-android-user-certs branch 4 times, most recently from db8d15e to b396b38 Compare July 25, 2025 06:11
@jfly
Copy link
Contributor Author

jfly commented Jul 25, 2025

@shenlong-tanwen, I'm ready for you to take another look at this PR. I've updated my checklist with what I have done and not done.

Notably, with these changes, self-signed certificates work, but Android only lets you install them if they have the X.509v3 CA:true flag. You get a really confusing message saying a "private key is required to install a certificate". See https://android.stackexchange.com/questions/237141/how-to-get-android-11-to-trust-a-user-root-ca-without-a-private-key for details.

So, we need to decide what to do here. A couple proposals:

  1. Just make the change. Folks who want to self-sign their own certificates can do so, but they should be running a proper certificate authority. The existing feature (where we don't validate certs at all) is dangerous and should go away.
  2. Keep the "Allow self-signed SSL certificates" feature around, but reword it so it's clear that you should not be using it if you're running your own CA.
    • I still think it's a dangerous feature and should go away. Ideally we'd tell people it's deprecated and will be removed [at some point].

@richardlt
Copy link

Personally I agree with your second point, since this feature here will not strictly replace the existing flag it could be a breaking change for some users to remove it. I'm also not sure about the options for IOS users to install custom CA.
Maybe another proposal could be to keep it for now and to improve it later. For example in some applications if you try to reach a server with a self signed cert you have to approve it directly in the app and then this app will remember your choice so if the cert change you'll have to approve it again.

@jfly
Copy link
Contributor Author

jfly commented Jul 25, 2025

I'm also not sure about the options for IOS users to install custom CA.

This works fine. My wife is on iOS and has our custom CA installed on her phone, and immich works great.

For example in some applications if you try to reach a server with a self signed cert you have to approve it directly in the app and then this app will remember your choice so if the cert change you'll have to approve it again.

Yeah, that would be a ssh-style TOFU scheme. I don't have the same safety concerns, but that's a whole bunch of code that I don't really see the value in anyone writing, nor having the core immich team maintain.

IMO, it would be better for immich to take an opinionated stance and say that anyone running their immich server without "universally trusted" certs is required to be have that cert chained off of a root of trust with CA:true (aka: running their own certificate authority). Anyone who is doing this is likely going to run into the same issue with other self hosted services, anyways. Baking the trust into your OS is simplest for both the app developers and the users.

Of course I am happy to do whatever the core team needs to get this PR landed. IMO, the best solution would be to rebrand the existing option as dangerous and mark it deprecated, with the intent to remove it before the stable release.

@jfly jfly force-pushed the support-android-user-certs branch from b396b38 to 5e45a35 Compare July 28, 2025 23:38
@jfly
Copy link
Contributor Author

jfly commented Dec 16, 2025

Thanks, @shenlong-tanwen.

But we have to keep this on hold till the next major release since this is still a breaking change

What if keep the existing option, but reword it a little bit (as I suggested here: #19830 (comment)).

before the next major release

Do you mean "after" the next major release?

@denysvitali
Copy link

FYI: #22768 supports user-provided certificates and fixes all the problems mentioned in #15230 by using the same client everywhere.

The idea here is to use OkHttp, which supports user certs on Android.

@jfly jfly force-pushed the support-android-user-certs branch from f24922b to 0cfc278 Compare January 13, 2026 09:41
@jfly
Copy link
Contributor Author

jfly commented Jan 13, 2026

@denysvitali, agreed that using a single client with support for Android user-installed certificates sounds like a better long term solution.

I did just rebase this PR and dropped the breaking change. Instead, I've just marked the existing "Allow self-signed SSL certificates" feature as deprecated. @shenlong-tanwen, I'd really love to get this PR merged. I've been daily driving it since July 2025 and have had no issues, other than my immich android client getting progressively more out of date.

@jfly jfly marked this pull request as ready for review January 13, 2026 09:50
@jfly jfly force-pushed the support-android-user-certs branch 3 times, most recently from 382e892 to 6fbe850 Compare January 17, 2026 19:46
Copy link
Member

@shenlong-tanwen shenlong-tanwen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that this got shelved for so long. Can you add the pigeon generator command to the mise.toml file in the mobile app directory as well?

The other parts looks good and we can actually merge this sooner now that we have proper backward support for current installs. Apologies again and Thanks a lot for working on this

jfly added 2 commits January 19, 2026 06:52
This is part of immich-app#15230.

Frustratingly, Dart/Flutter ignores user-installed certificates. Working
around this requires rooting your Android device to install certificates
as "system" certs, which isn't an option for everyone.

This is a known issue with Dart, see
dart-lang/sdk#50435 and
flutter/flutter#56607 for details.

I have read through
<immich-app#15230> and
<immich-app#13555>, and I
understand that switching to
[`cronnet_http`](immich-app#14335 (comment))
would also resolve this. While that may be the correct long-term
approach, it looks like there are [a lot of network
codepaths](immich-app#15230 (comment))
in Immich, and as I know basically nothing about Dart, nor Flutter, nor
Immich's codebase, I thought this would be a better short term approach.

For the record: the important code here is copied from my fork of
`johnstef99/flutter_user_certificates_android`. See this PR for more
context: <johnstef99/flutter_user_certificates_android#2>.
This is unnecessary now that we support user-installed certificates on
Android. Users should be adding certs to their system instead, which
will work in more places (such as browsers), and won't blindly trust the
cert.
@jfly jfly force-pushed the support-android-user-certs branch from 6fbe850 to d9c9ff5 Compare January 19, 2026 14:52
@jfly
Copy link
Contributor Author

jfly commented Jan 19, 2026

Can you add the pigeon generator command to the mise.toml file in the mobile app directory as well?

@shenlong-tanwen, done. It's unclear to me how this relates to the pigeon target in mobile/makefile. I've just followed the existing patterns.

@mertalev
Copy link
Member

I'd love to move away from the Dart HttpClient entirely after measuring just how much worse it is compared to the native clients. To that end, I'd prefer a solution that moves away from HttpClient rather than depending on it.

@denysvitali
Copy link

@mertalev My proposed solution uses OkHttp on Android and solve the issues mentioned here

@mertalev
Copy link
Member

@mertalev My proposed solution uses OkHttp on Android and solve the issues mentioned here

I think OkHttp is the right direction here. I have a WIP branch that handles certs app-wide for both platforms. It creates a shared client on the platform side and constructs the cupertino_http/ok_http client from its pointer on the Dart side. This would mean even all Flutter isolates and engines use the same client.

@jfly
Copy link
Contributor Author

jfly commented Jan 28, 2026

I don't care what networking stack we use. OkHttp sounds great if it can also do proper handling of user-installed certificates on Android. I just want to get back to installing Immich from f-droid, rather than infrequently rebasing this branch (and dealing with conflicts).

My preference would be to land this PR as is so certificate handling on android can get un-broken. I'd be happy for @denysvitali to resurrect their work in #22768 and delete the hack I've added here. I don't think landing this PR increases the effort to switch to OkHttp.

@shenlong-tanwen, WDYT?

@mertalev
Copy link
Member

I totally get that you want to fix this sooner rather than later and I agree! I'm just already working on a more complete solution, so it seems unnecessary to merge this code if it's going to be superseded soon.

@jfly
Copy link
Contributor Author

jfly commented Jan 29, 2026

Sorry, I'm getting little frustrated here. This wouldn't be the first time this PR has had to wait for a networking refactor you've done (#19830 (comment)). This PR exists, is reviewed and ready to land, and fixes a real issue in the wild. It also deprecates a very dangerous feature that IMO shouldn't exist. Why not merge as is? I'll still cheer the day you delete this hack!

@mertalev
Copy link
Member

Sorry for the frustration. This wouldn't be merged until 2.6, by which point I expect the whole app to be switched over to native clients. Merging this PR implies testing it only to revert it right after, which in turn means a larger diff for others in the team to review. It's honestly not a good use of time.

As a team, we have our own planning and this is a task I've already picked up, so please rest assured that I'll take care of it.

@jfly
Copy link
Contributor Author

jfly commented Jan 29, 2026

Ok, I appreciate the timeline.

@jfly jfly closed this Jan 29, 2026
@richardlt
Copy link

richardlt commented Jan 29, 2026

What a surprise to see this PR closed after all the discussions and changes made to remove any breaking changes.
Hope that we will finally have that fix/feature soon in 2.6 🙏🏼

@jfly
Copy link
Contributor Author

jfly commented Jan 29, 2026

@mertalev, could you share an issue/PR I can subscribe to so I know how your OkHttp work is going? Thanks, and good luck!

@mertalev
Copy link
Member

@mertalev, could you share an issue/PR I can subscribe to so I know how your OkHttp work is going? Thanks, and good luck!

It still needs some work before it's ready for a PR, but I'll ping you when it's up :)

@jfly
Copy link
Contributor Author

jfly commented Feb 2, 2026

For anyone else who wants to follow along, @mertalev and I are discussing this over in #25802

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants