From 0a6dc11f52eb7c6c49493695a8f3ad633f8dce87 Mon Sep 17 00:00:00 2001 From: Guilherme Lopes Date: Wed, 3 Apr 2019 21:43:01 +0200 Subject: [PATCH 1/3] Add request signing --- examples/signed_request.py | 23 +++++++++ messagebird/__init__.py | 1 + messagebird/signed_request.py | 46 ++++++++++++++++++ tests/test_signed_request.py | 88 +++++++++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 examples/signed_request.py create mode 100644 messagebird/signed_request.py create mode 100644 tests/test_signed_request.py diff --git a/examples/signed_request.py b/examples/signed_request.py new file mode 100644 index 0000000..0ff2574 --- /dev/null +++ b/examples/signed_request.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +import sys +import argparse +import messagebird + +parser = argparse.ArgumentParser() +parser.add_argument('--signingKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--requestTimestamp', help='the request timestamp', type=str, required=True) +parser.add_argument('--requestBody', help='the request body', type=str, required=True) +parser.add_argument('--signature', help='the signature', type=str, required=True) +args = vars(parser.parse_args()) + +signed_request = messagebird.SignedRequest(args['signature'], args['timestamp'], args['requestBody'], {}) + +if signed_request.verify(args['signingKey']): + print("The signed request has been verified.") +else: + print("The signed request cannot be verified.") + +if signed_request.isRecent(): + print("The signed request is recent.") +else: + print("The signed request is not recent.") \ No newline at end of file diff --git a/messagebird/__init__.py b/messagebird/__init__.py index cf57a14..926e330 100644 --- a/messagebird/__init__.py +++ b/messagebird/__init__.py @@ -1 +1,2 @@ from messagebird.client import Client, ErrorException +from messagebird.signed_request import SignedRequest \ No newline at end of file diff --git a/messagebird/signed_request.py b/messagebird/signed_request.py new file mode 100644 index 0000000..4bc43f6 --- /dev/null +++ b/messagebird/signed_request.py @@ -0,0 +1,46 @@ +import hashlib +import hmac +import base64 +import time +from collections import OrderedDict + +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + + +class SignedRequest: + + def __init__(self, requestSignature, requestTimestamp, requestBody, requestParameters): + self._requestSignature = requestSignature + self._requestTimestamp = str(requestTimestamp) + self._requestBody = requestBody + self._requestParameters = requestParameters + + def verify(self, signing_key): + payload = self._build_payload() + expectedSignature = base64.b64decode(self._requestSignature) + calculatedSignature = hmac.new(signing_key.encode('latin-1'), payload.encode('latin-1'), hashlib.sha256).digest() + return expectedSignature == calculatedSignature + + def isRecent(self, offset = 10): + return int(time.time()) - int(self._requestTimestamp) < offset + + def _build_payload(self,): + parts = [] + parts.append(self._requestTimestamp) + parts.append(urlencode(self._sort_dict(self._requestParameters), True)) + checksum_body = hashlib.sha256(self._requestBody.encode('latin-1')).digest() + str_checksum_body = checksum_body.decode('latin-1') + parts.append(str_checksum_body) + + return "\n".join(parts) + + def _sort_dict(self, dict): + sorted_dict = OrderedDict() + + for key in sorted(dict): + sorted_dict[key] = dict[key] + + return sorted_dict \ No newline at end of file diff --git a/tests/test_signed_request.py b/tests/test_signed_request.py new file mode 100644 index 0000000..1472f25 --- /dev/null +++ b/tests/test_signed_request.py @@ -0,0 +1,88 @@ +import unittest +import time +from messagebird.signed_request import SignedRequest + +try: + from unittest.mock import Mock +except ImportError: + # mock was added to unittest in Python 3.3, but was an external library + # before. + from mock import Mock + + +SIGNING_KEY = 'PlLrKaqvZNRR5zAjm42ZT6q1SQxgbbGd' + + +class TestMessage(unittest.TestCase): + + def test_signed_request_withou_body(self): + query = { + 'recipient': '31612345678', + 'reference': 'FOO', + 'statusDatetime': '2019-01-11T09:17:11+00:00', + 'id': 'eef0ab57a9e049be946f3821568c2b2e', + 'status': 'delivered', + 'mccmnc': '20408', + 'ported': '1', + } + + signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w=' + timestamp = 1547198231 + body = '' + + signed_request = SignedRequest(signature, timestamp, body, query) + self.assertTrue(signed_request.verify(SIGNING_KEY)) + + def test_signed_request_with_body(self): + query = { + 'recipient': '31612345678', + 'reference': 'FOO', + 'statusDatetime': '2019-01-11T09:17:11+00:00', + 'id': 'eef0ab57a9e049be946f3821568c2b2e', + 'status': 'delivered', + 'mccmnc': '20408', + 'ported': '1', + } + + signature = '2bl+38H4oHVg03pC3bk2LvCB0IHFgfC4cL5HPQ0LdmI=' + timestamp = 1547198231 + body = '{"foo":"bar"}' + + signed_request = SignedRequest(signature, timestamp, body, query) + self.assertTrue(signed_request.verify(SIGNING_KEY)) + + def test_incorrectly_signed_request(self): + query = { + 'recipient': '31612345678', + 'reference': 'BAR', + 'statusDatetime': '2019-01-11T09:17:11+00:00', + 'id': 'eef0ab57a9e049be946f3821568c2b2e', + 'status': 'delivered', + 'mccmnc': '20408', + 'ported': '1', + } + + signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w=' + timestamp = 1547198231 + body = '' + + signed_request = SignedRequest(signature, timestamp, body, query) + self.assertFalse(signed_request.verify(SIGNING_KEY)) + + def test_recent_signed_request(self): + query = {} + signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w=' + timestamp = int(time.time()) - 1 + body = '' + + signed_request = SignedRequest(signature, timestamp, body, query) + self.assertTrue(signed_request.isRecent()) + + def test_not_recent_signed_request(self): + query = {} + signature = 'KVBdcVdz2lYMwcBLZCRITgxUfA/WkwSi+T3Wxl2HL6w=' + timestamp = int(time.time()) - 100 + body = '' + + signed_request = SignedRequest(signature, timestamp, body, query) + self.assertFalse(signed_request.isRecent()) \ No newline at end of file From 9d21a6802287da66d1563768c0d5bf2fdafc5319 Mon Sep 17 00:00:00 2001 From: Guilherme Lopes Date: Mon, 8 Apr 2019 09:45:47 +0200 Subject: [PATCH 2/3] Update build payload --- messagebird/__init__.py | 2 +- messagebird/signed_request.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/messagebird/__init__.py b/messagebird/__init__.py index 926e330..a25b3e7 100644 --- a/messagebird/__init__.py +++ b/messagebird/__init__.py @@ -1,2 +1,2 @@ from messagebird.client import Client, ErrorException -from messagebird.signed_request import SignedRequest \ No newline at end of file +from messagebird.signed_request import SignedRequest diff --git a/messagebird/signed_request.py b/messagebird/signed_request.py index 4bc43f6..33c2bba 100644 --- a/messagebird/signed_request.py +++ b/messagebird/signed_request.py @@ -28,13 +28,9 @@ def isRecent(self, offset = 10): return int(time.time()) - int(self._requestTimestamp) < offset def _build_payload(self,): - parts = [] - parts.append(self._requestTimestamp) - parts.append(urlencode(self._sort_dict(self._requestParameters), True)) checksum_body = hashlib.sha256(self._requestBody.encode('latin-1')).digest() str_checksum_body = checksum_body.decode('latin-1') - parts.append(str_checksum_body) - + parts = [self._requestTimestamp, urlencode(self._sort_dict(self._requestParameters), True), str_checksum_body] return "\n".join(parts) def _sort_dict(self, dict): From 20ca9af031a97c0e35a87c643c6154ce08a4e0ed Mon Sep 17 00:00:00 2001 From: Guilherme Lopes Date: Mon, 8 Apr 2019 09:57:48 +0200 Subject: [PATCH 3/3] Format code --- examples/signed_request.py | 5 ++--- messagebird/signed_request.py | 13 +++++++------ tests/test_signed_request.py | 5 ++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/examples/signed_request.py b/examples/signed_request.py index 0ff2574..982457b 100644 --- a/examples/signed_request.py +++ b/examples/signed_request.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import sys import argparse import messagebird @@ -17,7 +16,7 @@ else: print("The signed request cannot be verified.") -if signed_request.isRecent(): +if signed_request.is_recent(): print("The signed request is recent.") else: - print("The signed request is not recent.") \ No newline at end of file + print("The signed request is not recent.") diff --git a/messagebird/signed_request.py b/messagebird/signed_request.py index 33c2bba..620f20b 100644 --- a/messagebird/signed_request.py +++ b/messagebird/signed_request.py @@ -20,14 +20,15 @@ def __init__(self, requestSignature, requestTimestamp, requestBody, requestParam def verify(self, signing_key): payload = self._build_payload() - expectedSignature = base64.b64decode(self._requestSignature) - calculatedSignature = hmac.new(signing_key.encode('latin-1'), payload.encode('latin-1'), hashlib.sha256).digest() - return expectedSignature == calculatedSignature + expected_signature = base64.b64decode(self._requestSignature) + calculated_signature = hmac.new(signing_key.encode('latin-1'), payload.encode('latin-1'), + hashlib.sha256).digest() + return expected_signature == calculated_signature - def isRecent(self, offset = 10): + def is_recent(self, offset=10): return int(time.time()) - int(self._requestTimestamp) < offset - def _build_payload(self,): + def _build_payload(self): checksum_body = hashlib.sha256(self._requestBody.encode('latin-1')).digest() str_checksum_body = checksum_body.decode('latin-1') parts = [self._requestTimestamp, urlencode(self._sort_dict(self._requestParameters), True), str_checksum_body] @@ -39,4 +40,4 @@ def _sort_dict(self, dict): for key in sorted(dict): sorted_dict[key] = dict[key] - return sorted_dict \ No newline at end of file + return sorted_dict diff --git a/tests/test_signed_request.py b/tests/test_signed_request.py index 1472f25..8598309 100644 --- a/tests/test_signed_request.py +++ b/tests/test_signed_request.py @@ -9,7 +9,6 @@ # before. from mock import Mock - SIGNING_KEY = 'PlLrKaqvZNRR5zAjm42ZT6q1SQxgbbGd' @@ -76,7 +75,7 @@ def test_recent_signed_request(self): body = '' signed_request = SignedRequest(signature, timestamp, body, query) - self.assertTrue(signed_request.isRecent()) + self.assertTrue(signed_request.is_recent()) def test_not_recent_signed_request(self): query = {} @@ -85,4 +84,4 @@ def test_not_recent_signed_request(self): body = '' signed_request = SignedRequest(signature, timestamp, body, query) - self.assertFalse(signed_request.isRecent()) \ No newline at end of file + self.assertFalse(signed_request.is_recent())