def test_saslprep_exceptions(source, exception): with pytest.raises(exception) as ex: saslprep(source)
def user_should_we_change_password(current_role_attrs, user, password, encrypted): """Check if we should change the user's password. Compare the proposed password with the existing one, comparing hashes if encrypted. If we can't access it assume yes. """ if current_role_attrs is None: # on some databases, E.g. AWS RDS instances, there is no access to # the pg_authid relation to check the pre-existing password, so we # just assume password is different return True # Do we actually need to do anything? pwchanging = False if password is not None: # Empty password means that the role shouldn't have a password, which # means we need to check if the current password is None. if password == '': if current_role_attrs['rolpassword'] is not None: pwchanging = True # SCRAM hashes are represented as a special object, containing hash data: # `SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>` # for reference, see https://www.postgresql.org/docs/current/catalog-pg-authid.html elif current_role_attrs['rolpassword'] is not None \ and pbkdf2_found \ and re.match(SCRAM_SHA256_REGEX, current_role_attrs['rolpassword']): r = re.match(SCRAM_SHA256_REGEX, current_role_attrs['rolpassword']) try: # extract SCRAM params from rolpassword it = int(r.group(1)) salt = b64decode(r.group(2)) server_key = b64decode(r.group(4)) # we'll never need `storedKey` as it is only used for server auth in SCRAM # storedKey = b64decode(r.group(3)) # from RFC5802 https://tools.ietf.org/html/rfc5802#section-3 # SaltedPassword := Hi(Normalize(password), salt, i) # ServerKey := HMAC(SaltedPassword, "Server Key") normalized_password = saslprep.saslprep(to_text(password)) salted_password = pbkdf2_hmac('sha256', to_bytes(normalized_password), salt, it) server_key_verifier = hmac.new(salted_password, digestmod=sha256) server_key_verifier.update(b'Server Key') if server_key_verifier.digest() != server_key: pwchanging = True except Exception: # We assume the password is not scram encrypted # or we cannot check it properly, e.g. due to missing dependencies pwchanging = True # 32: MD5 hashes are represented as a sequence of 32 hexadecimal digits # 3: The size of the 'md5' prefix # When the provided password looks like a MD5-hash, value of # 'encrypted' is ignored. elif (password.startswith('md5') and len(password) == 32 + 3) or encrypted == 'UNENCRYPTED': if password != current_role_attrs['rolpassword']: pwchanging = True elif encrypted == 'ENCRYPTED': hashed_password = '******'.format( md5(to_bytes(password) + to_bytes(user)).hexdigest()) if hashed_password != current_role_attrs['rolpassword']: pwchanging = True return pwchanging
def test_saslprep_conversions(source, target): assert saslprep(source) == target