1 # encoding: utf-8 |
|
2 |
|
3 import base64 |
|
4 import datetime |
|
5 import hashlib |
|
6 import os |
|
7 import os.path |
|
8 import string |
|
9 |
|
10 import pvl.invoke |
|
11 |
|
12 import logging; log = logging.getLogger('pvl.login.ssl') |
|
13 |
|
14 class Error (Exception) : |
|
15 pass |
|
16 |
|
17 class UsersCA (object) : |
|
18 OPENSSL = '/usr/bin/openssl' |
|
19 |
|
20 SIGN_DAYS = 1 |
|
21 |
|
22 VALID_USER = set(string.letters + string.digits + '-.') |
|
23 |
|
24 O = u"Päivölän Kansanopisto" |
|
25 OU = u"People" |
|
26 DC = ('paivola', 'fi') |
|
27 |
|
28 def __init__ (self, ca, users) : |
|
29 self.ca = ca |
|
30 self.users = users |
|
31 |
|
32 self.ca_config = os.path.join(ca, 'openssl.cnf') |
|
33 |
|
34 def sign_spkac (self, out, spkac, days=SIGN_DAYS) : |
|
35 """ |
|
36 Sign given request file (path). |
|
37 |
|
38 Creates the given output file (path). Empty file on errors.. |
|
39 """ |
|
40 |
|
41 pvl.invoke.invoke(self.OPENSSL, ('ca', |
|
42 '-config', self.ca_config, |
|
43 '-spkac', spkac, |
|
44 '-out', out, |
|
45 '-policy', 'policy_user', |
|
46 '-days', str(days), |
|
47 '-utf8', |
|
48 ), |
|
49 setenv={ |
|
50 'CA': self.ca, |
|
51 }, |
|
52 ) |
|
53 |
|
54 def generate_dn (self, uid, cn=None) : |
|
55 """ |
|
56 Generate OpenSSL (rdn, value) pairs for given user. |
|
57 """ |
|
58 |
|
59 if self.O : |
|
60 yield 'O', self.O |
|
61 |
|
62 elif self.DC : |
|
63 for index, dc in enumerate(self.DC, 1) : |
|
64 yield '{index}.DC'.format(index=index), dc |
|
65 |
|
66 yield 'OU', self.OU |
|
67 |
|
68 yield 'UID', uid |
|
69 |
|
70 if cn : |
|
71 yield 'CN', cn |
|
72 |
|
73 def write_spkac (self, path, spkac, dn) : |
|
74 """ |
|
75 Write out a spkac file to the given path, containing the given base64-encoded spkac and DN. |
|
76 """ |
|
77 |
|
78 # roundtrip the spkac for consistent formatting |
|
79 spkac = base64.b64encode(base64.b64decode(spkac)) |
|
80 |
|
81 file = open(path, 'w') |
|
82 |
|
83 file.write('SPKAC=') |
|
84 file.write(spkac) |
|
85 file.write('\n') |
|
86 |
|
87 for rdn, value in dn : |
|
88 file.write(u'{rdn}={value}\n'.format(rdn=rdn, value=value).encode('utf-8')) |
|
89 |
|
90 file.close() |
|
91 |
|
92 def sign_user (self, user, spkac, userinfo=None) : |
|
93 """ |
|
94 Sign given spkac string (base64-encoded) for given user. |
|
95 |
|
96 Returns a name for the signed cert. |
|
97 """ |
|
98 |
|
99 if not set(user).issubset(self.VALID_USER) : |
|
100 raise Error("Invalid username: {user}".format(user=user)) |
|
101 |
|
102 dir = os.path.join(self.users, user) |
|
103 |
|
104 if not os.path.exists(dir) : |
|
105 os.mkdir(dir) |
|
106 |
|
107 name = hashlib.sha1(user + spkac).hexdigest() |
|
108 spkac_file = os.path.join(dir, name) + '.spkac' |
|
109 cert_file = os.path.join(dir, name) |
|
110 tmp_file = os.path.join(dir, name) + '.tmp' |
|
111 |
|
112 # the req to sign |
|
113 if os.path.exists(spkac_file) : |
|
114 log.warning("spkac already exists: %s", spkac_file) |
|
115 else : |
|
116 log.info("%s: write spkac: %s", user, spkac_file) |
|
117 self.write_spkac(os.path.join(dir, name) + '.spkac', spkac, self.generate_dn(user, userinfo)) |
|
118 |
|
119 # sign it |
|
120 if os.path.exists(cert_file) : |
|
121 log.warning("cert already exists: %s", cert_file) |
|
122 return name |
|
123 |
|
124 if os.path.exists(tmp_file) : |
|
125 log.warning("cleaning out previous tmp file: %s", tmp_file) |
|
126 os.unlink(tmp_file) |
|
127 |
|
128 log.info("%s: sign cert: %s", user, cert_file) |
|
129 self.sign_spkac(tmp_file, spkac_file) |
|
130 |
|
131 log.debug("%s: rename %s -> %s", user, tmp_file, cert_file) |
|
132 os.rename(tmp_file, cert_file) |
|
133 |
|
134 return name |
|
135 |
|
136 def open_cert (self, user, name) : |
|
137 """ |
|
138 Return an opened cert file by username / cert name. |
|
139 """ |
|
140 |
|
141 if not set(user).issubset(self.VALID_USER) : |
|
142 raise Error("Invalid username: {user}".format(user=user)) |
|
143 |
|
144 path = os.path.join(self.users, user, name) |
|
145 |
|
146 if not os.path.exists(path) : |
|
147 raise Error("No cert found on server") |
|
148 |
|
149 return open(path) |
|