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 md5 
 27   
 28  import random 
 29   
 30  from flumotion.common import log 
 31  from twisted.cred import credentials 
 32  from zope.interface import implements 
 33   
 34  try: 
 35      import crypt 
 36  except ImportError: 
 37      from flumotion.extern import unixcrypt as crypt 
 38   
 39  __version__ = "$Rev: 6558 $" 
 40   
 41   
42 -class Username:
43 """ 44 I am your average username and password credentials. 45 """ 46 implements(credentials.IUsernamePassword)
47 - def __init__(self, username, password=''):
48 self.username = username 49 self.password = password
50
51 - def checkPassword(self, password):
52 return password == self.password
53 54 IUsernamePassword = credentials.IUsernamePassword 55 56 IUsernameHashedPassword = credentials.IUsernameHashedPassword 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 -class UsernameCryptPasswordPlaintext:
83 """ 84 I take a username and a plaintext password. 85 I implement IUsernameCryptPassword. 86 """ 87 88 implements(IUsernameCryptPassword)
89 - def __init__(self, username, password):
90 self.username = username 91 self.password = password
92
93 - def checkCryptPassword(self, cryptPassword):
94 """Check credentials against the given cryptPassword.""" 95 salt = cryptPassword[:2] 96 encrypted = crypt.crypt(self.password, salt) 97 return encrypted == cryptPassword
98
99 -class UsernameCryptPasswordCrypt:
100 """ 101 I take a username and a crypt password. 102 When using me you should make sure the password was crypted with the 103 correct salt (which is stored in the crypt password backend of whatever 104 checker you use); otherwise your password may be a valid crypt, but 105 with a different salt. 106 I implement IUsernameCryptPassword. 107 """ 108 109 implements(IUsernameCryptPassword)
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 -def cryptRespond(challenge, cryptPassword):
130 """ 131 Respond to a given crypt challenge with our cryptPassword. 132 """ 133 import md5 134 md = md5.new() 135 md.update(cryptPassword) 136 md.update(challenge) 137 return md.digest()
138
139 -def dataToHex(data):
140 """ 141 Take a string of bytes, and return a string of two-digit hex values. 142 """ 143 l = [] 144 for c in data: 145 l.append("%02x" % ord(c)) 146 return "".join(l)
147 148 # copied from twisted.spread.pb.challenge()
149 -def cryptChallenge():
150 """ 151 I return some random data. 152 """ 153 crap = '' 154 for x in range(random.randrange(15,25)): 155 crap = crap + chr(random.randint(65,90) + x - x) # pychecker madness 156 crap = md5.new(crap).digest() 157 return crap
158
159 -class UsernameCryptPasswordCryptChallenger:
160 """ 161 I take a username. 162 163 Authenticator will give me a salt and challenge me. 164 Requester will respond to the challenge. 165 At that point I'm ready to be used by a checker. 166 The response function used is 167 L{flumotion.twisted.credentials.cryptRespond()} 168 169 I implement IUsernameCryptPassword. 170 """ 171 172 implements(IUsernameCryptPassword) 173
174 - def __init__(self, username):
175 self.username = username 176 self.salt = None # set by authenticator 177 self.challenge = None # set by authenticator 178 self.response = None # set by requester
179
180 - def setPassword(self, password):
181 """ 182 I encode a given plaintext password using the salt, and respond 183 to the challenge. 184 """ 185 assert self.salt 186 assert self.challenge 187 assert len(self.salt) == 2 188 cryptPassword = crypt.crypt(password, self.salt) 189 self.response = cryptRespond(self.challenge, cryptPassword)
190
191 - def checkCryptPassword(self, cryptPassword):
192 """ 193 Check credentials against the given cryptPassword. 194 """ 195 if not self.response: 196 return False 197 198 expected = cryptRespond(self.challenge, cryptPassword) 199 return self.response == expected
200
201 -class IToken(credentials.ICredentials):
202 """I encapsulate a token. 203 204 This credential is used when a token is received from the 205 party requesting authentication. 206 207 @type token: C{str} 208 @ivar token: The token associated with these credentials. 209 """
210
211 -class Token:
212 implements(IToken) 213
214 - def __init__(self, token):
215 self.token = token
216
217 -class IUsernameSha256Password(credentials.ICredentials):
218 """ 219 I encapsulate a username and check SHA-256 passwords. 220 221 This credential interface is used when a SHA-256 algorithm is used 222 on the password by the party requesting authentication.. 223 CredentialCheckers which check this kind of credential must store 224 the passwords in plaintext or SHA-256 form. 225 226 @type username: C{str} 227 @ivar username: The username associated with these credentials. 228 """ 229
230 - def checkSha256Password(self, sha256Password):
231 """ 232 Validate these credentials against the correct SHA-256 password. 233 234 @param sha256Password: The correct SHA-256 password against which to 235 check. 236 237 @return: a deferred which becomes, or a boolean indicating if the 238 password matches. 239 """
240 241 # our Sha256 passwords are salted; 242 # ie the password string is salt + dataToHex(SHA256 digest(salt + password))
243 -class UsernameSha256PasswordCryptChallenger:
244 """ 245 I take a username. 246 247 Authenticator will give me a salt and challenge me. 248 Requester will respond to the challenge. 249 At that point I'm ready to be used by a checker. 250 The response function used is 251 L{flumotion.twisted.credentials.cryptRespond()} 252 253 I implement IUsernameSha256Password. 254 """ 255 256 implements(IUsernameSha256Password) 257
258 - def __init__(self, username):
259 self.username = username 260 self.salt = None # set by authenticator 261 self.challenge = None # set by authenticator 262 self.response = None # set by requester
263
264 - def setPassword(self, password):
265 """ 266 I encode a given plaintext password using the salt, and respond 267 to the challenge. 268 """ 269 assert self.salt 270 assert self.challenge 271 from Crypto.Hash import SHA256 272 hasher = SHA256.new() 273 hasher.update(self.salt) 274 hasher.update(password) 275 sha256Password = self.salt + dataToHex(hasher.digest()) 276 self.response = cryptRespond(self.challenge, sha256Password)
277
278 - def checkSha256Password(self, sha256Password):
279 """ 280 Check credentials against the given sha256Password. 281 """ 282 if not self.response: 283 return False 284 285 expected = cryptRespond(self.challenge, sha256Password) 286 return self.response == expected
287
288 -class HTTPDigestChallenger(log.Loggable):
289 _algorithm = "MD5" # MD5-sess also supported 290
291 - def __init__(self, username):
292 self.username = username 293 self.nonce = None 294 self.method = None 295 self.uri = None 296 297 self.qop = None # If non-None, the next two must be set 298 self.cnonce = None 299 self.ncvalue = None 300 301 self.response = None
302
303 - def checkHTTPDigestResponse(self, ha1):
304 expectedResponse = self._calculateRequestDigest( 305 self.username, ha1, self.nonce, self.cnonce, 306 self.method, self.uri, self.ncvalue, self.qop) 307 308 self.debug("Attempting to check calculated response %s against provided response %r", expectedResponse, self.response) 309 self.debug("Username %s, nonce %s, method %s, uri %s, qop %s, cnonce %s, ncvalue %s", self.username, self.nonce, self.method, self.uri, self.qop, self.cnonce, self.ncvalue) 310 self.debug("Using H(A1): %s", ha1) 311 312 if not self.response: 313 return False 314 315 return self.response == expectedResponse
316
317 - def _calculateHA1(self, ha1, nonce, cnonce):
318 """ 319 Calculate H(A1) as from specification (RFC2617) section 3.2.2, given 320 the initial hash H(username:realm:passwd), hex-encoded. 321 322 This basically applies the second-level hashing for MD5-sess, if 323 required. 324 """ 325 if self._algorithm == 'MD5': 326 return ha1 327 elif self._algorithm == 'MD5-sess': 328 HA1 = ha1.decode('hex') 329 330 m = md5.md5() 331 m.update(HA1) 332 m.update(':') 333 m.update(nonce) 334 m.update(':') 335 m.update(cnonce) 336 return m.digest().encode('hex') 337 else: 338 raise NotImplementedError("Unimplemented algorithm")
339
340 - def _calculateHA2(self, method, uri):
341 # We don't support auth-int, otherwise we'd optionally need to do 342 # some more work here 343 m = md5.md5() 344 m.update(method) 345 m.update(':') 346 m.update(uri) 347 return m.digest().encode('hex')
348
349 - def _calculateRequestDigest(self, username, ha1, nonce, cnonce, method, 350 uri, ncvalue, qop):
351 HA1 = self._calculateHA1(ha1, nonce, cnonce) 352 HA2 = self._calculateHA2(method, uri) 353 354 m = md5.md5() 355 m.update(HA1) 356 m.update(':') 357 m.update(nonce) 358 if qop: 359 m.update(':') 360 m.update(ncvalue) 361 m.update(':') 362 m.update(cnonce) 363 m.update(':') 364 m.update(qop) # Must be 'auth', others not supported 365 m.update(':') 366 m.update(HA2) 367 368 return m.digest().encode('hex')
369