123456789_123456789_123456789_123456789_123456789_

Class: Mongo::Auth::ScramConversationBase Private

Do not use. This class is for internal use only.
Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Mongo::Auth::Scram256::Conversation, Mongo::Auth::Scram::Conversation
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: Mongo::Auth::SaslConversationBase
Defined in: lib/mongo/auth/scram_conversation_base.rb

Overview

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

Since:

  • 2.0.0

Constant Summary

SaslConversationBase - Inherited

CLIENT_CONTINUE_MESSAGE, CLIENT_FIRST_MESSAGE

Class Method Summary

ConversationBase - Inherited

.new

Create the new conversation.

Instance Attribute Summary

ConversationBase - Inherited

Instance Method Summary

SaslConversationBase - Inherited

#start

Start the SASL conversation.

#auth_mechanism_name

Gets the auth mechanism name for the conversation class.

#client_first_document, #client_first_message_options,
#validate_server_nonce!

Helper method to validate that server nonce starts with the client nonce.

ConversationBase - Inherited

#build_message,
#speculative_auth_document

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

#validate_external_auth_source

Instance Attribute Details

#auth_message (readonly, private)

::Mongo::Auth message algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 161

attr_reader :auth_message

#client_nonceString (readonly)

Returns:

  • (String)

    client_nonce The client nonce.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 41

attr_reader :client_nonce

#idInteger (readonly)

Get the id of the conversation.

Examples:

Get the id of the conversation.

conversation.id

Returns:

  • (Integer)

    The conversation id.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 49

attr_reader :id

#iterations (readonly, private)

Get the iterations from the server response.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 282

attr_reader :iterations

#payload_data (readonly, private)

Get the data from the returned payload.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 289

attr_reader :payload_data

#salt (readonly, private)

Gets the salt from the server response.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 303

attr_reader :salt

#server_nonce (readonly, private)

Get the server nonce from the payload.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 296

attr_reader :server_nonce

#server_verified?true | false (readonly)

Whether the client verified the ServerSignature from the server.

Returns:

  • (true | false)

    Whether the server's signature was verified.

See Also:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 56

def server_verified?
  !!@server_verified
end

Instance Method Details

#cache_key(*extra) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 306

def cache_key(*extra)
  [ user.password, salt, iterations, @mechanism ] + extra
end

#check_server_signature(payload_data) (private)

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.

Raises:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 202

def check_server_signature(payload_data)
  return unless verifier = payload_data['v']
  raise Error::InvalidSignature.new(verifier, server_signature) unless compare_digest(verifier, server_signature)

  @server_verified = true
end

#client_empty_message (private)

Get the empty client message.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 168

def client_empty_message
  BSON::Binary.new('')
end

#client_final (private)

::Mongo::Client final implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 190

def client_final
  @client_final ||= client_proof(client_key,
                                 client_signature(stored_key(client_key),
                                                  auth_message))
end

#client_final_message (private)

Get the final client message.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 179

def client_final_message
  BSON::Binary.new("#{without_proof},p=#{client_final}")
end

#client_first_message_options (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 145

def client_first_message_options
  { skipEmptyExchange: true }
end

#client_first_payload (private)

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 150

def client_first_payload
  "n,,#{first_bare}"
end

#client_key (private)

::Mongo::Client key algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 216

def client_key
  @client_key ||= CredentialCache.cache(cache_key(:client_key)) do
    hmac(salted_password, 'Client Key')
  end
end

#client_proof(key, signature) (private)

::Mongo::Client proof algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 229

def client_proof(key, signature)
  @client_proof ||= Base64.strict_encode64(xor(key, signature))
end

#client_signature(key, message) (private)

::Mongo::Client signature algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 240

def client_signature(key, message)
  @client_signature ||= hmac(key, message)
end

#compare_digest(a, b) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 365

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

#continue(reply_document, connection) ⇒ Protocol::Message

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

Parameters:

  • reply_document (BSON::Document)

    The reply document of the previous message.

  • connection (Server::Connection)

    The connection being authenticated.

Returns:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 70

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) ⇒ Protocol::Message

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

Parameters:

Returns:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 109

def finalize(connection)
  selector = CLIENT_CONTINUE_MESSAGE.merge(
    payload: client_empty_message,
    conversationId: id
  )
  build_message(connection, user.auth_source, selector)
end

#first_bare (private)

First bare implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 251

def first_bare
  @first_bare ||= "n=#{user.encoded_name},r=#{client_nonce}"
end

#h(string) (private)

H algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 262

def h(string)
  digest.digest(string)
end

#hmac(data, key) (private)

HMAC algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 273

def hmac(data, key)
  OpenSSL::HMAC.digest(digest, data, key)
end

#parse_payload(payload) ⇒ Hash (private)

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

Parameters:

  • payload (String)

    The payload to parse.

Returns:

  • (Hash)

    Parsed key-value pairs.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 136

def parse_payload(payload)
  Hash[payload.split(',').reject { |v| v == '' }.map do |pair|
    k, v, = pair.split('=', 2)
    raise Error::InvalidServerAuthResponse, 'Payload malformed: missing key' if k == ''

    [ k, v ]
  end]
end

#process_continue_response(reply_document)

Processes the second response from the server.

Parameters:

  • reply_document (BSON::Document)

    The reply document of the continue response.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 98

def process_continue_response(reply_document)
  payload_data = parse_payload(reply_document['payload'].data)
  check_server_signature(payload_data)
end

#server_key (private)

::Mongo::Server key algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 317

def server_key
  @server_key ||= CredentialCache.cache(cache_key(:server_key)) do
    hmac(salted_password, 'Server Key')
  end
end

#server_signature (private)

::Mongo::Server signature algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 330

def server_signature
  @server_signature ||= Base64.strict_encode64(hmac(server_key, auth_message))
end

#speculative_auth_documentHash | nil

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.

Returns:

  • (Hash | nil)

    Speculative authentication document.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 124

def speculative_auth_document
  client_first_document.merge(db: user.auth_source)
end

#stored_key(key) (private)

Stored key algorithm implementation.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 341

def stored_key(key)
  h(key)
end

#without_proof (private)

Get the without proof message.

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 352

def without_proof
  @without_proof ||= "c=biws,r=#{server_nonce}"
end

#xor(first, second) (private)

XOR operation for two strings.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/auth/scram_conversation_base.rb', line 361

def xor(first, second)
  first.bytes.zip(second.bytes).map { |(a, b)| (a ^ b).chr }.join('')
end