1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """
19 Flumotion Twisted credentials
20 """
21
22 import random
23
24 from flumotion.common import log, python
25 from twisted.cred import credentials
26 from zope.interface import implements
27
28 try:
29 import crypt
30 except ImportError:
31 from flumotion.extern import unixcrypt as crypt
32
33 __version__ = "$Rev$"
34
35
37 """
38 I am your average username and password credentials.
39 """
40 implements(credentials.IUsernamePassword)
41
42 - def __init__(self, username, password=''):
45
48
49 IUsernamePassword = credentials.IUsernamePassword
50
51 IUsernameHashedPassword = credentials.IUsernameHashedPassword
52
53
55 """
56 I encapsulate a username and check crypted passwords.
57
58 This credential interface is used when a crypt password is received
59 from the party requesting authentication.
60 CredentialCheckers which check this kind of credential must store
61 the passwords in plaintext or crypt form.
62
63 @type username: C{str}
64 @ivar username: The username associated with these credentials.
65 """
66
68 """
69 Validate these credentials against the correct crypt password.
70
71 @param cryptPassword: The correct, crypt password against which to
72 check.
73
74 @return: a deferred which becomes, or a boolean indicating if the
75 password matches.
76 """
77
78
80 """
81 I take a username and a plaintext password.
82 I implement IUsernameCryptPassword.
83 """
84
85 implements(IUsernameCryptPassword)
86
87 - def __init__(self, username, password):
90
91 - def checkCryptPassword(self, cryptPassword):
92 """Check credentials against the given cryptPassword."""
93 salt = cryptPassword[:2]
94 encrypted = crypt.crypt(self.password, salt)
95 return encrypted == cryptPassword
96
97
99 """
100 I take a username and a crypt password.
101 When using me you should make sure the password was crypted with the
102 correct salt (which is stored in the crypt password backend of whatever
103 checker you use); otherwise your password may be a valid crypt, but
104 with a different salt.
105 I implement IUsernameCryptPassword.
106 """
107
108 implements(IUsernameCryptPassword)
109
110 - def __init__(self, username, cryptPassword=None):
113
115 """
116 Given the plaintext password and the salt,
117 set the correct cryptPassword.
118 """
119 assert len(salt) == 2
120
121 self.cryptPassword = crypt.crypt(password, salt)
122
124 """
125 Check credentials against the given cryptPassword.
126 """
127 return self.cryptPassword == cryptPassword
128
129
131 """
132 Respond to a given crypt challenge with our cryptPassword.
133 """
134 md = python.md5()
135 md.update(cryptPassword)
136 md.update(challenge)
137 return md.digest()
138
139
141 """
142 Take a string of bytes, and return a string of two-digit hex values.
143 """
144 l = []
145 for c in data:
146 l.append("%02x" % ord(c))
147 return "".join(l)
148
149
150
151
153 """
154 I return some random data.
155 """
156 crap = ''
157 for x in range(random.randrange(15, 25)):
158 crap = crap + chr(random.randint(65, 90) + x - x)
159 crap = python.md5(crap).digest()
160 return crap
161
162
164 """
165 I take a username.
166
167 Authenticator will give me a salt and challenge me.
168 Requester will respond to the challenge.
169 At that point I'm ready to be used by a checker.
170 The response function used is
171 L{flumotion.twisted.credentials.cryptRespond()}
172
173 I implement IUsernameCryptPassword.
174 """
175
176 implements(IUsernameCryptPassword)
177
179 self.username = username
180 self.salt = None
181 self.challenge = None
182 self.response = None
183
185 """
186 I encode a given plaintext password using the salt, and respond
187 to the challenge.
188 """
189 assert self.salt
190 assert self.challenge
191 assert len(self.salt) == 2
192 cryptPassword = crypt.crypt(password, self.salt)
193 self.response = cryptRespond(self.challenge, cryptPassword)
194
196 """
197 Check credentials against the given cryptPassword.
198 """
199 if not self.response:
200 return False
201
202 expected = cryptRespond(self.challenge, cryptPassword)
203 return self.response == expected
204
205
206 -class IToken(credentials.ICredentials):
207 """I encapsulate a token.
208
209 This credential is used when a token is received from the
210 party requesting authentication.
211
212 @type token: C{str}
213 @ivar token: The token associated with these credentials.
214 """
215
216
222
223
225 """I encapsulate HTTP GET request arguments.
226
227 This credential is used when authentication
228 depend on HTTP request arguments.
229
230 @type arguments: C{dict}
231 @ivar arguments: The HTTP request arguments.
232 """
233
234
240
241
243 """
244 I encapsulate a username and check SHA-256 passwords.
245
246 This credential interface is used when a SHA-256 algorithm is used
247 on the password by the party requesting authentication..
248 CredentialCheckers which check this kind of credential must store
249 the passwords in plaintext or SHA-256 form.
250
251 @type username: C{str}
252 @ivar username: The username associated with these credentials.
253 """
254
256 """
257 Validate these credentials against the correct SHA-256 password.
258
259 @param sha256Password: The correct SHA-256 password against which to
260 check.
261
262 @return: a deferred which becomes, or a boolean indicating if the
263 password matches.
264 """
265
266
267
268
269
271 """
272 I take a username.
273
274 Authenticator will give me a salt and challenge me.
275 Requester will respond to the challenge.
276 At that point I'm ready to be used by a checker.
277 The response function used is
278 L{flumotion.twisted.credentials.cryptRespond()}
279
280 I implement IUsernameSha256Password.
281 """
282
283 implements(IUsernameSha256Password)
284
286 self.username = username
287 self.salt = None
288 self.challenge = None
289 self.response = None
290
292 """
293 I encode a given plaintext password using the salt, and respond
294 to the challenge.
295 """
296 assert self.salt
297 assert self.challenge
298 from Crypto.Hash import SHA256
299 hasher = SHA256.new()
300 hasher.update(self.salt)
301 hasher.update(password)
302 sha256Password = self.salt + dataToHex(hasher.digest())
303 self.response = cryptRespond(self.challenge, sha256Password)
304
306 """
307 Check credentials against the given sha256Password.
308 """
309 if not self.response:
310 return False
311
312 expected = cryptRespond(self.challenge, sha256Password)
313 return self.response == expected
314
315
317 _algorithm = "MD5"
318
320 self.username = username
321 self.nonce = None
322 self.method = None
323 self.uri = None
324
325 self.qop = None
326 self.cnonce = None
327 self.ncvalue = None
328
329 self.response = None
330
332 expectedResponse = self._calculateRequestDigest(
333 self.username, ha1, self.nonce, self.cnonce,
334 self.method, self.uri, self.ncvalue, self.qop)
335
336 self.debug(
337 "Attempting to check calculated response %s against "
338 " provided response %r", expectedResponse, self.response)
339 self.debug("Username %s, nonce %s, method %s, uri %s, qop %s, "
340 "cnonce %s, ncvalue %s", self.username, self.nonce,
341 self.method, self.uri, self.qop, self.cnonce,
342 self.ncvalue)
343 self.debug("Using H(A1): %s", ha1)
344
345 if not self.response:
346 return False
347
348 return self.response == expectedResponse
349
351 """
352 Calculate H(A1) as from specification (RFC2617) section 3.2.2, given
353 the initial hash H(username:realm:passwd), hex-encoded.
354
355 This basically applies the second-level hashing for MD5-sess, if
356 required.
357 """
358 if self._algorithm == 'MD5':
359 return ha1
360 elif self._algorithm == 'MD5-sess':
361 HA1 = ha1.decode('hex')
362
363 m = python.md5()
364 m.update(HA1)
365 m.update(':')
366 m.update(nonce)
367 m.update(':')
368 m.update(cnonce)
369 return m.digest().encode('hex')
370 else:
371 raise NotImplementedError("Unimplemented algorithm")
372
381
402