You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2014/10/31 11:39:42 UTC

couch commit: updated refs/heads/two-factor-auth to eef532c

Repository: couchdb-couch
Updated Branches:
  refs/heads/two-factor-auth 9938fac18 -> eef532ca3 (forced update)


Implement two factor authentication

If enabled, require a second factor to acquire a session cookie and
reject basic authentication attempts (as second factor cannot be
presented). Allow previous and next token for clock skew.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/eef532ca
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/eef532ca
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/eef532ca

Branch: refs/heads/two-factor-auth
Commit: eef532ca35e2f63b63e1723769b74523cbdc1619
Parents: f02a101
Author: Robert Newson <rn...@apache.org>
Authored: Wed Oct 29 17:39:21 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Oct 31 10:39:30 2014 +0000

----------------------------------------------------------------------
 src/couch_hotp.erl        | 40 ++++++++++++++++++++++++++++++
 src/couch_httpd_auth.erl  | 54 +++++++++++++++++++++++++++++++++++++++++
 src/couch_totp.erl        | 23 ++++++++++++++++++
 test/couch_hotp_tests.erl | 28 +++++++++++++++++++++
 test/couch_totp_tests.erl | 55 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 200 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/eef532ca/src/couch_hotp.erl
----------------------------------------------------------------------
diff --git a/src/couch_hotp.erl b/src/couch_hotp.erl
new file mode 100644
index 0000000..896c52a
--- /dev/null
+++ b/src/couch_hotp.erl
@@ -0,0 +1,40 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hotp).
+
+-export([generate/4]).
+
+generate(Alg, Key, Counter, OutputLen)
+  when is_atom(Alg), is_binary(Key), is_integer(Counter), is_integer(OutputLen) ->
+    Hmac = hmac(Alg, Key, <<Counter:64>>),
+    Offset = binary:last(Hmac) band 16#f,
+    Code =
+        ((binary:at(Hmac, Offset) band 16#7f) bsl 24) +
+        ((binary:at(Hmac, Offset + 1) band 16#ff) bsl 16) +
+        ((binary:at(Hmac, Offset + 2) band 16#ff) bsl 8) +
+        ((binary:at(Hmac, Offset + 3) band 16#ff)),
+    case OutputLen of
+        6 -> Code rem 1000000;
+        7 -> Code rem 10000000;
+        8 -> Code rem 100000000
+    end.
+
+hmac(Alg, Key, Data) ->
+    case {Alg, erlang:function_exported(crypto, hmac, 3)} of
+        {_, true} ->
+            crypto:hmac(Alg, Key, Data);
+        {sha, false} ->
+            crypto:sha_mac(Key, Data);
+        {Alg, false} ->
+            throw({unsupported, Alg})
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/eef532ca/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 7c55a2b..cda51c5 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -23,6 +23,8 @@
 
 -import(couch_httpd, [header_value/2, send_json/2,send_json/4, send_method_not_allowed/2]).
 
+-compile({no_auto_import,[integer_to_binary/1]}).
+
 special_test_authentication_handler(Req) ->
     case header_value(Req, "WWW-Authenticate") of
     "X-Couch-Test-Auth " ++ NamePass ->
@@ -75,6 +77,7 @@ default_authentication_handler(Req, AuthModule) ->
             nil ->
                 throw({unauthorized, <<"Name or password is incorrect.">>});
             UserProps ->
+                reject_if_totp(UserProps),
                 UserName = ?l2b(User),
                 Password = ?l2b(Pass),
                 case authenticate(Password, UserProps) of
@@ -287,6 +290,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
     end,
     case authenticate(Password, UserProps) of
         true ->
+            verify_totp(UserProps, Form),
             UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule),
             % setup the session cookie
             Secret = ?l2b(ensure_cookie_auth_secret()),
@@ -430,3 +434,53 @@ max_age() ->
                 config:get("couch_httpd_auth", "timeout", "600")),
             [{max_age, Timeout}]
     end.
+
+reject_if_totp(User) ->
+    case get_totp_config(User) of
+        undefined ->
+            ok;
+        _ ->
+            throw({unauthorized, <<"Name or password is incorrect.">>})
+    end.
+
+verify_totp(User, Form) ->
+    case get_totp_config(User) of
+        undefined ->
+            ok;
+        {Props} ->
+            Key = couch_util:get_value(<<"key">>, Props),
+            Alg = couch_util:to_existing_atom(
+                couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)),
+            Len = couch_util:get_value(<<"length">>, Props, 6),
+            Token = ?l2b(couch_util:get_value("token", Form, "")),
+            verify_token(Alg, Key, Len, Token)
+    end.
+
+get_totp_config(User) ->
+    couch_util:get_value(<<"totp">>, User).
+
+verify_token(Alg, Key, Len, Token) ->
+    Now = make_cookie_time(),
+    Tokens = [generate_token(Alg, Key, Len, Now - 30),
+              generate_token(Alg, Key, Len, Now),
+              generate_token(Alg, Key, Len, Now + 30)],
+    %% evaluate all tokens in constant time
+    Match = lists:foldl(fun(T, Acc) -> couch_util:verify(T, Token) or Acc end,
+                        false, Tokens),
+    case Match of
+        true ->
+            ok;
+        _ ->
+            throw({unauthorized, <<"Name or password is incorrect.">>})
+    end.
+
+generate_token(Alg, Key, Len, Timestamp) ->
+    integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len)).
+
+integer_to_binary(Int) when is_integer(Int) ->
+    case erlang:function_exported(erlang, integer_to_binary, 1) of
+        true ->
+            erlang:integer_to_binary(Int);
+        false ->
+            ?l2b(integer_to_list(Int))
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/eef532ca/src/couch_totp.erl
----------------------------------------------------------------------
diff --git a/src/couch_totp.erl b/src/couch_totp.erl
new file mode 100644
index 0000000..56e70d8
--- /dev/null
+++ b/src/couch_totp.erl
@@ -0,0 +1,23 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_totp).
+
+-export([generate/5]).
+
+generate(Alg, Key, CounterSecs, StepSecs, OutputLen)
+  when is_atom(Alg),
+       is_binary(Key),
+       is_integer(CounterSecs),
+       is_integer(StepSecs),
+       is_integer(OutputLen) ->
+    couch_hotp:generate(Alg, Key, CounterSecs div StepSecs, OutputLen).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/eef532ca/test/couch_hotp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_hotp_tests.erl b/test/couch_hotp_tests.erl
new file mode 100644
index 0000000..fee10ff
--- /dev/null
+++ b/test/couch_hotp_tests.erl
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hotp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+hotp_test() ->
+    Key = <<"12345678901234567890">>,
+    ?assertEqual(755224, couch_hotp:generate(sha, Key, 0, 6)),
+    ?assertEqual(287082, couch_hotp:generate(sha, Key, 1, 6)),
+    ?assertEqual(359152, couch_hotp:generate(sha, Key, 2, 6)),
+    ?assertEqual(969429, couch_hotp:generate(sha, Key, 3, 6)),
+    ?assertEqual(338314, couch_hotp:generate(sha, Key, 4, 6)),
+    ?assertEqual(254676, couch_hotp:generate(sha, Key, 5, 6)),
+    ?assertEqual(287922, couch_hotp:generate(sha, Key, 6, 6)),
+    ?assertEqual(162583, couch_hotp:generate(sha, Key, 7, 6)),
+    ?assertEqual(399871, couch_hotp:generate(sha, Key, 8, 6)),
+    ?assertEqual(520489, couch_hotp:generate(sha, Key, 9, 6)).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/eef532ca/test/couch_totp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_totp_tests.erl b/test/couch_totp_tests.erl
new file mode 100644
index 0000000..6817a09
--- /dev/null
+++ b/test/couch_totp_tests.erl
@@ -0,0 +1,55 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_totp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+totp_sha_test() ->
+    Key = <<"12345678901234567890">>,
+    ?assertEqual(94287082, couch_totp:generate(sha, Key, 59, 30, 8)),
+    ?assertEqual(07081804, couch_totp:generate(sha, Key, 1111111109, 30, 8)),
+    ?assertEqual(14050471, couch_totp:generate(sha, Key, 1111111111, 30, 8)),
+    ?assertEqual(89005924, couch_totp:generate(sha, Key, 1234567890, 30, 8)),
+    ?assertEqual(69279037, couch_totp:generate(sha, Key, 2000000000, 30, 8)),
+    ?assertEqual(65353130, couch_totp:generate(sha, Key, 20000000000, 30, 8)).
+
+totp_sha256_test() ->
+    Key = <<"12345678901234567890123456789012">>,
+    case sha_256_512_supported() of
+        true ->
+            ?assertEqual(46119246, couch_totp:generate(sha256, Key, 59, 30, 8)),
+            ?assertEqual(68084774, couch_totp:generate(sha256, Key, 1111111109, 30, 8)),
+            ?assertEqual(67062674, couch_totp:generate(sha256, Key, 1111111111, 30, 8)),
+            ?assertEqual(91819424, couch_totp:generate(sha256, Key, 1234567890, 30, 8)),
+            ?assertEqual(90698825, couch_totp:generate(sha256, Key, 2000000000, 30, 8)),
+            ?assertEqual(77737706, couch_totp:generate(sha256, Key, 20000000000, 30, 8));
+        false ->
+            ?debugMsg("sha256 not supported, tests skipped")
+    end.
+
+totp_sha512_test() ->
+    Key = <<"1234567890123456789012345678901234567890123456789012345678901234">>,
+    case sha_256_512_supported() of
+        true ->
+            ?assertEqual(90693936, couch_totp:generate(sha512, Key, 59, 30, 8)),
+            ?assertEqual(25091201, couch_totp:generate(sha512, Key, 1111111109, 30, 8)),
+            ?assertEqual(99943326, couch_totp:generate(sha512, Key, 1111111111, 30, 8)),
+            ?assertEqual(93441116, couch_totp:generate(sha512, Key, 1234567890, 30, 8)),
+            ?assertEqual(38618901, couch_totp:generate(sha512, Key, 2000000000, 30, 8)),
+            ?assertEqual(47863826, couch_totp:generate(sha512, Key, 20000000000, 30, 8));
+        false ->
+            ?debugMsg("sha512 not supported, tests skipped")
+    end.
+
+sha_256_512_supported() ->
+    erlang:function_exported(crypto, hmac, 3).