Projet

Général

Profil

0001-signature-do-not-require-nonce-if-not-verified-and-o.patch

Thomas Noël, 02 avril 2020 14:26

Télécharger (5,83 ko)

Voir les différences:

Subject: [PATCH] signature: do not require nonce if not verified, and other
 fixes (#)

 hobo/signature.py       | 25 +++++++++++++++----------
 tests/test_signature.py | 27 ++++++++++++++++++---------
 2 files changed, 33 insertions(+), 19 deletions(-)
hobo/signature.py
2 2
import base64
3 3
import hmac
4 4
import hashlib
5
import urllib
6 5
import random
7 6

  
8 7
from django.utils import six
......
11 10
from django.utils.six.moves.urllib import parse as urlparse
12 11

  
13 12

  
14
# Simple signature scheme for query strings
13
'''Simple signature scheme for query strings'''
14

  
15 15

  
16 16
def sign_url(url, key, algo='sha256', timestamp=None, nonce=None):
17 17
    parsed = urlparse.urlparse(url)
18 18
    new_query = sign_query(parsed.query, key, algo, timestamp, nonce)
19 19
    return urlparse.urlunparse(parsed[:4] + (new_query,) + parsed[5:])
20 20

  
21

  
21 22
def sign_query(query, key, algo='sha256', timestamp=None, nonce=None):
22 23
    if timestamp is None:
23 24
        timestamp = datetime.datetime.utcnow()
......
29 30
        new_query += '&'
30 31
    new_query += urlencode((
31 32
        ('algo', algo),
32
        ('timestamp', timestamp),
33
        ('nonce', nonce)))
33
        ('timestamp', timestamp)))
34
    if nonce:  # we don't add nonce if it's an empty string
35
        new_query += '&nonce=' + quote(nonce)
34 36
    signature = base64.b64encode(sign_string(new_query, key, algo=algo))
35 37
    new_query += '&signature=' + quote(signature)
36 38
    return new_query
37 39

  
38
def sign_string(s, key, algo='sha256', timedelta=30):
40

  
41
def sign_string(s, key, algo='sha256'):
39 42
    digestmod = getattr(hashlib, algo)
43
    if isinstance(key, six.text_type):
44
        key = key.encode('utf-8')
40 45
    hash = hmac.HMAC(smart_bytes(key), digestmod=digestmod, msg=smart_bytes(s))
41 46
    return hash.digest()
42 47

  
43 48

  
44 49
def check_url(url, key, known_nonce=None, timedelta=30):
45 50
    parsed = urlparse.urlparse(url, 'https')
46
    return check_query(parsed.query, key)
51
    return check_query(parsed.query, key, known_nonce=known_nonce, timedelta=timedelta)
47 52

  
48 53

  
49 54
def check_query(query, key, known_nonce=None, timedelta=30):
50 55
    parsed = urlparse.parse_qs(query)
51 56
    if not ('signature' in parsed and 'algo' in parsed and
52
            'timestamp' in parsed and 'nonce' in parsed):
57
            'timestamp' in parsed):
53 58
        return False
59
    if known_nonce is not None:
60
        if ('nonce' not in parsed) or known_nonce(parsed['nonce'][0]):
61
            return False
54 62
    unsigned_query, signature_content = query.split('&signature=', 1)
55 63
    if '&' in signature_content:
56 64
        return False  # signature must be the last parameter
......
58 66
    algo = parsed['algo'][0]
59 67
    timestamp = parsed['timestamp'][0]
60 68
    timestamp = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
61
    nonce = parsed['nonce']
62
    if known_nonce is not None and known_nonce(nonce):
63
        return False
64 69
    if abs(datetime.datetime.utcnow() - timestamp) > datetime.timedelta(seconds=timedelta):
65 70
        return False
66 71
    return check_string(unsigned_query, signature, key, algo=algo)
tests/test_signature.py
43 43

  
44 44
    # Test nonce parameter
45 45
    assert '&nonce=uuu&' in signature.sign_url(URL, KEY, nonce='uuu')
46
    assert '&nonce=' in signature.sign_url(URL, KEY)
47
    assert '&nonce=' not in signature.sign_url(URL, KEY, nonce='')
46 48

  
47 49
    # Test known_nonce
48
    assert signature.check_url(signature.sign_url(URL, KEY), KEY,
49
                               known_nonce=lambda nonce: nonce == 'xxx')
50
    assert signature.check_url(signature.sign_url(URL, KEY, nonce='xxx'), KEY,
51
                               known_nonce=lambda nonce: nonce == 'xxx')
50
    def known_nonce(nonce):
51
        return nonce == 'xxx'
52
    assert signature.check_url(signature.sign_url(URL, KEY), KEY, known_nonce=known_nonce)
53
    assert signature.check_url(signature.sign_url(URL, KEY, nonce='zzz'), KEY, known_nonce=known_nonce)
54
    assert not signature.check_url(signature.sign_url(URL, KEY, nonce='xxx'), KEY, known_nonce=known_nonce)
55
    assert not signature.check_url(signature.sign_url(URL, KEY, nonce=''), KEY, known_nonce=known_nonce)
52 56

  
53 57
    # Test timedelta
54
    now = (datetime.datetime.utcnow() - datetime.timedelta(seconds=29))
58
    now = (datetime.datetime.utcnow() - datetime.timedelta(seconds=20))
55 59
    assert signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY)
56
    now = (datetime.datetime.utcnow() - datetime.timedelta(seconds=30))
57
    assert not signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY)
58
    now = (datetime.datetime.utcnow() - datetime.timedelta(seconds=2))
60
    now = (datetime.datetime.utcnow() + datetime.timedelta(seconds=20))
59 61
    assert signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY)
60
    assert signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY, timedelta=2)
62
    # too late
63
    now = (datetime.datetime.utcnow() - datetime.timedelta(seconds=40))
64
    assert not signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY)
65
    now = (datetime.datetime.utcnow() - datetime.timedelta(seconds=20))
66
    assert not signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY, timedelta=10)
67
    # too early
68
    now = (datetime.datetime.utcnow() + datetime.timedelta(seconds=40))
69
    assert not signature.check_url(signature.sign_url(URL, KEY, timestamp=now), KEY)
61
-