def response(self,response): """Process a client reponse. :Parameters: - `response`: the response from the client. :Types: - `response`: `str` :return: a challenge, a success indicator or a failure indicator. :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" s=response.split("\000") if len(s)!=3: self.__logger.debug("Bad response: %r" % (response,)) return Failure("not-authorized") authzid,username,password=s authzid=from_utf8(authzid) username=from_utf8(username) password=from_utf8(password) if not self.password_manager.check_password(username,password): self.__logger.debug("Bad password. Response was: %r" % (response,)) return Failure("not-authorized") info={"mechanism":"PLAIN","username":username} if self.password_manager.check_authzid(authzid,info): return Success(username,None,authzid) else: self.__logger.debug("Authzid verification failed.") return Failure("invalid-authzid")
def _get_realm(self, realms, charset): """Choose a realm from the list specified by the server. :Parameters: - `realms`: the realm list. - `charset`: encoding of realms on the list. :Types: - `realms`: `list` of `str` - `charset`: `str` :return: the realm chosen or a failure indicator. :returntype: `str` or `Failure`""" if realms: realms = [unicode(r, charset) for r in realms] realm = self.password_manager.choose_realm(realms) else: realm = self.password_manager.choose_realm([]) if realm: if type(realm) is unicode: try: realm = realm.encode(charset) except UnicodeError: self.__logger.debug("Couldn't encode realm to %r" % (charset, )) return Failure("incompatible-charset") elif charset != "utf-8": try: realm = unicode(realm, "utf-8").encode(charset) except UnicodeError: self.__logger.debug( "Couldn't encode realm from utf-8 to %r" % (charset, )) return Failure("incompatible-charset") self.realm = realm return realm
def _parse_response(self, response): """Parse a client reponse and pass to further processing. :Parameters: - `response`: the response from the client. :Types: - `response`: `str` :return: a challenge, a success indicator or a failure indicator. :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" response = response.split('\x00')[ 0] # workaround for some SASL implementations if self.realm: realm = to_utf8(self.realm) realm = _quote(realm) else: realm = None username = None cnonce = None digest_uri = None response_val = None authzid = None nonce_count = None while response: m = _param_re.match(response) if not m: self.__logger.debug("Response syntax error: %r" % (response, )) return Failure("not-authorized") response = m.group("rest") var = m.group("var") val = m.group("val") self.__logger.debug("%r: %r" % (var, val)) if var == "realm": realm = val[1:-1] elif var == "cnonce": if cnonce: self.__logger.debug("Duplicate cnonce") return Failure("not-authorized") cnonce = val[1:-1] elif var == "qop": if val != 'auth': self.__logger.debug("qop other then 'auth'") return Failure("not-authorized") elif var == "digest-uri": digest_uri = val[1:-1] elif var == "authzid": authzid = val[1:-1] elif var == "username": username = val[1:-1] elif var == "response": response_val = val elif var == "nc": nonce_count = val self.last_nonce_count += 1 if int(nonce_count) != self.last_nonce_count: self.__logger.debug("bad nonce: %r != %r" % (nonce_count, self.last_nonce_count)) return Failure("not-authorized") return self._check_params(username, realm, cnonce, digest_uri, response_val, authzid, nonce_count)
def finish(self, data): """Process success indicator from the server. Process any addiitional data passed with the success. Fail if the server was not authenticated. :Parameters: - `data`: an optional additional data with success. :Types: - `data`: `str` :return: success or failure indicator. :returntype: `sasl.Success` or `sasl.Failure`""" if not self.response_auth: self.__logger.debug("Got success too early") return Failure("bad-success") if self.rspauth_checked: return Success(self.username, self.realm, self.authzid) else: r = self._final_challenge(data) if isinstance(r, Failure): return r if self.rspauth_checked: return Success(self.username, self.realm, self.authzid) else: self.__logger.debug( "Something went wrong when processing additional data with success?" ) return Failure("bad-success")
def challenge(self, challenge): """Process a challenge and return the response. :Parameters: - `challenge`: the challenge from server. :Types: - `challenge`: `str` :return: the response or a failure indicator. :returntype: `sasl.Response` or `sasl.Failure`""" if not challenge: self.__logger.debug("Empty challenge") return Failure("bad-challenge") challenge = challenge.split('\x00')[ 0] # workaround for some buggy implementations if self.response_auth: return self._final_challenge(challenge) realms = [] nonce = None charset = "iso-8859-1" while challenge: m = _param_re.match(challenge) if not m: self.__logger.debug("Challenge syntax error: %r" % (challenge, )) return Failure("bad-challenge") challenge = m.group("rest") var = m.group("var") val = m.group("val") self.__logger.debug("%r: %r" % (var, val)) if var == "realm": realms.append(_unquote(val)) elif var == "nonce": if nonce: self.__logger.debug("Duplicate nonce") return Failure("bad-challenge") nonce = _unquote(val) elif var == "qop": qopl = _unquote(val).split(",") if "auth" not in qopl: self.__logger.debug("auth not supported") return Failure("not-implemented") elif var == "charset": val = _unquote(val) if val != "utf-8": self.__logger.debug("charset given and not utf-8") return Failure("bad-challenge") charset = "utf-8" elif var == "algorithm": val = _unquote(val) if val != "md5-sess": self.__logger.debug("algorithm given and not md5-sess") return Failure("bad-challenge") if not nonce: self.__logger.debug("nonce not given") return Failure("bad-challenge") self._get_password() return self._make_response(charset, realms, nonce)
def _check_params(self, username, realm, cnonce, digest_uri, response_val, authzid, nonce_count): """Check parameters of a client reponse and pass them to further processing. :Parameters: - `username`: user name. - `realm`: realm. - `cnonce`: cnonce value. - `digest_uri`: digest-uri value. - `response_val`: response value computed by the client. - `authzid`: authorization id. - `nonce_count`: nonce count value. :Types: - `username`: `str` - `realm`: `str` - `cnonce`: `str` - `digest_uri`: `str` - `response_val`: `str` - `authzid`: `str` - `nonce_count`: `int` :return: a challenge, a success indicator or a failure indicator. :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" if not cnonce: self.__logger.debug("Required 'cnonce' parameter not given") return Failure("not-authorized") if not response_val: self.__logger.debug("Required 'response' parameter not given") return Failure("not-authorized") if not username: self.__logger.debug("Required 'username' parameter not given") return Failure("not-authorized") if not digest_uri: self.__logger.debug("Required 'digest_uri' parameter not given") return Failure("not-authorized") if not nonce_count: self.__logger.debug("Required 'nc' parameter not given") return Failure("not-authorized") return self._make_final_challenge(username, realm, cnonce, digest_uri, response_val, authzid, nonce_count)
def challenge(self, challenge): """Process the challenge and return the response. :Parameters: - `challenge`: the challenge. :Types: - `challenge`: `str` :return: the response or a failure indicator. :returntype: `sasl.Response` or `sasl.Failure`""" _unused = challenge if self.finished: self.__logger.debug("Already authenticated") return Failure("extra-challenge") self.finished=1 if self.password is None: self.password,pformat=self.password_manager.get_password(self.username) if not self.password or pformat!="plain": self.__logger.debug("Couldn't retrieve plain password") return Failure("password-unavailable") return Response("%s\000%s\000%s" % ( to_utf8(self.authzid), to_utf8(self.username), to_utf8(self.password)))
def _get_password(self): """Retrieve user's password from the password manager. Set `self.password` to the password and `self.pformat` to its format name ('plain' or 'md5:user:realm:pass').""" if self.password is None: self.password, self.pformat = self.password_manager.get_password( self.username, ["plain", "md5:user:realm:pass"]) if not self.password or self.pformat not in ("plain", "md5:user:realm:pass"): self.__logger.debug( "Couldn't get plain password. Password: %r Format: %r" % (self.password, self.pformat)) return Failure("password-unavailable")
def response(self, response): """Process a client reponse. :Parameters: - `response`: the response from the client. :Types: - `response`: `str` :return: a challenge, a success indicator or a failure indicator. :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" if self.done: return Success(self.username, self.realm, self.authzid) if not response: return Failure("not-authorized") return self._parse_response(response)
def _final_challenge(self, challenge): """Process the second challenge from the server and return the response. :Parameters: - `challenge`: the challenge from server. :Types: - `challenge`: `str` :return: the response or a failure indicator. :returntype: `sasl.Response` or `sasl.Failure`""" if self.rspauth_checked: return Failure("extra-challenge") challenge = challenge.split('\x00')[0] rspauth = None while challenge: m = _param_re.match(challenge) if not m: self.__logger.debug("Challenge syntax error: %r" % (challenge, )) return Failure("bad-challenge") challenge = m.group("rest") var = m.group("var") val = m.group("val") self.__logger.debug("%r: %r" % (var, val)) if var == "rspauth": rspauth = val if not rspauth: self.__logger.debug("Final challenge without rspauth") return Failure("bad-success") if rspauth == self.response_auth: self.rspauth_checked = 1 return Response("") else: self.__logger.debug("Wrong rspauth value - peer is cheating?") self.__logger.debug("my rspauth: %r" % (self.response_auth, )) return Failure("bad-success")
def _make_final_challenge(self, username, realm, cnonce, digest_uri, response_val, authzid, nonce_count): """Send the second challenge in reply to the client response. :Parameters: - `username`: user name. - `realm`: realm. - `cnonce`: cnonce value. - `digest_uri`: digest-uri value. - `response_val`: response value computed by the client. - `authzid`: authorization id. - `nonce_count`: nonce count value. :Types: - `username`: `str` - `realm`: `str` - `cnonce`: `str` - `digest_uri`: `str` - `response_val`: `str` - `authzid`: `str` - `nonce_count`: `int` :return: a challenge, a success indicator or a failure indicator. :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`""" username_uq = from_utf8(username.replace('\\', '')) if authzid: authzid_uq = from_utf8(authzid.replace('\\', '')) else: authzid_uq = None if realm: realm_uq = from_utf8(realm.replace('\\', '')) else: realm_uq = None digest_uri_uq = digest_uri.replace('\\', '') self.username = username_uq self.realm = realm_uq password, pformat = self.password_manager.get_password( username_uq, realm_uq, ("plain", "md5:user:realm:pass")) if pformat == "md5:user:realm:pass": urp_hash = password elif pformat == "plain": urp_hash = _make_urp_hash(username, realm, password) else: self.__logger.debug("Couldn't get password.") return Failure("not-authorized") valid_response = _compute_response(urp_hash, self.nonce, cnonce, nonce_count, authzid, digest_uri) if response_val != valid_response: self.__logger.debug("Response mismatch: %r != %r" % (response_val, valid_response)) return Failure("not-authorized") s = digest_uri_uq.split("/") if len(s) == 3: serv_type, host, serv_name = s elif len(s) == 2: serv_type, host = s serv_name = None else: self.__logger.debug("Bad digest_uri: %r" % (digest_uri_uq, )) return Failure("not-authorized") info = {} info["mechanism"] = "DIGEST-MD5" info["username"] = username_uq info["serv-type"] = serv_type info["host"] = host info["serv-name"] = serv_name if self.password_manager.check_authzid(authzid_uq, info): rspauth = _compute_response_auth(urp_hash, self.nonce, cnonce, nonce_count, authzid, digest_uri) self.authzid = authzid self.done = 1 return Challenge("rspauth=" + rspauth) else: self.__logger.debug("Authzid check failed") return Failure("invalid_authzid")
def _make_response(self, charset, realms, nonce): """Make a response for the first challenge from the server. :Parameters: - `charset`: charset name from the challenge. - `realms`: realms list from the challenge. - `nonce`: nonce value from the challenge. :Types: - `charset`: `str` - `realms`: `str` - `nonce`: `str` :return: the response or a failure indicator. :returntype: `sasl.Response` or `sasl.Failure`""" params = [] realm = self._get_realm(realms, charset) if isinstance(realm, Failure): return realm elif realm: realm = _quote(realm) params.append('realm="%s"' % (realm, )) try: username = self.username.encode(charset) except UnicodeError: self.__logger.debug("Couldn't encode username to %r" % (charset, )) return Failure("incompatible-charset") username = _quote(username) params.append('username="******"' % (username, )) cnonce = self.password_manager.generate_nonce() cnonce = _quote(cnonce) params.append('cnonce="%s"' % (cnonce, )) params.append('nonce="%s"' % (_quote(nonce), )) self.nonce_count += 1 nonce_count = "%08x" % (self.nonce_count, ) params.append('nc=%s' % (nonce_count, )) params.append('qop=auth') serv_type = self.password_manager.get_serv_type().encode("us-ascii") host = self.password_manager.get_serv_host().encode("idna") serv_name = self.password_manager.get_serv_name().encode("utf-8") if serv_name and serv_name != host: digest_uri = "%s/%s/%s" % (serv_type, host, serv_name) else: digest_uri = "%s/%s" % (serv_type, host) digest_uri = _quote(digest_uri) params.append('digest-uri="%s"' % (digest_uri, )) if self.authzid: try: authzid = self.authzid.encode(charset) except UnicodeError: self.__logger.debug("Couldn't encode authzid to %r" % (charset, )) return Failure("incompatible-charset") authzid = _quote(authzid) else: authzid = "" if self.pformat == "md5:user:realm:pass": urp_hash = self.password else: urp_hash = _make_urp_hash(username, realm, self.password) response = _compute_response(urp_hash, nonce, cnonce, nonce_count, authzid, digest_uri) self.response_auth = _compute_response_auth(urp_hash, nonce, cnonce, nonce_count, authzid, digest_uri) params.append('response=%s' % (response, )) if authzid: params.append('authzid="%s"' % (authzid, )) return Response(",".join(params))
def challenge(self, challenge): if self.password is None: self.password, pformat = self.password_manager.get_password( self.username) if not self.password or pformat != "plain": self.__logger.debug("Couldn't retrieve plain password") return Failure("password-unavailable") if self.step == 0: self.step = 1 return Response(''.join( pstrIlist([to_utf8(self.authzid), to_utf8(self.username)]))) elif self.step == 1: self.step = 2 srv_rsa_key = None self.__logger.critical("loading server certificate") try: srv_cert = M2Crypto.X509.load_cert_string(challenge) if srv_cert is not None: self.__logger.critical("retrieving server pubkey") srv_key = srv_cert.get_pubkey() if srv_key is not None: self.__logger.critical("retrieving server RSA pubkey") srv_rsa_key = srv_key.get_rsa() except Exception: traceback.print_exc() if srv_rsa_key is None: return Failure("bad-server-cert") if not srv_cert.verify(ROOT_CERT.get_pubkey()): return Failure("bad-server-cert") self.srv_rsa_key = srv_rsa_key self.__logger.critical("generating Nonce") from M2Crypto import m2 nonce = m2.rand_bytes(16) self.__logger.critical("encrypting Nonce") enonce = digsbyrsa.DIGSBY_RSA_public_encrypt( nonce, srv_rsa_key, M2Crypto.RSA.pkcs1_oaep_padding) self.__logger.critical("loading key") try: self.key = _get_client_key_pem(self.username, self.password) except Exception: self.key = None if self.key is None: self.__logger.critical("generating new key") self.key = M2Crypto.RSA.gen_key(2048, 0x10001) self.__logger.critical("saving new key") if not self.key.check_key(): raise ValueError("failed to generate key") try: _save_client_key_pem(self.key, self.username, self.password) except Exception: traceback.print_exc() self.__logger.critical("creating buffer") buff = M2Crypto.BIO.MemoryBuffer() self.__logger.critical("serializing client public key to buffer") self.key.save_pub_key_bio(buff) self.__logger.critical_s("Nonce: %r", nonce) genkey = buff.getvalue() self.__logger.critical_s("Key: %r", genkey) eKey = pstrAES(nonce, genkey) self.__logger.critical("returning response") return Response(''.join(pstrIlist([enonce, eKey]))) elif self.step == 2: self.step = 3 package_nonce_C_userpub, package_C_AES_C_serverpriv = unpackpstrlist( challenge) package_nonce = digsbyrsa.DIGSBY_RSA_private_decrypt( package_nonce_C_userpub, self.key, M2Crypto.RSA.pkcs1_oaep_padding) package_C_serverpriv = util.cryptography.decrypt( package_nonce, package_C_AES_C_serverpriv, padchar=None, mode=util.cryptography.Mode.CBC) nonce = digsbyrsa.DIGSBY_RSA_public_decrypt( package_C_serverpriv, self.srv_rsa_key, M2Crypto.RSA.pkcs1_padding) return Response(pstrAES(nonce, self.password)) else: return Failure("extra-challenge")