|
| 1 | +# taken from https://github.com/PyMySQL/PyMySQL/pull/791/commits |
| 2 | +# stripped the patches for tests and development files |
| 3 | + |
| 4 | +From: Damien Ciabrini <damien.ciabrini@gmail.com> |
| 5 | +Date: Tue, 4 Jun 2019 09:28:29 +0200 |
| 6 | +Subject: [PATCH] Support for MariaDB's auth_ed25519 authentication plugin |
| 7 | + (#786) |
| 8 | + |
| 9 | +diff --git a/pymysql/_auth.py b/pymysql/_auth.py |
| 10 | +index aa082dfe..0a09de4e 100644 |
| 11 | +--- a/pymysql/_auth.py |
| 12 | ++++ b/pymysql/_auth.py |
| 13 | +@@ -114,6 +114,66 @@ def _hash_password_323(password): |
| 14 | + return struct.pack(">LL", r1, r2) |
| 15 | + |
| 16 | + |
| 17 | ++# MariaDB's client_ed25519-plugin |
| 18 | ++# https://mariadb.com/kb/en/library/connection/#client_ed25519-plugin |
| 19 | ++ |
| 20 | ++_nacl_bindings = False |
| 21 | ++ |
| 22 | ++ |
| 23 | ++def _init_nacl(): |
| 24 | ++ global _nacl_bindings |
| 25 | ++ try: |
| 26 | ++ from nacl import bindings |
| 27 | ++ _nacl_bindings = bindings |
| 28 | ++ except ImportError: |
| 29 | ++ raise RuntimeError("'pynacl' package is required for ed25519_password auth method") |
| 30 | ++ |
| 31 | ++ |
| 32 | ++def _scalar_clamp(s32): |
| 33 | ++ ba = bytearray(s32) |
| 34 | ++ ba0 = bytes(bytearray([ba[0] & 248])) |
| 35 | ++ ba31 = bytes(bytearray([(ba[31] & 127) | 64])) |
| 36 | ++ return ba0 + bytes(s32[1:31]) + ba31 |
| 37 | ++ |
| 38 | ++ |
| 39 | ++def ed25519_password(password, scramble): |
| 40 | ++ """Sign a random scramble with elliptic curve Ed25519. |
| 41 | ++ |
| 42 | ++ Secret and public key are derived from password. |
| 43 | ++ """ |
| 44 | ++ # variable names based on rfc8032 section-5.1.6 |
| 45 | ++ # |
| 46 | ++ if not _nacl_bindings: |
| 47 | ++ _init_nacl() |
| 48 | ++ |
| 49 | ++ # h = SHA512(password) |
| 50 | ++ h = hashlib.sha512(password).digest() |
| 51 | ++ |
| 52 | ++ # s = prune(first_half(h)) |
| 53 | ++ s = _scalar_clamp(h[:32]) |
| 54 | ++ |
| 55 | ++ # r = SHA512(second_half(h) || M) |
| 56 | ++ r = hashlib.sha512(h[32:] + scramble).digest() |
| 57 | ++ |
| 58 | ++ # R = encoded point [r]B |
| 59 | ++ r = _nacl_bindings.crypto_core_ed25519_scalar_reduce(r) |
| 60 | ++ R = _nacl_bindings.crypto_scalarmult_ed25519_base_noclamp(r) |
| 61 | ++ |
| 62 | ++ # A = encoded point [s]B |
| 63 | ++ A = _nacl_bindings.crypto_scalarmult_ed25519_base_noclamp(s) |
| 64 | ++ |
| 65 | ++ # k = SHA512(R || A || M) |
| 66 | ++ k = hashlib.sha512(R + A + scramble).digest() |
| 67 | ++ |
| 68 | ++ # S = (k * s + r) mod L |
| 69 | ++ k = _nacl_bindings.crypto_core_ed25519_scalar_reduce(k) |
| 70 | ++ ks = _nacl_bindings.crypto_core_ed25519_scalar_mul(k, s) |
| 71 | ++ S = _nacl_bindings.crypto_core_ed25519_scalar_add(ks, r) |
| 72 | ++ |
| 73 | ++ # signature = R || S |
| 74 | ++ return R + S |
| 75 | ++ |
| 76 | ++ |
| 77 | + # sha256_password |
| 78 | + |
| 79 | + |
| 80 | +diff --git a/pymysql/connections.py b/pymysql/connections.py |
| 81 | +index d9ade9a2..df092929 100644 |
| 82 | +--- a/pymysql/connections.py |
| 83 | ++++ b/pymysql/connections.py |
| 84 | +@@ -889,6 +889,8 @@ def _process_auth(self, plugin_name, auth_packet): |
| 85 | + return _auth.sha256_password_auth(self, auth_packet) |
| 86 | + elif plugin_name == b"mysql_native_password": |
| 87 | + data = _auth.scramble_native_password(self.password, auth_packet.read_all()) |
| 88 | ++ elif plugin_name == b'client_ed25519': |
| 89 | ++ data = _auth.ed25519_password(self.password, auth_packet.read_all()) |
| 90 | + elif plugin_name == b"mysql_old_password": |
| 91 | + data = _auth.scramble_old_password(self.password, auth_packet.read_all()) + b'\0' |
| 92 | + elif plugin_name == b"mysql_clear_password": |
0 commit comments