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
@return [ Hash ] auth The authentication details.
@return [ String ] #hashed_password The user's hashed password
@return [ String ] nonce The initial user nonce.
@return [ BSON::OrderedHash ] reply The current reply in the conversation.
Public Class Methods
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 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
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
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 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
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 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 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
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 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
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
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 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 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 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
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 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 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 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 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
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
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
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
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 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 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 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 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
# 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
# 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
# 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
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
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 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