Skip to content

Commit abeb8b1

Browse files
committed
fixes #5
1 parent 016fc16 commit abeb8b1

File tree

7 files changed

+92
-76
lines changed

7 files changed

+92
-76
lines changed

lib/mongo/auth.ex

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
defmodule Mongo.Auth do
22
@moduledoc false
33

4-
def run(opts, s) do
5-
auth = setup(opts)
6-
auther = mechanism(s)
7-
8-
auth_source = opts[:auth_source]
9-
wire_version = s[:wire_version]
10-
11-
s = if auth_source != nil && wire_version > 0,
12-
do: Map.put(s, :database, auth_source),
13-
else: s
14-
15-
Enum.find_value(auth, fn opts ->
16-
case auther.auth(opts, s) do
17-
:ok ->
18-
nil
4+
def run(opts, state) do
5+
6+
db = opts[:database]
7+
auth = setup(opts)
8+
auther = mechanism(state)
9+
auth_source = opts[:auth_source]
10+
wire_version = state[:wire_version]
11+
12+
# change database for auth
13+
state = case auth_source != nil && wire_version > 0 do
14+
true -> Map.put(state, :database, auth_source)
15+
false -> state
16+
end
17+
18+
# do auth
19+
Enum.find_value(auth, fn credentials ->
20+
case auther.auth(credentials, db, state) do
21+
:ok -> nil # everything is okay, then return nil
1922
error ->
20-
{mod, socket} = s.connection
23+
{mod, socket} = state.connection
2124
mod.close(socket)
2225
error
2326
end
24-
end) || {:ok, Map.put(s,:database, opts[:database])}
27+
end) || {:ok, Map.put(state, :database, opts[:database])} # restore old database
2528
end
2629

2730
defp setup(opts) do
@@ -39,10 +42,7 @@ defmodule Mongo.Auth do
3942
if username && password, do: auth ++ [{username, password}], else: auth
4043
end
4144

42-
defp mechanism(%{wire_version: version, auth_mechanism: :x509}) when version >= 3,
43-
do: Mongo.Auth.X509
44-
defp mechanism(%{wire_version: version}) when version >= 3,
45-
do: Mongo.Auth.SCRAM
46-
defp mechanism(_),
47-
do: Mongo.Auth.CR
45+
defp mechanism(%{wire_version: version, auth_mechanism: :x509}) when version >= 3, do: Mongo.Auth.X509
46+
defp mechanism(%{wire_version: version}) when version >= 3, do: Mongo.Auth.SCRAM
47+
defp mechanism(_), do: Mongo.Auth.CR
4848
end

lib/mongo/auth/cr.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Mongo.Auth.CR do
22
@moduledoc false
33
alias Mongo.MongoDBConnection.Utils
44

5-
def auth({username, password}, s) do
5+
def auth({username, password}, _db, s) do
66
with {:ok, message} <- Utils.command(-2, [getnonce: 1], s),
77
do: nonce(message, username, password, s)
88
end

lib/mongo/auth/scram.ex

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@ defmodule Mongo.Auth.SCRAM do
55

66
alias Mongo.MongoDBConnection.Utils
77

8-
def auth({username, password}, s) do
9-
# TODO: Wrap and log error
8+
def auth({username, password}, db, s) do
109

11-
nonce = nonce()
12-
first_bare = first_bare(username, nonce)
13-
payload = first_message(first_bare)
14-
message = [saslStart: 1, mechanism: "SCRAM-SHA-1", payload: payload]
10+
{mechanism, digest} = select_digest(db, username, s)
11+
nonce = nonce()
12+
first_bare = first_bare(username, nonce)
13+
payload = first_message(first_bare)
14+
message = [saslStart: 1, mechanism: mechanism, payload: payload]
1515

1616
result =
17-
with {:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-2, message, s),
18-
{message, signature} = first(reply, first_bare, username, password, nonce),
19-
{:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-3, message, s),
20-
message = second(reply, signature),
17+
with {:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-3, message, s),
18+
{message, signature} = first(reply, first_bare, username, password, nonce, digest),
2119
{:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-4, message, s),
20+
message = second(reply, signature),
21+
{:ok, %{"ok" => ok} = reply} when ok == 1 <- Utils.command(-5, message, s),
2222
do: final(reply)
2323

2424
case result do
@@ -31,21 +31,21 @@ defmodule Mongo.Auth.SCRAM do
3131
end
3232
end
3333

