class Mongo::Auth::SCRAM::Conversation

Defines behaviour around a single SCRAM-SHA-1 conversation between the client and server.

@since 2.0.0

Constants

CLIENT_CONTINUE_MESSAGE

The base client continue message.

@since 2.0.0

CLIENT_FIRST_MESSAGE

The base client first message.

@since 2.0.0

CLIENT_KEY

The client key string.

@since 2.0.0

DIGEST

The digest to use for encryption.

@since 2.0.0

DONE

The key for the done field in the responses.

@since 2.0.0

ID

The conversation id field.

@since 2.0.0

ITERATIONS

The iterations key in the responses.

@since 2.0.0

PAYLOAD

The payload field.

@since 2.0.0

RNONCE

The rnonce key in the responses.

@since 2.0.0

SALT

The salt key in the responses.

@since 2.0.0

SERVER_KEY

The server key string.

@since 2.0.0

VERIFIER

The server signature verifier in the response.

@since 2.0.0

Attributes

nonce[R]

@return [ String ] nonce The initial user nonce.

reply[R]

@return [ Protocol::Reply ] reply The current reply in the

conversation.
user[R]

@return [ User ] user The user for the conversation.

Public Class Methods

new(user) click to toggle source

Create the new conversation.

@example Create the new conversation.

Conversation.new(user)

@param [ Auth::User ] user The user to converse about.

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 181
def initialize(user)
  @user = user
  @nonce = SecureRandom.base64
end

Public Instance Methods

continue(reply) 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.

@example Continue the conversation.

conversation.continue(reply)

@param [ Protocol::Reply ] reply The reply of the previous

message.

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

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 111
def continue(reply)
  validate_first_message!(reply)
  Protocol::Query.new(
    user.auth_source,
    Database::COMMAND,
    CLIENT_CONTINUE_MESSAGE.merge(payload: client_final_message, conversationId: id),
    limit: -1
  )
end
finalize(reply) click to toggle source

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

@example Finalize the conversation.

conversation.finalize(reply)

@param [ Protocol::Reply ] reply The reply of the previous

message.

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

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 133
def finalize(reply)
  validate_final_message!(reply)
  Protocol::Query.new(
    user.auth_source,
    Database::COMMAND,
    CLIENT_CONTINUE_MESSAGE.merge(payload: client_empty_message, conversationId: id),
    limit: -1
  )
end
id() click to toggle source

Get the id of the conversation.

@example Get the id of the conversation.

conversation.id

@return [ Integer ] The conversation id.

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 169
def id
  reply.documents[0][ID]
end
start() click to toggle source

Start the SCRAM conversation. This returns the first message that needs to be send to the server.

@example Start the conversation.

conversation.start

@return [ Protocol::Query ] The first SCRAM conversation message.

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 152
def start
  Protocol::Query.new(
    user.auth_source,
    Database::COMMAND,
    CLIENT_FIRST_MESSAGE.merge(payload: client_first_message, mechanism: SCRAM::MECHANISM),
    limit: -1
  )
end

Private Instance Methods

auth_message() click to toggle source

Auth message algorithm implementation.

@api private

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

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 195
def auth_message
  @auth_message ||= "#{first_bare},#{reply.documents[0][PAYLOAD].data},#{without_proof}"
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.rb, line 204
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.rb, line 237
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.rb, line 215
def client_final_message
  BSON::Binary.new("#{without_proof},p=#{client_final}")
end
client_first_message() click to toggle source

Get the client first message

@api private

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

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 226
def client_first_message
  BSON::Binary.new("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.rb, line 248
def client_key
  @client_key ||= hmac(salted_password, CLIENT_KEY)
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.rb, line 259
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.rb, line 270
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.rb, line 432
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
digest() click to toggle source
# File lib/mongo/auth/scram/conversation.rb, line 457
def digest
  @digest ||= OpenSSL::Digest::SHA1.new.freeze
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.rb, line 281
def first_bare
  @first_bare ||= "n=#{user.encoded_name},r=#{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.rb, line 292
def h(string)
  digest.digest(string)
end
hi(data) click to toggle source

HI algorithm implementation.

@api private

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

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 303
def hi(data)
  OpenSSL::PKCS5.pbkdf2_hmac_sha1(
    data,
    Base64.strict_decode64(salt),
    iterations,
    digest.size
  )
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.rb, line 319
def hmac(data, key)
  OpenSSL::HMAC.digest(digest, data, key)
end
iterations() click to toggle source

Get the iterations from the server response.

@api private

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 328
def iterations
  @iterations ||= payload_data.match(ITERATIONS)[1].to_i
end
payload_data() click to toggle source

Get the data from the returned payload.

@api private

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 337
def payload_data
  reply.documents[0][PAYLOAD].data
end
rnonce() click to toggle source

Get the server nonce from the payload.

@api private

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 346
def rnonce
  @rnonce ||= payload_data.match(RNONCE)[1]
end
salt() click to toggle source

Gets the salt from the server response.

@api private

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 355
def salt
  @salt ||= payload_data.match(SALT)[1]
end
salted_password() click to toggle source

Salted password algorithm implementation.

@api private

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

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 366
def salted_password
  @salted_password ||= hi(user.hashed_password)
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.rb, line 377
def server_key
  @server_key ||= hmac(salted_password, SERVER_KEY)
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.rb, line 388
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.rb, line 399
def stored_key(key)
  h(key)
end
validate!(reply) click to toggle source
# File lib/mongo/auth/scram/conversation.rb, line 450
def validate!(reply)
  raise Unauthorized.new(user) unless reply.documents[0][Operation::Result::OK] == 1
  @reply = reply
end
validate_final_message!(reply) click to toggle source
# File lib/mongo/auth/scram/conversation.rb, line 438
def validate_final_message!(reply)
  validate!(reply)
  unless compare_digest(verifier, server_signature)
    raise Error::InvalidSignature.new(verifier, server_signature)
  end
end
validate_first_message!(reply) click to toggle source
# File lib/mongo/auth/scram/conversation.rb, line 445
def validate_first_message!(reply)
  validate!(reply)
  raise Error::InvalidNonce.new(nonce, rnonce) unless rnonce.start_with?(nonce)
end
verifier() click to toggle source

Get the verifier token from the server response.

@api private

@since 2.0.0

# File lib/mongo/auth/scram/conversation.rb, line 408
def verifier
  @verifier ||= payload_data.match(VERIFIER)[1]
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.rb, line 419
def without_proof
  @without_proof ||= "c=biws,r=#{rnonce}"
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.rb, line 428
def xor(first, second)
  first.bytes.zip(second.bytes).map{ |(a,b)| (a ^ b).chr }.join('')
end