Package flumotion :: Package twisted :: Module credentials
[hide private]

Source Code for Module flumotion.twisted.credentials

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_credentials -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 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   
36 -class Username:
37 """ 38 I am your average username and password credentials. 39 """ 40 implements(credentials.IUsernamePassword) 41
42 - def __init__(self, username, password=''):
43 self.username = username 44 self.password = password
45
46 - def checkPassword(self, password):
47 return password == self.password
48 49 IUsernamePassword = credentials.IUsernamePassword 50 51 IUsernameHashedPassword = credentials.IUsernameHashedPassword 52 53
54 -class IUsernameCryptPassword(credentials.ICredentials):
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
67 - def checkCryptPassword(self, cryptPassword):
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
79 -class UsernameCryptPasswordPlaintext:
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):
88 self.username = username 89 self.password = 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
98 -class UsernameCryptPasswordCrypt:
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):
111 self.username = username 112 self.cryptPassword = cryptPassword
113
114 - def setPasswordSalt(self, password, salt):
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
123 - def checkCryptPassword(self, cryptPassword):
124 """ 125 Check credentials against the given cryptPassword. 126 """ 127 return self.cryptPassword == cryptPassword
128 129
130 -def cryptRespond(challenge, cryptPassword):
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
140 -def dataToHex(data):
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 # copied from twisted.spread.pb.challenge() 150 151
152 -def cryptChallenge():
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) # pychecker madness 159 crap = python.md5(crap).digest() 160 return crap
161 162
163 -class UsernameCryptPasswordCryptChallenger:
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
178 - def __init__(self, username):
179 self.username = username 180 self.salt = None # set by authenticator 181 self.challenge = None # set by authenticator 182 self.response = None # set by requester
183
184 - def setPassword(self, password):
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
195 - def checkCryptPassword(self, cryptPassword):
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
217 -class Token:
218 implements(IToken) 219
220 - def __init__(self, token):
221 self.token = token
222 223
224 -class IHTTPGetArguments(credentials.ICredentials):
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
235 -class HTTPGetArguments:
236 implements(IHTTPGetArguments) 237
238 - def __init__(self, arguments):
239 self.arguments = arguments
240 241
242 -class IUsernameSha256Password(credentials.ICredentials):
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
255 - def checkSha256Password(self, sha256Password):
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 # our Sha256 passwords are salted; 267 # ie the password string is salt + dataToHex(SHA256 digest(salt + password)) 268 269
270 -class UsernameSha256PasswordCryptChallenger:
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
285 - def __init__(self, username):
286 self.username = username 287 self.salt = None # set by authenticator 288 self.challenge = None # set by authenticator 289 self.response = None # set by requester
290
291 - def setPassword(self, password):
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
305 - def checkSha256Password(self, sha256Password):
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
316 -class HTTPDigestChallenger(log.Loggable):
317 _algorithm = "MD5" # MD5-sess also supported 318
319 - def __init__(self, username):
320 self.username = username 321 self.nonce = None 322 self.method = None 323 self.uri = None 324 325 self.qop = None # If non-None, the next two must be set 326 self.cnonce = None 327 self.ncvalue = None 328 329 self.response = None
330
331 - def checkHTTPDigestResponse(self, ha1):
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
350 - def _calculateHA1(self, ha1, nonce, cnonce):
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
373 - def _calculateHA2(self, method, uri):
374 # We don't support auth-int, otherwise we'd optionally need to do 375 # some more work here 376 m = python.md5() 377 m.update(method) 378 m.update(':') 379 m.update(uri) 380 return m.digest().encode('hex')
381
382 - def _calculateRequestDigest(self, username, ha1, nonce, cnonce, method, 383 uri, ncvalue, qop):
384 HA1 = self._calculateHA1(ha1, nonce, cnonce) 385 HA2 = self._calculateHA2(method, uri) 386 387 m = python.md5() 388 m.update(HA1) 389 m.update(':') 390 m.update(nonce) 391 if qop: 392 m.update(':') 393 m.update(ncvalue) 394 m.update(':') 395 m.update(cnonce) 396 m.update(':') 397 m.update(qop) # Must be 'auth', others not supported 398 m.update(':') 399 m.update(HA2) 400 401 return m.digest().encode('hex')
402