1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
20
-export([process_incoming_msg/4, generate_outgoing_msg/5]).
22
-define(SNMP_USE_V3, true).
23
-include("snmp_types.hrl").
24
-include("SNMP-USER-BASED-SM-MIB.hrl").
25
-include("SNMP-USM-AES-MIB.hrl").
26
-include("SNMPv2-TC.hrl").
28
-define(VMODULE,"A-USM").
29
-include("snmp_verbosity.hrl").
32
%%-----------------------------------------------------------------
33
%% This module implements the User Based Security Model for SNMP,
34
%% as defined in rfc2274.
35
%%-----------------------------------------------------------------
37
%% Columns not accessible via SNMP
38
-define(usmUserAuthKey, 14).
39
-define(usmUserPrivKey, 15).
41
-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255).
42
-define(i64(Int), (Int bsr 56) band 255, (Int bsr 48) band 255, (Int bsr 40) band 255, (Int bsr 32) band 255, (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255).
45
%%-----------------------------------------------------------------
46
%% Func: process_incoming_msg(Packet, Data, SecParams, SecLevel) ->
47
%% {ok, {SecEngineID, SecName, ScopedPDUBytes, SecData}} |
48
%% {error, Reason} | {error, Reason, ErrorInfo}
49
%% Return value may be throwed.
50
%% Types: Reason -> term()
52
%%-----------------------------------------------------------------
53
process_incoming_msg(Packet, Data, SecParams, SecLevel) ->
55
?vtrace("process_incoming_msg -> check security parms: 3.2.1",[]),
57
case catch snmp_pdus:dec_usm_security_parameters(SecParams) of
59
inc(snmpInASNParseErrs),
60
error({parseError, Reason}, []);
64
#usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID,
65
msgUserName = MsgUserName} = UsmSecParams,
66
?vlog("~n authEngineID: \"~s\", userName: \"~s\"",
67
[MsgAuthEngineID, MsgUserName]),
69
?vtrace("process_incoming_msg -> check engine id: 3.2.3",[]),
70
case snmp_user_based_sm_mib:is_engine_id_known(MsgAuthEngineID) of
74
SecData1 = [MsgUserName],
75
error(usmStatsUnknownEngineIDs,
76
?usmStatsUnknownEngineIDs_instance, %% OTP-3542
77
undefined, [{sec_data, SecData1}])
80
?vtrace("process_incoming_msg -> retrieve usm user: 3.2.4",[]),
82
case snmp_user_based_sm_mib:get_user(MsgAuthEngineID, MsgUserName) of
83
User when element(?usmUserStatus, User) == ?'RowStatus_active' ->
85
{_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
86
?vdebug("process_incoming_msg -> "
87
"found user ~p with wrong row status: ~p",
89
SecData2 = [MsgUserName],
90
error(usmStatsUnknownUserNames,
91
?usmStatsUnknownUserNames_instance, %% OTP-3542
92
undefined, [{sec_data, SecData2}]);
93
_ -> % undefined or not active user
94
SecData2 = [MsgUserName],
95
error(usmStatsUnknownUserNames,
96
?usmStatsUnknownUserNames_instance, %% OTP-3542
97
undefined, [{sec_data, SecData2}])
99
SecName = element(?usmUserSecurityName, UsmUser),
100
?vtrace("process_incoming_msg -> securityName: ~p",[SecName]),
101
%% 3.2.5 - implicit in following checks
103
?vtrace("process_incoming_msg -> authenticate incoming: 3.2.5 - 3.2.7"
105
authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel),
107
?vtrace("process_incoming_msg -> decrypt scoped data: 3.2.8",[]),
108
ScopedPDUBytes = decrypt(Data, UsmUser, UsmSecParams, SecLevel),
110
%% Means that if AuthKey/PrivKey are changed; the old values
112
?vtrace("process_incoming_msg -> "
113
"AuthKey/PrivKey are changed - use old values: 3.2.9",[]),
114
CachedSecData = {MsgUserName,
115
element(?usmUserAuthProtocol, UsmUser),
116
element(?usmUserPrivProtocol, UsmUser),
117
element(?usmUserAuthKey, UsmUser),
118
element(?usmUserPrivKey, UsmUser)},
119
{ok, {MsgAuthEngineID, SecName, ScopedPDUBytes, CachedSecData}}.
122
authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) ->
124
?vtrace("authenticate_incoming -> 3.2.6",[]),
125
AuthProtocol = element(?usmUserAuthProtocol, UsmUser),
126
#usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID,
127
msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
128
msgAuthoritativeEngineTime = MsgAuthEngineTime,
129
msgAuthenticationParameters = MsgAuthParams} =
131
case snmp_misc:is_auth(SecLevel) of
133
SecName = element(?usmUserSecurityName, UsmUser),
134
case is_auth(AuthProtocol,
135
element(?usmUserAuthKey, UsmUser),
141
MsgAuthEngineTime) of
143
false -> error(usmStatsWrongDigests,
144
?usmStatsWrongDigests_instance, % OTP-5464
151
is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _) -> % 3.2.5
152
error(usmStatsUnsupportedSecLevels,
153
?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464
154
is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName,
155
MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) ->
156
IsAuth = auth_in(AuthProtocol, AuthKey, AuthParams, Packet),
160
?vtrace("is_auth -> "
161
"retrieve EngineBoots and EngineTime: 3.2.7",[]),
162
SnmpEngineID = snmp_framework_mib:get_engine_id(),
163
?vtrace("is_auth -> SnmpEngineID: ~p",[SnmpEngineID]),
164
case MsgAuthEngineID of
165
SnmpEngineID -> %% 3.2.7a
166
?vtrace("is_auth -> we are authoritative: 3.2.7a",[]),
167
SnmpEngineBoots = snmp_framework_mib:get_engine_boots(),
168
?vtrace("is_auth -> SnmpEngineBoots: ~p",
170
SnmpEngineTime = snmp_framework_mib:get_engine_time(),
173
SnmpEngineBoots == 2147483647 -> false;
174
MsgAuthEngineBoots /= SnmpEngineBoots -> false;
175
MsgAuthEngineTime + 150 < SnmpEngineTime -> false;
176
MsgAuthEngineTime - 150 > SnmpEngineTime -> false;
183
%% OTP-4090 (OTP-3542)
184
?vinfo("NOT in time window: "
186
"~n SnmpEngineBoots: ~p"
187
"~n MsgAuthEngineBoots: ~p"
188
"~n SnmpEngineTime: ~p"
189
"~n MsgAuthEngineTime: ~p",
191
SnmpEngineBoots, MsgAuthEngineBoots,
192
SnmpEngineTime, MsgAuthEngineTime]),
193
error(usmStatsNotInTimeWindows,
194
?usmStatsNotInTimeWindows_instance,
196
[{securityLevel, 1}]) % authNoPriv
198
_ -> %% 3.2.7b - we're non-authoritative
199
?vtrace("is_auth -> we are non-authoritative: 3.2.7b",[]),
200
SnmpEngineBoots = get_engine_boots(MsgAuthEngineID),
201
?vtrace("is_auth -> SnmpEngineBoots: ~p",
203
SnmpEngineTime = get_engine_time(MsgAuthEngineID),
204
LatestRecvTime = get_engine_latest_time(MsgAuthEngineID),
207
MsgAuthEngineBoots > SnmpEngineBoots -> true;
208
MsgAuthEngineBoots == SnmpEngineBoots,
209
MsgAuthEngineTime > LatestRecvTime -> true;
214
?vtrace("is_auth -> "
215
"update msgAuthoritativeEngineID: 3.2.7b1",
217
set_engine_boots(MsgAuthEngineID,
219
set_engine_time(MsgAuthEngineID,
221
set_engine_latest_time(MsgAuthEngineID,
227
?vtrace("is_auth -> "
228
"check if message is outside time window: 3.2.7b2",
232
SnmpEngineBoots == 2147483647 ->
234
MsgAuthEngineBoots < SnmpEngineBoots ->
236
MsgAuthEngineBoots == SnmpEngineBoots,
237
MsgAuthEngineTime < (SnmpEngineTime - 150) ->
243
?vinfo("NOT in time window: "
245
"~n SnmpEngineBoots: ~p"
246
"~n MsgAuthEngineBoots: ~p"
247
"~n SnmpEngineTime: ~p"
248
"~n MsgAuthEngineTime: ~p",
250
SnmpEngineBoots, MsgAuthEngineBoots,
251
SnmpEngineTime, MsgAuthEngineTime]),
252
error(notInTimeWindow, []);
263
decrypt(Data, UsmUser, UsmSecParams, SecLevel) ->
264
case snmp_misc:is_priv(SecLevel) of
266
do_decrypt(Data, UsmUser, UsmSecParams);
271
do_decrypt(Data, UsmUser, UsmSecParams) ->
272
EncryptedPDU = snmp_pdus:dec_scoped_pdu_data(Data),
273
SecName = element(?usmUserSecurityName, UsmUser),
274
PrivP = element(?usmUserPrivProtocol, UsmUser),
275
PrivKey = element(?usmUserPrivKey, UsmUser),
276
try_decrypt(PrivP, PrivKey, UsmSecParams, EncryptedPDU, SecName).
278
try_decrypt(?usmNoPrivProtocol, _, _, _, SecName) -> % 3.2.5
279
error(usmStatsUnsupportedSecLevels,
280
?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464
281
try_decrypt(?usmDESPrivProtocol,
282
PrivKey, UsmSecParams, EncryptedPDU, SecName) ->
283
case (catch des_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of
284
{ok, DecryptedData} ->
287
error(usmStatsDecryptionErrors,
288
?usmStatsDecryptionErrors_instance, % OTP-5464
291
try_decrypt(?usmAesCfb128Protocol,
292
PrivKey, UsmSecParams, EncryptedPDU, SecName) ->
293
case (catch aes_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of
294
{ok, DecryptedData} ->
297
error(usmStatsDecryptionErrors,
298
?usmStatsDecryptionErrors_instance, % OTP-5464
303
generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
305
?vtrace("generate_outgoing_msg -> entry [3.1.1]",[]),
306
{UserName, AuthProtocol, PrivProtocol, AuthKey, PrivKey} =
309
%% Not a response - read from LCD
310
case snmp_user_based_sm_mib:get_user_from_security_name(
311
SecEngineID, SecName) of
312
User when element(?usmUserStatus, User) ==
313
?'RowStatus_active' ->
314
{element(?usmUserName, User),
315
element(?usmUserAuthProtocol, User),
316
element(?usmUserPrivProtocol, User),
317
element(?usmUserAuthKey, User),
318
element(?usmUserPrivKey, User)};
319
{_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
320
?vdebug("generate_outgoing_msg -> "
321
"found user ~p with wrong row status: ~p",
323
error(unknownSecurityName);
325
error(unknownSecurityName)
328
%% This means the user at the engine is unknown
329
{MsgUserName, ?usmNoAuthProtocol, ?usmNoPrivProtocol, "", ""};
334
?vtrace("generate_outgoing_msg -> [3.1.4]",[]),
335
ScopedPduBytes = Message#message.data,
336
{ScopedPduData, MsgPrivParams} =
337
encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
338
SnmpEngineID = snmp_framework_mib:get_engine_id(),
339
?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]",
342
{MsgAuthEngineBoots, MsgAuthEngineTime} =
343
case snmp_misc:is_auth(SecLevel) of
344
false when SecData == [] -> % not a response
346
true when SecEngineID /= SnmpEngineID ->
347
{get_engine_boots(SecEngineID),
348
get_engine_time(SecEngineID)};
350
{snmp_framework_mib:get_engine_boots(),
351
snmp_framework_mib:get_engine_time()}
354
?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]),
356
#usmSecurityParameters{msgAuthoritativeEngineID = SecEngineID,
357
msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
358
msgAuthoritativeEngineTime = MsgAuthEngineTime,
359
msgUserName = UserName,
360
msgPrivacyParameters = MsgPrivParams},
361
Message2 = Message#message{data = ScopedPduData},
363
?vtrace("generate_outgoing_msg -> [3.1.8]",[]),
364
authenticate_outgoing(Message2, UsmSecParams,
365
AuthKey, AuthProtocol, SecLevel).
368
%% Ret: {ScopedPDU, MsgPrivParams} - both are already encoded as OCTET STRINGs
369
encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
370
case snmp_misc:is_priv(SecLevel) of
372
?vtrace("encrypt -> 3.1.4b",[]),
375
?vtrace("encrypt -> 3.1.4a",[]),
376
case try_encrypt(PrivProtocol, PrivKey, Data) of
377
{ok, ScopedPduData, MsgPrivParams} ->
378
?vtrace("encrypt -> encode tag",[]),
379
{snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams};
383
error(encryptionError)
387
try_encrypt(?usmNoPrivProtocol, _PrivKey, _Data) -> % 3.1.2
388
error(unsupportedSecurityLevel);
389
try_encrypt(?usmDESPrivProtocol, PrivKey, Data) ->
390
des_encrypt(PrivKey, Data);
391
try_encrypt(?usmAesCfb128Protocol, PrivKey, Data) ->
392
aes_encrypt(PrivKey, Data).
395
authenticate_outgoing(Message, UsmSecParams,
396
AuthKey, AuthProtocol, SecLevel) ->
398
case snmp_misc:is_auth(SecLevel) of
400
auth_out(AuthProtocol, AuthKey, Message, UsmSecParams);
402
set_msg_auth_params(Message, UsmSecParams)
404
?vtrace("authenticate_outgoing -> encode message only",[]),
405
snmp_pdus:enc_message_only(Message2).
409
%%-----------------------------------------------------------------
410
%% Auth and priv algorithms
411
%%-----------------------------------------------------------------
412
auth_in(AuthProtocol, AuthKey, AuthParams, Packet) ->
413
snmp_usm:auth_in(AuthProtocol, AuthKey, AuthParams, Packet).
415
auth_out(AuthProtocol, AuthKey, Message, UsmSecParams) ->
416
snmp_usm:auth_out(AuthProtocol, AuthKey, Message, UsmSecParams).
418
set_msg_auth_params(Message, UsmSecParams) ->
419
snmp_usm:set_msg_auth_params(Message, UsmSecParams, []).
421
des_encrypt(PrivKey, Data) ->
422
snmp_usm:des_encrypt(PrivKey, Data, fun get_des_salt/0).
424
des_decrypt(PrivKey, UsmSecParams, EncData) ->
425
#usmSecurityParameters{msgPrivacyParameters = PrivParms} = UsmSecParams,
426
snmp_usm:des_decrypt(PrivKey, PrivParms, EncData).
430
case catch ets:update_counter(snmp_agent_table, usm_des_salt, 1) of
431
N when N =< 4294967295 ->
433
N when integer(N) -> % wrap
434
ets:insert(snmp_agent_table, {usm_des_salt, 0}),
436
_ -> % it doesn't exist, initialize
437
{A1,A2,A3} = erlang:now(),
438
random:seed(A1,A2,A3),
439
R = random:uniform(4294967295),
440
ets:insert(snmp_agent_table, {usm_des_salt, R}),
443
EngineBoots = snmp_framework_mib:get_engine_boots(),
444
[?i32(EngineBoots), ?i32(SaltInt)].
446
aes_encrypt(PrivKey, Data) ->
447
snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0).
449
aes_decrypt(PrivKey, UsmSecParams, EncData) ->
450
#usmSecurityParameters{msgPrivacyParameters = PrivParams,
451
msgAuthoritativeEngineTime = EngineTime,
452
msgAuthoritativeEngineBoots = EngineBoots} =
454
snmp_usm:aes_decrypt(PrivKey, PrivParams, EncData,
455
EngineBoots, EngineTime).
459
case catch ets:update_counter(snmp_agent_table, usm_aes_salt, 1) of
460
N when N =< 36893488147419103231 ->
462
N when integer(N) -> % wrap
463
ets:insert(snmp_agent_table, {usm_aes_salt, 0}),
465
_ -> % it doesn't exist, initialize
466
{A1,A2,A3} = erlang:now(),
467
random:seed(A1,A2,A3),
468
R = random:uniform(36893488147419103231),
469
ets:insert(snmp_agent_table, {usm_aes_salt, R}),
476
%%-----------------------------------------------------------------
477
%% We cache the local values of all non-auth engines we know.
478
%% Keep the values in the snmp_agent_table.
479
%% See section 2.3 of the RFC.
480
%%-----------------------------------------------------------------
481
get_engine_boots(SnmpEngineID) ->
482
case ets:lookup(snmp_agent_table, {usm_eboots, SnmpEngineID}) of
483
[{_Key, Boots}] -> Boots;
487
get_engine_time(SnmpEngineID) ->
488
case ets:lookup(snmp_agent_table, {usm_etime, SnmpEngineID}) of
489
[{_Key, Diff}] -> snmp_misc:now(sec) - Diff;
493
get_engine_latest_time(SnmpEngineID) ->
494
case ets:lookup(snmp_agent_table, {usm_eltime, SnmpEngineID}) of
495
[{_Key, Time}] -> Time;
500
set_engine_boots(SnmpEngineID, EngineBoots) ->
501
ets:insert(snmp_agent_table, {{usm_eboots, SnmpEngineID}, EngineBoots}).
503
set_engine_time(SnmpEngineID, EngineTime) ->
504
Diff = snmp_misc:now(sec) - EngineTime,
505
ets:insert(snmp_agent_table, {{usm_etime, SnmpEngineID}, Diff}).
507
set_engine_latest_time(SnmpEngineID, EngineTime) ->
508
ets:insert(snmp_agent_table, {{usm_eltime, SnmpEngineID}, EngineTime}).
511
%%-----------------------------------------------------------------
513
%%-----------------------------------------------------------------
515
throw({error, Reason}).
517
error(Reason, ErrorInfo) ->
518
throw({error, Reason, ErrorInfo}).
520
error(Variable, Oid, SecName) ->
521
error(Variable, Oid, SecName, []).
522
error(Variable, Oid, SecName, Opts) ->
524
ErrorInfo = {#varbind{oid = Oid,
525
variabletype = 'Counter32',
529
throw({error, Variable, ErrorInfo}).
531
inc(Name) -> ets:update_counter(snmp_agent_table, Name, 1).