1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%% AES: RFC 3826
21%%
22
23-module(snmp_usm).
24
25%% Avoid warning for local function error/1 clashing with autoimported BIF.
26-compile({no_auto_import,[error/1]}).
27-export([passwd2localized_key/3, localize_key/3]).
28-export([auth_in/4, auth_out/4, set_msg_auth_params/3]).
29-export([des_encrypt/3, des_decrypt/3]).
30-export([aes_encrypt/5, aes_decrypt/5]).
31
32
33-define(SNMP_USE_V3, true).
34-include("snmp_types.hrl").
35-include("SNMP-USER-BASED-SM-MIB.hrl").
36-include("SNMP-USM-AES-MIB.hrl").
37
38-define(VMODULE,"USM").
39-include("snmp_verbosity.hrl").
40
41
42%%-----------------------------------------------------------------
43
44-define(twelwe_zeros, [0,0,0,0,0,0,0,0,0,0,0,0]).
45
46-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255).
47
48-define(BLOCK_CIPHER_AES, aes_cfb128).
49-define(BLOCK_CIPHER_DES, des_cbc).
50
51
52%%-----------------------------------------------------------------
53%% Func: passwd2localized_key/3
54%% Types: Alg      = md5 | sha
55%%        Passwd   = string()
56%%        EngineID = string()
57%% Purpose: Generates a key that can be used as an authentication
58%%          or privacy key using MD5 och SHA.  The key is
59%%          localized for EngineID.
60%%          The algorithm is described in appendix A.1 2) of
61%%          rfc2274.
62%%-----------------------------------------------------------------
63passwd2localized_key(Alg, Passwd, EngineID) when length(Passwd) > 0 ->
64    Key = mk_digest(Alg, Passwd),
65    localize_key(Alg, Key, EngineID).
66
67
68%%-----------------------------------------------------------------
69%% Func: localize_key/3
70%% Types: Alg      = md5 | sha
71%%        Passwd   = string()
72%%        EngineID = string()
73%% Purpose: Localizes an unlocalized key for EngineID.  See rfc2274
74%%          section 2.6 for a definition of localized keys.
75%%-----------------------------------------------------------------
76localize_key(Alg, Key, EngineID) ->
77    Str = [Key, EngineID, Key],
78    binary_to_list(crypto:hash(Alg, Str)).
79
80
81mk_digest(md5, Passwd) ->
82    mk_md5_digest(Passwd);
83mk_digest(sha, Passwd) ->
84    mk_sha_digest(Passwd).
85
86mk_md5_digest(Passwd) ->
87    Ctx = crypto:hash_init(md5),
88    Ctx2 = md5_loop(0, [], Ctx, Passwd, length(Passwd)),
89    crypto:hash_final(Ctx2).
90
91md5_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 ->
92    {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen),
93    NCtx = crypto:hash_update(Ctx, Buf64),
94    md5_loop(Count+64, NBuf, NCtx, Passwd, PasswdLen);
95md5_loop(_Count, _Buf, Ctx, _Passwd, _PasswdLen) ->
96    Ctx.
97
98mk_sha_digest(Passwd) ->
99    Ctx = crypto:hash_init(sha),
100    Ctx2 = sha_loop(0, [], Ctx, Passwd, length(Passwd)),
101    crypto:hash_final(Ctx2).
102
103sha_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 ->
104    {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen),
105    NCtx = crypto:hash_update(Ctx, Buf64),
106    sha_loop(Count+64, NBuf, NCtx, Passwd, PasswdLen);
107sha_loop(_Count, _Buf, Ctx, _Passwd, _PasswdLen) ->
108    Ctx.
109
110%% Create a 64 bytes long string, by repeating Passwd as many times
111%% as necessary. Output is the 64 byte string, and the rest of the
112%% last repetition of the Passwd. This is used as input in the next
113%% invocation.
114mk_buf64(BufLen, Buf, Passwd, PasswdLen) ->
115    case BufLen + PasswdLen of
116	TotLen when TotLen > 64 ->
117	    {[Buf, lists:sublist(Passwd, 64-BufLen)],
118	     lists:sublist(Passwd, 65-BufLen, PasswdLen)};
119	TotLen ->
120	    mk_buf64(TotLen, [Buf, Passwd], Passwd, PasswdLen)
121    end.
122
123
124%%-----------------------------------------------------------------
125%% Auth and priv algorithms
126%%-----------------------------------------------------------------
127
128auth_in(usmHMACMD5AuthProtocol, AuthKey, AuthParams, Packet) ->
129    md5_auth_in(AuthKey, AuthParams, Packet);
130auth_in(?usmHMACMD5AuthProtocol, AuthKey, AuthParams, Packet) ->
131    md5_auth_in(AuthKey, AuthParams, Packet);
132auth_in(usmHMACSHAAuthProtocol, AuthKey, AuthParams, Packet) ->
133    sha_auth_in(AuthKey, AuthParams, Packet);
134auth_in(?usmHMACSHAAuthProtocol, AuthKey, AuthParams, Packet) ->
135    sha_auth_in(AuthKey, AuthParams, Packet).
136
137auth_out(usmNoAuthProtocol, _AuthKey, _Message, _UsmSecParams) -> % 3.1.3
138    error(unSupportedSecurityLevel);
139auth_out(?usmNoAuthProtocol, _AuthKey, _Message, _UsmSecParams) -> % 3.1.3
140    error(unSupportedSecurityLevel);
141auth_out(usmHMACMD5AuthProtocol, AuthKey, Message, UsmSecParams) ->
142    md5_auth_out(AuthKey, Message, UsmSecParams);
143auth_out(?usmHMACMD5AuthProtocol, AuthKey, Message, UsmSecParams) ->
144    md5_auth_out(AuthKey, Message, UsmSecParams);
145auth_out(usmHMACSHAAuthProtocol, AuthKey, Message, UsmSecParams) ->
146    sha_auth_out(AuthKey, Message, UsmSecParams);
147auth_out(?usmHMACSHAAuthProtocol, AuthKey, Message, UsmSecParams) ->
148    sha_auth_out(AuthKey, Message, UsmSecParams).
149
150md5_auth_out(AuthKey, Message, UsmSecParams) ->
151    %% ?vtrace("md5_auth_out -> entry with"
152    %%  	    "~n   AuthKey:      ~w"
153    %% 	    "~n   Message:      ~w"
154    %%  	    "~n   UsmSecParams: ~w", [AuthKey, Message, UsmSecParams]),
155    %% 6.3.1.1
156    Message2 = set_msg_auth_params(Message, UsmSecParams, ?twelwe_zeros),
157    Packet   = snmp_pdus:enc_message_only(Message2),
158    %% 6.3.1.2-4 is done by the crypto function
159    %% 6.3.1.4
160    MAC = binary_to_list(crypto:hmac(md5, AuthKey, Packet, 12)),
161    %% ?vtrace("md5_auth_out -> crypto (md5) encoded"
162    %%  	    "~n   MAC: ~w", [MAC]),
163    %% 6.3.1.5
164    set_msg_auth_params(Message, UsmSecParams, MAC).
165
166md5_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) == 12 ->
167    %% ?vtrace("md5_auth_in -> entry with"
168    %%  	    "~n   AuthKey:    ~w"
169    %%  	    "~n   AuthParams: ~w"
170    %%  	    "~n   Packet:     ~w", [AuthKey, AuthParams, Packet]),
171    %% 6.3.2.3
172    Packet2 = patch_packet(binary_to_list(Packet)),
173    %% 6.3.2.5
174    MAC = binary_to_list(crypto:hmac(md5, AuthKey, Packet2, 12)),
175    %% 6.3.2.6
176    %% ?vtrace("md5_auth_in -> crypto (md5) encoded"
177    %%  	    "~n   MAC: ~w", [MAC]),
178    MAC == AuthParams;
179md5_auth_in(_AuthKey, _AuthParams, _Packet) ->
180    %% 6.3.2.1
181    ?vtrace("md5_auth_in -> entry with"
182	    "~n   _AuthKey:    ~p"
183	    "~n   _AuthParams: ~p", [_AuthKey, _AuthParams]),
184    false.
185
186
187sha_auth_out(AuthKey, Message, UsmSecParams) ->
188    %% 7.3.1.1
189    Message2 = set_msg_auth_params(Message, UsmSecParams, ?twelwe_zeros),
190    Packet = snmp_pdus:enc_message_only(Message2),
191    %% 7.3.1.2-4 is done by the crypto function
192    %% 7.3.1.4
193    MAC = binary_to_list(crypto:hmac(sha, AuthKey, Packet, 12)),
194    %% 7.3.1.5
195    set_msg_auth_params(Message, UsmSecParams, MAC).
196
197sha_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) =:= 12 ->
198    %% 7.3.2.3
199    Packet2 = patch_packet(binary_to_list(Packet)),
200    %% 7.3.2.5
201    MAC = binary_to_list(crypto:hmac(sha, AuthKey, Packet2, 12)),
202    %% 7.3.2.6
203    MAC == AuthParams;
204sha_auth_in(_AuthKey, _AuthParams, _Packet) ->
205    %% 7.3.2.1
206    ?vtrace("sha_auth_in -> entry with"
207	    "~n   _AuthKey:    ~p"
208	    "~n   _AuthParams: ~p", [_AuthKey, _AuthParams]),
209    false.
210
211
212des_encrypt(PrivKey, Data, SaltFun) ->
213    [A,B,C,D,E,F,G,H | PreIV] = PrivKey,
214    DesKey = [A,B,C,D,E,F,G,H],
215    Salt = SaltFun(),
216    IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)),
217    TailLen = (8 - (length(Data) rem 8)) rem 8,
218    Tail = mk_tail(TailLen),
219    EncData = crypto:block_encrypt(?BLOCK_CIPHER_DES,
220				   DesKey, IV, [Data,Tail]),
221    {ok, binary_to_list(EncData), Salt}.
222
223des_decrypt(PrivKey, MsgPrivParams, EncData)
224  when length(MsgPrivParams) =:= 8 ->
225    ?vtrace("des_decrypt -> entry with"
226	    "~n   PrivKey:       ~p"
227	    "~n   MsgPrivParams: ~p"
228	    "~n   EncData:       ~p", [PrivKey, MsgPrivParams, EncData]),
229    [A,B,C,D,E,F,G,H | PreIV] = PrivKey,
230    DesKey = [A,B,C,D,E,F,G,H],
231    Salt = MsgPrivParams,
232    IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)),
233    %% Whatabout errors here???  E.g. not a mulitple of 8!
234    Data = binary_to_list(crypto:block_decrypt(?BLOCK_CIPHER_DES,
235					       DesKey, IV, EncData)),
236    Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data),
237    {ok, Data2};
238des_decrypt(PrivKey, BadMsgPrivParams, EncData) ->
239    ?vtrace("des_decrypt -> entry when bad MsgPrivParams"
240	    "~n   PrivKey:          ~p"
241	    "~n   BadMsgPrivParams: ~p"
242	    "~n   EncData:          ~p",
243	    [PrivKey, BadMsgPrivParams, EncData]),
244    throw({error, {bad_msgPrivParams, PrivKey, BadMsgPrivParams, EncData}}).
245
246
247aes_encrypt(PrivKey, Data, SaltFun, EngineBoots, EngineTime) ->
248    AesKey = PrivKey,
249    Salt = SaltFun(),
250    IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]),
251    EncData = crypto:block_encrypt(?BLOCK_CIPHER_AES,
252				   AesKey, IV, Data),
253    {ok, binary_to_list(EncData), Salt}.
254
255aes_decrypt(PrivKey, MsgPrivParams, EncData, EngineBoots, EngineTime)
256  when length(MsgPrivParams) =:= 8 ->
257    AesKey = PrivKey,
258    Salt = MsgPrivParams,
259    IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]),
260    %% Whatabout errors here???  E.g. not a mulitple of 8!
261    Data = binary_to_list(crypto:block_decrypt(?BLOCK_CIPHER_AES,
262					       AesKey, IV, EncData)),
263    Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data),
264    {ok, Data2}.
265
266
267%%-----------------------------------------------------------------
268%% Utility functions
269%%-----------------------------------------------------------------
270mk_tail(N) when N > 0 ->
271    [0 | mk_tail(N-1)];
272mk_tail(0) ->
273    [].
274
275set_msg_auth_params(Message, UsmSecParams, AuthParams) ->
276    NUsmSecParams =
277	UsmSecParams#usmSecurityParameters{msgAuthenticationParameters =
278					   AuthParams},
279    SecBytes = snmp_pdus:enc_usm_security_parameters(NUsmSecParams),
280    VsnHdr   = Message#message.vsn_hdr,
281    NVsnHdr  = VsnHdr#v3_hdr{msgSecurityParameters = SecBytes},
282    Message#message{vsn_hdr = NVsnHdr}.
283
284
285%% Not very nice...
286%% This function patches the asn.1 encoded message. It changes the
287%% AuthenticationParameters to 12 zeros.
288%% NOTE: returns a deep list of bytes
289patch_packet([48 | T]) ->
290    %% Length for whole packet - 2 is tag for version
291    {Len1, [2 | T1]} = split_len(T),
292    %% Length for version - 48 is tag for header data
293    {Len2, [Vsn,48|T2]} = split_len(T1),
294    %% Length for header data
295    {Len3, T3} = split_len(T2),
296    [48,Len1,2,Len2,Vsn,48,Len3|pp2(dec_len(Len3),T3)].
297
298%% Skip HeaderData - 4 is tag for SecurityParameters
299pp2(0,[4|T]) ->
300    %% 48 is tag for UsmSecParams
301    {Len1,[48|T1]} = split_len(T),
302    %% 4 is tag for EngineID
303    {Len2,[4|T2]} = split_len(T1),
304    %% Len 3 is length for EngineID
305    {Len3,T3} = split_len(T2),
306    [4,Len1,48,Len2,4,Len3|pp3(dec_len(Len3),T3)];
307pp2(N,[H|T]) ->
308    [H|pp2(N-1,T)].
309
310%% Skip EngineID - 2 is tag for EngineBoots
311pp3(0,[2|T]) ->
312    {Len1,T1} = split_len(T),
313    [2,Len1|pp4(dec_len(Len1),T1)];
314pp3(N,[H|T]) ->
315    [H|pp3(N-1,T)].
316
317%% Skip EngineBoots - 2 is tag for EngineTime
318pp4(0,[2|T]) ->
319    {Len1,T1} = split_len(T),
320    [2,Len1|pp5(dec_len(Len1),T1)];
321pp4(N,[H|T]) ->
322    [H|pp4(N-1,T)].
323
324%% Skip EngineTime - 4 is tag for UserName
325pp5(0,[4|T]) ->
326    {Len1,T1} = split_len(T),
327    [4,Len1|pp6(dec_len(Len1),T1)];
328pp5(N,[H|T]) ->
329    [H|pp5(N-1,T)].
330
331%% Skip UserName - 4 is tag for AuthenticationParameters
332%% This is what we're looking for!
333pp6(0,[4|T]) ->
334    {Len1,[_,_,_,_,_,_,_,_,_,_,_,_|T1]} = split_len(T),
335    12 = dec_len(Len1),
336    [4,Len1,?twelwe_zeros|T1];
337pp6(N,[H|T]) ->
338    [H|pp6(N-1,T)].
339
340
341%% Returns {LengthOctets, Rest}
342split_len([Hd|Tl]) ->
343    %% definite form
344    case is8set(Hd) of
345	0 -> % Short form
346	    {Hd,Tl};
347	1 -> % Long form - at least one more octet
348	    No = clear(Hd, 8),
349	    {DigList,Rest} = head(No,Tl),
350	    {[Hd | DigList], Rest}
351    end.
352
353dec_len(D) when is_integer(D) ->
354    D;
355dec_len([_LongOctet|T]) ->
356    dl(T).
357dl([D]) ->
358    D;
359dl([A,B]) ->
360    (A bsl 8) bor B;
361dl([A,B,C]) ->
362    (A bsl 16) bor (B bsl 8) bor C;
363dl([0 | T]) ->
364    dl(T).
365
366head(L,List) when length(List) == L -> {List,[]};
367head(L,List) ->
368    head(L,List,[]).
369
370head(0,L,Res) ->
371    {lists:reverse(Res),L};
372
373head(Int,[H|Tail],Res) ->
374    head(Int-1,Tail,[H|Res]).
375
376clear(Byte, 8) ->
377    Byte band 127.
378%% clear(Byte,Pos) when Pos < 9 ->
379%%     Mask = bnot bset(0,Pos),
380%%     Mask band Byte.
381
382%% bset(Byte, 8) ->
383%%     Byte bor 2#10000000;
384%% bset(Byte, Pos) when (Pos < 9) ->
385%%     Mask = 1 bsl (Pos-1),
386%%     Byte bor Mask.
387
388is8set(Byte) ->
389    if
390	Byte > 127 -> 1;
391	true -> 0
392    end.
393
394error(Reason) ->
395    throw({error, Reason}).
396
397