Просмотр исходного кода

Add sasl_prep implementation for validating passwords according to sasl specification

Enid Gjoleka 5 лет назад
Родитель
Сommit
c28566c73f
2 измененных файлов с 146 добавлено и 14 удалено
  1. 145 0
      src/epgsql_sasl_prep_profile.erl
  2. 1 14
      src/epgsql_scram.erl

+ 145 - 0
src/epgsql_sasl_prep_profile.erl

@@ -0,0 +1,145 @@
+%%% coding: utf-8
+%%% @doc
+%%% sal_prep profile helper module that will validate a utf-8
+%%% string based on sasl_prep profile as defined in
+%%% https://tools.ietf.org/html/rfc4013
+%%% @end
+
+-module(epgsql_sasl_prep_profile).
+
+-export([validate/1]).
+
+validate(Str) ->
+    lists:foreach(fun(F) ->
+                      lists:any(F, unicode:characters_to_list(Str, utf8))
+                          andalso error({non_valid_scram_password, Str})
+                  end, [ fun is_non_asci_space_character/1
+                       , fun is_ascii_control_character/1
+                       , fun is_non_ascii_control_character/1
+                       , fun is_private_use_characters/1
+                       , fun is_non_character_code_points/1
+                       , fun is_surrogate_code_points/1
+                       , fun is_inappropriate_for_plain_text_char/1
+                       , fun is_inappropriate_for_canonical_representation_char/1
+                       , fun is_change_display_properties_or_deprecated_char/1
+                       , fun is_tagging_char/1 ]),
+    Str.
+
+%% @doc Return true if the given character is a non-ASCII space character
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.1.2
+is_non_asci_space_character(C) ->
+    C == 16#00A0
+        orelse C == 16#1680
+        orelse (16#2000 =< C andalso C =< 16#200B)
+        orelse C == 16#202F
+        orelse C == 16#205F
+        orelse C == 16#3000.
+
+%% @doc Return true if the given character is an ASCII control character
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.2.1
+is_ascii_control_character(C) ->
+    C =< 16#001F orelse C == 16#007F.
+
+%% @doc Return true if the given character is a non-ASCII control character
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.2.2
+is_non_ascii_control_character(C) ->
+    (16#0080 =< C andalso C =< 16#009F)
+        orelse C == 16#06DD
+        orelse C == 16#070F
+        orelse C == 16#180E
+        orelse C == 16#200C
+        orelse C == 16#200D
+        orelse C == 16#2028
+        orelse C == 16#2029
+        orelse C == 16#2060
+        orelse C == 16#2061
+        orelse C == 16#2062
+        orelse C == 16#2063
+        orelse (16#206A =< C andalso C =< 16#206F)
+        orelse C == 16#FEFF
+        orelse (16#FFF9 =< C andalso C =< 16#FFFC)
+        orelse (16#1D173 =< C andalso C =< 16#1D17A).
+
+%% @doc Return true if the given character is a private use character
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.3
+is_private_use_characters(C) ->
+    (16#E000 =< C andalso C =< 16#F8FF)
+         orelse (16#F000 =< C andalso C =< 16#FFFFD)
+        orelse (16#100000 =< C andalso C =< 16#10FFFD).
+
+%% @doc Return true if the given character is a non-character code point
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.4
+is_non_character_code_points(C) ->
+    (16#FDD0 =< C andalso C =< 16#FDEF)
+        orelse (16#FFFE =< C andalso C =< 16#FFFF)
+        orelse (16#1FFFE =< C andalso C =< 16#1FFFF)
+        orelse (16#2FFFE =< C andalso C =< 16#2FFFF)
+        orelse (16#3FFFE =< C andalso C =< 16#3FFFF)
+        orelse (16#4FFFE =< C andalso C =< 16#4FFFF)
+        orelse (16#5FFFE =< C andalso C =< 16#5FFFF)
+        orelse (16#6FFFE =< C andalso C =< 16#6FFFF)
+        orelse (16#7FFFE =< C andalso C =< 16#7FFFF)
+        orelse (16#8FFFE =< C andalso C =< 16#8FFFF)
+        orelse (16#9FFFE =< C andalso C =< 16#9FFFF)
+        orelse (16#AFFFE =< C andalso C =< 16#AFFFF)
+        orelse (16#BFFFE =< C andalso C =< 16#BFFFF)
+        orelse (16#CFFFE =< C andalso C =< 16#CFFFF)
+        orelse (16#DFFFE =< C andalso C =< 16#DFFFF)
+        orelse (16#EFFFE =< C andalso C =< 16#EFFFF)
+        orelse (16#FFFFE =< C andalso C =< 16#FFFFF)
+        orelse (16#10FFFE =< C andalso C =< 16#10FFFF).
+
+%% @doc Return true if the given character is a surrogate code point as defined by
+%% https://tools.ietf.org/html/rfc3454#appendix-C.5
+is_surrogate_code_points(C) ->
+    16#D800 =< C andalso C =< 16#DFFF.
+
+%% @doc Return true if the given character is inappropriate for plain text characters
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.6
+is_inappropriate_for_plain_text_char(C) ->
+    C == 16#FFF9
+        orelse C == 16#FFFA
+        orelse C == 16#FFFB
+        orelse C == 16#FFFC
+        orelse C == 16#FFFD.
+
+%% @doc Return true if the given character is inappropriate for canonical representation
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.7
+is_inappropriate_for_canonical_representation_char(C) ->
+    16#2FF0 =< C andalso C =< 16#2FFB.
+
+%% @doc Return true if the given character is change display properties or deprecated
+%% characters as defined by https://tools.ietf.org/html/rfc3454#appendix-C.8
+is_change_display_properties_or_deprecated_char(C) ->
+    C == 16#0340
+        orelse C == 16#0341
+        orelse C == 16#200E
+        orelse C == 16#200F
+        orelse C == 16#202A
+        orelse C == 16#202B
+        orelse C == 16#202C
+        orelse C == 16#202D
+        orelse C == 16#202E
+        orelse C == 16#206A
+        orelse C == 16#206B
+        orelse C == 16#206C
+        orelse C == 16#206D
+        orelse C == 16#206E
+        orelse C == 16#206F.
+
+%% @doc Return true if the given character is a tagging character as defined by
+%% https://tools.ietf.org/html/rfc3454#appendix-C.9
+is_tagging_char(C) ->
+    C == 16#E0001 orelse
+        (16#E0020 =< C andalso C =< 16#E007F).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+normalize_test() ->
+    ?assertEqual(<<"123 !~">>, validate(<<"123 !~">>)),
+    ?assertEqual(<<"привет"/utf8>>, validate(<<"привет"/utf8>>)),
+    ?assertEqual(<<"Χαίρετε"/utf8>>, validate(<<"Χαίρετε"/utf8>>)),
+    ?assertEqual(<<"你好"/utf8>>, validate(<<"你好"/utf8>>)).
+
+-endif.

+ 1 - 14
src/epgsql_scram.erl

@@ -76,7 +76,7 @@ get_client_final(SrvFirst, ClientNonce, UserName, Password) ->
     Salt = proplists:get_value(salt, SrvFirst),
     I = proplists:get_value(i, SrvFirst),
 
-    SaltedPassword = hi(normalize(Password), Salt, I),
+    SaltedPassword = hi(epgsql_sasl_prep_profile:validate(Password), Salt, I),
     ClientKey = hmac(SaltedPassword, "Client Key"),
     StoredKey = h(ClientKey),
     ClientFirstBare = client_first_bare(UserName, ClientNonce),
@@ -100,15 +100,6 @@ parse_server_final(<<"e=", ServerError/binary>>) ->
 
 %% Helpers
 
-%% TODO: implement according to rfc3454
-normalize(Str) ->
-    lists:all(fun is_ascii_non_control/1, unicode:characters_to_list(Str, utf8))
-        orelse error({scram_non_ascii_password, Str}),
-    Str.
-
-is_ascii_non_control(C) when C > 16#1F, C < 16#7F -> true;
-is_ascii_non_control(_) -> false.
-
 check_nonce(ClientNonce, ServerNonce) ->
     Size = size(ClientNonce),
     <<ClientNonce:Size/binary, _/binary>> = ServerNonce,
@@ -159,8 +150,4 @@ exchange_test() ->
     ?assertEqual(ClientFinal, iolist_to_binary(CF)),
     ?assertEqual({ok, ServerProof}, parse_server_final(ServerFinal)).
 
-normalize_test() ->
-    ?assertEqual(<<"123 !~">>, normalize(<<"123 !~">>)),
-    ?assertError({scram_non_ascii_password, _}, normalize(<<"привет"/utf8>>)).
-
 -endif.