Skip to content

Commit a8cb85b

Browse files
committed
feat(auth): support sha256 password
1 parent e91ccfa commit a8cb85b

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

redshift_connector/core.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,61 @@ def handle_AUTHENTICATION_REQUEST(self: "Connection", data: bytes, cursor: Curso
13281328
# self._write(NULL_BYTE)
13291329
self._flush()
13301330

1331+
elif auth_code == 13: # AUTH_REQ_DIGEST
1332+
offset: int = 4
1333+
algo: int = i_unpack(data, offset)[0]
1334+
algo_names: typing.Tuple[str] = ("SHA256",)
1335+
offset += 4
1336+
1337+
salt_len: int = i_unpack(data, offset)[0]
1338+
offset += 4
1339+
1340+
salt = data[offset : offset + salt_len]
1341+
offset += salt_len
1342+
1343+
server_nonce_len: int = i_unpack(data, offset)[0]
1344+
offset += 4
1345+
1346+
server_nonce: bytes = data[offset : offset + server_nonce_len]
1347+
offset += server_nonce_len
1348+
1349+
ms_since_epoch: int = int((Datetime.utcnow() - Datetime.utcfromtimestamp(0)).total_seconds() * 1000.0)
1350+
client_nonce: bytes = str(ms_since_epoch).encode("utf-8")
1351+
1352+
_logger.debug("handle_AUTHENTICATION_REQUEST: AUTH_REQ_DIGEST")
1353+
_logger.debug("Algo:{}".format(algo))
1354+
1355+
if self.password is None:
1356+
raise InterfaceError(
1357+
"The server requested password-based authentication, but no password was provided."
1358+
)
1359+
1360+
if algo > len(algo_names):
1361+
raise InterfaceError(
1362+
"The server requested password-based authentication, "
1363+
"but requested algorithm {} is not supported.".format(algo)
1364+
)
1365+
1366+
from redshift_connector.utils.extensible_digest import ExtensibleDigest
1367+
1368+
digest: bytes = ExtensibleDigest.encode(
1369+
client_nonce=client_nonce,
1370+
password=typing.cast(bytes, self.password),
1371+
salt=salt,
1372+
algo_name=algo_names[algo],
1373+
server_nonce=server_nonce,
1374+
)
1375+
1376+
_logger.debug("Password(extensible digest)")
1377+
1378+
self._write(b"d")
1379+
self._write(i_pack(4 + 4 + len(digest) + 4 + len(client_nonce)))
1380+
self._write(i_pack(len(digest)))
1381+
self._write(digest)
1382+
self._write(i_pack(len(client_nonce)))
1383+
self._write(client_nonce)
1384+
self._flush()
1385+
13311386
elif auth_code in (2, 4, 6, 7, 8, 9):
13321387
raise InterfaceError("Authentication method " + str(auth_code) + " not supported by redshift_connector.")
13331388
else:
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import typing
2+
from hashlib import new as hashlib_new
3+
4+
from redshift_connector import InterfaceError
5+
6+
if typing.TYPE_CHECKING:
7+
from hashlib import _Hash
8+
9+
10+
class ExtensibleDigest:
11+
"""
12+
Encodes user/password/salt information in the following way: SHA2(SHA2(password + user) + salt).
13+
"""
14+
15+
@staticmethod
16+
def encode(client_nonce: bytes, password: bytes, salt: bytes, algo_name: str, server_nonce: bytes) -> bytes:
17+
"""
18+
Encodes user/password/salt information in the following way: SHA2(SHA2(password + user) + salt).
19+
:param client_nonce: The client nonce
20+
:type client_nonce: bytes
21+
:param password: The connecting user's password
22+
:type password: bytes
23+
:param salt: salt sent by the server
24+
:type salt: bytes
25+
:param algo_name: Algorithm name such as "SHA256" etc.
26+
:type algo_name: str
27+
:param server_nonce: random number generated by server
28+
:type server_nonce: bytes
29+
:return: the digest
30+
:rtype: bytes
31+
"""
32+
try:
33+
hl1: "_Hash" = hashlib_new(name=algo_name)
34+
except ImportError:
35+
raise InterfaceError("Unable to encode password with extensible hashing: {}".format(algo_name))
36+
hl1.update(password)
37+
hl1.update(salt)
38+
pass_digest1: bytes = hl1.digest() # SHA2(user + password)
39+
40+
hl2: "_Hash" = hashlib_new(name=algo_name)
41+
hl2.update(pass_digest1)
42+
hl2.update(server_nonce)
43+
hl2.update(client_nonce)
44+
pass_digest2 = hl2.digest() # SHA2(SHA2(password + user) + salt)
45+
46+
return pass_digest2

0 commit comments

Comments
 (0)