class Mongo::Auth::ScramConversationBase

Defines common behavior around authentication conversations between the client and the server.

@api private

Constants

MIN_ITER_COUNT

The minimum iteration count for SCRAM-SHA-1 and SCRAM-SHA-256.

Attributes

auth_message[R]

Auth message algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

client_nonce[R]

@return [ String ] client_nonce The client nonce.

id[R]

Get the id of the conversation.

@example Get the id of the conversation.

conversation.id

@return [ Integer ] The conversation id.

iterations[R]

Get the iterations from the server response.

@api private

@since 2.0.0

payload_data[R]

Get the data from the returned payload.

@api private

@since 2.0.0

salt[R]

Gets the salt from the server response.

@api private

@since 2.0.0

server_nonce[R]

Get the server nonce from the payload.

@api private

@since 2.0.0

Public Class Methods

new(user, connection, client_nonce: nil) click to toggle source

Create the new conversation.

@param [ Auth::User ] user The user to converse about. @param [ String | nil ] client_nonce The client nonce to use.

If this conversation is created for a connection that performed
speculative authentication, this client nonce must be equal to the
client nonce used for speculative authentication; otherwise, the
client nonce must not be specified.
Calls superclass method Mongo::Auth::ConversationBase::new
# File lib/mongo/auth/scram_conversation_base.rb, line 38
def initialize(user, connection, client_nonce: nil)
  super
  @client_nonce = client_nonce || SecureRandom.base64
end

Public Instance Methods

continue(reply_document, connection) click to toggle source

Continue the SCRAM conversation. This sends the client final message to the server after setting the reply from the previous server communication.

@param [ BSON::Document ] reply_document The reply document of the

previous message.

@param [ Server::Connection ] connection The connection being

authenticated.

@return [ Protocol::Message ] The next message to send.

# File lib/mongo/auth/scram_conversation_base.rb, line 73
def continue(reply_document, connection)
  @id = reply_document['conversationId']
  payload_data = reply_document['payload'].data
  parsed_data = parse_payload(payload_data)
  @server_nonce = parsed_data.fetch('r')
  @salt = Base64.strict_decode64(parsed_data.fetch('s'))
  @iterations = parsed_data.fetch('i').to_i.tap do |i|
    if i < MIN_ITER_COUNT
      raise Error::InsufficientIterationCount.new(
        Error::InsufficientIterationCount.message(MIN_ITER_COUNT, i))
    end
  end
  @auth_message = "#{first_bare},#{payload_data},#{without_proof}"

  validate_server_nonce!

  selector = CLIENT_CONTINUE_MESSAGE.merge(
    payload: client_final_message,
    conversationId: id,
  )
  build_message(connection, user.auth_source, selector)
end
finalize(connection) click to toggle source

Finalize the SCRAM conversation. This is meant to be iterated until the provided reply indicates the conversation is finished.

@param [ Server::Connection ] connection The connection being authenticated.

@return [ Protocol::Message ] The next message to send.

# File lib/mongo/auth/scram_conversation_base.rb, line 111
def finalize(connection)
  selector = CLIENT_CONTINUE_MESSAGE.merge(
    payload: client_empty_message,
    conversationId: id,
  )
  build_message(connection, user.auth_source, selector)
end
process_continue_response(reply_document) click to toggle source

Processes the second response from the server.

@param [ BSON::Document ] reply_document The reply document of the

continue response.
# File lib/mongo/auth/scram_conversation_base.rb, line 100
def process_continue_response(reply_document)
  payload_data = parse_payload(reply_document['payload'].data)
  check_server_signature(payload_data)
end
server_verified?() click to toggle source

Whether the client verified the ServerSignature from the server.

@see jira.mongodb.org/browse/SECURITY-621

@return [ true | fase ] Whether the server's signature was verified.

# File lib/mongo/auth/scram_conversation_base.rb, line 59
def server_verified?
  !!@server_verified
end
speculative_auth_document() click to toggle source

Returns the hash to provide to the server in the handshake as value of the speculativeAuthenticate key.

If the auth mechanism does not support speculative authentication, this method returns nil.

@return [ Hash | nil ] Speculative authentication document.

# File lib/mongo/auth/scram_conversation_base.rb, line 126
def speculative_auth_document
  client_first_document.merge(db: user.auth_source)
end

Private Instance Methods

cache_key(*extra) click to toggle source

@api private

# File lib/mongo/auth/scram_conversation_base.rb, line 312
def cache_key(*extra)
  [user.password, salt, iterations, @mechanism] + extra
