def on_challenge(self, session, challenge): assert challenge.method == u"scram" assert self._client_nonce is not None required_args = ['nonce', 'kdf', 'salt', 'iterations'] optional_args = ['memory', 'channel_binding'] for k in required_args: if k not in challenge.extra: raise RuntimeError("WAMP-SCRAM challenge option '{}' is " " required but not specified".format(k)) for k in challenge.extra: if k not in optional_args + required_args: raise RuntimeError( "WAMP-SCRAM challenge has unknown attribute '{}'".format( k)) channel_binding = challenge.extra.get(u'channel_binding', u'') server_nonce = challenge.extra[u'nonce'].encode('ascii') # base64 salt = challenge.extra[u'salt'] # base64 iterations = int(challenge.extra[u'iterations']) memory = int(challenge.extra.get(u'memory', -1)) password = self._args['password'].encode('utf8') # supplied by user authid = saslprep(self._args['authid']) algorithm = challenge.extra[u'kdf'] client_nonce = self._client_nonce self._auth_message = ( u"{client_first_bare},{server_first},{client_final_no_proof}". format( client_first_bare=u"n={},r={}".format(authid, client_nonce), server_first=u"r={},s={},i={}".format( server_nonce.decode('ascii'), salt.decode('ascii'), iterations), client_final_no_proof=u"c={},r={}".format( channel_binding, server_nonce.decode('ascii')), ).encode('ascii')) if algorithm == u'argon2id-13': if memory == -1: raise ValueError( "WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter" ) self._salted_password = _hash_argon2id13_secret( password, salt, iterations, memory) elif algorithm == u'pbkdf2': self._salted_password = _hash_pbkdf2_secret( password, salt, iterations) else: raise RuntimeError( "WAMP-SCRAM specified unknown KDF '{}'".format(algorithm)) client_key = hmac.new(self._salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() client_signature = hmac.new(stored_key, self._auth_message, hashlib.sha256).digest() client_proof = xor_array(client_key, client_signature) return base64.b64encode(client_proof)
def on_challenge(self, session, challenge): assert challenge.method == u"scram" assert self._client_nonce is not None required_args = ['nonce', 'kdf', 'salt', 'iterations'] optional_args = ['memory', 'channel_binding'] for k in required_args: if k not in challenge.extra: raise RuntimeError( "WAMP-SCRAM challenge option '{}' is " " required but not specified".format(k) ) for k in challenge.extra: if k not in optional_args + required_args: raise RuntimeError( "WAMP-SCRAM challenge has unknown attribute '{}'".format(k) ) channel_binding = challenge.extra.get(u'channel_binding', u'') server_nonce = challenge.extra[u'nonce'].encode('ascii') # base64 salt = challenge.extra[u'salt'] # base64 iterations = int(challenge.extra[u'iterations']) memory = int(challenge.extra.get(u'memory', -1)) password = self._args['password'].encode('utf8') # supplied by user authid = saslprep(self._args['authid']) algorithm = challenge.extra[u'kdf'] client_nonce = self._client_nonce self._auth_message = ( u"{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare=u"n={},r={}".format(authid, client_nonce), server_first=u"r={},s={},i={}".format(server_nonce.decode('ascii'), salt.decode('ascii'), iterations), client_final_no_proof=u"c={},r={}".format(channel_binding, server_nonce.decode('ascii')), ).encode('ascii') ) if algorithm == u'argon2id-13': if memory == -1: raise ValueError( "WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter" ) self._salted_password = _hash_argon2id13_secret(password, salt, iterations, memory) elif algorithm == u'pbkdf2': self._salted_password = _hash_pbkdf2_secret(password, salt, iterations) else: raise RuntimeError( "WAMP-SCRAM specified unknown KDF '{}'".format(algorithm) ) client_key = hmac.new(self._salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() client_signature = hmac.new(stored_key, self._auth_message, hashlib.sha256).digest() client_proof = xor_array(client_key, client_signature) return base64.b64encode(client_proof)
def on_challenge(self, session, challenge): assert challenge.method == u"scram" assert self._client_nonce is not None required_args = ['nonce', 'salt', 'cost'] optional_args = ['memory', 'parallel', 'channel_binding'] # probably want "algorithm" too, with either "argon2id-19" or # "pbkdf2" as values for k in required_args: if k not in challenge.extra: raise RuntimeError( "WAMP-SCRAM challenge option '{}' is " " required but not specified".format(k) ) for k in challenge.extra: if k not in optional_args + required_args: raise RuntimeError( "WAMP-SCRAM challenge has unknown attribute '{}'".format(k) ) channel_binding = challenge.extra.get(u'channel_binding', u'') server_nonce = challenge.extra[u'nonce'] # base64 salt = challenge.extra[u'salt'] # base64 cost = int(challenge.extra[u'cost']) memory = int(challenge.extra.get(u'memory', 512)) parallel = int(challenge.extra.get(u'parallel', 2)) password = self._args['password'].encode('utf8') # supplied by user authid = saslprep(self._args['authid']) client_nonce = self._client_nonce auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare="n={},r={}".format(authid, client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, cost), client_final_no_proof="c={},r={}".format(channel_binding, server_nonce), ) ) rawhash = hash_secret( secret=password, salt=base64.b64decode(salt), time_cost=cost, memory_cost=memory, parallelism=parallel, hash_len=16, # another knob? type=Type.ID, version=19, ) # spits out stuff like: # '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ' _, tag, ver, options, salt_data, hash_data = rawhash.split(b'$') salted_password = hash_data client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() client_signature = hmac.new(stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() client_proof = xor_array(client_key, client_signature) def confirm_server_signature(session, details): """ When the server is satisfied, it sends a 'WELCOME' message. This will cause the session to be set up and 'join' gets notified. Here, we check the server-signature thus authorizing the server -- if it fails we drop the connection. """ alleged_server_sig = base64.b64decode(details.authextra['scram_server_signature']) server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest() server_signature = hmac.new(server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if not hmac.compare_digest(server_signature, alleged_server_sig): session.log.error("Verification of server SCRAM signature failed") session.leave( u"wamp.error.cannot_authenticate", u"Verification of server signature failed", ) else: session.log.info( "Verification of server SCRAM signature successful" ) session.on('join', confirm_server_signature) return base64.b64encode(client_proof)