Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add encrypted http pusher (MSC3013) #11512

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

Sorunome
Copy link
Contributor

@Sorunome Sorunome commented Dec 6, 2021

Pull Request Checklist

Server-side implementation of MSC3013 Encrypted Push matrix-org/matrix-spec-proposals#3013

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
  • Pull request includes a sign off
  • Code style is correct
    (run the linters)

Signed-Off-By: Sorunome [email protected]

@Sorunome Sorunome requested a review from a team as a code owner December 6, 2021 12:58
@reivilibre reivilibre changed the title Add encrypted http pusher Add encrypted http pusher (MSC3013) Dec 6, 2021
@Sorunome Sorunome force-pushed the soru/encrypted-push branch 4 times, most recently from 0ffcbcb to dc70952 Compare December 8, 2021 10:56
@DMRobertson DMRobertson self-assigned this Dec 8, 2021
Copy link
Contributor

@DMRobertson DMRobertson left a comment

Choose a reason for hiding this comment

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

A few thoughts.

I'm not a cryptography expert, but AFAICS this implements the process described in the MSC. I don't suppose you've had a chance to test it in action?

synapse/push/httppusher.py Outdated Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
synapse/push/httppusher.py Show resolved Hide resolved
synapse/push/httppusher.py Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
tests/push/test_http.py Outdated Show resolved Hide resolved
@Sorunome Sorunome force-pushed the soru/encrypted-push branch from dc70952 to 848aed2 Compare December 9, 2021 10:29
Copy link
Contributor

@DMRobertson DMRobertson left a comment

Choose a reason for hiding this comment

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

Where possible, please try to avoid a force push. It makes it harder to see what's changed between review rounds!

I think the case of a missing public_key needs handling, and the mypy config change to get rid of the type-ignore in the code would be nice to have (but not crucial).

Otherwise LGTM. Would appreciate another pair of eyes from @matrix-org/synapse-core though---sticking it into the review queue.

@DMRobertson DMRobertson requested a review from a team December 9, 2021 19:26
@Sorunome
Copy link
Contributor Author

I don't suppose you've had a chance to test it in action?

oh, totally missed this, yes this is tested in action

@reivilibre reivilibre self-requested a review December 13, 2021 12:01
Copy link
Contributor

@reivilibre reivilibre left a comment

Choose a reason for hiding this comment

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

Looks sensible, will wait for the discussed changes about which library to use for the Curve25519 exchange.

Thanks!

synapse/push/httppusher.py Show resolved Hide resolved
synapse/push/httppusher.py Show resolved Hide resolved
synapse/push/httppusher.py Outdated Show resolved Hide resolved
@callahad
Copy link
Contributor

matrix-org/olm#53 may be relevant to the discussion about curve25519; we may have boxed ourselves into a corner. This is being looked at now.

@Sorunome
Copy link
Contributor Author

Sorunome commented Dec 13, 2021 via email

Copy link
Contributor

@DMRobertson DMRobertson left a comment

Choose a reason for hiding this comment

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

Glad to see the dependency stuff was sorted out.

I don't think we want to merge it as-is unless:

  • the MSC is accepted, or
  • these changes were guarded by an experimental config flag

because this change presently enables an unstandardised feature for all Synapse deployments.

synapse/python_dependencies.py Outdated Show resolved Hide resolved
@DMRobertson DMRobertson dismissed their stale review December 14, 2021 12:53

changes made

@DMRobertson DMRobertson requested a review from a team December 16, 2021 12:37
@Sorunome
Copy link
Contributor Author

Sorunome commented Dec 23, 2021

Because of stuff that came up around encrypted push and iOS needing some slight adjustments soru marked this PR as WIP again, the main part of the PR will still the same, though. Will un-mark as WIP (should soru ping someone, then?) once those are resolved.

EDIT: Oh, github doesn't allow re-marking a PR as WIP

@richvdh richvdh marked this pull request as draft December 23, 2021 11:20
@richvdh richvdh removed request for a team, callahad and reivilibre December 23, 2021 11:23
@richvdh
Copy link
Member

richvdh commented Dec 23, 2021

EDIT: Oh, github doesn't allow re-marking a PR as WIP

yes it does! have done so.

(should soru ping someone, then?)

no need, it should automatically come up on our review queue.

@Sorunome Sorunome marked this pull request as ready for review December 31, 2021 10:41
@Sorunome
Copy link
Contributor Author

This PR is ready for review again. There are merge conflicts in this PR by now, should soru merge in master or rebase onto master?

@reivilibre
Copy link
Contributor

This PR is ready for review again. There are merge conflicts in this PR by now, should soru merge in master or rebase onto master?

:) After there's already been discussion/review, prefer merge rather than rebase so that the conversations stay in place in the history of the PR.

@DMRobertson
Copy link
Contributor

