TOTP secrets are stored in Base32
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/bfbf8ebe Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/bfbf8ebe Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/bfbf8ebe Branch: refs/heads/2491-refactor-couch-httpd-auth Commit: bfbf8ebed19ba511fc50f916f3fd41cea84e815f Parents: 6125862 Author: Robert Newson <[email protected]> Authored: Sun Nov 2 17:15:49 2014 +0000 Committer: Robert Newson <[email protected]> Committed: Sun Nov 2 18:09:18 2014 +0000 ---------------------------------------------------------------------- src/couch_base32.erl | 127 +++++++++++++++++++++++++++++++++++++++ src/couch_httpd_auth.erl | 2 +- test/couch_base32_tests.erl | 28 +++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/src/couch_base32.erl ---------------------------------------------------------------------- diff --git a/src/couch_base32.erl b/src/couch_base32.erl new file mode 100644 index 0000000..d8d754f --- /dev/null +++ b/src/couch_base32.erl @@ -0,0 +1,127 @@ +% 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_base32). + +-export([encode/1, decode/1]). + +-define(SET, <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567">>). + + +-spec encode(binary()) -> binary(). +encode(Plain) when is_binary(Plain) -> + IoList = encode(Plain, 0, byte_size(Plain) * 8, []), + iolist_to_binary(lists:reverse(IoList)). + +encode(_Plain, _ByteOffset, 0, Acc) -> + Acc; + +encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 8 -> + <<A:5, B:3>> = binary:part(Plain, ByteOffset, 1), + [<<(binary:at(?SET, A)), + (binary:at(?SET, B bsl 2)), + "======">> | Acc]; + +encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 16 -> + <<A:5, B:5, C:5, D:1>> = binary:part(Plain, ByteOffset, 2), + [<<(binary:at(?SET, A)), + (binary:at(?SET, B)), + (binary:at(?SET, C)), + (binary:at(?SET, D bsl 4)), + "====">> | Acc]; + +encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 24 -> + <<A:5, B:5, C:5, D:5, E:4>> = binary:part(Plain, ByteOffset, 3), + [<<(binary:at(?SET, A)), + (binary:at(?SET, B)), + (binary:at(?SET, C)), + (binary:at(?SET, D)), + (binary:at(?SET, E bsl 1)), + "===">> | Acc]; + +encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 32 -> + <<A:5, B:5, C:5, D:5, E:5, F:5, G:2>> = binary:part(Plain, ByteOffset, 4), + [<<(binary:at(?SET, A)), + (binary:at(?SET, B)), + (binary:at(?SET, C)), + (binary:at(?SET, D)), + (binary:at(?SET, E)), + (binary:at(?SET, F)), + (binary:at(?SET, G bsl 3)), + "=">> | Acc]; + +encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining >= 40 -> + <<A:5, B:5, C:5, D:5, E:5, F:5, G:5, H:5>> = + binary:part(Plain, ByteOffset, 5), + Output = <<(binary:at(?SET, A)), + (binary:at(?SET, B)), + (binary:at(?SET, C)), + (binary:at(?SET, D)), + (binary:at(?SET, E)), + (binary:at(?SET, F)), + (binary:at(?SET, G)), + (binary:at(?SET, H))>>, + encode(Plain, ByteOffset + 5, BitsRemaining - 40, [Output | Acc]). + + +-spec decode(binary()) -> binary(). +decode(Encoded) when is_binary(Encoded) -> + IoList = decode(Encoded, 0, []), + iolist_to_binary(lists:reverse(IoList)). + +decode(Encoded, ByteOffset, Acc) when ByteOffset == byte_size(Encoded) -> + Acc; +decode(Encoded, ByteOffset, Acc) -> + case binary:part(Encoded, ByteOffset, 8) of + <<A:1/binary, B:1/binary, "======">> -> + [<<(find_in_set(A)):5, + (find_in_set(B) bsr 2):3>> | Acc]; + <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, "====">> -> + [<<(find_in_set(A)):5, + (find_in_set(B)):5, + (find_in_set(C)):5, + (find_in_set(D) bsr 4):1>> | Acc]; + <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, E:1/binary, "===">> -> + [<<(find_in_set(A)):5, + (find_in_set(B)):5, + (find_in_set(C)):5, + (find_in_set(D)):5, + (find_in_set(E) bsr 1):4>> | Acc]; + <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, + E:1/binary, F:1/binary, G:1/binary, "=">> -> + [<<(find_in_set(A)):5, + (find_in_set(B)):5, + (find_in_set(C)):5, + (find_in_set(D)):5, + (find_in_set(E)):5, + (find_in_set(F)):5, + (find_in_set(G) bsr 3):2>> | Acc]; + <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, + E:1/binary, F:1/binary, G:1/binary, H:1/binary>> -> + decode(Encoded, ByteOffset + 8, + [<<(find_in_set(A)):5, + (find_in_set(B)):5, + (find_in_set(C)):5, + (find_in_set(D)):5, + (find_in_set(E)):5, + (find_in_set(F)):5, + (find_in_set(G)):5, + (find_in_set(H)):5>> | Acc]) + end. + +find_in_set(Char) -> + case binary:match(?SET, Char) of + nomatch -> + erlang:error(not_base32); + {Offset, _} -> + Offset + end. http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/src/couch_httpd_auth.erl ---------------------------------------------------------------------- diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl index cda51c5..5e528d9 100644 --- a/src/couch_httpd_auth.erl +++ b/src/couch_httpd_auth.erl @@ -448,7 +448,7 @@ verify_totp(User, Form) -> undefined -> ok; {Props} -> - Key = couch_util:get_value(<<"key">>, Props), + Key = couch_base32:decode(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), http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/test/couch_base32_tests.erl ---------------------------------------------------------------------- diff --git a/test/couch_base32_tests.erl b/test/couch_base32_tests.erl new file mode 100644 index 0000000..7e4d59a --- /dev/null +++ b/test/couch_base32_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_base32_tests). + +-include_lib("eunit/include/eunit.hrl"). + +base32_test() -> + roundtrip(<<"">>, <<"">>), + roundtrip(<<"f">>, <<"MY======">>), + roundtrip(<<"fo">>, <<"MZXQ====">>), + roundtrip(<<"foo">>, <<"MZXW6===">>), + roundtrip(<<"foob">>, <<"MZXW6YQ=">>), + roundtrip(<<"fooba">>, <<"MZXW6YTB">>), + roundtrip(<<"foobar">>, <<"MZXW6YTBOI======">>). + +roundtrip(Plain, Encoded) -> + ?assertEqual(Plain, couch_base32:decode(Encoded)), + ?assertEqual(Encoded, couch_base32:encode(Plain)).
