BlueBubblesApp / bluebubbles-server

Server for forwarding iMessages to clients within the BlueBubbles App ecosystem
https://bluebubbles.app
Apache License 2.0
506 stars 42 forks source link

Feature Request: Enable End-to-End Encryption #421

Open mike-lloyd03 opened 1 year ago

mike-lloyd03 commented 1 year ago

All communication is protected by TLS encryption already if BlueBubbles is being served on an https domain. But per our discussion on Discord today, is it possible to enable end-to-end encryption so messages are not readable by Google's push notification servers similar to AirMessage's setup discussed here?

zlshames commented 1 year ago

Yup this is something that i've discussed doing. It will take both a client update and server update to accomplish this. I will have to agree upon a "standard" format so that the client can properly decrypt the payloads. It needs to be done. I also want to migrate authentication to be oauth-style

mike-lloyd03 commented 1 year ago

Thanks for being open to the idea. If switching to OAuth, would there still be an option for password based auth? Users like myself would like to keep as many third parties out of the process as possible.

zlshames commented 1 year ago

So, if I did move to a more o-auth like workflow, it would still require a "password", however, only for the initial connection. The client would then get a token back that it can use to authenticate with the server. The password will be used for encrypting and decrypting payloads only.

That way, if somehow you get MITM-attacked, the attacker would only have your access token, and not the real password to decrypt the payload. OAuth is theoretically more secure than just password-based auth.

mike-lloyd03 commented 1 year ago

How much work would it be to enable this? I'd like to contribute but it's been a few years since I've written any JavaScript/TypeScript.

zlshames commented 1 year ago

How much work would it be to enable this? I'd like to contribute but it's been a few years since I've written any JavaScript/TypeScript.

I think it would take a few steps to get the initial iteration of it. For the initial iteration, I'd like to just implement token generation and using that as a token instead of the original password. To do that, I'd expect a few steps:

  1. A new token field in the server DB. This should default to a randomly generated string.
  2. A way to "reset" the token. This will require a "reconfigure" on the android side too, to sync the new token.
  3. Add functionality to the HTTP middleware to utilize the new token DB field instead of the password
  4. Add a new API endpoint for initial authentication when the user-set password is passed, and the token is returned. For example /api/token?type=password

^ This will allow the client to initiate the connection using the user-set password, and get a token back that will be used for all requests after that.

Phase 2 will be to utilize the original password to encrypt/decrypt the API payloads. Both phases will require changes on the Android/client side to work as well.

For phase 1, i think overall effort on the server side isn't too bad. Maybe 2 hours worth of work. Probably less if you know your way around the codebase. For phase 2, it will be longer because we need to implement the new "format", which the client will also need to implement handlers for.

mike-lloyd03 commented 1 year ago

Most of that seems above my head but I could probably tackle that first point. Adding a new column to the DB and writing the necessary migrations is right in my wheelhouse. Would a PR to get this small step going be okay?

The rest of it would take me getting familiarized with the codebase and reviving my Javascript knowledge.

Also, does this conversation have any connection to #82?

zlshames commented 1 year ago

Most of that seems above my head but I could probably tackle that first point. Adding a new column to the DB and writing the necessary migrations is right in my wheelhouse. Would a PR to get this small step going be okay?

The rest of it would take me getting familiarized with the codebase and reviving my Javascript knowledge.

Also, does this conversation have any connection to #82?

It's actually not a new column in the DB. It's just a new entry in the config table in the DB. It's basically just adding one line to the constants file in the database/server/ directory I think.

That said, it might not be worth making a PR for unless you plan on implementing other code as well. If you're not too familiar with the codebase, I'm not sure this is the best issue to tackle at this point.

As for #82, it's sorta related. That ticket was done when we used the websocketd for API communication. Now weve moved away from that and don't really use it anymore. But the encryption algorithm was one plus that came from that ticket. Which we can use here

mr-ransel commented 11 months ago

Came to circle back on this and see if there's an updated path to using the existing encryption library for Firebase notifications and the regular API.

I saw that in the v1.8.0 beta release we're automatically disabling encryption entirely over the socket due to a problem with it not working on Android, which made me want to check in and see if we've given more consideration on how to get that functionality migrated to the non-websocket API and firebase payloads. I've reviewed the above description and if it's still accurate I'd be interested in collaborating on it to get the server-side ready but updating all the different clients to support it might be out of my depth.

zlshames commented 11 months ago

Came to circle back on this and see if there's an updated path to using the existing encryption library for Firebase notifications and the regular API.

I saw that in the v1.8.0 beta release we're automatically disabling encryption entirely over the socket due to a problem with it not working on Android, which made me want to check in and see if we've given more consideration on how to get that functionality migrated to the non-websocket API and firebase payloads. I've reviewed the above description and if it's still accurate I'd be interested in collaborating on it to get the server-side ready but updating all the different clients to support it might be out of my depth.