34-
defp first(%{"conversationId" => 1, "payload" => server_payload, "done" => false},
35-
first_bare, username, password, client_nonce) do
34+
defp first(%{"conversationId" => 1, "payload" => server_payload, "done" => false}, first_bare, username, password, client_nonce, digest) do
35+
3636
params = parse_payload(server_payload)
3737
server_nonce = params["r"]
3838
salt = params["s"] |> Base.decode64!
3939
iter = params["i"] |> String.to_integer
40-
pass = Utils.digest_password(username, password)
41-
salted_password = hi(pass, salt, iter)
40+
pass = Utils.digest_password(username, password, digest)
41+
salted_password = hi(pass, salt, iter, digest)
4242

4343
<<^client_nonce::binary(24), _::binary>> = server_nonce
4444

4545
client_message = "c=biws,r=#{server_nonce}"
4646
auth_message = "#{first_bare},#{server_payload.binary},#{client_message}"
47-
server_signature = generate_signature(salted_password, auth_message)
48-
proof = generate_proof(salted_password, auth_message)
47+
server_signature = generate_signature(salted_password, auth_message, digest)
48+
proof = generate_proof(salted_password, auth_message, digest)
4949
client_final_message = %BSON.Binary{binary: "#{client_message},#{proof}"}
5050
message = [saslContinue: 1, conversationId: 1, payload: client_final_message]
5151

@@ -70,32 +70,28 @@ defmodule Mongo.Auth.SCRAM do
7070
"n=#{encode_username(username)},r=#{nonce}"
7171
end
7272

73-
defp hi(password, salt, iterations) do
74-
Mongo.PBKDF2Cache.pbkdf2(password, salt, iterations)
73+
defp hi(password, salt, iterations, digest) do
74+
Mongo.PBKDF2Cache.pbkdf2(password, salt, iterations, digest)
7575
end
7676

77-
defp generate_proof(salted_password, auth_message) do
78-
client_key = :crypto.hmac(:sha, salted_password, "Client Key")
79-
stored_key = :crypto.hash(:sha, client_key)
80-
signature = :crypto.hmac(:sha, stored_key, auth_message)
77+
defp generate_proof(salted_password, auth_message, digest) do
78+
client_key = :crypto.hmac(digest, salted_password, "Client Key")
79+
stored_key = :crypto.hash(digest, client_key)
80+
signature = :crypto.hmac(digest, stored_key, auth_message)
8181
client_proof = xor_keys(client_key, signature, "")
8282
"p=#{Base.encode64(client_proof)}"
8383
end
8484

85-
defp generate_signature(salted_password, auth_message) do
86-
server_key = :crypto.hmac(:sha, salted_password, "Server Key")
87-
:crypto.hmac(:sha, server_key, auth_message)
85+
defp generate_signature(salted_password, auth_message, digest) do
86+
server_key = :crypto.hmac(digest, salted_password, "Server Key")
87+
:crypto.hmac(digest, server_key, auth_message)
8888
end
8989

90-
defp xor_keys("", "", result),
91-
do: result
92-
defp xor_keys(<<fa, ra::binary>>, <<fb, rb::binary>>, result),
93-
do: xor_keys(ra, rb, <<result::binary, fa ^^^ fb>>)
94-
90+
defp xor_keys("", "", result), do: result
91+
defp xor_keys(<<fa, ra::binary>>, <<fb, rb::binary>>, result), do: xor_keys(ra, rb, <<result::binary, fa ^^^ fb>>)
9592

9693
defp nonce do
97-
:crypto.strong_rand_bytes(18)
98-
|> Base.encode64
94+
:crypto.strong_rand_bytes(18) |> Base.encode64
9995
end
10096

10197
defp encode_username(username) do
@@ -109,4 +105,22 @@ defmodule Mongo.Auth.SCRAM do
109105
|> String.split(",")
110106
|> Enum.into(%{}, &List.to_tuple(String.split(&1, "=", parts: 2)))
111107
end
108+
109+
##
110+
# selects the supported sasl mechanism
111+
# It calls isMaster with saslSupportedMechs option to ask for the selected user which mechanism is supported
112+
#
113+
defp select_digest(database, username, state) do
114+
with {:ok, reply} <- Utils.command(-2, [isMaster: 1, saslSupportedMechs: database <> "." <> username], state ) do
115+
select_digest(reply)
116+
end
117+
end
118+
defp select_digest(%{"saslSupportedMechs" => mechs}) do
119+
case Enum.member?(mechs, "SCRAM-SHA-256") do
120+
true -> {"SCRAM-SHA-256", :sha256}
121+
false -> {"SCRAM-SHA-1", :sha}
122+
end
123+
end
124+
defp select_digest(_), do: {"SCRAM-SHA-1", :sha}
125+
112126
end

lib/mongo/auth/x509.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Mongo.Auth.X509 do
22
@moduledoc false
33
alias Mongo.MongoDBConnection.Utils
44

