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