2019-08-14 11:52:59 -05:00
|
|
|
# -*- coding: utf-8 -*-
|
2019-09-02 04:56:45 -05:00
|
|
|
|
2019-08-14 11:52:59 -05:00
|
|
|
from collections import OrderedDict
|
2019-09-02 04:56:45 -05:00
|
|
|
import os
|
2019-08-14 11:52:59 -05:00
|
|
|
|
2019-09-14 02:38:56 -05:00
|
|
|
from nacl import signing
|
|
|
|
from nacl.exceptions import BadSignatureError
|
2019-08-14 11:52:59 -05:00
|
|
|
from django.utils.crypto import constant_time_compare, salted_hmac
|
|
|
|
|
|
|
|
from .utils import Base64, Encoder
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-09-02 04:56:45 -05:00
|
|
|
|
|
|
|
|
2019-08-14 11:52:59 -05:00
|
|
|
class HMAC(object):
|
|
|
|
"""
|
|
|
|
Utility class for generating and verifying HMAC signatures.
|
|
|
|
|
|
|
|
This class relies on Django's built in :func:`salted_hmac`
|
|
|
|
to compute actual HMAC values by using ``SECRET_KEY`` as key.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
nut : SQRLNut
|
|
|
|
Nut from which necessary data is extracted to add a salt value
|
|
|
|
to the HMAC input data.
|
|
|
|
Currently only :attr:`.models.SQRLNut.session_key` is used.
|
|
|
|
data : OrderedDict
|
|
|
|
Dict for which to either compute or validate HMAC signature.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, nut, data):
|
|
|
|
self.nut = nut
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def sign_data(self):
|
|
|
|
"""
|
|
|
|
Generate HMAC signature for the provided data.
|
|
|
|
|
|
|
|
Note
|
|
|
|
----
|
|
|
|
``max`` key is ignored in the input data if that key is present.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
bytes
|
|
|
|
Binary signature of the data
|
|
|
|
"""
|
|
|
|
assert isinstance(self.data, OrderedDict)
|
|
|
|
|
|
|
|
encoded = Encoder.base64_dumps(OrderedDict(
|
|
|
|
(k, v) for k, v in self.data.items()
|
|
|
|
if k != 'mac'
|
|
|
|
))
|
|
|
|
signature = salted_hmac(self.nut.session_key, encoded).digest()
|
|
|
|
|
|
|
|
return signature
|
|
|
|
|
|
|
|
def is_signature_valid(self, other_signature):
|
|
|
|
"""
|
|
|
|
Check if the ``other_signature`` is a valid signature for the
|
|
|
|
provided data and the nut.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
bool
|
|
|
|
Boolean indicating whether validation has succeeded.
|
|
|
|
"""
|
|
|
|
expected_signature = self.sign_data()
|
|
|
|
return constant_time_compare(expected_signature, other_signature)
|
|
|
|
|
|
|
|
|
|
|
|
class Ed25519(object):
|
|
|
|
"""
|
|
|
|
Utility class for signing and verifying ed25519 signatures.
|
|
|
|
|
|
|
|
More information about ed25519 can be found at `<http://ed25519.cr.yp.to/>`_.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
public_key : bytes
|
|
|
|
Key used for verifying signature.
|
|
|
|
private_key : bytes
|
|
|
|
Key used for signing data.
|
|
|
|
msg : bytes
|
|
|
|
Binary data for which to generate the signature.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, public_key, private_key, msg):
|
|
|
|
self.public_key = public_key
|
|
|
|
self.private_key = private_key
|
2019-09-14 02:38:56 -05:00
|
|
|
if private_key and type(private_key) == bytes:
|
|
|
|
self.private_key = private_key[:32]
|
2019-08-14 11:52:59 -05:00
|
|
|
self.msg = msg
|
|
|
|
|
|
|
|
def is_signature_valid(self, other_signature):
|
|
|
|
"""
|
|
|
|
Check if ``other_signature`` is a valid signature for the provided message.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
bool
|
|
|
|
Boolean indicating whether validation has succeeded.
|
|
|
|
"""
|
|
|
|
try:
|
2019-09-14 02:38:56 -05:00
|
|
|
vk = signing.VerifyKey(self.public_key)
|
|
|
|
vk.verify(self.msg, other_signature)
|
2019-08-14 11:52:59 -05:00
|
|
|
return True
|
2019-09-14 02:38:56 -05:00
|
|
|
except (AssertionError, BadSignatureError) as e:
|
2019-08-14 11:52:59 -05:00
|
|
|
return False
|
|
|
|
|
|
|
|
def sign_data(self):
|
|
|
|
"""
|
|
|
|
Generate ed25519 signature for the provided data.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
bytes
|
|
|
|
ed25519 signature
|
|
|
|
"""
|
2019-09-14 02:38:56 -05:00
|
|
|
sk = signing.SigningKey(self.private_key)
|
|
|
|
return sk.sign(self.msg).signature
|
2019-08-14 11:52:59 -05:00
|
|
|
|
|
|
|
|
|
|
|
def generate_randomness(size=32):
|
|
|
|
"""
|
|
|
|
Generate random sample of specified size ``size``.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
size : int, optional
|
|
|
|
Number of bytes to generate random sample
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
str
|
|
|
|
:meth:`.Base64.encode` encoded random sample
|
|
|
|
"""
|
2019-09-02 04:56:45 -05:00
|
|
|
return Base64.encode(bytearray(os.urandom(size)))
|