%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(snmp_usm). %% Avoid warning for local function error/1 clashing with autoimported BIF. -compile({no_auto_import,[error/1]}). -export([passwd2localized_key/3, localize_key/3]). -export([auth_in/4, auth_out/4, set_msg_auth_params/3]). -export([des_encrypt/3, des_decrypt/3]). -export([aes_encrypt/3, aes_decrypt/5]). -define(SNMP_USE_V3, true). -include("snmp_types.hrl"). -include("SNMP-USER-BASED-SM-MIB.hrl"). -include("SNMP-USM-AES-MIB.hrl"). -define(VMODULE,"USM"). -include("snmp_verbosity.hrl"). %%----------------------------------------------------------------- -define(twelwe_zeros, [0,0,0,0,0,0,0,0,0,0,0,0]). -define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255). -define(BLOCK_CIPHER_AES, aes_cfb128). -define(BLOCK_CIPHER_DES, des_cbc). %%----------------------------------------------------------------- %% Func: passwd2localized_key/3 %% Types: Alg = md5 | sha %% Passwd = string() %% EngineID = string() %% Purpose: Generates a key that can be used as an authentication %% or privacy key using MD5 och SHA. The key is %% localized for EngineID. %% The algorithm is described in appendix A.1 2) of %% rfc2274. %%----------------------------------------------------------------- passwd2localized_key(Alg, Passwd, EngineID) when length(Passwd) > 0 -> Key = mk_digest(Alg, Passwd), localize_key(Alg, Key, EngineID). %%----------------------------------------------------------------- %% Func: localize_key/3 %% Types: Alg = md5 | sha %% Passwd = string() %% EngineID = string() %% Purpose: Localizes an unlocalized key for EngineID. See rfc2274 %% section 2.6 for a definition of localized keys. %%----------------------------------------------------------------- localize_key(Alg, Key, EngineID) -> Str = [Key, EngineID, Key], binary_to_list(crypto:hash(Alg, Str)). mk_digest(md5, Passwd) -> mk_md5_digest(Passwd); mk_digest(sha, Passwd) -> mk_sha_digest(Passwd). mk_md5_digest(Passwd) -> Ctx = crypto:hash_init(md5), Ctx2 = md5_loop(0, [], Ctx, Passwd, length(Passwd)), crypto:hash_final(Ctx2). md5_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 -> {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen), NCtx = crypto:hash_update(Ctx, Buf64), md5_loop(Count+64, NBuf, NCtx, Passwd, PasswdLen); md5_loop(_Count, _Buf, Ctx, _Passwd, _PasswdLen) -> Ctx. mk_sha_digest(Passwd) -> Ctx = crypto:hash_init(sha), Ctx2 = sha_loop(0, [], Ctx, Passwd, length(Passwd)), crypto:hash_final(Ctx2). sha_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 -> {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen), NCtx = crypto:hash_update(Ctx, Buf64), sha_loop(Count+64, NBuf, NCtx, Passwd, PasswdLen); sha_loop(_Count, _Buf, Ctx, _Passwd, _PasswdLen) -> Ctx. %% Create a 64 bytes long string, by repeating Passwd as many times %% as necessary. Output is the 64 byte string, and the rest of the %% last repetition of the Passwd. This is used as input in the next %% invocation. mk_buf64(BufLen, Buf, Passwd, PasswdLen) -> case BufLen + PasswdLen of TotLen when TotLen > 64 -> {[Buf, lists:sublist(Passwd, 64-BufLen)], lists:sublist(Passwd, 65-BufLen, PasswdLen)}; TotLen -> mk_buf64(TotLen, [Buf, Passwd], Passwd, PasswdLen) end. %%----------------------------------------------------------------- %% Auth and priv algorithms %%----------------------------------------------------------------- auth_in(usmHMACMD5AuthProtocol, AuthKey, AuthParams, Packet) -> md5_auth_in(AuthKey, AuthParams, Packet); auth_in(?usmHMACMD5AuthProtocol, AuthKey, AuthParams, Packet) -> md5_auth_in(AuthKey, AuthParams, Packet); auth_in(usmHMACSHAAuthProtocol, AuthKey, AuthParams, Packet) -> sha_auth_in(AuthKey, AuthParams, Packet); auth_in(?usmHMACSHAAuthProtocol, AuthKey, AuthParams, Packet) -> sha_auth_in(AuthKey, AuthParams, Packet). auth_out(usmNoAuthProtocol, _AuthKey, _Message, _UsmSecParams) -> % 3.1.3 error(unSupportedSecurityLevel); auth_out(?usmNoAuthProtocol, _AuthKey, _Message, _UsmSecParams) -> % 3.1.3 error(unSupportedSecurityLevel); auth_out(usmHMACMD5AuthProtocol, AuthKey, Message, UsmSecParams) -> md5_auth_out(AuthKey, Message, UsmSecParams); auth_out(?usmHMACMD5AuthProtocol, AuthKey, Message, UsmSecParams) -> md5_auth_out(AuthKey, Message, UsmSecParams); auth_out(usmHMACSHAAuthProtocol, AuthKey, Message, UsmSecParams) -> sha_auth_out(AuthKey, Message, UsmSecParams); auth_out(?usmHMACSHAAuthProtocol, AuthKey, Message, UsmSecParams) -> sha_auth_out(AuthKey, Message, UsmSecParams). md5_auth_out(AuthKey, Message, UsmSecParams) -> %% ?vtrace("md5_auth_out -> entry with" %% "~n AuthKey: ~w" %% "~n Message: ~w" %% "~n UsmSecParams: ~w", [AuthKey, Message, UsmSecParams]), %% 6.3.1.1 Message2 = set_msg_auth_params(Message, UsmSecParams, ?twelwe_zeros), Packet = snmp_pdus:enc_message_only(Message2), %% 6.3.1.2-4 is done by the crypto function %% 6.3.1.4 MAC = binary_to_list(crypto:hmac(md5, AuthKey, Packet, 12)), %% ?vtrace("md5_auth_out -> crypto (md5) encoded" %% "~n MAC: ~w", [MAC]), %% 6.3.1.5 set_msg_auth_params(Message, UsmSecParams, MAC). md5_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) == 12 -> %% ?vtrace("md5_auth_in -> entry with" %% "~n AuthKey: ~w" %% "~n AuthParams: ~w" %% "~n Packet: ~w", [AuthKey, AuthParams, Packet]), %% 6.3.2.3 Packet2 = patch_packet(binary_to_list(Packet)), %% 6.3.2.5 MAC = binary_to_list(crypto:hmac(md5, AuthKey, Packet2, 12)), %% 6.3.2.6 %% ?vtrace("md5_auth_in -> crypto (md5) encoded" %% "~n MAC: ~w", [MAC]), MAC == AuthParams; md5_auth_in(_AuthKey, _AuthParams, _Packet) -> %% 6.3.2.1 ?vtrace("md5_auth_in -> entry with" "~n _AuthKey: ~p" "~n _AuthParams: ~p", [_AuthKey, _AuthParams]), false. sha_auth_out(AuthKey, Message, UsmSecParams) -> %% 7.3.1.1 Message2 = set_msg_auth_params(Message, UsmSecParams, ?twelwe_zeros), Packet = snmp_pdus:enc_message_only(Message2), %% 7.3.1.2-4 is done by the crypto function %% 7.3.1.4 MAC = binary_to_list(crypto:hmac(sha, AuthKey, Packet, 12)), %% 7.3.1.5 set_msg_auth_params(Message, UsmSecParams, MAC). sha_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) =:= 12 -> %% 7.3.2.3 Packet2 = patch_packet(binary_to_list(Packet)), %% 7.3.2.5 MAC = binary_to_list(crypto:hmac(sha, AuthKey, Packet2, 12)), %% 7.3.2.6 MAC == AuthParams; sha_auth_in(_AuthKey, _AuthParams, _Packet) -> %% 7.3.2.1 ?vtrace("sha_auth_in -> entry with" "~n _AuthKey: ~p" "~n _AuthParams: ~p", [_AuthKey, _AuthParams]), false. des_encrypt(PrivKey, Data, SaltFun) -> [A,B,C,D,E,F,G,H | PreIV] = PrivKey, DesKey = [A,B,C,D,E,F,G,H], Salt = SaltFun(), IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)), TailLen = (8 - (length(Data) rem 8)) rem 8, Tail = mk_tail(TailLen), EncData = crypto:block_encrypt(?BLOCK_CIPHER_DES, DesKey, IV, [Data,Tail]), {ok, binary_to_list(EncData), Salt}. des_decrypt(PrivKey, MsgPrivParams, EncData) when length(MsgPrivParams) =:= 8 -> ?vtrace("des_decrypt -> entry with" "~n PrivKey: ~p" "~n MsgPrivParams: ~p" "~n EncData: ~p", [PrivKey, MsgPrivParams, EncData]), [A,B,C,D,E,F,G,H | PreIV] = PrivKey, DesKey = [A,B,C,D,E,F,G,H], Salt = MsgPrivParams, IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)), %% Whatabout errors here??? E.g. not a mulitple of 8! Data = binary_to_list(crypto:block_decrypt(?BLOCK_CIPHER_DES, DesKey, IV, EncData)), Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data), {ok, Data2}; des_decrypt(PrivKey, BadMsgPrivParams, EncData) -> ?vtrace("des_decrypt -> entry when bad MsgPrivParams" "~n PrivKey: ~p" "~n BadMsgPrivParams: ~p" "~n EncData: ~p", [PrivKey, BadMsgPrivParams, EncData]), throw({error, {bad_msgPrivParams, PrivKey, BadMsgPrivParams, EncData}}). aes_encrypt(PrivKey, Data, SaltFun) -> AesKey = PrivKey, Salt = SaltFun(), EngineBoots = snmp_framework_mib:get_engine_boots(), EngineTime = snmp_framework_mib:get_engine_time(), IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]), EncData = crypto:block_encrypt(?BLOCK_CIPHER_AES, AesKey, IV, Data), {ok, binary_to_list(EncData), Salt}. aes_decrypt(PrivKey, MsgPrivParams, EncData, EngineBoots, EngineTime) when length(MsgPrivParams) =:= 8 -> AesKey = PrivKey, Salt = MsgPrivParams, IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]), %% Whatabout errors here??? E.g. not a mulitple of 8! Data = binary_to_list(crypto:block_decrypt(?BLOCK_CIPHER_AES, AesKey, IV, EncData)), Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data), {ok, Data2}. %%----------------------------------------------------------------- %% Utility functions %%----------------------------------------------------------------- mk_tail(N) when N > 0 -> [0 | mk_tail(N-1)]; mk_tail(0) -> []. set_msg_auth_params(Message, UsmSecParams, AuthParams) -> NUsmSecParams = UsmSecParams#usmSecurityParameters{msgAuthenticationParameters = AuthParams}, SecBytes = snmp_pdus:enc_usm_security_parameters(NUsmSecParams), VsnHdr = Message#message.vsn_hdr, NVsnHdr = VsnHdr#v3_hdr{msgSecurityParameters = SecBytes}, Message#message{vsn_hdr = NVsnHdr}. %% Not very nice... %% This function patches the asn.1 encoded message. It changes the %% AuthenticationParameters to 12 zeros. %% NOTE: returns a deep list of bytes patch_packet([48 | T]) -> %% Length for whole packet - 2 is tag for version {Len1, [2 | T1]} = split_len(T), %% Length for version - 48 is tag for header data {Len2, [Vsn,48|T2]} = split_len(T1), %% Length for header data {Len3, T3} = split_len(T2), [48,Len1,2,Len2,Vsn,48,Len3|pp2(dec_len(Len3),T3)]. %% Skip HeaderData - 4 is tag for SecurityParameters pp2(0,[4|T]) -> %% 48 is tag for UsmSecParams {Len1,[48|T1]} = split_len(T), %% 4 is tag for EngineID {Len2,[4|T2]} = split_len(T1), %% Len 3 is length for EngineID {Len3,T3} = split_len(T2), [4,Len1,48,Len2,4,Len3|pp3(dec_len(Len3),T3)]; pp2(N,[H|T]) -> [H|pp2(N-1,T)]. %% Skip EngineID - 2 is tag for EngineBoots pp3(0,[2|T]) -> {Len1,T1} = split_len(T), [2,Len1|pp4(dec_len(Len1),T1)]; pp3(N,[H|T]) -> [H|pp3(N-1,T)]. %% Skip EngineBoots - 2 is tag for EngineTime pp4(0,[2|T]) -> {Len1,T1} = split_len(T), [2,Len1|pp5(dec_len(Len1),T1)]; pp4(N,[H|T]) -> [H|pp4(N-1,T)]. %% Skip EngineTime - 4 is tag for UserName pp5(0,[4|T]) -> {Len1,T1} = split_len(T), [4,Len1|pp6(dec_len(Len1),T1)]; pp5(N,[H|T]) -> [H|pp5(N-1,T)]. %% Skip UserName - 4 is tag for AuthenticationParameters %% This is what we're looking for! pp6(0,[4|T]) -> {Len1,[_,_,_,_,_,_,_,_,_,_,_,_|T1]} = split_len(T), 12 = dec_len(Len1), [4,Len1,?twelwe_zeros|T1]; pp6(N,[H|T]) -> [H|pp6(N-1,T)]. %% Returns {LengthOctets, Rest} split_len([Hd|Tl]) -> %% definite form case is8set(Hd) of 0 -> % Short form {Hd,Tl}; 1 -> % Long form - at least one more octet No = clear(Hd, 8), {DigList,Rest} = head(No,Tl), {[Hd | DigList], Rest} end. dec_len(D) when is_integer(D) -> D; dec_len([_LongOctet|T]) -> dl(T). dl([D]) -> D; dl([A,B]) -> (A bsl 8) bor B; dl([A,B,C]) -> (A bsl 16) bor (B bsl 8) bor C; dl([0 | T]) -> dl(T). head(L,List) when length(List) == L -> {List,[]}; head(L,List) -> head(L,List,[]). head(0,L,Res) -> {lists:reverse(Res),L}; head(Int,[H|Tail],Res) -> head(Int-1,Tail,[H|Res]). clear(Byte, 8) -> Byte band 127. %% clear(Byte,Pos) when Pos < 9 -> %% Mask = bnot bset(0,Pos), %% Mask band Byte. %% bset(Byte, 8) -> %% Byte bor 2#10000000; %% bset(Byte, Pos) when (Pos < 9) -> %% Mask = 1 bsl (Pos-1), %% Byte bor Mask. is8set(Byte) -> if Byte > 127 -> 1; true -> 0 end. error(Reason) -> throw({error, Reason}).