Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/postgres/postgres-pr/scram_sha_256.rb
Views: 11778
# -*- coding: binary -*-12require 'base64'3require 'openssl'4require 'net/imap'56# Namespace for Metasploit branch.7module Msf8module Db9module PostgresPR1011# Implements SCRAM-SHA-256 authentication; The caller of #negotiate can additionally wrap the calculated authentication12# models with SASL/GSSAPI as appropriate13#14# https://datatracker.ietf.org/doc/html/rfc7677#section-315class ScramSha25616class NormalizeError < ArgumentError17end1819# @param [String] user20# @param [String] password21def negotiate(user, password)22random_nonce = b64(SecureRandom.bytes(32))2324# Attributes: https://datatracker.ietf.org/doc/html/rfc5802#section-525client_first_without_gs2_header = "n=#{normalize(user)},r=#{random_nonce}"26client_gs2_header = gs2_header(channel_binding: false)27client_first = "#{client_gs2_header}#{client_first_without_gs2_header}"2829server_first_string = yield :client_first, client_first3031server_first = parse_server_response(server_first_string)32server_nonce = server_first[:r]33server_salt = Base64.strict_decode64(server_first[:s])34iterations = server_first[:i].to_i3536# https://datatracker.ietf.org/doc/html/rfc5802#section-337salted_password = hi(normalize(password), server_salt, iterations)38client_key = hmac(salted_password, "Client Key")39stored_key = h(client_key)4041client_final_without_proof = "c=#{b64(client_gs2_header)},r=#{server_nonce}"4243auth_message = [client_first_without_gs2_header, server_first_string, client_final_without_proof].join(',')44client_signature = hmac(stored_key, auth_message)45client_proof = xor_strings(client_key, client_signature)46server_key = hmac(salted_password, "Server Key")47expected_server_signature = hmac(server_key, auth_message)4849client_final = "#{client_final_without_proof},p=#{b64(client_proof)}"5051server_final = yield :client_final, client_final52raise AuthenticationMethodMismatch, 'Server proof failed' if server_final != "v=#{b64(expected_server_signature)}"5354nil55end5657# Implements Normalize from https://datatracker.ietf.org/doc/html/rfc4013 -58# Apply the SASLprep profile [RFC4013] of the "stringprep" algorithm [RFC3454]59#60# @param [String] value61# @return [String]62def normalize(value)63::Net::IMAP::SASL.saslprep(value, exception: true)64rescue ArgumentError => e65raise NormalizeError, e.message66end6768# Hi function implementation from69# https://datatracker.ietf.org/doc/html/rfc5802#section-2.270#71# @param [String] str72# @param [String] salt73# @param [Numeric] iteration_count74def hi(str, salt, iteration_count)75u = hmac(str, "#{salt.b}#{"\x00\x00\x00\x01".b}")76u_i = u77(iteration_count - 1).times do78u_i = hmac(str, u_i)79u = xor_strings(u, u_i)80end8182u83end8485# @return [String]86def hash_function_name87'SHA256'88end8990# H function from91# https://datatracker.ietf.org/doc/html/rfc5802#section-2.292#93# @param [String] str94def h(str)95OpenSSL::Digest.digest(hash_function_name, str)96end9798# @param [String] key99# @param [String] message100# @return [String]101def hmac(key, message)102OpenSSL::HMAC.digest(hash_function_name, key, message)103end104105# Implements https://datatracker.ietf.org/doc/html/rfc5801#section-4106# @return [String] The bytes for a gs2 header107def gs2_header(channel_binding: false)108# Specified as gs2-cb-flag109if channel_binding110# gs2_channel_binding_flag = 'y'111# Implementation skipped for now, just haven't112raise NotImplementedError, 'Channel binding not implemented'113else114gs2_channel_binding_flag = 'n'115end116117gs2_authzid = nil118gs2_header = "#{gs2_channel_binding_flag},#{gs2_authzid},"119gs2_header120end121122private123124# @param [String] value125def b64(value)126Base64.strict_encode64(value)127end128129# @param [String] s1130# @param [String] s2131# @return [String] the strings XOR'd132def xor_strings(s1, s2)133s1.bytes.zip(s2.bytes).map { |(b1, b2)| b1 ^ b2 }.pack("C*")134end135136# Parses a server response string such as 'r=2kRpTcHEFyoG+UgDEpRBdVcJLTWh5WtxARhYOHcG27i7YxAi,s=GNpgixWS5E4INbrMf665Kw==,i=4096'137# into a Ruby hash equivalent { r: '2kRpT...', i: '4096' }138# @param [String] string Server string response string139def parse_server_response(string)140string.split(',')141.each_with_object({}) do |key_value, result|142key, value = key_value.split('=', 2)143result[key.to_sym] = value144end145end146end147148end149end150end151152153