def test_saslprep(self): try: import stringprep except ImportError: self.assertRaises(TypeError, saslprep, u"anything...") # Bytes strings are ignored. self.assertEqual(saslprep(b"user"), b"user") else: # Examples from RFC4013, Section 3. self.assertEqual(saslprep(u"I\u00ADX"), u"IX") self.assertEqual(saslprep(u"user"), u"user") self.assertEqual(saslprep(u"USER"), u"USER") self.assertEqual(saslprep(u"\u00AA"), u"a") self.assertEqual(saslprep(u"\u2168"), u"IX") self.assertRaises(ValueError, saslprep, u"\u0007") self.assertRaises(ValueError, saslprep, u"\u0627\u0031") # Bytes strings are ignored. self.assertEqual(saslprep(b"user"), b"user")
def test_saslprep(self): try: import stringprep except ImportError: self.assertRaises(TypeError, saslprep, u"anything...") # Bytes strings are ignored. self.assertEqual(saslprep(b"user"), b"user") else: # Examples from RFC4013, Section 3. self.assertEqual(saslprep(u"I\u00ADX"), u"IX") self.assertEqual(saslprep(u"user"), u"user") self.assertEqual(saslprep(u"USER"), u"USER") self.assertEqual(saslprep(u"\u00AA"), u"a") self.assertEqual(saslprep(u"\u2168"), u"IX") self.assertRaises(ValueError, saslprep, u"\u0007") self.assertRaises(ValueError, saslprep, u"\u0627\u0031") # Bytes strings are ignored. self.assertEqual(saslprep(b"user"), b"user")
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 = hashlib.pbkdf2_hmac( 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 hmac.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 test_scram(self): host, port = client_context.host, client_context.port client_context.create_user('testscram', 'sha1', 'pwd', roles=['dbOwner'], mechanisms=['SCRAM-SHA-1']) client_context.create_user('testscram', 'sha256', 'pwd', roles=['dbOwner'], mechanisms=['SCRAM-SHA-256']) client_context.create_user('testscram', 'both', 'pwd', roles=['dbOwner'], mechanisms=['SCRAM-SHA-1', 'SCRAM-SHA-256']) client = rs_or_single_client_noauth(event_listeners=[self.listener]) self.assertTrue(client.testscram.authenticate('sha1', 'pwd')) client.testscram.command('dbstats') client.testscram.logout() self.assertTrue( client.testscram.authenticate('sha1', 'pwd', mechanism='SCRAM-SHA-1')) client.testscram.command('dbstats') client.testscram.logout() self.assertRaises(OperationFailure, client.testscram.authenticate, 'sha1', 'pwd', mechanism='SCRAM-SHA-256') self.assertTrue(client.testscram.authenticate('sha256', 'pwd')) client.testscram.command('dbstats') client.testscram.logout() self.assertTrue( client.testscram.authenticate('sha256', 'pwd', mechanism='SCRAM-SHA-256')) client.testscram.command('dbstats') client.testscram.logout() self.assertRaises(OperationFailure, client.testscram.authenticate, 'sha256', 'pwd', mechanism='SCRAM-SHA-1') self.listener.results.clear() self.assertTrue(client.testscram.authenticate('both', 'pwd')) started = self.listener.results['started'][0] self.assertEqual(started.command.get('mechanism'), 'SCRAM-SHA-256') client.testscram.command('dbstats') client.testscram.logout() self.assertTrue( client.testscram.authenticate('both', 'pwd', mechanism='SCRAM-SHA-256')) client.testscram.command('dbstats') client.testscram.logout() self.assertTrue( client.testscram.authenticate('both', 'pwd', mechanism='SCRAM-SHA-1')) client.testscram.command('dbstats') client.testscram.logout() self.assertRaises(OperationFailure, client.testscram.authenticate, 'not-a-user', 'pwd') if HAVE_STRINGPREP: client_context.create_user('testscram', saslprep(u'\u2168'), u'\u2168', roles=['dbOwner'], mechanisms=['SCRAM-SHA-256']) self.assertTrue(client.testscram.authenticate( u'\u2168', u'\u2168')) client.testscram.command('dbstats') client.testscram.logout() self.assertTrue( client.testscram.authenticate(u'\u2168', u'\u2168', mechanism='SCRAM-SHA-256')) client.testscram.command('dbstats') client.testscram.logout() self.assertRaises(OperationFailure, client.testscram.authenticate, u'\u2168', u'\u2168', mechanism='SCRAM-SHA-1') client = rs_or_single_client_noauth( u'mongodb://\u2168:\u2168@%s:%d/testscram' % (host, port)) client.testscram.command('dbstats') self.listener.results.clear() client = rs_or_single_client_noauth( 'mongodb://*****:*****@%s:%d/testscram' % (host, port), event_listeners=[self.listener]) client.testscram.command('dbstats') started = self.listener.results['started'][0] self.assertEqual(started.command.get('mechanism'), 'SCRAM-SHA-256') client = rs_or_single_client_noauth( 'mongodb://*****:*****@%s:%d/testscram?authMechanism=SCRAM-SHA-1' % (host, port)) client.testscram.command('dbstats') client = rs_or_single_client_noauth( 'mongodb://*****:*****@%s:%d/testscram?authMechanism=SCRAM-SHA-256' % (host, port)) client.testscram.command('dbstats') if client_context.is_rs: uri = ('mongodb://*****:*****@%s:%d/testscram' '?replicaSet=%s' % (host, port, client_context.replica_set_name)) client = single_client_noauth(uri) client.testscram.command('dbstats') db = client.get_database('testscram', read_preference=ReadPreference.SECONDARY) db.command('dbstats')