What are firebase's existing encryption library for notifications? Wouldn't it be build into the Firebase SDK by default? Also, I wouldnt want to move completely to Firebase messages purely because some users on web & desktop dont use firebase

mr-ransel commented 11 months ago

Dug this up, need to look into the extra examples they cite: FCM does not provide an end-to-end solution. However, there are external solutions available such as Capillary or DTLS. https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages

derekmorr commented 11 months ago

Another option is what Signal does - send an empty notification. The notification causes the app to connect to the server and download the message directly.

mr-ransel commented 11 months ago

Honestly that's... kind of brilliant tbh. Side steps the entire issue. And if people want to use ngrok or not it's entirely up to them, letting people handle encryption however they want.

Cuts down on the size and complexity of the codebase too, just run the same "sync recent messages" code and don't even have special notification data structure handling

zlshames commented 11 months ago

https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages

So, one of the things discussed was making it so the "encryption module" that we put together would be able to handle a ton of different things. For instance, it would include metadata letting the module know if 1, it is encrypted or not. 2, what encryption method. 3, the encoding method. 4, if compression was used. And 5, If it's the full payload, or if more data needs to be fetched.

This way, clients would be able to know exactly what to do with the payload, and if more data needs to be fetched, it can be. Like I said before, it would require us to update the client to handle these message types far before we implement it in the server. Mainly due to compatibility concerns. Of course, there is also us needing to implement it too :)

The only issue with an "empty" message that needs to be fulfilled is that it'll require a server connection. And one kinda great thing about the way we do things now is you can still receive messages if your server connection is failing (for one reason or another). Though, you can't send of course.

Also, as far as DTS or Capillary go, my main concern of cross-platform availability. We don't just serve android, and i'm not sure it's compatible with Web/Desktop. So that will factor in, and likely, we will need to implement our own. Capillary is also marked as "archived" on GitHub and no longer maintained. Not sure why

derekmorr commented 10 months ago

I think having one encryption module serve multiple roles would unnecessarily complicate the design. Ideally the payload data would be versioned, and that payload data would be encrypted — providing a separation of concerns.

As to empty vs non-empty notification messages — I don’t really see the benefit of only being able to receive messages if I can’t reply to them. If that were to happened, I would probably just resort to SMS to contact the person, which defeats on the main benefits of iMessage (encrypted comms).

I’m concerned about Capillary for several reasons. The project looks to be unmaintained, and it has a suspect design. It looks like the client registers a long-term public key with the server, and all messages are encrypted with this key. This is similar to how iMessage and PGP work, which means it lacks Forward Secrecy. While I would much prefer to see an approach which rotates the keys regularly (like the Signal protocol), I’m not sure if it’s worth the extra design complexity since iMessage doesn’t have Forward Secrecy anyway.

zlshames commented 10 months ago

I think having one encryption module serve multiple roles would unnecessarily complicate the design. Ideally the payload data would be versioned, and that payload data would be encrypted — providing a separation of concerns.

As to empty vs non-empty notification messages — I don’t really see the benefit of only being able to receive messages if I can’t reply to them. If that were to happened, I would probably just resort to SMS to contact the person, which defeats on the main benefits of iMessage (encrypted comms).

I’m concerned about Capillary for several reasons. The project looks to be unmaintained, and it has a suspect design. It looks like the client registers a long-term public key with the server, and all messages are encrypted with this key. This is similar to how iMessage and PGP work, which means it lacks Forward Secrecy. While I would much prefer to see an approach which rotates the keys regularly (like the Signal protocol), I’m not sure if it’s worth the extra design complexity since iMessage doesn’t have Forward Secrecy anyway.

Our previous encryption would rely on the server password as a "password-based" encryption. So if you want to update the "key", it would be similar to just updating your password. Which you'd need to change on the client side too. I would think we'd do something similar, and not rely on Capillary or anything.

derekmorr commented 7 months ago

Of note, we know that Google and Apple have been tasked with monitoring push notification data and metadata of at least some accounts. https://www.reuters.com/technology/cybersecurity/governments-spying-apple-google-users-through-push-notifications-us-senator-2023-12-06/

cameronaaron commented 2 days ago

I suggest adopting a phased approach:

Token-Based Authentication:

Implement token generation and storage in the server config table. Create an endpoint (/api/token?type=password) to return the token upon initial authentication. Update middleware to use tokens instead of passwords. End-to-End Encryption:

Use the user-set password to encrypt/decrypt API payloads. Add metadata to payloads to indicate encryption status and method. Implement empty notifications prompting the client to fetch the actual message from the server, similar to Signal's approach.

derekmorr commented 2 days ago

If we do empty notifications, why do we need tokens?

zlshames commented 2 days ago

If we do empty notifications, why do we need tokens?

Empty notifications would basically just tell the client, hey there is a notification for this message with this ID. Go fetch the corresponding message and pull the relevant metadata to save the message and notify for it.

To fetch the data, it still needs to connect to the server. So it still needs to authenticate, however it does it. I think token based authentication is a completely different request and has nothing to do with the E2E encryption request