diff options
Diffstat (limited to 'lib/snmp/src/misc/snmp_usm.erl')
-rw-r--r-- | lib/snmp/src/misc/snmp_usm.erl | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/lib/snmp/src/misc/snmp_usm.erl b/lib/snmp/src/misc/snmp_usm.erl new file mode 100644 index 0000000000..6d216e65d6 --- /dev/null +++ b/lib/snmp/src/misc/snmp_usm.erl @@ -0,0 +1,367 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2009. 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). + +-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). + + +%%----------------------------------------------------------------- +%% 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:Alg(Str)). + + +mk_digest(md5, Passwd) -> + mk_md5_digest(Passwd); +mk_digest(sha, Passwd) -> + mk_sha_digest(Passwd). + +mk_md5_digest(Passwd) -> + Ctx = crypto:md5_init(), + Ctx2 = md5_loop(0, [], Ctx, Passwd, length(Passwd)), + crypto:md5_final(Ctx2). + +md5_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 -> + {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen), + NCtx = crypto:md5_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:sha_init(), + Ctx2 = sha_loop(0, [], Ctx, Passwd, length(Passwd)), + crypto:sha_final(Ctx2). + +sha_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 -> + {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen), + NCtx = crypto:sha_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) -> + %% 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:md5_mac_96(AuthKey, Packet)), + %% 6.3.1.5 + set_msg_auth_params(Message, UsmSecParams, MAC). + +md5_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) == 12 -> + %% 6.3.2.3 + Packet2 = patch_packet(binary_to_list(Packet)), + %% 6.3.2.5 + MAC = binary_to_list(crypto:md5_mac_96(AuthKey, Packet2)), + %% 6.3.2.6 +%% ?vtrace("md5_auth_in -> entry with" +%% "~n Packet2: ~w" +%% "~n AuthKey: ~w" +%% "~n AuthParams: ~w" +%% "~n MAC: ~w", [Packet2, AuthKey, AuthParams, 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:sha_mac_96(AuthKey, Packet)), + %% 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:sha_mac_96(AuthKey, Packet2)), + %% 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 = snmp_misc:str_xor(PreIV, Salt), + TailLen = (8 - (length(Data) rem 8)) rem 8, + Tail = mk_tail(TailLen), + EncData = crypto:des_cbc_encrypt(DesKey, IV, [Data,Tail]), + {ok, binary_to_list(EncData), Salt}. + +des_decrypt(PrivKey, MsgPrivParams, EncData) + when length(MsgPrivParams) =:= 8 -> + [A,B,C,D,E,F,G,H | PreIV] = PrivKey, + DesKey = [A,B,C,D,E,F,G,H], + Salt = MsgPrivParams, + IV = snmp_misc:str_xor(PreIV, Salt), + %% Whatabout errors here??? E.g. not a mulitple of 8! + Data = binary_to_list(crypto:des_cbc_decrypt(DesKey, IV, EncData)), + Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data), + {ok, Data2}. + +aes_encrypt(PrivKey, Data, SaltFun) -> + AesKey = PrivKey, + Salt = SaltFun(), + EngineBoots = snmp_framework_mib:get_engine_boots(), + EngineTime = snmp_framework_mib:get_engine_time(), + IV = [?i32(EngineBoots), ?i32(EngineTime) | Salt], + EncData = crypto:aes_cfb_128_encrypt(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 = [?i32(EngineBoots), ?i32(EngineTime) | Salt], + %% Whatabout errors here??? E.g. not a mulitple of 8! + Data = binary_to_list(crypto:aes_cfb_128_decrypt(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}). + |