Permalink
Browse files

scram-sha-256 authentication method is supported

  • Loading branch information...
nakagami committed Sep 22, 2018
1 parent 02b6fdc commit d4ef01ae2867b267c4e4dca8550b9cfce0a61ccc
Showing with 94 additions and 3 deletions.
  1. +1 −1 README.rst
  2. +93 −2 minipg/pgcore.py
@@ -71,7 +71,7 @@ As sqlalchemy dialect::
Restrictions and Unsupported Features
--------------------------------------

- Authentication METHOD only can 'trust' or 'md5' in pg_hba.conf.
- Supported Authentication METHOD are only 'trust', 'md5' and 'scram-sha-256'.
- Not full support for array data types.
- Not support for prepared statements.

@@ -26,6 +26,7 @@
from __future__ import print_function
import sys
import socket
import string
import decimal
import datetime
import time
@@ -34,6 +35,10 @@
import binascii
import json
import re
import random
import hashlib
import base64
import hmac
from argparse import ArgumentParser

from .err import (
@@ -348,6 +353,10 @@ def __enter__(self):
def __exit__(self, exc, value, traceback):
self.close()

def _send_data(self, message, data):
DEBUG_OUTPUT('<- {}:{}'.format(message, data))
self._write(b''.join([message, _bint_to_bytes(len(data) + 4), data]))

def _send_message(self, message, data):
DEBUG_OUTPUT('<- {}:{}'.format(message, data))
self._write(b''.join([message, _bint_to_bytes(len(data) + 4), data, b'H\x00\x00\x00\x04']))
@@ -487,11 +496,93 @@ def _process_messages(self, obj):
if auth_method == 0: # trust
pass
elif auth_method == 5: # md5
import hashlib
salt = data[4:]
hash1 = hashlib.md5(self.password.encode('ascii') + self.user.encode("ascii")).hexdigest().encode("ascii")
hash2 = hashlib.md5(hash1+salt).hexdigest().encode("ascii")
self._send_message(b'p', b''.join([b'md5', hash2, b'\x00']))
self._send_data(b'p', b''.join([b'md5', hash2, b'\x00']))

# accept
code = ord(self._read(1))
assert code == 82
ln = _bytes_to_bint(self._read(4)) - 4
data = self._read(ln)
assert _bytes_to_bint(data[:4]) == 0
elif auth_method == 10: # SASL
assert data[4:-2].decode('utf-8') == 'SCRAM-SHA-256'
printable = string.ascii_letters + string.digits + '+/'
client_nonce = ''.join(
printable[random.randrange(0, len(printable))]
for i in range(24)
)

# send client first message
first_message = 'n,,n=,r=' + client_nonce
self._send_data(b'p', b''.join([
b'SCRAM-SHA-256\x00',
_bint_to_bytes(len(first_message)),
first_message.encode('utf-8')
]))

code = ord(self._read(1))
assert code == 82
ln = _bytes_to_bint(self._read(4)) - 4
data = self._read(ln)
_bytes_to_bint(data[:4]) == 11 # SCRAM first

server = {
kv[0]: kv[2:]
for kv in data[4:].decode('utf-8').split(',')
}
# r: server nonce
# s: servre salt
# i: iteration count

# send client final message
salted_pass = hashlib.pbkdf2_hmac(
'sha256',
self.password.encode('utf-8'),
base64.standard_b64decode(server['s']),
int(server['i']),
)

client_key = hmac.HMAC(
salted_pass, b"Client Key", hashlib.sha256
).digest()

client_first_message_bare = "n=,r=" + client_nonce
server_first_message = "r=%s,s=%s,i=%s" % (server['r'], server['s'], server['i'])
client_final_message_without_proof = "c=biws,r=" + server['r']
auth_msg = ','.join([
client_first_message_bare,
server_first_message,
client_final_message_without_proof
])

client_sig = hmac.HMAC(
hashlib.sha256(client_key).digest(),
auth_msg.encode('utf-8'),
hashlib.sha256
).digest()

proof = base64.standard_b64encode(
b"".join([bytes([x ^ y]) for x, y in zip(client_key, client_sig)])
)
self._send_data(b'p',
(client_final_message_without_proof + ",p=").encode('utf-8') + proof
)

code = ord(self._read(1))
assert code == 82
ln = _bytes_to_bint(self._read(4)) - 4
data = self._read(ln)
_bytes_to_bint(data[:4]) == 12 # SCRAM final

# accept
code = ord(self._read(1))
assert code == 82
ln = _bytes_to_bint(self._read(4)) - 4
data = self._read(ln)
assert _bytes_to_bint(data[:4]) == 0
else:
errobj = InterfaceError("Authentication method %d not supported." % (auth_method,))
elif code == 83:

0 comments on commit d4ef01a

Please sign in to comment.