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 164

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 44

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 52

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 288

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 295

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 309

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 302

attr_reader :server_nonce

#server_verified?true | fase (readonly)

Whether the client verified the ServerSignature from the server.

Returns:

  • (true | fase)

    Whether the server’s signature was verified.

See Also:

Since:

  • 2.0.0

[ GitHub ]

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

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 312

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.

Since:

  • 2.0.0

[ GitHub ]

  
# 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 (private)

Get the empty client message.

Since:

  • 2.0.0

[ GitHub ]

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

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 193

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 182

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 148

def client_first_message_options
  {skipEmptyExchange: true}
end

#client_first_payload (private)

[ GitHub ]

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

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 222

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 235

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 246

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 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

#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 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) ⇒ 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 111

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 257

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 268

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

#hmac(data, key) (private)

HMAC algorithm implementation.

[ GitHub ]

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

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 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

#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 100

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 323

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 336

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 126

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 347

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 358

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 367

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