And note: please merge in develop, rather than master.

Comment on lines +183 to +184
devices = cleartext_notif["devices"]
del cleartext_notif["devices"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Could use

Suggested change
devices = cleartext_notif["devices"]
del cleartext_notif["devices"]
devices = cleartext_notif.pop("devices")

As in https://docs.python.org/3/library/stdtypes.html?highlight=dict%20pop#dict.pop

# create the mac
mac = hmac.new(mac_key, ciphertext, hashlib.sha256).digest()[0:8]

d = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have a more descriptive name than d here? encrypted_notification or payload_with_encryption?

Comment on lines +221 to +226
# now, if the push frame is a counts only frame, we have to respect the counts_only_type setting
if counts_only:
if self.counts_only_type == "boolean":
d["is_counts_only"] = True
elif self.counts_only_type == "full":
d["counts"] = cleartext_notif["counts"]
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure this logic belongs in a function called _encrypt_notification_dict---this isn't doing any encryption. I take the point that this is needed to make iOS E2EE work, but doesn't this setting make sense even if we don't want to use encryption?

Put differently: why is this tied to the use of curve25519-aes-sha2 specifically?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the MSC https://github.com/Sorunome/matrix-doc/blob/soru/encrypted-push/proposals/3013-encrypted-push.md#ios

Why curve25519-aes-sha2 specifically? Because only plain and curve25519-aes-sha2 exist in this PR, and it is not in plain

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so maybe call the method _process_notification_dict_for_sending instead?

Comment on lines +288 to +309
# do the exchange
exchanged = X25519PrivateKey.from_private_bytes(
unpaddedbase64.decode_base64(private_key)
).exchange(X25519PublicKey.from_public_bytes(ephemeral))
# expand with HKDF
zerosalt = bytes([0] * 32)
prk = hmac.new(zerosalt, exchanged, hashlib.sha256).digest()
aes_key = hmac.new(prk, bytes([1]), hashlib.sha256).digest()
mac_key = hmac.new(prk, aes_key + bytes([2]), hashlib.sha256).digest()
aes_iv = hmac.new(prk, mac_key + bytes([3]), hashlib.sha256).digest()[0:16]
# create the cleartext with AES-CBC-256
decryptor = Cipher(algorithms.AES(aes_key), modes.CBC(aes_iv)).decryptor()
# AES blocksize is always 128 bits
unpadder = padding.PKCS7(128).unpadder()
cleartext = json.loads(
(
unpadder.update(decryptor.update(ciphertext) + decryptor.finalize())
+ unpadder.finalize()
).decode("utf-8")
)
# create the mac
calculated_mac = hmac.new(mac_key, ciphertext, hashlib.sha256).digest()[0:8]
Copy link
Contributor

Choose a reason for hiding this comment

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

This look suspiciously like a test which re-implements the function under test. What's going on here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are testing if the encrypted push frame we created is decryptable. Soru was pondering between this and just testing if the mac/cipher/ephemeral we created are strings, however this seemed to at least have a bit more integrity. Due to the nature of the push frame being encrypted, we can't test its contents without decrypting them again

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We aren't 100% re-implementing it, this is a decrypt implementation while in synapse is an encrypt implementation. But yeah, soru couldn't figure out a better way to test this, sadly :/

def tests_encrypted_push_counts_only_none(self):
"""
The HTTP pusher will not add any extra fields to encrypted push frames if the counts_only_type
is none.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
is none.
is none.
is 'none'.

To avoid confusion with the Python object None.

@@ -591,7 +793,7 @@ def test_push_unread_count_message_count(self):
self.push_attempts[5][2]["notification"]["counts"]["unread"], 4
)

def _test_push_unread_count(self):
def _test_push_unread_count(self, pusher_data=None, perform_checks=True):
Copy link
Contributor

Choose a reason for hiding this comment

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

Type annotations here, please.

That said, I'm not so keen on a method called _test_... which might not actually enforce any assertions, i.e. if perform_checks is falsey. What is the intention here? My first thought was that it was an optimisation to not run repeated checks that don't test anything new. But I think you're specifically opting out of the the checks which interrogate the unread count---because they need to be handled differently under the proposed scheme.

I think it would make more sense to create a new helper function

  • which does setup only, making no assertions
  • only does the minimum required to generate a counts-only and normal push notification (the present test helper seems to make many normal notifications?)

@DMRobertson DMRobertson added the X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged label Jan 14, 2022
@DMRobertson DMRobertson removed their assignment Apr 27, 2022
@reivilibre
Copy link
Contributor

Hi, are you still interested in working on this?

@Sorunome
Copy link
Contributor Author

Sorunome commented Aug 5, 2022 via email

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
X-Awaiting-Changes A contributed PR which needs changes and re-review before it can be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants