def authenticate_scram_sha1(self, database_name, username, password): # Totally stolen from pymongo.auth user = username.replace('=', "=3D").replace(',', "=2C") nonce = base64.standard_b64encode(str(SystemRandom().random()).encode('ascii'))[2:] first_bare = "n={0},r={1}".format(user, nonce.decode()).encode('ascii') cmd = SON([("saslStart", 1), ("mechanism", "SCRAM-SHA-1"), ("autoAuthorize", 1), ("payload", Binary(b"n,," + first_bare))]) result = yield self.__run_command(database_name, cmd) server_first = result["payload"] parsed = auth._parse_scram_response(server_first) iterations = int(parsed[b'i']) salt = parsed[b's'] rnonce = parsed[b'r'] if not rnonce.startswith(nonce): raise MongoAuthenticationError("TxMongo: server returned an invalid nonce.") without_proof = b"c=biws,r=" + rnonce salted_pass = _hi('sha1', auth._password_digest(username, password).encode("utf-8"), base64.standard_b64decode(salt), iterations) client_key = hmac.HMAC(salted_pass, b"Client Key", sha1).digest() stored_key = sha1(client_key).digest() auth_msg = b','.join((first_bare, server_first, without_proof)) client_sig = hmac.HMAC(stored_key, auth_msg, sha1).digest() client_proof = b"p=" + base64.standard_b64encode(auth._xor(client_key, client_sig)) client_final = b','.join((without_proof, client_proof)) server_key = hmac.HMAC(salted_pass, b"Server Key", sha1).digest() server_sig = base64.standard_b64encode( hmac.HMAC(server_key, auth_msg, sha1).digest()) cmd = SON([("saslContinue", 1), ("conversationId", result["conversationId"]), ("payload", Binary(client_final))]) result = yield self.__run_command(database_name, cmd) if not result["ok"]: raise MongoAuthenticationError("TxMongo: authentication failed.") parsed = auth._parse_scram_response(result["payload"]) if parsed[b'v'] != server_sig: raise MongoAuthenticationError("TxMongo: server returned an invalid signature.") # Depending on how it's configured, Cyrus SASL (which the server uses) # requires a third empty challenge. if not result["done"]: cmd = SON([("saslContinue", 1), ("conversationId", result["conversationId"]), ("payload", Binary(b''))]) result = yield self.__run_command(database_name, cmd) if not result["done"]: raise MongoAuthenticationError("TxMongo: SASL conversation failed to complete.")
def _authenticate_scram(credentials, sock_info, mechanism): """Authenticate using SCRAM.""" username = credentials.username if mechanism == 'SCRAM-SHA-256': digest = "sha256" digestmod = hashlib.sha256 data = saslprep(credentials.password).encode("utf-8") else: digest = "sha1" digestmod = hashlib.sha1 data = _password_digest(username, credentials.password).encode("utf-8") source = credentials.source cache = credentials.cache # Make local _hmac = hmac.HMAC user = username.encode("utf-8").replace(b"=", b"=3D").replace(b",", b"=2C") nonce = standard_b64encode(os.urandom(32)) first_bare = b"n=" + user + b",r=" + nonce cmd = SON([('saslStart', 1), ('mechanism', mechanism), ('payload', Binary(b"n,," + first_bare)), ('autoAuthorize', 1)]) res = sock_info.command(source, cmd) server_first = res['payload'] parsed = _parse_scram_response(server_first) iterations = int(parsed[b'i']) if iterations < 4096: raise OperationFailure("Server returned an invalid iteration count.") salt = parsed[b's'] rnonce = parsed[b'r'] if not rnonce.startswith(nonce): raise OperationFailure("Server returned an invalid nonce.") without_proof = b"c=biws,r=" + rnonce if cache.data: client_key, server_key, csalt, citerations = cache.data else: client_key, server_key, csalt, citerations = None, None, None, None # Salt and / or iterations could change for a number of different # reasons. Either changing invalidates the cache. if not client_key or salt != csalt or iterations != citerations: salted_pass = _hi( digest, data, standard_b64decode(salt), iterations) client_key = _hmac(salted_pass, b"Client Key", digestmod).digest() server_key = _hmac(salted_pass, b"Server Key", digestmod).digest() cache.data = (client_key, server_key, salt, iterations) stored_key = digestmod(client_key).digest() auth_msg = b",".join((first_bare, server_first, without_proof)) client_sig = _hmac(stored_key, auth_msg, digestmod).digest() client_proof = b"p=" + standard_b64encode(_xor(client_key, client_sig)) client_final = b",".join((without_proof, client_proof)) server_sig = standard_b64encode( _hmac(server_key, auth_msg, digestmod).digest()) cmd = SON([('saslContinue', 1), ('conversationId', res['conversationId']), ('payload', Binary(client_final))]) res = sock_info.command(source, cmd) parsed = _parse_scram_response(res['payload']) if not compare_digest(parsed[b'v'], server_sig): raise OperationFailure("Server returned an invalid signature.") # Depending on how it's configured, Cyrus SASL (which the server uses) # requires a third empty challenge. if not res['done']: cmd = SON([('saslContinue', 1), ('conversationId', res['conversationId']), ('payload', Binary(b''))]) res = sock_info.command(source, cmd) if not res['done']: raise OperationFailure('SASL conversation failed to complete.')
def _authenticate_scram(credentials, sock_info, mechanism): """Authenticate using SCRAM.""" username = credentials.username if mechanism == 'SCRAM-SHA-256': digest = "sha256" digestmod = hashlib.sha256 data = saslprep(credentials.password).encode("utf-8") else: digest = "sha1" digestmod = hashlib.sha1 data = _password_digest(username, credentials.password).encode("utf-8") source = credentials.source cache = credentials.cache # Make local _hmac = hmac.HMAC ctx = sock_info.auth_ctx.get(credentials) if ctx and ctx.speculate_succeeded(): nonce, first_bare = ctx.scram_data res = ctx.speculative_authenticate else: nonce, first_bare, cmd = _authenticate_scram_start( credentials, mechanism) res = sock_info.command(source, cmd) server_first = res['payload'] parsed = _parse_scram_response(server_first) iterations = int(parsed[b'i']) if iterations < 4096: raise OperationFailure("Server returned an invalid iteration count.") salt = parsed[b's'] rnonce = parsed[b'r'] if not rnonce.startswith(nonce): raise OperationFailure("Server returned an invalid nonce.") without_proof = b"c=biws,r=" + rnonce if cache.data: client_key, server_key, csalt, citerations = cache.data else: client_key, server_key, csalt, citerations = None, None, None, None # Salt and / or iterations could change for a number of different # reasons. Either changing invalidates the cache. if not client_key or salt != csalt or iterations != citerations: salted_pass = _hi(digest, data, standard_b64decode(salt), iterations) client_key = _hmac(salted_pass, b"Client Key", digestmod).digest() server_key = _hmac(salted_pass, b"Server Key", digestmod).digest() cache.data = (client_key, server_key, salt, iterations) stored_key = digestmod(client_key).digest() auth_msg = b",".join((first_bare, server_first, without_proof)) client_sig = _hmac(stored_key, auth_msg, digestmod).digest() client_proof = b"p=" + standard_b64encode(_xor(client_key, client_sig)) client_final = b",".join((without_proof, client_proof)) server_sig = standard_b64encode( _hmac(server_key, auth_msg, digestmod).digest()) cmd = SON([('saslContinue', 1), ('conversationId', res['conversationId']), ('payload', Binary(client_final))]) res = sock_info.command(source, cmd) parsed = _parse_scram_response(res['payload']) if not compare_digest(parsed[b'v'], server_sig): raise OperationFailure("Server returned an invalid signature.") # A third empty challenge may be required if the server does not support # skipEmptyExchange: SERVER-44857. if not res['done']: cmd = SON([('saslContinue', 1), ('conversationId', res['conversationId']), ('payload', Binary(b''))]) res = sock_info.command(source, cmd) if not res['done']: raise OperationFailure('SASL conversation failed to complete.')
def authenticate_scram_sha1(self, database_name, username, password): # Totally stolen from pymongo.auth user = username.replace('=', "=3D").replace(',', "=2C") nonce = base64.standard_b64encode( str(SystemRandom().random()).encode('ascii'))[2:] first_bare = "n={0},r={1}".format(user, nonce.decode()).encode('ascii') cmd = SON([("saslStart", 1), ("mechanism", "SCRAM-SHA-1"), ("autoAuthorize", 1), ("payload", Binary(b"n,," + first_bare))]) result = yield self.__run_command(database_name, cmd) server_first = result["payload"] parsed = auth._parse_scram_response(server_first) iterations = int(parsed[b'i']) salt = parsed[b's'] rnonce = parsed[b'r'] if not rnonce.startswith(nonce): raise MongoAuthenticationError( "TxMongo: server returned an invalid nonce.") without_proof = b"c=biws,r=" + rnonce salted_pass = _hi( 'sha1', auth._password_digest(username, password).encode("utf-8"), base64.standard_b64decode(salt), iterations) client_key = hmac.HMAC(salted_pass, b"Client Key", sha1).digest() stored_key = sha1(client_key).digest() auth_msg = b','.join((first_bare, server_first, without_proof)) client_sig = hmac.HMAC(stored_key, auth_msg, sha1).digest() client_proof = b"p=" + base64.standard_b64encode( auth._xor(client_key, client_sig)) client_final = b','.join((without_proof, client_proof)) server_key = hmac.HMAC(salted_pass, b"Server Key", sha1).digest() server_sig = base64.standard_b64encode( hmac.HMAC(server_key, auth_msg, sha1).digest()) cmd = SON([("saslContinue", 1), ("conversationId", result["conversationId"]), ("payload", Binary(client_final))]) result = yield self.__run_command(database_name, cmd) if not result["ok"]: raise MongoAuthenticationError("TxMongo: authentication failed.") parsed = auth._parse_scram_response(result["payload"]) if parsed[b'v'] != server_sig: raise MongoAuthenticationError( "TxMongo: server returned an invalid signature.") # Depending on how it's configured, Cyrus SASL (which the server uses) # requires a third empty challenge. if not result["done"]: cmd = SON([("saslContinue", 1), ("conversationId", result["conversationId"]), ("payload", Binary(b''))]) result = yield self.__run_command(database_name, cmd) if not result["done"]: raise MongoAuthenticationError( "TxMongo: SASL conversation failed to complete.")