Authenticate using SCRAM.
(
credentials: MongoCredential, conn: AsyncConnection, mechanism: str
)
| 70 | |
| 71 | |
| 72 | async def _authenticate_scram( |
| 73 | credentials: MongoCredential, conn: AsyncConnection, mechanism: str |
| 74 | ) -> None: |
| 75 | """Authenticate using SCRAM.""" |
| 76 | username = credentials.username |
| 77 | if mechanism == "SCRAM-SHA-256": |
| 78 | digest = "sha256" |
| 79 | digestmod = hashlib.sha256 |
| 80 | data = saslprep(credentials.password).encode("utf-8") |
| 81 | else: |
| 82 | digest = "sha1" |
| 83 | digestmod = hashlib.sha1 |
| 84 | data = _password_digest(username, credentials.password).encode("utf-8") |
| 85 | source = credentials.source |
| 86 | cache = credentials.cache |
| 87 | |
| 88 | # Make local |
| 89 | _hmac = hmac.HMAC |
| 90 | |
| 91 | ctx = conn.auth_ctx |
| 92 | if ctx and ctx.speculate_succeeded(): |
| 93 | assert isinstance(ctx, _ScramContext) |
| 94 | assert ctx.scram_data is not None |
| 95 | nonce, first_bare = ctx.scram_data |
| 96 | res = ctx.speculative_authenticate |
| 97 | else: |
| 98 | nonce, first_bare, cmd = _authenticate_scram_start(credentials, mechanism) |
| 99 | res = await conn.command(source, cmd) |
| 100 | |
| 101 | assert res is not None |
| 102 | server_first = res["payload"] |
| 103 | parsed = _parse_scram_response(server_first) |
| 104 | iterations = int(parsed[b"i"]) |
| 105 | if iterations < 4096: |
| 106 | raise OperationFailure("Server returned an invalid iteration count.") |
| 107 | salt = parsed[b"s"] |
| 108 | rnonce = parsed[b"r"] |
| 109 | if not rnonce.startswith(nonce): |
| 110 | raise OperationFailure("Server returned an invalid nonce.") |
| 111 | |
| 112 | without_proof = b"c=biws,r=" + rnonce |
| 113 | if cache.data: |
| 114 | client_key, server_key, csalt, citerations = cache.data |
| 115 | else: |
| 116 | client_key, server_key, csalt, citerations = None, None, None, None |
| 117 | |
| 118 | # Salt and / or iterations could change for a number of different |
| 119 | # reasons. Either changing invalidates the cache. |
| 120 | if not client_key or salt != csalt or iterations != citerations: |
| 121 | salted_pass = hashlib.pbkdf2_hmac(digest, data, standard_b64decode(salt), iterations) |
| 122 | client_key = _hmac(salted_pass, b"Client Key", digestmod).digest() |
| 123 | server_key = _hmac(salted_pass, b"Server Key", digestmod).digest() |
| 124 | cache.data = (client_key, server_key, salt, iterations) |
| 125 | stored_key = digestmod(client_key).digest() |
| 126 | auth_msg = b",".join((first_bare, server_first, without_proof)) |
| 127 | client_sig = _hmac(stored_key, auth_msg, digestmod).digest() |
| 128 | client_proof = b"p=" + standard_b64encode(_xor(client_key, client_sig)) |
| 129 | client_final = b",".join((without_proof, client_proof)) |
no test coverage detected