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 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """ 
 23  Flumotion Twisted credentials 
 24  """ 
 25   
 26  import random 
 27   
 28  from flumotion.common import log, python 
 29  from twisted.cred import credentials 
 30  from zope.interface import implements 
 31   
 32  try: 
 33      import crypt 
 34  except ImportError: 
 35      from flumotion.extern import unixcrypt as crypt 
 36   
 37  __version__ = "$Rev: 7990 $" 
 38   
 39   
40 -class Username:
41 """ 42 I am your average username and password credentials. 43 """ 44 implements(credentials.IUsernamePassword) 45
46 - def __init__(self, username, password=''):
47 self.username = username 48 self.password = password
49
50 - def checkPassword(self, password):
51 return password == self.password
52 53 IUsernamePassword = credentials.IUsernamePassword 54 55 IUsernameHashedPassword = credentials.IUsernameHashedPassword 56 57
58 -class IUsernameCryptPassword(credentials.ICredentials):
59 """ 60 I encapsulate a username and check crypted passwords. 61 62 This credential interface is used when a crypt password is received 63 from the party requesting authentication. 64 CredentialCheckers which check this kind of credential must store 65 the passwords in plaintext or crypt form. 66 67 @type username: C{str} 68 @ivar username: The username associated with these credentials. 69 """ 70
71 - def checkCryptPassword(self, cryptPassword):
72 """ 73 Validate these credentials against the correct crypt password. 74 75 @param cryptPassword: The correct, crypt password against which to 76 check. 77 78 @return: a deferred which becomes, or a boolean indicating if the 79 password matches. 80 """
81 82
83 -class UsernameCryptPasswordPlaintext:
84 """ 85 I take a username and a plaintext password. 86 I implement IUsernameCryptPassword. 87 """ 88 89 implements(IUsernameCryptPassword) 90
91 - def __init__(self, username, password):
92 self.username = username 93 self.password = password
94
95 - def checkCryptPassword(self, cryptPassword):
96 """Check credentials against the given cryptPassword.""" 97 salt = cryptPassword[:2] 98 encrypted = crypt.crypt(self.password, salt) 99 return encrypted == cryptPassword
100 101
102 -class UsernameCryptPasswordCrypt:
103 """ 104 I take a username and a crypt password. 105 When using me you should make sure the password was crypted with the 106 correct salt (which is stored in the crypt password backend of whatever 107 checker you use); otherwise your password may be a valid crypt, but 108 with a different salt. 109 I implement IUsernameCryptPassword. 110 """ 111 112 implements(IUsernameCryptPassword) 113
114 - def __init__(self, username, cryptPassword=None):
115 self.username = username 116 self.cryptPassword = cryptPassword
117
118 - def setPasswordSalt(self, password, salt):
119 """ 120 Given the plaintext password and the salt, 121 set the correct cryptPassword. 122 """ 123 assert len(salt) == 2 124 125 self.cryptPassword = crypt.crypt(password, salt)
126
127 - def checkCryptPassword(self, cryptPassword):
128 """ 129 Check credentials against the given cryptPassword. 130 """ 131 return self.cryptPassword == cryptPassword
132 133
134 -def cryptRespond(challenge, cryptPassword):
135 """ 136 Respond to a given crypt challenge with our cryptPassword. 137 """ 138 md = python.md5() 139 md.update(cryptPassword) 140 md.update(challenge) 141 return md.digest()
142 143
144 -def dataToHex(data):
145 """ 146 Take a string of bytes, and return a string of two-digit hex values. 147 """ 148 l = [] 149 for c in data: 150 l.append("%02x" % ord(c)) 151 return "".join(l)
152 153 # copied from twisted.spread.pb.challenge() 154 155
156 -def cryptChallenge():
157 """ 158 I return some random data. 159 """ 160 crap = '' 161 for x in range(random.randrange(15, 25)): 162 crap = crap + chr(random.randint(65, 90) + x - x) # pychecker madness 163 crap = python.md5(crap).digest() 164 return crap
165 166
167 -class UsernameCryptPasswordCryptChallenger:
168 """ 169 I take a username. 170 171 Authenticator will give me a salt and challenge me. 172 Requester will respond to the challenge. 173 At that point I'm ready to be used by a checker. 174 The response function used is 175 L{flumotion.twisted.credentials.cryptRespond()} 176 177 I implement IUsernameCryptPassword. 178 """ 179 180 implements(IUsernameCryptPassword) 181
182 - def __init__(self, username):
183 self.username = username 184 self.salt = None # set by authenticator 185 self.challenge = None # set by authenticator 186 self.response = None # set by requester
187
188 - def setPassword(self, password):
189 """ 190 I encode a given plaintext password using the salt, and respond 191 to the challenge. 192 """ 193 assert self.salt 194 assert self.challenge 195 assert len(self.salt) == 2 196 cryptPassword = crypt.crypt(password, self.salt) 197 self.response = cryptRespond(self.challenge, cryptPassword)
198
199 - def checkCryptPassword(self, cryptPassword):
200 """ 201 Check credentials against the given cryptPassword. 202 """ 203 if not self.response: 204 return False 205 206 expected = cryptRespond(self.challenge, cryptPassword) 207 return self.response == expected
208 209
210 -class IToken(credentials.ICredentials):
211 """I encapsulate a token. 212 213 This credential is used when a token is received from the 214 party requesting authentication. 215 216 @type token: C{str} 217 @ivar token: The token associated with these credentials. 218 """
219 220
221 -class Token:
222 implements(IToken) 223
224 - def __init__(self, token):
225 self.token = token
226 227
228 -class IHTTPGetArguments(credentials.ICredentials):
229 """I encapsulate HTTP GET request arguments. 230 231 This credential is used when authentication 232 depend on HTTP request arguments. 233 234 @type arguments: C{dict} 235 @ivar arguments: The HTTP request arguments. 236 """
237 238
239 -class HTTPGetArguments:
240 implements(IHTTPGetArguments) 241
242 - def __init__(self, arguments):
243 self.arguments = arguments
244 245
246 -class IUsernameSha256Password(credentials.ICredentials):
247 """ 248 I encapsulate a username and check SHA-256 passwords. 249 250 This credential interface is used when a SHA-256 algorithm is used 251 on the password by the party requesting authentication.. 252 CredentialCheckers which check this kind of credential must store 253 the passwords in plaintext or SHA-256 form. 254 255 @type username: C{str} 256 @ivar username: The username associated with these credentials. 257 """ 258
259 - def checkSha256Password(self, sha256Password):
260 """ 261 Validate these credentials against the correct SHA-256 password. 262 263 @param sha256Password: The correct SHA-256 password against which to 264 check. 265 266 @return: a deferred which becomes, or a boolean indicating if the 267 password matches. 268 """
269 270 # our Sha256 passwords are salted; 271 # ie the password string is salt + dataToHex(SHA256 digest(salt + password)) 272 273
274 -class UsernameSha256PasswordCryptChallenger:
275 """ 276 I take a username. 277 278 Authenticator will give me a salt and challenge me. 279 Requester will respond to the challenge. 280 At that point I'm ready to be used by a checker. 281 The response function used is 282 L{flumotion.twisted.credentials.cryptRespond()} 283 284 I implement IUsernameSha256Password. 285 """ 286 287 implements(IUsernameSha256Password) 288
289 - def __init__(self, username):
290 self.username = username 291 self.salt = None # set by authenticator 292 self.challenge = None # set by authenticator 293 self.response = None # set by requester
294
295 - def setPassword(self, password):
296 """ 297 I encode a given plaintext password using the salt, and respond 298 to the challenge. 299 """ 300 assert self.salt 301 assert self.challenge 302 from Crypto.Hash import SHA256 303 hasher = SHA256.new() 304 hasher.update(self.salt) 305 hasher.update(password) 306 sha256Password = self.salt + dataToHex(hasher.digest()) 307 self.response = cryptRespond(self.challenge, sha256Password)
308
309 - def checkSha256Password(self, sha256Password):
310 """ 311 Check credentials against the given sha256Password. 312 """ 313 if not self.response: 314 return False 315 316 expected = cryptRespond(self.challenge, sha256Password) 317 return self.response == expected
318 319
320 -class HTTPDigestChallenger(log.Loggable):
321 _algorithm = "MD5" # MD5-sess also supported 322
323 - def __init__(self, username):
324 self.username = username 325 self.nonce = None 326 self.method = None 327 self.uri = None 328 329 self.qop = None # If non-None, the next two must be set 330 self.cnonce = None 331 self.ncvalue = None 332 333 self.response = None
334
335 - def checkHTTPDigestResponse(self, ha1):
336 expectedResponse = self._calculateRequestDigest( 337 self.username, ha1, self.nonce, self.cnonce, 338 self.method, self.uri, self.ncvalue, self.qop) 339 340 self.debug( 341 "Attempting to check calculated response %s against " 342 " provided response %r", expectedResponse, self.response) 343 self.debug("Username %s, nonce %s, method %s, uri %s, qop %s, " 344 "cnonce %s, ncvalue %s", self.username, self.nonce, 345 self.method, self.uri, self.qop, self.cnonce, 346 self.ncvalue) 347 self.debug("Using H(A1): %s", ha1) 348 349 if not self.response: 350 return False 351 352 return self.response == expectedResponse
353
354 - def _calculateHA1(self, ha1, nonce, cnonce):
355 """ 356 Calculate H(A1) as from specification (RFC2617) section 3.2.2, given 357 the initial hash H(username:realm:passwd), hex-encoded. 358 359 This basically applies the second-level hashing for MD5-sess, if 360 required. 361 """ 362 if self._algorithm == 'MD5': 363 return ha1 364 elif self._algorithm == 'MD5-sess': 365 HA1 = ha1.decode('hex') 366 367 m = python.md5() 368 m.update(HA1) 369 m.update(':') 370 m.update(nonce) 371 m.update(':') 372 m.update(cnonce) 373 return m.digest().encode('hex') 374 else: 375 raise NotImplementedError("Unimplemented algorithm")
376
377 - def _calculateHA2(self, method, uri):
378 # We don't support auth-int, otherwise we'd optionally need to do 379 # some more work here 380 m = python.md5() 381 m.update(method) 382 m.update(':') 383 m.update(uri) 384 return m.digest().encode('hex')
385
386 - def _calculateRequestDigest(self, username, ha1, nonce, cnonce, method, 387 uri, ncvalue, qop):
388 HA1 = self._calculateHA1(ha1, nonce, cnonce) 389 HA2 = self._calculateHA2(method, uri) 390 391 m = python.md5() 392 m.update(HA1) 393 m.update(':') 394 m.update(nonce) 395 if qop: 396 m.update(':') 397 m.update(ncvalue) 398 m.update(':') 399 m.update(cnonce) 400 m.update(':') 401 m.update(qop) # Must be 'auth', others not supported 402 m.update(':') 403 m.update(HA2) 404 405 return m.digest().encode('hex')
406