def test_new_user_to_existing_group(self): user_priv, user_pub = example_keys.C1_priv, example_keys.C1_pub group_priv, group_pub = example_keys.G1_priv, example_keys.G1_pub payload = json.dumps({ 'user_key': user_pub, 'group_uuid': str(self.group.uuid) }) signature = crypto.sign(group_priv, payload) response = self.client.post(reverse('rest:group:register-user'), { 'author': group_pub, 'signature': signature, 'payload': payload }) assert response.status_code == 201 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['group_uuid'] == str(self.group.uuid) assert payload['user'] == user_pub
def wrapper(request): # https://docs.djangoproject.com/en/1.11/topics/http/middleware/#process-view # verify the JSON B64 string. return None if it's fine, # return an HTTPResponse with an error if not try: author, signature, payload = request.POST['author'], request.POST[ 'signature'], request.POST['payload'] except KeyError: logger.info('Request with missing author, signature or payload') return HttpResponseBadRequest() # get user pubkey # what if the author CAN'T already be registered? i.e.: group key # maybe check view_func and ignore a few? # or let the view itself verify if the author is registered... # NOTE: This does not verify if the signer is authorized for the operation. # It only verifies if the signature matches the given pub key try: crypto.verify(author, signature, payload) return view(request) except (crypto.InvalidSignature, crypto.InvalidKey): logger.info('Request with invalid author key or signature') return HttpResponseForbidden()
def test_get_totals_one_confirmed_uome(self): uome = UserDebt.objects.create(group=self.group, lender=self.user2, borrower=self.user1, value=1000) self.user2.balance = +uome.value self.user2.save() self.user1.balance = -uome.value self.user1.save() response = self.client.post( reverse('rest:uome:get-totals'), { 'author': self.key, 'signature': self.signature, 'payload': self.payload }) assert response.status_code == 200 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['user_balance'] == -uome.value assert payload['suggested_transactions'] == { self.user2.key: uome.value }
def test_SigningSomeTextWithKey1AndVerifyingWithPubkey1_DoesNotRaiseInvalidSignature( self): plain_text = "some text" key, pubkey = ec_secp256k1.generate_keys() valid_signature = ec_secp256k1.sign(key, plain_text) ec_secp256k1.verify(pubkey, valid_signature, plain_text)
def test_SigningSomeTextWithKey1AndVerifyingWithPubkey1_RaisesInvalidSignature( self): plain_text = "some text" key_1, pubkey_1 = ec_secp256k1.generate_keys() key_2, pubkey_2 = ec_secp256k1.generate_keys() with raises(ec_secp256k1.InvalidSignature): valid_signature = ec_secp256k1.sign(key_1, plain_text) ec_secp256k1.verify(pubkey_2, valid_signature, plain_text)
def test_cancel_unconfirmed_uome(self): uome = UOMe.objects.create(group=self.group, lender=self.user, borrower=self.borrower, value=10, description='test') issuer_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'issuer': self.user.key, 'borrower': self.borrower.key, 'value': 10, 'description': 'test', 'uome_uuid': str(uome.uuid), }) issuer_signature = crypto.sign(self.private_key, issuer_payload) uome.issuer_signature = issuer_signature uome.save() payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'user': self.user.key, 'uome_uuid': str(uome.uuid), }) signature = crypto.sign(self.private_key, payload) response = self.client.post(reverse('rest:uome:cancel'), { 'author': self.user.key, 'signature': signature, 'payload': payload }) assert response.status_code == 200 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['group_uuid'] == str(self.group.uuid) assert payload['user'] == self.user.key assert payload['uome_uuid'] == str(uome.uuid) assert UOMe.objects.filter(uuid=uome.uuid).first() is None
def test_get_totals_no_uome(self): response = self.client.post( reverse('rest:uome:get-totals'), { 'author': self.key, 'signature': self.signature, 'payload': self.payload }) assert response.status_code == 200 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['user_balance'] == 0 assert payload['suggested_transactions'] == {}
def test_invalid_message(self): group_name = 'test_name' priv_key, pub_key = example_keys.G1_priv, example_keys.G1_pub payload = json.dumps({'group_name': group_name}) # missing 'group_key' field signature = crypto.sign(priv_key, payload) response = self.client.post(reverse('rest:group:register'), { 'author': pub_key, 'signature': signature, 'payload': payload }) assert response.status_code == 400 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode())
def test_add_first_uome(self): auth_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'user': self.user.key }) auth_signature = crypto.sign(self.private_key, auth_payload) payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'user': self.user.key, 'borrower': self.borrower.key, 'value': 1000, 'description': 'my description', 'user_signature': auth_signature }) # todo: recheck if this is a security issue (might count as accepting the uome) signature = crypto.sign(self.private_key, payload) response = self.client.post(reverse('rest:uome:issue'), { 'author': self.user.key, 'signature': signature, 'payload': payload }) assert response.status_code == 201 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['group_uuid'] == str(self.group.uuid) assert payload['user'] == self.user.key assert payload['borrower'] == self.borrower.key assert payload['value'] == 1000 assert payload['description'] == 'my description' uome = UOMe.objects.get(pk=payload['uome_uuid']) assert uome.issuer_signature == ''
def test_new_user_invalid_group_uuid(self): user_priv, user_pub = example_keys.C1_priv, example_keys.C1_pub group_priv, group_pub = example_keys.G1_priv, example_keys.G1_pub payload = json.dumps({ 'user_key': user_pub, 'group_uuid': 'random_uuid' }) signature = crypto.sign(group_priv, payload) response = self.client.post(reverse('rest:group:register-user'), { 'author': group_pub, 'signature': signature, 'payload': payload }) assert response.status_code == 400 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode())
def test_correct_inputs(self): group_name = 'test_name' priv_key, pub_key = example_keys.G1_priv, example_keys.G1_pub id = pub_key payload = json.dumps({'group_name': group_name, 'group_key': pub_key}) signature = crypto.sign(priv_key, payload) response = self.client.post(reverse('rest:group:register'), { 'author': id, 'signature': signature, 'payload': payload }) assert response.status_code == 201 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['group_name'] == group_name assert payload['group_key'] == pub_key UUID(payload['group_uuid'])
def test_get_totals_one_unconfirmed_uome(self): UOMe.objects.create(group=self.group, lender=self.user2, borrower=self.user1, value=10, description="test", issuer_signature='meh') response = self.client.post( reverse('rest:uome:get-totals'), { 'author': self.key, 'signature': self.signature, 'payload': self.payload }) assert response.status_code == 200 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['user_balance'] == 0 assert payload['suggested_transactions'] == {}
def test_VerifyingACompletelyBrokenSignature_RaisesInvalidSignature(self): key, pubkey = ec_secp256k1.generate_keys() with raises(ec_secp256k1.InvalidSignature): ec_secp256k1.verify(pubkey, "completelyBogûsÇigna_!ture", "not a chance!")
def get_totals(request): """ Used by a user to check the totals of users in the group """ try: payload = json.loads(request.POST['payload']) except json.JSONDecodeError: logger.info('Malformed request') return HttpResponseBadRequest() try: group_uuid = payload['group_uuid'] user_id = payload['user'] user_signature = payload['user_signature'] except KeyError: logger.info('Request with missing attributes') return HttpResponseBadRequest() try: # check that the group exists and get it group = Group.objects.get(pk=group_uuid) user = User.objects.get(group=group, key=user_id) except (ValidationError, ObjectDoesNotExist): # ValidationError if the key is invalid logger.info('Request tried to get the totals for non-existent group %s' 'or user %s' % (group_uuid, user_id)) return HttpResponseBadRequest() user_payload = json.dumps({ 'group_uuid': str(group.uuid), 'user': user.key, }) # todo: probably unnecessary because of the verify author decorator try: # verify the signatures crypto.verify(user.key, user_signature, user_payload) except (crypto.InvalidKey, crypto.InvalidSignature): logger.info('Request with invalid signature or key by author %s' % user_id) return HttpResponseForbidden() # example: {'user1': val1, 'user2': val2} suggested_transactions = {} # todo: send the actual totals along with the suggested transactions if user.balance < 0: # filter by borrower for debt in UserDebt.objects.filter(group=group, borrower=user): suggested_transactions[debt.lender.key] = debt.value elif user.balance > 0: # filter by lender for debt in UserDebt.objects.filter(group=group, lender=user): suggested_transactions[debt.borrower.key] = debt.value response = json.dumps({ 'group_uuid': str(group.uuid), 'user': user.key, 'user_balance': user.balance, 'suggested_transactions': suggested_transactions, }) logger.info('Totals sent to user %s' % user_id) return HttpResponse(response, status=200)
def issue(request): """ Used by a user to issue an unconfirmed UOMe to another user """ try: payload = json.loads(request.POST['payload']) except json.JSONDecodeError: logger.info('Malformed request') return HttpResponseBadRequest() try: group_uuid = payload['group_uuid'] user_id = payload['user'] borrower_id = payload['borrower'] value = payload['value'] description = payload['description'] auth_signature = payload['user_signature'] except KeyError: logger.info('Request with missing attributes') return HttpResponseBadRequest() if request.POST['author'] != user_id: logger.info('Request made by unauthorized author %s' % request.POST['author']) return HttpResponse('401 Unauthorized', status=401) auth_payload = json.dumps({'group_uuid': str(group_uuid), 'user': user_id}) try: crypto.verify(user_id, auth_signature, auth_payload) except (crypto.InvalidKey, crypto.InvalidSignature): logger.info('Request with invalid signature or key by author %s' % user_id) return HttpResponseForbidden() try: # check that the group exists and get it group = Group.objects.get(pk=group_uuid) user = User.objects.get(group=group, key=user_id) borrower = User.objects.get(group=group, key=borrower_id) except (ValidationError, ObjectDoesNotExist): # ValidationError if key is not valid logger.info('Request tried to issue uome for non-existent group %s' ', user %s or borrower %s' % (group_uuid, user_id, borrower_id)) return HttpResponseBadRequest() if value <= 0: # So it's not possible to invert the direction of the UOMe logger.info( 'Request tried to issue a uome with negative value (user %s)', user) return HttpResponseBadRequest() if len(description) > UOME_DESCRIPTION_MAX_LENGTH: logger.info( 'Request tried to issue a uome with invalid description (user %s)', user) return HttpResponseBadRequest() if user == borrower: # That would just be weird... logger.info( 'Request tried to issue a uome from a user (%s) to themselves', user) return HttpResponseBadRequest() # TODO: the description can leak information, maybe it should be encrypted uome = UOMe.objects.create(group=group, lender=user, borrower=borrower, value=value, description=description) response = json.dumps({ 'group_uuid': str(group.uuid), 'user': user.key, 'borrower': borrower.key, 'value': value, 'description': description, 'uome_uuid': str(uome.uuid) }) logger.info('New uome %s issued in group %s by user %s' % (uome.uuid, group_uuid, user_id)) return HttpResponse(response, status=201)
def confirm(request): """ Used by a user to confirm an unconfirmed UOMe after the server assigns it an uuid """ try: payload = json.loads(request.POST['payload']) except json.JSONDecodeError: logger.info('Malformed request') return HttpResponseBadRequest() try: group_uuid = payload['group_uuid'] user_id = payload['user'] uome_uuid = payload['uome_uuid'] user_signature = payload['user_signature'] except KeyError: logger.info('Request with missing attributes') return HttpResponseBadRequest() if request.POST['author'] != user_id: logger.info('Request made by unauthorized author %s' % request.POST['author']) return HttpResponse('401 Unauthorized', status=401) try: # check that the group exists and get it group = Group.objects.get(pk=group_uuid) user = User.objects.get(group=group, key=user_id) uome = UOMe.objects.get(pk=uome_uuid) except (ValidationError, ObjectDoesNotExist): # ValidationError if key not valid logger.info( 'Request tried to confirm uome %s for non-existent group %s' ', user %s' % (uome_uuid, group_uuid, user_id)) return HttpResponseBadRequest() uome_payload = json.dumps({ 'group_uuid': str(uome.group.uuid), 'user': uome.lender.key, 'borrower': uome.borrower.key, 'value': uome.value, 'description': uome.description, 'uome_uuid': str(uome.uuid), }) try: crypto.verify(user_id, user_signature, uome_payload) except (crypto.InvalidKey, crypto.InvalidSignature): logger.info('Request with invalid signature or key by author %s' % user_id) return HttpResponseForbidden() # TODO: the description can leak information, maybe it should be encrypted uome.issuer_signature = payload['user_signature'] uome.save() # user created, create the response object response = json.dumps({'group_uuid': str(group.uuid), 'user': user.key}) logger.info('New uome %s confirmed in group %s by user %s' % (uome.uuid, group_uuid, user_id)) return HttpResponse(response, status=200)
def test_confirm_first_uome(self): uome = UOMe.objects.create( group=self.group, lender=self.lender, borrower=self.user, value=10, description='test', ) issuer_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'issuer': self.lender.key, 'borrower': self.user.key, 'value': 10, 'description': 'test', 'uome_uuid': str(uome.uuid), }) issuer_signature = crypto.sign(example_keys.C2_priv, issuer_payload) uome.issuer_signature = issuer_signature uome.save() assert uome.borrower_signature == '' borrower_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'issuer': self.lender.key, 'borrower': self.user.key, 'value': 10, 'description': 'test', 'uome_uuid': str(uome.uuid), }) borrower_signature = crypto.sign(self.private_key, borrower_payload) payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'user': self.user.key, 'uome_uuid': str(uome.uuid), 'user_signature': borrower_signature, }) signature = crypto.sign(self.private_key, payload) response = self.client.post(reverse('rest:uome:accept'), { 'author': self.user.key, 'signature': signature, 'payload': payload }) assert response.status_code == 200 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['group_uuid'] == str(self.group.uuid) assert payload['user'] == self.user.key uome = UOMe.objects.filter(group=self.group, uuid=uome.uuid).first() assert uome.borrower_signature == borrower_signature # Confirm totals totals = {} for user in User.objects.filter(group=self.group): totals[user] = user.balance assert totals == {self.user: -uome.value, self.lender: uome.value} # Confirm simplified debt simplified_debt = defaultdict(dict) for user_debt in UserDebt.objects.filter(group=self.group): simplified_debt[user_debt.borrower][ user_debt.lender] = user_debt.value assert simplified_debt == {self.user: {self.lender: uome.value}}
def accept(request): """ Used by a user to accept a pending UOMe issued to them """ try: payload = json.loads(request.POST['payload']) except json.JSONDecodeError: logger.info('Malformed request') return HttpResponseBadRequest() try: group_uuid = payload['group_uuid'] user_id = payload['user'] uome_uuid = payload['uome_uuid'] uome_signature = payload['user_signature'] except KeyError: logger.info('Request with missing attributes') return HttpResponseBadRequest() try: # check that the group exists and get it group = Group.objects.get(pk=group_uuid) user = User.objects.get(group=group, key=user_id) uome = UOMe.objects.get(group=group, uuid=uome_uuid) except (ValidationError, ObjectDoesNotExist): # ValidationError if the key is invalid logger.info('Request tried accepting a uomes for non-existent group %s' ', user %s or uome %s' % (group_uuid, user_id, uome_uuid)) return HttpResponseBadRequest() if request.POST['author'] != user_id or request.POST[ 'author'] != uome.borrower.key: logger.info('Request made by unauthorized author %s' % request.POST['author']) return HttpResponse('401 Unauthorized', status=401) uome_payload = json.dumps({ 'group_uuid': str(uome.group.uuid), 'issuer': uome.lender.key, 'borrower': uome.borrower.key, 'value': uome.value, 'description': uome.description, 'uome_uuid': str(uome.uuid), }) try: # verify the signatures crypto.verify(user.key, uome_signature, uome_payload) except (crypto.InvalidKey, crypto.InvalidSignature): logger.info('Request with invalid signature or key by author %s' % user_id) return HttpResponseForbidden() uome.borrower_signature = uome_signature uome.save() # update the balances and suggestions of users group_users = User.objects.filter(group=group) totals = defaultdict(int) for group_user in group_users: totals[group_user.key] = group_user.balance new_uome = [uome.borrower.key, uome.lender.key, uome.value] new_totals, new_simplified_debt = simplify_debt.update_total_debt( totals, [new_uome]) for group_user in group_users: group_user.balance = new_totals[group_user.key] group_user.save() # drop the previous user debt for this group, since it's now useless UserDebt.objects.filter(group=group).delete() for borrower, user_debts in new_simplified_debt.items(): # debts is a dict of users this borrower owes to, like {'user1': 3, 'user2':8} for lender, value in user_debts.items(): UserDebt.objects.create(group=group, value=value, borrower=User.objects.get(key=borrower), lender=User.objects.get(key=lender)) response = json.dumps({ 'group_uuid': str(uome.group.uuid), 'user': user.key, 'uome_uuid': str(uome.uuid), }) logger.info('UOMe %s was accepted by user %s' % (str(uome_uuid), user_id)) return HttpResponse(response, status=200)
def get_pending(request): """ Used by a user to request a list of pending (not yet accepted) UOMes issued to/by them """ try: payload = json.loads(request.POST['payload']) except json.JSONDecodeError: logger.info('Malformed request') return HttpResponseBadRequest() try: group_uuid = payload['group_uuid'] user_id = payload['user'] auth_signature = payload['user_signature'] except KeyError: logger.info('Request with missing attributes') return HttpResponseBadRequest() if request.POST['author'] != user_id: logger.info('Request made by unauthorized author %s' % request.POST['author']) return HttpResponse('401 Unauthorized', status=401) try: # check that the group exists and get it group = Group.objects.get(pk=group_uuid) user = User.objects.get(group=group, key=user_id) except (ValidationError, ObjectDoesNotExist): # ValidationError if the key is invalid logger.info( 'Request tried accessing pending uomes for non-existent group %s' ', user %s' % (group_uuid, user_id)) return HttpResponseBadRequest() auth_payload = json.dumps({ 'group_uuid': str(group.uuid), 'user': user.key, }) try: # verify the signatures crypto.verify(user.key, auth_signature, auth_payload) except (crypto.InvalidKey, crypto.InvalidSignature): logger.info('Request with invalid signature or key by author %s' % user_id) return HttpResponseForbidden() # TODO: add a test for uome's without issuer signatures uomes_by_user = UOMe.objects.filter( group=group, borrower_signature='', lender=user).exclude(issuer_signature='') uomes_for_user = UOMe.objects.filter( group=group, borrower_signature='', borrower=user).exclude(issuer_signature='') issued_by_user = [] for uome in uomes_by_user: issued_by_user.append(uome.to_dict_unconfirmed()) waiting_for_user = [] for uome in uomes_for_user: waiting_for_user.append(uome.to_dict_unconfirmed()) response = json.dumps({ 'group_uuid': str(group.uuid), 'user': user.key, 'issued_by_user': json.dumps(issued_by_user), 'waiting_for_user': json.dumps(waiting_for_user), }) logger.info('Sent pending uome list to user %s' % user_id) return HttpResponse(response, status=200)
def test_one_by_user_uome_and_one_for_user_uome(self): uome_by_user = UOMe.objects.create(group=self.group, lender=self.user, borrower=self.other_user, value=30, description="by user") uome_by_user_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'issuer': self.user.key, 'borrower': self.other_user.key, 'value': 30, 'description': 'by user', 'uome_uuid': str(uome_by_user.uuid), }) uome_by_user_signature = crypto.sign(self.private_key, uome_by_user_payload) uome_by_user.issuer_signature = uome_by_user_signature uome_by_user.save() uome_for_user = UOMe.objects.create(group=self.group, lender=self.other_user, borrower=self.user, value=20, description="for user") uome_for_user_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'issuer': self.other_user.key, 'borrower': self.user.key, 'value': 20, 'description': 'for user', 'uome_uuid': str(uome_for_user.uuid), }) uome_for_user_signature = crypto.sign(self.private_key, uome_for_user_payload) uome_for_user.issuer_signature = uome_for_user_signature uome_for_user.save() assert uome_by_user.borrower_signature == '' assert uome_by_user.issuer_signature != '' assert uome_for_user.borrower_signature == '' assert uome_for_user.issuer_signature != '' auth_payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'user': self.user.key }) auth_signature = crypto.sign(self.private_key, auth_payload) payload = json.dumps({ 'group_uuid': str(self.group.uuid), 'user': self.user.key, 'user_signature': auth_signature }) signature = crypto.sign(self.private_key, payload) response = self.client.post(reverse('rest:uome:get-pending'), { 'author': self.user.key, 'signature': signature, 'payload': payload }) assert response.status_code == 200 assert response['author'] == server_key crypto.verify(server_key, response['signature'], response.content.decode()) payload = json.loads(response.content.decode()) assert payload['group_uuid'] == str(self.group.uuid) assert payload['user'] == self.user.key issued_by_user = json.loads(payload['issued_by_user']) for uome in issued_by_user: assert uome['group_uuid'] == str(uome_by_user.group.uuid) assert uome['lender'] == uome_by_user.lender.key assert uome['borrower'] == uome_by_user.borrower.key assert uome['value'] == uome_by_user.value assert uome['description'] == uome_by_user.description assert uome['uuid'] == str(uome_by_user.uuid) assert uome['issuer_signature'] == uome_by_user_signature waiting_for_user = json.loads(payload['waiting_for_user']) for uome in waiting_for_user: assert uome['group_uuid'] == str(uome_by_user.group.uuid) assert uome['lender'] == uome_for_user.lender.key assert uome['borrower'] == uome_for_user.borrower.key assert uome['value'] == uome_for_user.value assert uome['description'] == uome_for_user.description assert uome['uuid'] == str(uome_for_user.uuid) assert uome['issuer_signature'] == uome_for_user_signature