_site/cover/pgmp_scram.COVER.html

1 %% Copyright (c) 2022 Peter Morgan <peter.james.morgan@gmail.com>
2 %%
3 %% Licensed under the Apache License, Version 2.0 (the "License");
4 %% you may not use this file except in compliance with the License.
5 %% You may obtain a copy of the License at
6 %%
7 %% http://www.apache.org/licenses/LICENSE-2.0
8 %%
9 %% Unless required by applicable law or agreed to in writing, software
10 %% distributed under the License is distributed on an "AS IS" BASIS,
11 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 %% See the License for the specific language governing permissions and
13 %% limitations under the License.
14
15
16 -module(pgmp_scram).
17
18
19 %%
20 %% https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism
21 %%
22
23
24 -export([auth_message/3]).
25 -export([client_final/3]).
26 -export([client_final_without_proof/2]).
27 -export([client_first_bare/2]).
28 -export([client_key/2]).
29 -export([client_proof/2]).
30 -export([client_signature/3]).
31 -export([decode/1]).
32 -export([normalize/1]).
33 -export([output_length/1]).
34 -export([salted_password/4]).
35 -export([server_key/2]).
36 -export([server_signature/3]).
37 -export([stored_key/2]).
38
39
40 %% SaltedPassword := Hi(Normalize(password), salt, i)
41 salted_password(Mechanism, Password, Salt, Iterations) ->
42 68 hi(Mechanism, normalize(Password), Salt, Iterations).
43
44
45 %% ClientKey := HMAC(SaltedPassword, "Client Key")
46 client_key(Mechanism, SaltedPassword) ->
47 68 hmac(Mechanism, SaltedPassword, "Client Key").
48
49 %% StoredKey := H(ClientKey)
50 stored_key(Mechanism, ClientKey) ->
51 68 h(Mechanism, ClientKey).
52
53
54 %% AuthMessage := client-first-message-bare + "," +
55 %% server-first-message + "," +
56 %% client-final-message-without-proof
57 auth_message(ClientFirstBare, ServerFirstMessage, ClientFinalWithoutProof) ->
58 68 lists:join(
59 ",",
60 [ClientFirstBare,
61 ServerFirstMessage,
62 ClientFinalWithoutProof]).
63
64
65 client_first_bare(Username, Nonce) ->
66 134 io_lib:fwrite("n=~s,r=~s", [Username, Nonce]).
67
68
69 client_final_without_proof(Header, R) ->
70 136 io_lib:fwrite(
71 "c=~s,r=~s",
72 [base64:encode(Header), R]).
73
74
75 %% ClientSignature := HMAC(StoredKey, AuthMessage)
76 client_signature(Mechanism, StoredKey, AuthMessage) ->
77 68 hmac(Mechanism, StoredKey, AuthMessage).
78
79
80 %% ClientProof := ClientKey XOR ClientSignature
81 client_proof(ClientKey, ClientSignature) ->
82 68 crypto:exor(ClientKey, ClientSignature).
83
84
85 %% ServerKey := HMAC(SaltedPassword, "Server Key")
86 server_key(Mechanism, SaltedPassword) ->
87 67 hmac(Mechanism, SaltedPassword, "Server Key").
88
89
90 %% ServerSignature := HMAC(ServerKey, AuthMessage)
91 server_signature(Mechanism, ServerKey, AuthMessage) ->
92 67 hmac(Mechanism, ServerKey, AuthMessage).
93
94
95 client_final(Header, R, ClientProof) ->
96 68 lists:join(
97 ",",
98 [client_final_without_proof(Header, R),
99 io_lib:fwrite("p=~s", [base64:encode(ClientProof)])]).
100
101
102 hi(Mechanism, Password, Salt, Iterations) ->
103 68 crypto:pbkdf2_hmac(sub_type(Mechanism),
104 Password,
105 Salt,
106 Iterations,
107 output_length(Mechanism)).
108
109 hmac(Mechanism, Key, Data) ->
110 270 crypto:mac(hmac, sub_type(Mechanism), Key, Data).
111
112 h(Mechanism, Data) ->
113 138 crypto:hash(sub_type(Mechanism), Data).
114
115
116 sub_type(<<"SCRAM-SHA-1">>) ->
117 6 sha;
118 sub_type(<<"SCRAM-SHA-256">>) ->
119 470 sha256.
120
121
122 output_length(Mechanism) ->
123 70 byte_size(h(Mechanism, <<>>)).
124
125
126 normalize(X) ->
127 134 X.
128
129
130 decode(Encoded) ->
131 200 maps:map(
132 fun
133 (K, V) when K == s;
134 K == v ->
135 134 base64:decode(V);
136
137 (i, V) ->
138 68 binary_to_integer(V);
139
140 (_, V) ->
141 68 V
142 end,
143 lists:foldl(
144 fun
145 (<<K:1/bytes, "=", V/bytes>>, A) ->
146 270 A#{binary_to_existing_atom(K) => V}
147 end,
148 #{},
149 binary:split(Encoded, <<",">>, [trim_all, global]))).
Line Hits Source