class Mongo::Authentication::SCRAM

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

@since 1.12.0

Constants

CLIENT_KEY

The client key string.

@since 1.12.0

DIGEST

The digest to use for encryption.

@since 1.12.0

DONE

The key for the done field in the responses.

@since 1.12.0

ID

The conversation id field.

@since 1.12.0

ITERATIONS

The iterations key in the responses.

@since 1.12.0

PAYLOAD

The payload field.

@since 1.12.0

RNONCE

The rnonce key in the responses.

@since 1.12.0

SALT

The salt key in the responses.

@since 1.12.0

SERVER_KEY

The server key string.

@since 1.12.0

VERIFIER

The server signature verifier in the response.

@since 1.12.0

Attributes

auth[R]

@return [ Hash ] auth The authentication details.

hashed_password[R]

@return [ String ] #hashed_password The user's hashed password

nonce[R]

@return [ String ] nonce The initial user nonce.

reply[R]

@return [ BSON::OrderedHash ] reply The current reply in the conversation.

Public Class Methods

new(auth, hashed_password, opts={}) click to toggle source

Create the new conversation.

@example Create the new conversation.

Conversation.new(auth, password)

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 215
def initialize(auth, hashed_password, opts={})
  @auth = auth
  @hashed_password = hashed_password
  @nonce = SecureRandom.base64
  @copy_db = opts[:copy_db] if opts[:copy_db]
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 [ BSON::OrderedHash ] reply The reply of the previous

message.

@return [ BSON::OrderedHash ] The next message to send.

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 104
def continue(reply)
  validate_first_message!(reply)
  command = BSON::OrderedHash.new
  command['saslContinue'] = 1
  command[PAYLOAD] = client_final_message
  command[ID] = id
  command
end
copy_db_continue(reply) click to toggle source

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

@example Continue the conversation when copying a database.

conversation.copy_db_continue(reply)

@param [ BSON::OrderedHash ] reply The reply of the previous

message.

@return [ BSON::OrderedHash ] The next message to send.

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 126
def copy_db_continue(reply)
  validate_first_message!(reply)
  command = BSON::OrderedHash.new
  command['copydb'] = 1
  command['fromhost'] = @copy_db[:from_host]
  command['fromdb'] = @copy_db[:from_db]
  command['todb'] = @copy_db[:to_db]
  command[PAYLOAD] = client_final_message
  command[ID] = id
  command
end
copy_db_start() click to toggle source

Start the SCRAM conversation for copying a database. This returns the first message that needs to be sent to the server.

@example Start the copydb conversation.

conversation.copy_db_start

@return [ BSON::OrderedHash ] The first SCRAM copy_db conversation message.

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 186
def copy_db_start
  command = BSON::OrderedHash.new
  command['copydbsaslstart'] = 1
  command['autoAuthorize'] = 1
  command['fromhost'] = @copy_db[:from_host]
  command['fromdb'] = @copy_db[:from_db]
  command[PAYLOAD] = client_first_message
  command['mechanism'] = 'SCRAM-SHA-1'
  command
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 [ BSON::OrderedHash ] reply The reply of the previous

message.

@return [ BSON::OrderedHash ] The next message to send.

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 150
def finalize(reply)
  validate_final_message!(reply)
  command = BSON::OrderedHash.new
  command['saslContinue'] = 1
  command[PAYLOAD] = client_empty_message
  command[ID] = id
  command
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 1.12.0

# File lib/mongo/functional/scram.rb, line 205
def id
  reply[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 [ BSON::OrderedHash ] The first SCRAM conversation message.

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 168
def start
  command = BSON::OrderedHash.new
  command['saslStart'] = 1
  command['autoAuthorize'] = 1
  command[PAYLOAD] = client_first_message
  command['mechanism'] = 'SCRAM-SHA-1'
  command
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 1.12.0

# File lib/mongo/functional/scram.rb, line 231
def auth_message
  @auth_message ||= "#{first_bare},#{payload_data},#{without_proof}"
end
client_empty_message() click to toggle source

Get the empty client message.

@api private

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 240
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 1.12.0

# File lib/mongo/functional/scram.rb, line 273
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 1.12.0

# File lib/mongo/functional/scram.rb, line 251
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 1.12.0

# File lib/mongo/functional/scram.rb, line 262
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 1.12.0

# File lib/mongo/functional/scram.rb, line 284
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 1.12.0

# File lib/mongo/functional/scram.rb, line 297
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 1.12.0

# File lib/mongo/functional/scram.rb, line 321
def client_signature(key, message)
  @client_signature ||= hmac(key, message)
end
decoded_salt() click to toggle source

Get the base 64 decoded salt.

@api private

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 332
def decoded_salt
  @decoded_salt ||= Base64.strict_decode64(salt)
end
first_bare() click to toggle source

First bare implementation.

@api private

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

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 354
def first_bare
  @first_bare ||= "n=#{auth[:username].gsub('=','=3D').gsub(',','=2C')},r=#{nonce}"
end
h(string) click to toggle source

H algorithm implementation.

@api private

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

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 365
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 1.12.0

# File lib/mongo/functional/scram.rb, line 378
def hi(data)
  OpenSSL::PKCS5.pbkdf2_hmac_sha1(data, decoded_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 1.12.0

# File lib/mongo/functional/scram.rb, line 408
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 1.12.0

# File lib/mongo/functional/scram.rb, line 417
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 1.12.0

# File lib/mongo/functional/scram.rb, line 426
def payload_data
  reply[PAYLOAD].to_s
end
rnonce() click to toggle source

Get the server nonce from the payload.

@api private

@since 1.12.0

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

Gets the salt from the server response.

@api private

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 444
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 1.12.0

# File lib/mongo/functional/scram.rb, line 455
def salted_password
  @salted_password ||= hi(hashed_password)
end
server_key() click to toggle source

Server key algorithm implementation.

@api private

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

@since 1.12.0

# File lib/mongo/functional/scram.rb, line 466
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 1.12.0

# File lib/mongo/functional/scram.rb, line 479
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 1.12.0

# File lib/mongo/functional/scram.rb, line 503
def stored_key(key)
  h(key)
end
validate!(reply) click to toggle source
# File lib/mongo/functional/scram.rb, line 548
def validate!(reply)
  unless Support.ok?(reply)
    raise AuthenticationError, "Could not authorize user #{auth[:username]} on database #{auth[:db_name]}."
  end
  @reply = reply
end
validate_final_message!(reply) click to toggle source
# File lib/mongo/functional/scram.rb, line 536
def validate_final_message!(reply)
  validate!(reply)
  unless verifier == server_signature
    raise InvalidSignature.new(verifier, server_signature)
  end
end
validate_first_message!(reply) click to toggle source
# File lib/mongo/functional/scram.rb, line 543
def validate_first_message!(reply)
  validate!(reply)
  raise 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 1.12.0

# File lib/mongo/functional/scram.rb, line 512
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 1.12.0

# File lib/mongo/functional/scram.rb, line 523
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 1.12.0

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