Mercurial > templog
annotate web/secure.py @ 523:21ce2b15128f
title
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Thu, 29 Jan 2015 22:52:26 +0800 |
parents | ad846b9bdd10 |
children | da769023bf08 |
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 | |
59 _csrf_fd = open('%s/csrf.dat' % config.DATA_PATH, 'r+') | |
60 | |
61 try: | |
62 fcntl.lockf(_csrf_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) | |
63 os.fchmod(_csrf_fd.fileno(), 0600) | |
64 _csrf_fd.write("%d-%s" % (os.getpid(), binascii.hexlify(os.urandom(NONCE_SIZE)))) | |
65 _csrf_fd.flush() | |
66 _csrf_fd.seek(0) | |
67 except IOError: | |
68 pass | |
69 fcntl.lockf(_csrf_fd, fcntl.LOCK_SH) | |
70 _csrf_key = _csrf_fd.read() | |
71 # keep the lock open until we go away | |
72 | |
73 | |
74 def get_csrf_blob(): | |
75 expiry = int(config.CSRF_TIMEOUT + time.time()) | |
76 content = '%s-%s' % (get_user_hash(), expiry) | |
77 mac = hmac.new(_csrf_key, content).hexdigest() | |
78 return "%s-%s" % (content, mac) | |
79 | |
80 def check_csrf_blob(blob): | |
81 toks = blob.split('-') | |
82 if len(toks) != 3: | |
492 | 83 print>>sys.stderr, "wrong toks" |
485 | 84 return False |
85 | |
86 user, expiry, mac = toks | |
87 if user != get_user_hash(): | |
492 | 88 print>>sys.stderr, "wrong user" |
485 | 89 return False |
90 | |
91 try: | |
92 exp = int(expiry) | |
93 except ValueError: | |
492 | 94 print>>sys.stderr, "failed exp" |
485 | 95 return False |
96 | |
97 if exp < 1000000000: | |
98 return False | |
99 | |
492 | 100 if exp < time.time(): |
101 print>>sys.stderr, "expired %d %d" % (exp, time.time()) | |
485 | 102 return False |
103 | |
104 check_content = "%s-%s" % (user, expiry) | |
492 | 105 check_mac = hmac.new(_csrf_key, check_content).hexdigest() |
485 | 106 if mac == check_mac: |
492 | 107 print>>sys.stderr, "good hmac" |
485 | 108 return True |
109 | |
492 | 110 print>>sys.stderr, "fail" |
485 | 111 return False |
112 |