end
check_server_signature(payload_data) click to toggle source

Looks for field 'v' in payload data, if it is present verifies the server signature. If verification succeeds, sets @server_verified to true. If verification fails, raises InvalidSignature.

This method can be called from different conversation steps depending on whether the short SCRAM conversation is used.

# File lib/mongo/auth/scram_conversation_base.rb, line 205
def check_server_signature(payload_data)
  if verifier = payload_data['v']
    if compare_digest(verifier, server_signature)
      @server_verified = true
    else
      raise Error::InvalidSignature.new(verifier, server_signature)
    end
  end
end
client_empty_message() click to toggle source

Get the empty client message.

@api private

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 171
def client_empty_message
  BSON::Binary.new('')
end
client_final() click to toggle source

Client final implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-7

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 193
def client_final
  @client_final ||= client_proof(client_key,
    client_signature(stored_key(client_key),
    auth_message))
end
client_final_message() click to toggle source

Get the final client message.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 182
def client_final_message
  BSON::Binary.new("#{without_proof},p=#{client_final}")
end
client_first_message_options() click to toggle source
# File lib/mongo/auth/scram_conversation_base.rb, line 148
def client_first_message_options
  {skipEmptyExchange: true}
end
client_first_payload() click to toggle source

@see tools.ietf.org/html/rfc5802#section-3

# File lib/mongo/auth/scram_conversation_base.rb, line 153
def client_first_payload
  "n,,#{first_bare}"
end
client_key() click to toggle source

Client key algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 222
def client_key
  @client_key ||= CredentialCache.cache(cache_key(:client_key)) do
    hmac(salted_password, 'Client Key')
  end
end
client_proof(key, signature) click to toggle source

Client proof algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 235
def client_proof(key, signature)
  @client_proof ||= Base64.strict_encode64(xor(key, signature))
end
client_signature(key, message) click to toggle source

Client signature algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 246
def client_signature(key, message)
  @client_signature ||= hmac(key, message)
end
compare_digest(a, b) click to toggle source
# File lib/mongo/auth/scram_conversation_base.rb, line 371
def compare_digest(a, b)
  check = a.bytesize ^ b.bytesize
  a.bytes.zip(b.bytes){ |x, y| check |= x ^ y.to_i }
  check == 0
end
first_bare() click to toggle source

First bare implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-7

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 257
def first_bare
  @first_bare ||= "n=#{user.encoded_name},r=#{client_nonce}"
end
h(string) click to toggle source

H algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-2.2

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 268
def h(string)
  digest.digest(string)
end
hmac(data, key) click to toggle source

HMAC algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-2.2

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 279
def hmac(data, key)
  OpenSSL::HMAC.digest(digest, data, key)
end
parse_payload(payload) click to toggle source

Parses a payload like a=value,b=value2 into a hash like {'a' => 'value', 'b' => 'value2'}.

@param [ String ] payload The payload to parse.

@return [ Hash ] Parsed key-value pairs.

# File lib/mongo/auth/scram_conversation_base.rb, line 138
def parse_payload(payload)
  Hash[payload.split(',').reject { |v| v == '' }.map do |pair|
    k, v, = pair.split('=', 2)
    if k == ''
      raise Error::InvalidServerAuthResponse, 'Payload malformed: missing key'
    end
    [k, v]
  end]
end
server_key() click to toggle source

Server key algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 323
def server_key
  @server_key ||= CredentialCache.cache(cache_key(:server_key)) do
    hmac(salted_password, 'Server Key')
  end
end
server_signature() click to toggle source

Server signature algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 336
def server_signature
  @server_signature ||= Base64.strict_encode64(hmac(server_key, auth_message))
end
stored_key(key) click to toggle source

Stored key algorithm implementation.

@api private

@see tools.ietf.org/html/rfc5802#section-3

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 347
def stored_key(key)
  h(key)
end
without_proof() click to toggle source

Get the without proof message.

@api private

@see tools.ietf.org/html/rfc5802#section-7

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 358
def without_proof
  @without_proof ||= "c=biws,r=#{server_nonce}"
end
xor(first, second) click to toggle source

XOR operation for two strings.

@api private

@since 2.0.0

# File lib/mongo/auth/scram_conversation_base.rb, line 367
def xor(first, second)
  first.bytes.zip(second.bytes).map{ |(a,b)| (a ^ b).chr }.join('')
end