~ubuntu-branches/ubuntu/utopic/ejabberd/utopic

« back to all changes in this revision

Viewing changes to src/cyrsasl_scram.erl

  • Committer: Bazaar Package Importer
  • Author(s): Konstantin Khomoutov, Konstantin Khomoutov
  • Date: 2011-10-03 20:27:12 UTC
  • mfrom: (1.1.19 upstream)
  • Revision ID: james.westby@ubuntu.com-20111003202712-w9071rc7vleldxwx
Tags: 2.1.9-1
[ Konstantin Khomoutov ]
* New upstream release.
* Remove obsoleted version.patch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%%%----------------------------------------------------------------------
 
2
%%% File    : cyrsasl_scram.erl
 
3
%%% Author  : Stephen Röttger <stephen.roettger@googlemail.com>
 
4
%%% Purpose : SASL SCRAM authentication
 
5
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
 
6
%%%
 
7
%%%
 
8
%%% ejabberd, Copyright (C) 2002-2011   ProcessOne
 
9
%%%
 
10
%%% This program is free software; you can redistribute it and/or
 
11
%%% modify it under the terms of the GNU General Public License as
 
12
%%% published by the Free Software Foundation; either version 2 of the
 
13
%%% License, or (at your option) any later version.
 
14
%%%
 
15
%%% This program is distributed in the hope that it will be useful,
 
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
18
%%% General Public License for more details.
 
19
%%%
 
20
%%% You should have received a copy of the GNU General Public License
 
21
%%% along with this program; if not, write to the Free Software
 
22
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 
23
%%% 02111-1307 USA
 
24
%%%
 
25
%%%----------------------------------------------------------------------
 
26
 
 
27
-module(cyrsasl_scram).
 
28
-author('stephen.roettger@googlemail.com').
 
29
 
 
30
-export([start/1,
 
31
         stop/0,
 
32
         mech_new/4,
 
33
         mech_step/2]).
 
34
 
 
35
-include("ejabberd.hrl").
 
36
 
 
37
-behaviour(cyrsasl).
 
38
 
 
39
-record(state, {step, stored_key, server_key, username, get_password, check_password,
 
40
                auth_message, client_nonce, server_nonce}).
 
41
 
 
42
-define(SALT_LENGTH, 16).
 
43
-define(NONCE_LENGTH, 16).
 
44
 
 
45
start(_Opts) ->
 
46
    cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
 
47
 
 
48
stop() ->
 
49
    ok.
 
50
 
 
51
mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
 
