0001-signature-do-not-require-nonce-if-not-verified-and-o.patch
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 |
- |