5-
def auth({username, _password}, s) do
5+
def auth({username, _password}, _db, s) do
66
cmd = [authenticate: 1, user: username, mechanism: "MONGODB-X509"]
77
with {:ok, _message} <- Utils.command(-2, cmd, s) do
88
:ok

lib/mongo/pbkdf2.ex

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ defmodule Mongo.PBKDF2 do
2525
"""
2626
def generate(secret, salt, opts \\ []) do
2727
iterations = Keyword.get(opts, :iterations, 1000)
28-
length = Keyword.get(opts, :length, 32)
29-
digest = Keyword.get(opts, :digest, :sha256)
28+
length = Keyword.get(opts, :length, 32)
29+
digest = Keyword.get(opts, :digest, :sha256)
3030

3131
if length > @max_length do
3232
raise ArgumentError, "length must be less than or equal to #{@max_length}"
@@ -35,8 +35,7 @@ defmodule Mongo.PBKDF2 do
3535
end
3636
end
3737

38-
defp generate(_fun, _salt, _iterations, max_length, _block_index, acc, length)
39-
when length >= max_length do
38+
defp generate(_fun, _salt, _iterations, max_length, _block_index, acc, length) when length >= max_length do
4039
key = acc |> Enum.reverse |> IO.iodata_to_binary
4140
<<bin::binary-size(max_length), _::binary>> = key
4241
bin
@@ -45,8 +44,7 @@ defmodule Mongo.PBKDF2 do
4544
defp generate(fun, salt, iterations, max_length, block_index, acc, length) do
4645
initial = fun.(<<salt::binary, block_index::integer-size(32)>>)
4746
block = iterate(fun, iterations - 1, initial, initial)
48-
generate(fun, salt, iterations, max_length, block_index + 1,
49-
[block | acc], byte_size(block) + length)
47+
generate(fun, salt, iterations, max_length, block_index + 1, [block | acc], byte_size(block) + length)
5048
end
5149

5250
defp iterate(_fun, 0, _prev, acc), do: acc

lib/mongo/pbkdf2_cache.ex

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ defmodule Mongo.PBKDF2Cache do
77
GenServer.start_link(__MODULE__, [], name: @name)
88
end
99

10-
def pbkdf2(password, salt, iterations) do
11-
GenServer.call(@name, {password, salt, iterations})
10+
def pbkdf2(password, salt, iterations, digest) do
11+
GenServer.call(@name, {password, salt, iterations, digest})
1212
end
1313

1414
def init([]) do
@@ -17,10 +17,8 @@ defmodule Mongo.PBKDF2Cache do
1717

1818
def handle_call(key, from, s) do
1919
cond do
20-
salted_password = s.cache[key] ->
21-
{:reply, salted_password, s}
22-
list = s.pending[key] ->
23-
{:noreply, put_in(s.pending[key], [from|list])}
20+
salted_password = s.cache[key] -> {:reply, salted_password, s}
21+
list = s.pending[key] -> {:noreply, put_in(s.pending[key], [from|list])}
2422
true ->
2523
_ = run_task(key)
2624
{:noreply, put_in(s.pending[key], [from])}
@@ -41,12 +39,15 @@ defmodule Mongo.PBKDF2Cache do
4139
{:noreply, s}
4240
end
4341

44-
defp run_task({password, salt, iterations} = key) do
42+
defp run_task({password, salt, iterations, :sha256} = key) do
4543
Task.async(fn ->
46-
result = Mongo.PBKDF2.generate(password, salt,
47-
iterations: iterations,
48-
length: 20,
49-
digest: :sha)
44+
result = Mongo.PBKDF2.generate(password, salt, iterations: iterations, length: 32, digest: :sha256)
45+
{key, result}
46+
end)
47+
end
48+
defp run_task({password, salt, iterations, :sha} = key) do
49+
Task.async(fn ->
50+
result = Mongo.PBKDF2.generate(password, salt, iterations: iterations, length: 20, digest: :sha)
5051
{key, result}
5152
end)
5253
end

lib/mongo_db_connection/utils.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,15 @@ defmodule Mongo.MongoDBConnection.Utils do
9090
def namespace(coll, _, database), do: [database, ?. | coll]
9191

9292
def digest(nonce, username, password) do
93-
:crypto.hash(:md5, [nonce, username, digest_password(username, password)])
93+
:crypto.hash(:md5, [nonce, username, digest_password(username, password, :sha)])
9494
|> Base.encode16(case: :lower)
9595
end
9696

97-
def digest_password(username, password) do
97+
def digest_password(username, password, :sha) do
9898
:crypto.hash(:md5, [username, ":mongo:", password])
9999
|> Base.encode16(case: :lower)
100100
end
101+
def digest_password(username, password, :sha256) do
102+
password
103+
end
101104
end

0 commit comments

Comments
 (0)