From 0aab34004b2e56c3ab79f514be264c568ad71fd3 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Mon, 19 Oct 2015 14:40:15 +0100 Subject: [PATCH 1/5] Initial minimial hack at a test of event hashing and signing --- tests/crypto/test_event_signing.py | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/crypto/test_event_signing.py diff --git a/tests/crypto/test_event_signing.py b/tests/crypto/test_event_signing.py new file mode 100644 index 000000000..0b560e931 --- /dev/null +++ b/tests/crypto/test_event_signing.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from tests import unittest +from tests.utils import MockClock + +from synapse.events.builder import EventBuilderFactory +from synapse.crypto.event_signing import add_hashes_and_signatures +from synapse.types import EventID + +from unpaddedbase64 import decode_base64 + +import nacl.signing + + +# Perform these tests using given secret key so we get entirely deterministic +# signatures output that we can test against. +SIGNING_KEY_SEED = decode_base64( + "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1" +) + +KEY_ALG = "ed25519" +KEY_VER = 1 +KEY_NAME = "%s:%d" % (KEY_ALG, KEY_VER) + +HOSTNAME = "domain" + + +class EventBuilderFactoryWithPredicableIDs(EventBuilderFactory): + """ A subclass of EventBuilderFactory that generates entirely predicatable + event IDs, so we can assert on them. """ + def create_event_id(self): + i = str(self.event_id_count) + self.event_id_count += 1 + + return EventID.create(i, self.hostname).to_string() + + +class EventSigningTestCase(unittest.TestCase): + + def setUp(self): + self.event_builder_factory = EventBuilderFactoryWithPredicableIDs( + clock=MockClock(), + hostname=HOSTNAME, + ) + + self.signing_key = nacl.signing.SigningKey(SIGNING_KEY_SEED) + self.signing_key.alg = KEY_ALG + self.signing_key.version = KEY_VER + + def test_sign(self): + builder = self.event_builder_factory.new( + {'type': "X"} + ) + self.assertEquals( + builder.build().get_dict(), + { + 'event_id': "$0:domain", + 'origin': "domain", + 'origin_server_ts': 1000000, + 'signatures': {}, + 'type': "X", + 'unsigned': {'age_ts': 1000000}, + }, + ) + + add_hashes_and_signatures(builder, HOSTNAME, self.signing_key) + + event = builder.build() + + self.assertTrue(hasattr(event, 'hashes')) + self.assertTrue('sha256' in event.hashes) + self.assertEquals( + event.hashes['sha256'], + "6tJjLpXtggfke8UxFhAKg82QVkJzvKOVOOSjUDK4ZSI", + ) + + self.assertTrue(hasattr(event, 'signatures')) + self.assertTrue(HOSTNAME in event.signatures) + self.assertTrue(KEY_NAME in event.signatures["domain"]) + self.assertEquals( + event.signatures[HOSTNAME][KEY_NAME], + "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+" + "aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA", + ) From 07b58a431f9e0367f8c08d2bc8983473c8a0c379 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Mon, 19 Oct 2015 15:00:52 +0100 Subject: [PATCH 2/5] Another signing test vector using an 'm.room.message' with content, so that the implementation will have to redact it --- tests/crypto/test_event_signing.py | 50 +++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/crypto/test_event_signing.py b/tests/crypto/test_event_signing.py index 0b560e931..0f487d9c7 100644 --- a/tests/crypto/test_event_signing.py +++ b/tests/crypto/test_event_signing.py @@ -61,7 +61,7 @@ class EventSigningTestCase(unittest.TestCase): self.signing_key.alg = KEY_ALG self.signing_key.version = KEY_VER - def test_sign(self): + def test_sign_minimal(self): builder = self.event_builder_factory.new( {'type': "X"} ) @@ -96,3 +96,51 @@ class EventSigningTestCase(unittest.TestCase): "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+" "aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA", ) + + def test_sign_message(self): + builder = self.event_builder_factory.new( + { + 'type': "m.room.message", + 'sender': "@u:domain", + 'room_id': "!r:domain", + 'content': { + 'body': "Here is the message content", + }, + } + ) + self.assertEquals( + builder.build().get_dict(), + { + 'content': { + 'body': "Here is the message content", + }, + 'event_id': "$0:domain", + 'origin': "domain", + 'origin_server_ts': 1000000, + 'type': "m.room.message", + 'room_id': "!r:domain", + 'sender': "@u:domain", + 'signatures': {}, + 'unsigned': {'age_ts': 1000000}, + } + ) + + add_hashes_and_signatures(builder, HOSTNAME, self.signing_key) + + event = builder.build() + + self.assertTrue(hasattr(event, 'hashes')) + self.assertTrue('sha256' in event.hashes) + self.assertEquals( + event.hashes['sha256'], + "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g", + ) + + self.assertTrue(hasattr(event, 'signatures')) + self.assertTrue(HOSTNAME in event.signatures) + self.assertTrue(KEY_NAME in event.signatures["domain"]) + self.assertEquals( + event.signatures[HOSTNAME][KEY_NAME], + "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUw" + "u6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA" + ) From a8795c9644d555e95a6be3211b4e79e447087697 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Mon, 19 Oct 2015 15:24:49 +0100 Subject: [PATCH 3/5] Use assertIn() instead of assertTrue on the 'in' operator --- tests/crypto/test_event_signing.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/crypto/test_event_signing.py b/tests/crypto/test_event_signing.py index 0f487d9c7..010fe4ed3 100644 --- a/tests/crypto/test_event_signing.py +++ b/tests/crypto/test_event_signing.py @@ -82,15 +82,15 @@ class EventSigningTestCase(unittest.TestCase): event = builder.build() self.assertTrue(hasattr(event, 'hashes')) - self.assertTrue('sha256' in event.hashes) + self.assertIn('sha256', event.hashes) self.assertEquals( event.hashes['sha256'], "6tJjLpXtggfke8UxFhAKg82QVkJzvKOVOOSjUDK4ZSI", ) self.assertTrue(hasattr(event, 'signatures')) - self.assertTrue(HOSTNAME in event.signatures) - self.assertTrue(KEY_NAME in event.signatures["domain"]) + self.assertIn(HOSTNAME, event.signatures) + self.assertIn(KEY_NAME, event.signatures["domain"]) self.assertEquals( event.signatures[HOSTNAME][KEY_NAME], "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+" @@ -130,15 +130,15 @@ class EventSigningTestCase(unittest.TestCase): event = builder.build() self.assertTrue(hasattr(event, 'hashes')) - self.assertTrue('sha256' in event.hashes) + self.assertIn('sha256', event.hashes) self.assertEquals( event.hashes['sha256'], "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g", ) self.assertTrue(hasattr(event, 'signatures')) - self.assertTrue(HOSTNAME in event.signatures) - self.assertTrue(KEY_NAME in event.signatures["domain"]) + self.assertIn(HOSTNAME, event.signatures) + self.assertIn(KEY_NAME, event.signatures["domain"]) self.assertEquals( event.signatures[HOSTNAME][KEY_NAME], "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUw" From 531e3aa75effdec137c1ffbdb1fb0e8cb0cbe40e Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Mon, 19 Oct 2015 17:37:35 +0100 Subject: [PATCH 4/5] Capture __init__.py --- tests/crypto/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/crypto/__init__.py diff --git a/tests/crypto/__init__.py b/tests/crypto/__init__.py new file mode 100644 index 000000000..9bff9ec16 --- /dev/null +++ b/tests/crypto/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + From 9ed784098a94cf80d2582cc1d98484ac9d748eee Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Mon, 19 Oct 2015 17:42:34 +0100 Subject: [PATCH 5/5] Invoke EventBuilder directly instead of going via the EventBuilderFactory --- tests/crypto/test_event_signing.py | 38 +++--------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/tests/crypto/test_event_signing.py b/tests/crypto/test_event_signing.py index 010fe4ed3..791347294 100644 --- a/tests/crypto/test_event_signing.py +++ b/tests/crypto/test_event_signing.py @@ -15,11 +15,9 @@ from tests import unittest -from tests.utils import MockClock -from synapse.events.builder import EventBuilderFactory +from synapse.events.builder import EventBuilder from synapse.crypto.event_signing import add_hashes_and_signatures -from synapse.types import EventID from unpaddedbase64 import decode_base64 @@ -39,34 +37,15 @@ KEY_NAME = "%s:%d" % (KEY_ALG, KEY_VER) HOSTNAME = "domain" -class EventBuilderFactoryWithPredicableIDs(EventBuilderFactory): - """ A subclass of EventBuilderFactory that generates entirely predicatable - event IDs, so we can assert on them. """ - def create_event_id(self): - i = str(self.event_id_count) - self.event_id_count += 1 - - return EventID.create(i, self.hostname).to_string() - - class EventSigningTestCase(unittest.TestCase): def setUp(self): - self.event_builder_factory = EventBuilderFactoryWithPredicableIDs( - clock=MockClock(), - hostname=HOSTNAME, - ) - self.signing_key = nacl.signing.SigningKey(SIGNING_KEY_SEED) self.signing_key.alg = KEY_ALG self.signing_key.version = KEY_VER def test_sign_minimal(self): - builder = self.event_builder_factory.new( - {'type': "X"} - ) - self.assertEquals( - builder.build().get_dict(), + builder = EventBuilder( { 'event_id': "$0:domain", 'origin': "domain", @@ -98,18 +77,7 @@ class EventSigningTestCase(unittest.TestCase): ) def test_sign_message(self): - builder = self.event_builder_factory.new( - { - 'type': "m.room.message", - 'sender': "@u:domain", - 'room_id': "!r:domain", - 'content': { - 'body': "Here is the message content", - }, - } - ) - self.assertEquals( - builder.build().get_dict(), + builder = EventBuilder( { 'content': { 'body': "Here is the message content",