52
    {ok, #state{step = 2, get_password = GetPassword}}.
 
53
 
 
54
mech_step(#state{step = 2} = State, ClientIn) ->
 
55
        case string:tokens(ClientIn, ",") of
 
56
        [CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
 
57
                case parse_attribute(UserNameAttribute) of
 
58
                {error, Reason} ->
 
59
                        {error, Reason};
 
60
                {_, EscapedUserName} ->
 
61
                        case unescape_username(EscapedUserName) of
 
62
                        error ->
 
63
                                {error, "protocol-error-bad-username"};
 
64
                        UserName ->
 
65
                                case parse_attribute(ClientNonceAttribute) of
 
66
                                {$r, ClientNonce} ->
 
67
                                        case (State#state.get_password)(UserName) of
 
68
                                        {false, _} ->
 
69
                                                {error, "not-authorized", UserName};
 
70
                                        {Ret, _AuthModule} ->
 
71
                                                {StoredKey, ServerKey, Salt, IterationCount} = if
 
72
                                                is_tuple(Ret) ->
 
73
                                                        Ret;
 
74
                                                true ->
 
75
                                                        TempSalt = crypto:rand_bytes(?SALT_LENGTH),
 
76
                                                        SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT),
 
77
                                                        {scram:stored_key(scram:client_key(SaltedPassword)),
 
78
                                                         scram:server_key(SaltedPassword), TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT}
 
79
                                                end,
 
80
                                                ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
 
81
                                                ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
 
82
                                                ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++
 
83
                                                                                        "s=" ++ base64:encode_to_string(Salt) ++ "," ++
 
84
                                                                                        "i=" ++ integer_to_list(IterationCount),
 
85
                                                {continue,
 
86
                                                 ServerFirstMessage,
 
87
                                                 State#state{step = 4, stored_key = StoredKey, server_key = ServerKey,
 
88
                                                                         auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage,
 
89
                                                                         client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
 
90
                                        end;
 
91
                                _Else ->
 
92
                                        {error, "not-supported"}
 
93
                                end
 
94
                        end
 
95
                end;
 
96
        _Else ->
 
97
            {error, "bad-protocol"}
 
98
        end;
 
99
mech_step(#state{step = 4} = State, ClientIn) ->
 
100
        case string:tokens(ClientIn, ",") of
 
101
        [GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
 
102
                case parse_attribute(GS2ChannelBindingAttribute) of
 
103
                {$c, CVal} when (CVal == "biws") or (CVal == "eSws") ->
 
104
                    %% biws is base64 for n,, => channelbinding not supported
 
105
                    %% eSws is base64 for y,, => channelbinding supported by client only
 
106
                        Nonce = State#state.client_nonce ++ State#state.server_nonce,
 
107
                        case parse_attribute(NonceAttribute) of
 
108
                        {$r, CompareNonce} when CompareNonce == Nonce ->
 
109
                                case parse_attribute(ClientProofAttribute) of
 
110
                                {$p, ClientProofB64} ->
 
111
                                        ClientProof = base64:decode(ClientProofB64),
 
112
                                        AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
 
113
                                        ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
 
114
                                        ClientKey = scram:client_key(ClientProof, ClientSignature),
 
115
                                        CompareStoredKey = scram:stored_key(ClientKey),
 
116
                                        if CompareStoredKey == State#state.stored_key ->
 
117
                                                ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
 
118
                                                {ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
 
119
                                        true ->
 
120
                                                {error, "bad-auth"}
 
121
                                        end;
 
122
                                _Else ->
 
123
                                        {error, "bad-protocol"}
 
124
                                end;
 
125
                        {$r, _} ->
 
126
                                {error, "bad-nonce"};
 
127
                        _Else ->
 
128
                                {error, "bad-protocol"}
 
129
                        end;
 
130
                _Else ->
 
131
                        {error, "bad-protocol"}
 
132
                end;
 
133
        _Else ->
 
134
                {error, "bad-protocol"}
 
135
        end.
 
136
 
 
137
parse_attribute(Attribute) ->
 
138
        AttributeLen = string:len(Attribute),
 
139
        if
 
140
        AttributeLen >= 3 ->
 
141
                SecondChar = lists:nth(2, Attribute),
 
142
                case is_alpha(lists:nth(1, Attribute)) of
 
143
                        true ->
 
144
                                if
 
145
                                SecondChar == $= ->
 
146
                                        String = string:substr(Attribute, 3),
 
147
                                        {lists:nth(1, Attribute), String};
 
148
                                true ->
 
149
                                        {error, "bad-format second char not equal sign"}
 
150
                                end;
 
151
                        _Else ->
 
152
                                {error, "bad-format first char not a letter"}
 
153
                end;
 
154
        true -> 
 
155
                {error, "bad-format attribute too short"}
 
156
        end.
 
157
 
 
158
unescape_username("") ->
 
159
        "";
 
160
unescape_username(EscapedUsername) ->
 
161
        Pos = string:str(EscapedUsername, "="),
 
162
        if
 
163
        Pos == 0 ->
 
164
                EscapedUsername;
 
165
        true ->
 
166
                Start = string:substr(EscapedUsername, 1, Pos-1),
 
167
                End = string:substr(EscapedUsername, Pos),
 
168
                EndLen = string:len(End),
 
169
                if
 
170
                EndLen < 3 ->
 
171
                        error;
 
172
                true ->
 
173
                        case string:substr(End, 1, 3) of
 
174
                        "=2C" ->
 
175
                                Start ++ "," ++ unescape_username(string:substr(End, 4));
 
176
                        "=3D" ->
 
177
                                Start ++ "=" ++ unescape_username(string:substr(End, 4));
 
178
                        _Else ->
 
179
                                error
 
180
                        end
 
181
                end
 
182
        end.
 
183
 
 
184
is_alpha(Char) when Char >= $a, Char =< $z ->
 
185
    true;
 
186
is_alpha(Char) when Char >= $A, Char =< $Z -> 
 
187
    true;
 
188
is_alpha(_) ->
 
189
    false.
 
190