Mercurial > templog
annotate web/secure.py @ 547:7689af9f56a8
Update to bottle 0.12.8
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 27 May 2015 22:17:39 +0800 |
parents | 8559078a82ff |
children | f7261dd970da |
rev | line source |
---|---|
505
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
1 import re |
485 | 2 import os |
3 import time | |
4 import fcntl | |
5 import hmac | |
6 import binascii | |
7 import sys | |
488 | 8 import hashlib |
9 | |
10 import bottle | |
485 | 11 |
12 import config | |
13 | |
505
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
14 __all__ = ["get_csrf_blob", "check_csrf_blob", "setup_csrf", "get_user_hash", |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
15 "check_user_hash"] |
488 | 16 |
17 HASH=hashlib.sha1 | |
485 | 18 |
505
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
19 CLEAN_RE = re.compile('[^a-z0-9A-Z]') |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
20 |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
21 def clean_hash(h): |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
22 return CLEAN_RE.sub('', h.lower()) |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
23 |
485 | 24 def get_user_hash(): |
505
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
25 """ |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
26 Uses the following apache config. |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
27 Needs a separate port or IP to no-certificate SSL, SNI isn't good enough. |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
28 |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
29 <location /~matt/templog/set> |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
30 Require all granted |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
31 SSLVerifyClient optional_no_ca |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
32 SSLVerifyDepth 1 |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
33 SSLOptions +StdEnvVars +ExportCertData +OptRenegotiate |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
34 </location> |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
35 """ |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
36 |
489 | 37 verify = bottle.request.environ.get('SSL_CLIENT_VERIFY', '') |
38 if not (verify == 'GENEROUS' or verify == 'SUCCESS'): | |
488 | 39 return 'FAILVERIFY' |
40 blob = bottle.request.environ.get('SSL_CLIENT_CERT') | |
41 if not blob: | |
42 return 'NOCERT' | |
43 | |
44 b64 = ''.join(l for l in blob.split('\n') | |
45 if not l.startswith('-')) | |
46 | |
47 return HASH(binascii.a2b_base64(b64)).hexdigest() | |
485 | 48 |
505
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
49 def check_user_hash(allowed_users): |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
50 current_hash = clean_hash(get_user_hash()) |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
51 for a in allowed_users: |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
52 if current_hash == clean_hash(a): |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
53 return True |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
54 return False |
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
55 |
485 | 56 def setup_csrf(): |
57 NONCE_SIZE=16 | |
58 global _csrf_fd, _csrf_key | |
525 | 59 _csrf_fd = os.fdopen(os.open('%s/csrf.dat' % config.DATA_PATH, os.O_RDWR | os.O_CREAT, 0600), 'r+') |
485 | 60 |
61 try: | |
62 fcntl.lockf(_csrf_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) | |
63 _csrf_fd.write("%d-%s" % (os.getpid(), binascii.hexlify(os.urandom(NONCE_SIZE)))) | |
64 _csrf_fd.flush() | |
65 _csrf_fd.seek(0) | |
66 except IOError: | |
67 pass | |
68 fcntl.lockf(_csrf_fd, fcntl.LOCK_SH) | |
69 _csrf_key = _csrf_fd.read() | |
70 # keep the lock open until we go away | |
71 | |
72 | |
73 def get_csrf_blob(): | |
74 expiry = int(config.CSRF_TIMEOUT + time.time()) | |
75 content = '%s-%s' % (get_user_hash(), expiry) | |
76 mac = hmac.new(_csrf_key, content).hexdigest() | |
77 return "%s-%s" % (content, mac) | |
78 | |
79 def check_csrf_blob(blob): | |
80 toks = blob.split('-') | |
81 if len(toks) != 3: | |
492 | 82 print>>sys.stderr, "wrong toks" |
485 | 83 return False |
84 | |
85 user, expiry, mac = toks | |
86 if user != get_user_hash(): | |
492 | 87 print>>sys.stderr, "wrong user" |
485 | 88 return False |
89 | |
90 try: | |
91 exp = int(expiry) | |
92 except ValueError: | |
492 | 93 print>>sys.stderr, "failed exp" |
485 | 94 return False |
95 | |
96 if exp < 1000000000: | |
97 return False | |
98 | |
492 | 99 if exp < time.time(): |
100 print>>sys.stderr, "expired %d %d" % (exp, time.time()) | |
485 | 101 return False |
102 | |
103 check_content = "%s-%s" % (user, expiry) | |
492 | 104 check_mac = hmac.new(_csrf_key, check_content).hexdigest() |
485 | 105 if mac == check_mac: |
492 | 106 print>>sys.stderr, "good hmac" |
485 | 107 return True |
108 | |
492 | 109 print>>sys.stderr, "fail" |
485 | 110 return False |
111 |