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