File Coverage

blib/lib/Net/SNMP/Security/USM.pm
Criterion Covered Total %
statement 224 594 37.7
branch 103 404 25.5
condition 19 73 26.0
subroutine 42 72 58.3
pod 0 26 0.0
total 388 1169 33.1


line stmt bran cond sub pod time code
1             # -*- mode: perl -*-
2             # ============================================================================
3              
4             package Net::SNMP::Security::USM;
5              
6             # $Id: USM.pm,v 4.1 2010/09/10 00:01:22 dtown Rel $
7              
8             # Object that implements the SNMPv3 User-based Security Model.
9              
10             # Copyright (c) 2001-2010 David M. Town
11             # All rights reserved.
12              
13             # This program is free software; you may redistribute it and/or modify it
14             # under the same terms as the Perl 5 programming language system itself.
15              
16             # ============================================================================
17              
18 1     1   993 use strict;
  1         2  
  1         39  
19              
20 1     1   597 use Net::SNMP::Security qw( :ALL );
  1         3  
  1         164  
21              
22 1         160 use Net::SNMP::Message qw(
23             :msgFlags asn1_itoa OCTET_STRING SEQUENCE INTEGER SNMP_VERSION_3 TRUE FALSE
24 1     1   6 );
  1         1  
25              
26 1     1   831 use Crypt::DES();
  1         1014  
  1         23  
27 1     1   8 use Digest::MD5();
  1         1  
  1         15  
28 1     1   766 use Digest::SHA1();
  1         804  
  1         21  
29 1     1   761 use Digest::HMAC();
  1         492  
  1         32  
30              
31             ## Version of the Net::SNMP::Security::USM module
32              
33             our $VERSION = v4.0.1;
34              
35             ## Handle importing/exporting of symbols
36              
37 1     1   5 use base qw( Net::SNMP::Security );
  1         2  
  1         7428  
38              
39             our @EXPORT_OK;
40              
41             our %EXPORT_TAGS = (
42             authprotos => [
43             qw( AUTH_PROTOCOL_NONE AUTH_PROTOCOL_HMACMD5 AUTH_PROTOCOL_HMACSHA )
44             ],
45             levels => [
46             qw( SECURITY_LEVEL_NOAUTHNOPRIV SECURITY_LEVEL_AUTHNOPRIV
47             SECURITY_LEVEL_AUTHPRIV )
48             ],
49             models => [
50             qw( SECURITY_MODEL_ANY SECURITY_MODEL_SNMPV1 SECURITY_MODEL_SNMPV2C
51             SECURITY_MODEL_USM )
52             ],
53             privprotos => [
54             qw( PRIV_PROTOCOL_NONE PRIV_PROTOCOL_DES PRIV_PROTOCOL_AESCFB128
55             PRIV_PROTOCOL_DRAFT_3DESEDE PRIV_PROTOCOL_DRAFT_AESCFB128
56             PRIV_PROTOCOL_DRAFT_AESCFB192 PRIV_PROTOCOL_DRAFT_AESCFB256 )
57             ],
58             );
59              
60             Exporter::export_ok_tags( qw( authprotos levels models privprotos ) );
61              
62             $EXPORT_TAGS{ALL} = [ @EXPORT_OK ];
63              
64             ## RCC 3414 - Authentication protocols
65              
66 0     0 0 0 sub AUTH_PROTOCOL_NONE { '1.3.6.1.6.3.10.1.1.1' } # usmNoAuthProtocol
67 15     15 0 49 sub AUTH_PROTOCOL_HMACMD5 { '1.3.6.1.6.3.10.1.1.2' } # usmHMACMD5AuthProtocol
68 12     12 0 49 sub AUTH_PROTOCOL_HMACSHA { '1.3.6.1.6.3.10.1.1.3' } # usmHMACSHAAuthProtocol
69              
70             ## RFC 3414 - Privacy protocols
71              
72 0     0 0 0 sub PRIV_PROTOCOL_NONE { '1.3.6.1.6.3.10.1.2.1' } # usmNoPrivProtocol
73 15     15 0 57 sub PRIV_PROTOCOL_DES { '1.3.6.1.6.3.10.1.2.2' } # usmDESPrivProtocol
74              
75             ## RFC 3826 - The AES Cipher Algorithm in the SNMP USM
76              
77             # usmAesCfb128Protocol
78 16     16 0 47 sub PRIV_PROTOCOL_AESCFB128 { '1.3.6.1.6.3.10.1.2.4' }
79              
80             # The privacy protocols below have been implemented using the draft
81             # specifications intended to extend the User-based Security Model
82             # defined in RFC 3414. Since the object definitions have not been
83             # standardized, they have been based on the Extended Security Options
84             # Consortium MIB found at http://www.snmp.com/eso/esoConsortiumMIB.txt.
85              
86             # Extension to Support Triple-DES EDE
87             # Reeder and Gudmunsson; October 1999, expired April 2000
88              
89             # usm3DESPrivProtocol
90 19     19 0 66 sub PRIV_PROTOCOL_DRAFT_3DESEDE { '1.3.6.1.4.1.14832.1.1' }
91              
92             # AES Cipher Algorithm in the USM
93             # Blumenthal, Maino, and McCloghrie; October 2002, expired April 2003
94              
95             # usmAESCfb128PrivProtocol
96 1     1 0 3 sub PRIV_PROTOCOL_DRAFT_AESCFB128 { '1.3.6.1.4.1.14832.1.2' }
97              
98             # usmAESCfb192PrivProtocol
99 17     17 0 64 sub PRIV_PROTOCOL_DRAFT_AESCFB192 { '1.3.6.1.4.1.14832.1.3' }
100              
101             # usmAESCfb256PrivProtocol
102 17     17 0 79 sub PRIV_PROTOCOL_DRAFT_AESCFB256 { '1.3.6.1.4.1.14832.1.4' }
103              
104             ## Package variables
105              
106             our $ENGINE_ID; # Our authoritative snmpEngineID
107             # [public methods] -----------------------------------------------------------
108              
109             sub new
110             {
111 2     2 0 83 my ($class, %argv) = @_;
112              
113             # Create a new data structure for the object
114 2         9 my $this = bless {
115             '_error' => undef, # Error message
116             '_version' => SNMP_VERSION_3, # version
117             '_authoritative' => FALSE, # Authoritative flag
118             '_discovered' => FALSE, # Engine discovery flag
119             '_synchronized' => FALSE, # Synchronization flag
120             '_engine_id' => q{}, # snmpEngineID
121             '_engine_boots' => 0, # snmpEngineBoots
122             '_engine_time' => 0, # snmpEngineTime
123             '_latest_engine_time' => 0, # latestReceivedEngineTime
124             '_time_epoc' => time(), # snmpEngineBoots epoc
125             '_user_name' => q{}, # securityName
126             '_auth_data' => undef, # Authentication data
127             '_auth_key' => undef, # authKey
128             '_auth_password' => undef, # Authentication password
129             '_auth_protocol' => AUTH_PROTOCOL_HMACMD5, # authProtocol
130             '_priv_data' => undef, # Privacy data
131             '_priv_key' => undef, # privKey
132             '_priv_password' => undef, # Privacy password
133             '_priv_protocol' => PRIV_PROTOCOL_DES, # privProtocol
134             '_security_level' => SECURITY_LEVEL_NOAUTHNOPRIV
135             }, $class;
136              
137             # We first need to find out if we are an authoritative SNMP
138             # engine and set the authProtocol and privProtocol if they
139             # have been provided.
140              
141 2         10 foreach (keys %argv) {
142              
143 9 50       54 if (/^-?authoritative$/i) {
    100          
    100          
144 0 0       0 $this->{_authoritative} = (delete $argv{$_}) ? TRUE : FALSE;
145             } elsif (/^-?authprotocol$/i) {
146 1         13 $this->_auth_protocol(delete $argv{$_});
147             } elsif (/^-?privprotocol$/i) {
148 2         8 $this->_priv_protocol(delete $argv{$_});
149             }
150              
151 9 50       22 if (defined $this->{_error}) {
152 0 0       0 return wantarray ? (undef, $this->{_error}) : undef;
153             }
154             }
155              
156             # Now validate the rest of the passed arguments
157              
158 2         6 for (keys %argv) {
159              
160 6 50 33     58 if (/^-?version$/i) {
    50          
    50          
    100          
    50          
    100          
    50          
    50          
161 0         0 $this->_version($argv{$_});
162             } elsif (/^-?debug$/i) {
163 0         0 $this->debug($argv{$_});
164             } elsif ((/^-?engineid$/i) && ($this->{_authoritative})) {
165 0         0 $this->_engine_id($argv{$_});
166             } elsif (/^-?username$/i) {
167 2         6 $this->_user_name($argv{$_});
168             } elsif (/^-?authkey$/i) {
169 0         0 $this->_auth_key($argv{$_});
170             } elsif (/^-?authpassword$/i) {
171 2         6 $this->_auth_password($argv{$_});
172             } elsif (/^-?privkey$/i) {
173 0         0 $this->_priv_key($argv{$_});
174             } elsif (/^-?privpassword$/i) {
175 2         7 $this->_priv_password($argv{$_});
176             } else {
177 0         0 $this->_error('The argument "%s" is unknown', $_);
178             }
179              
180 6 50       14 if (defined $this->{_error}) {
181 0 0       0 return wantarray ? (undef, $this->{_error}) : undef;
182             }
183              
184             }
185              
186             # Generate a snmpEngineID and populate the object accordingly
187             # if we are an authoritative snmpEngine.
188              
189 2 50       7 if ($this->{_authoritative}) {
190 0         0 $this->_snmp_engine_init();
191             }
192              
193             # Define the securityParameters
194 2 50       5 if (!defined $this->_security_params()) {
195 0 0       0 return wantarray ? (undef, $this->{_error}) : undef;
196             }
197              
198             # Return the object and an empty error message (in list context)
199 2 50       17 return wantarray ? ($this, q{}) : $this;
200             }
201              
202             sub generate_request_msg
203             {
204 0     0 0 0 my ($this, $pdu, $msg) = @_;
205              
206             # Clear any previous errors
207 0         0 $this->_error_clear();
208              
209 0 0       0 if (@_ < 3) {
210 0         0 return $this->_error('The required PDU and/or Message object is missing');
211             }
212              
213             # Validate the SNMP version of the PDU
214 0 0       0 if ($pdu->version() != $this->{_version}) {
215 0         0 return $this->_error(
216             'The SNMP version %d was expected, but %d was found',
217             $this->{_version}, $pdu->version()
218             );
219             }
220              
221             # Validate the securityLevel of the PDU
222 0 0       0 if ($pdu->security_level() > $this->{_security_level}) {
223 0         0 return $this->_error(
224             'The PDU securityLevel %d is greater than the configured value %d',
225             $pdu->security_level(), $this->{_security_level}
226             );
227             }
228              
229             # Validate PDU type with snmpEngine type
230 0 0       0 if ($pdu->expect_response()) {
231 0 0       0 if ($this->{_authoritative}) {
232 0         0 return $this->_error(
233             'Must be a non-authoritative SNMP engine to generate a %s',
234             asn1_itoa($pdu->pdu_type())
235             );
236             }
237             } else {
238 0 0       0 if (!$this->{_authoritative}) {
239 0         0 return $this->_error(
240             'Must be an authoritative SNMP engine to generate a %s',
241             asn1_itoa($pdu->pdu_type())
242             );
243             }
244             }
245              
246             # Extract the msgGlobalData out of the message
247 0         0 my $msg_global_data = $msg->clear();
248              
249             # AES in the USM Section 3.1.2.1 - "The 128-bit IV is obtained as
250             # the concatenation of the... ...snmpEngineBoots, ...snmpEngineTime,
251             # and a local 64-bit integer. We store the current snmpEngineBoots
252             # and snmpEngineTime before encrypting the PDU so that the computed
253             # IV matches the transmitted msgAuthoritativeEngineBoots and
254             # msgAuthoritativeEngineTime.
255              
256 0         0 my $msg_engine_time = $this->_engine_time();
257 0         0 my $msg_engine_boots = $this->_engine_boots();
258              
259             # Copy the PDU into a "plain text" buffer
260 0         0 my $pdu_buffer = $pdu->copy();
261 0         0 my $priv_params = q{};
262              
263             # encryptedPDU::=OCTET STRING
264 0 0       0 if ($pdu->security_level() > SECURITY_LEVEL_AUTHNOPRIV) {
265 0 0       0 if (!defined $this->_encrypt_data($msg, $priv_params, $pdu_buffer)) {
266 0         0 return $this->_error();
267             }
268             }
269              
270             # msgPrivacyParameters::=OCTET STRING
271 0 0       0 if (!defined $msg->prepare(OCTET_STRING, $priv_params)) {
272 0         0 return $this->_error($msg->error());
273             }
274              
275             # msgAuthenticationParameters::=OCTET STRING
276              
277 0         0 my $auth_params = q{};
278 0         0 my $auth_location = 0;
279              
280 0 0       0 if ($pdu->security_level() > SECURITY_LEVEL_NOAUTHNOPRIV) {
281              
282             # Save the location to fill in msgAuthenticationParameters later
283 0         0 $auth_location = $msg->length() + 12 + length $pdu_buffer;
284              
285             # Set the msgAuthenticationParameters to all zeros
286 0         0 $auth_params = pack 'x12';
287             }
288              
289 0 0       0 if (!defined $msg->prepare(OCTET_STRING, $auth_params)) {
290 0         0 return $this->_error($msg->error());
291             }
292              
293             # msgUserName::=OCTET STRING
294 0 0       0 if (!defined $msg->prepare(OCTET_STRING, $pdu->security_name())) {
295 0         0 return $this->_error($msg->error());
296             }
297              
298             # msgAuthoritativeEngineTime::=INTEGER
299 0 0       0 if (!defined $msg->prepare(INTEGER, $msg_engine_time)) {
300 0         0 return $this->_error($msg->error());
301             }
302              
303             # msgAuthoritativeEngineBoots::=INTEGER
304 0 0       0 if (!defined $msg->prepare(INTEGER, $msg_engine_boots)) {
305 0         0 return $this->_error($msg->error());
306             }
307              
308             # msgAuthoritativeEngineID
309 0 0       0 if (!defined $msg->prepare(OCTET_STRING, $this->_engine_id())) {
310 0         0 return $this->_error($msg->error());
311             }
312              
313             # UsmSecurityParameters::= SEQUENCE
314 0 0       0 if (!defined $msg->prepare(SEQUENCE)) {
315 0         0 return $this->_error($msg->error());
316             }
317              
318             # msgSecurityParameters::=OCTET STRING
319 0 0       0 if (!defined $msg->prepare(OCTET_STRING, $msg->clear())) {
320 0         0 return $this->_error($msg->error());
321             }
322              
323             # Append the PDU
324 0 0       0 if (!defined $msg->append($pdu_buffer)) {
325 0         0 return $this->_error($msg->error());
326             }
327              
328             # Prepend the msgGlobalData
329 0 0       0 if (!defined $msg->prepend($msg_global_data)) {
330 0         0 return $this->_error($msg->error());
331             }
332              
333             # version::=INTEGER
334 0 0       0 if (!defined $msg->prepare(INTEGER, $this->{_version})) {
335 0         0 return $this->_error($msg->error());
336             }
337              
338             # message::=SEQUENCE
339 0 0       0 if (!defined $msg->prepare(SEQUENCE)) {
340 0         0 return $this->_error($msg->error());
341             }
342              
343             # Apply authentication
344 0 0       0 if ($pdu->security_level() > SECURITY_LEVEL_NOAUTHNOPRIV) {
345 0 0       0 if (!defined $this->_authenticate_outgoing_msg($msg, $auth_location)) {
346 0         0 return $this->_error($msg->error());
347             }
348             }
349              
350             # Return the Message
351 0         0 return $msg;
352             }
353              
354             sub process_incoming_msg
355             {
356 0     0 0 0 my ($this, $msg) = @_;
357              
358             # Clear any previous errors
359 0         0 $this->_error_clear();
360              
361 0 0       0 return $this->_error('The required Message object is missing') if (@_ < 2);
362              
363             # msgSecurityParameters::=OCTET STRING
364              
365 0         0 my $msg_params = $msg->process(OCTET_STRING);
366 0 0       0 return $this->_error($msg->error()) if !defined $msg_params;
367              
368             # Need to move the buffer index back to the begining of the data
369             # portion of the OCTET STRING that contains the msgSecurityParameters.
370              
371 0         0 $msg->index($msg->index() - length $msg_params);
372              
373             # UsmSecurityParameters::=SEQUENCE
374 0 0       0 return $this->_error($msg->error()) if !defined $msg->process(SEQUENCE);
375              
376             # msgAuthoritativeEngineID::=OCTET STRING
377 0         0 my $msg_engine_id;
378 0 0       0 if (!defined($msg_engine_id = $msg->process(OCTET_STRING))) {
379 0         0 return $this->_error($msg->error());
380             }
381              
382             # msgAuthoritativeEngineBoots::=INTEGER (0..2147483647)
383 0         0 my $msg_engine_boots;
384 0 0       0 if (!defined ($msg_engine_boots = $msg->process(INTEGER))) {
385 0         0 return $this->_error($msg->error());
386             }
387 0 0 0     0 if (($msg_engine_boots < 0) || ($msg_engine_boots > 2147483647)) {
388 0         0 return $this->_error(
389             'The msgAuthoritativeEngineBoots value %d is out of range ' .
390             '(0..2147483647)', $msg_engine_boots
391             );
392             }
393              
394             # msgAuthoritativeEngineTime::=INTEGER (0..2147483647)
395 0         0 my $msg_engine_time;
396 0 0       0 if (!defined ($msg_engine_time = $msg->process(INTEGER))) {
397 0         0 return $this->_error($msg->error());
398             }
399 0 0 0     0 if (($msg_engine_time < 0) || ($msg_engine_time > 2147483647)) {
400 0         0 return $this->_error(
401             'The msgAuthoritativeEngineTime value %d is out of range ' .
402             '(0..2147483647)', $msg_engine_time
403             );
404             }
405              
406             # msgUserName::=OCTET STRING (SIZE(0..32))
407 0 0       0 if (!defined $msg->security_name($msg->process(OCTET_STRING))) {
408 0         0 return $this->_error($msg->error());
409             }
410              
411             # msgAuthenticationParameters::=OCTET STRING
412 0         0 my $auth_params;
413 0 0       0 if (!defined ($auth_params = $msg->process(OCTET_STRING))) {
414 0         0 return $this->_error($msg->error());
415             }
416              
417             # We need to zero out the msgAuthenticationParameters in order
418             # to compute the HMAC properly.
419              
420 0 0       0 if (my $len = length $auth_params) {
421 0 0       0 if ($len != 12) {
422 0         0 return $this->_error(
423             'The msgAuthenticationParameters length of %d is invalid', $len
424             );
425             }
426 0         0 substr ${$msg->reference}, ($msg->index() - 12), 12, pack 'x12';
  0         0  
427             }
428              
429             # msgPrivacyParameters::=OCTET STRING
430 0         0 my $priv_params;
431 0 0       0 if (!defined ($priv_params = $msg->process(OCTET_STRING))) {
432 0         0 return $this->_error($msg->error());
433             }
434              
435             # Validate the msgAuthoritativeEngineID and msgUserName
436              
437 0 0       0 if ($this->{_discovered}) {
438              
439 0 0       0 if ($msg_engine_id ne $this->_engine_id()) {
440 0         0 return $this->_error(
441             'The msgAuthoritativeEngineID "%s" was expected, but "%s" was ' .
442             'found', unpack('H*', $this->_engine_id()),
443             unpack 'H*', $msg_engine_id
444             );
445             }
446              
447 0 0       0 if ($msg->security_name() ne $this->_user_name()) {
448 0         0 return $this->_error(
449             'The msgUserName "%s" was expected, but "%s" was found',
450             $this->_user_name(), $msg->security_name()
451             );
452             }
453              
454             } else {
455              
456             # Handle authoritativeEngineID discovery
457 0 0       0 if (!defined $this->_engine_id_discovery($msg_engine_id)) {
458 0         0 return $this->_error();
459             }
460              
461             }
462              
463             # Validate the incoming securityLevel
464              
465 0         0 my $security_level = $msg->security_level();
466              
467 0 0       0 if ($security_level > $this->{_security_level}) {
468 0         0 return $this->_error(
469             'The message securityLevel %d is greater than the configured ' .
470             'value %d', $security_level, $this->{_security_level}
471             );
472             }
473              
474 0 0       0 if ($security_level > SECURITY_LEVEL_NOAUTHNOPRIV) {
475              
476             # Authenticate the message
477 0 0       0 if (!defined $this->_authenticate_incoming_msg($msg, $auth_params)) {
478 0         0 return $this->_error();
479             }
480              
481             # Synchronize the time
482 0 0       0 if (!$this->_synchronize($msg_engine_boots, $msg_engine_time)) {
483 0         0 return $this->_error();
484             }
485              
486             # Check for timeliness
487 0 0       0 if (!defined $this->_timeliness($msg_engine_boots, $msg_engine_time)) {
488 0         0 return $this->_error();
489             }
490              
491 0 0       0 if ($security_level > SECURITY_LEVEL_AUTHNOPRIV) {
492              
493             # Validate the msgPrivacyParameters length.
494              
495 0 0       0 if (length($priv_params) != 8) {
496 0         0 return $this->_error(
497             'The msgPrivacyParameters length of %d is invalid',
498             length $priv_params
499             );
500             }
501              
502             # AES in the USM Section 3.1.2.1 - "The 128-bit IV is
503             # obtained as the concatenation of the... ...snmpEngineBoots,
504             # ...snmpEngineTime, and a local 64-bit integer. ...The
505             # 64-bit integer must be placed in the msgPrivacyParameters
506             # field..." We must prepend the snmpEngineBoots and
507             # snmpEngineTime as received in order to compute the IV.
508              
509 0 0 0     0 if (($this->{_priv_protocol} eq PRIV_PROTOCOL_AESCFB128) ||
      0        
510             ($this->{_priv_protocol} eq PRIV_PROTOCOL_DRAFT_AESCFB192) ||
511             ($this->{_priv_protocol} eq PRIV_PROTOCOL_DRAFT_AESCFB256))
512             {
513 0         0 substr $priv_params, 0, 0, pack 'NN', $msg_engine_boots,
514             $msg_engine_time;
515             }
516              
517             # encryptedPDU::=OCTET STRING
518              
519 0         0 return $this->_decrypt_data($msg,
520             $priv_params,
521             $msg->process(OCTET_STRING));
522              
523             }
524              
525             }
526              
527 0         0 return TRUE;
528             }
529              
530             sub user_name
531             {
532 0     0 0 0 return $_[0]->{_user_name};
533             }
534              
535             sub auth_protocol
536             {
537 0     0 0 0 my ($this) = @_;
538              
539 0 0       0 if ($this->{_security_level} > SECURITY_LEVEL_NOAUTHNOPRIV) {
540 0         0 return $this->{_auth_protocol};
541             }
542              
543 0         0 return AUTH_PROTOCOL_NONE;
544             }
545              
546             sub auth_key
547             {
548 2     2 0 256 return $_[0]->{_auth_key};
549             }
550              
551             sub priv_protocol
552             {
553 0     0 0 0 my ($this) = @_;
554              
555 0 0       0 if ($this->{_security_level} > SECURITY_LEVEL_AUTHNOPRIV) {
556 0         0 return $this->{_priv_protocol};
557             }
558              
559 0         0 return PRIV_PROTOCOL_NONE;
560             }
561              
562             sub priv_key
563             {
564 2     2 0 106 return $_[0]->{_priv_key};
565             }
566              
567             sub engine_id
568             {
569 0     0 0 0 return $_[0]->{_engine_id};
570             }
571              
572             sub engine_boots
573             {
574 0     0 0 0 goto _engine_boots;
575             }
576              
577             sub engine_time
578             {
579 0     0 0 0 goto &_engine_time;
580             }
581              
582             sub security_level
583             {
584 0     0 0 0 return $_[0]->{_security_level};
585             }
586              
587             sub security_model
588             {
589             # RFC 3411 - SnmpSecurityModel::=TEXTUAL-CONVENTION
590              
591 0     0 0 0 return SECURITY_MODEL_USM;
592             }
593              
594             sub security_name
595             {
596 0     0 0 0 goto &_user_name;
597             }
598              
599             sub discovered
600             {
601 0     0 0 0 my ($this) = @_;
602              
603 0 0       0 if ($this->{_security_level} > SECURITY_LEVEL_NOAUTHNOPRIV) {
604 0   0     0 return ($this->{_discovered} && $this->{_synchronized});
605             }
606              
607 0         0 return $this->{_discovered};
608             }
609              
610             # [private methods] ----------------------------------------------------------
611              
612             sub _version
613             {
614 0     0   0 my ($this, $version) = @_;
615              
616 0 0       0 if ($version != SNMP_VERSION_3) {
617 0         0 return $this->_error('The SNMP version %s is not supported', $version);
618             }
619              
620 0         0 return $this->{_version} = $version;
621             }
622              
623             sub _engine_id
624             {
625 0     0   0 my ($this, $engine_id) = @_;
626              
627 0 0       0 if (@_ < 2) {
628 0         0 return $this->{_engine_id};
629             }
630              
631 0 0       0 if ($engine_id =~ m/^(?:0x)?([A-F0-9]+)$/i) {
632 0 0       0 my $eid = pack 'H*', length($1) % 2 ? '0'.$1 : $1;
633 0         0 my $len = length $eid;
634 0 0 0     0 if ($len < 5 || $len > 32) {
635 0         0 return $this->_error(
636             'The authoritativeEngineID length of %d is out of range (5..32)',
637             $len
638             );
639             }
640 0         0 $this->{_engine_id} = $eid;
641             } else {
642 0         0 return $this->_error(
643             'The authoritativeEngineID "%s" is expected in hexadecimal format',
644             $engine_id
645             );
646             }
647              
648 0         0 return $this->{_engine_id};
649             }
650              
651             sub _user_name
652             {
653 2     2   3 my ($this, $user_name) = @_;
654              
655 2 50       6 if (@_ == 2) {
656 2 50       19 if ($user_name eq q{}) {
    50          
657 0         0 return $this->_error('An empty userName was specified');
658             } elsif (length($user_name) > 32) {
659 0         0 return $this->_error(
660             'The userName length of %d is out of range (1..32)',
661             length $user_name
662             );
663             }
664 2         4 $this->{_user_name} = $user_name;
665             }
666              
667             # RFC 3414 Section 4 - "Discovery... ...msgUserName of zero-length..."
668              
669 2 50       6 return ($this->{_discovered}) ? $this->{_user_name} : q{};
670             }
671              
672             sub _snmp_engine_init
673             {
674 0     0   0 my ($this) = @_;
675              
676 0 0       0 if ($this->{_engine_id} eq q{}) {
677              
678             # Initialize our snmpEngineID using the algorithm described
679             # in RFC 3411 - SnmpEngineID::=TEXTUAL-CONVENTION.
680              
681             # The first bit is set to one to indicate that the RFC 3411
682             # algorithm is being used. The first fours bytes are to be
683             # the agent's SNMP management private enterprise number, but
684             # they are set to all zeros. The fifth byte is set to one to
685             # indicate that the final four bytes are an IPv4 address.
686              
687 0 0       0 if (!defined $ENGINE_ID) {
688 0         0 $ENGINE_ID = eval {
689 0         0 require Sys::Hostname;
690 0         0 pack('H10', '8000000001') . gethostbyname Sys::Hostname::hostname();
691             };
692              
693             # Fallback in case gethostbyname() or hostname() fail
694 0 0       0 if ($@) {
695 0         0 $ENGINE_ID = pack 'x11H2', '01';
696             }
697             }
698              
699 0         0 $this->{_engine_id} = $ENGINE_ID;
700             }
701              
702 0         0 $this->{_engine_boots} = 1;
703 0         0 $this->{_time_epoc} = $^T;
704 0         0 $this->{_synchronized} = TRUE;
705 0         0 $this->{_discovered} = TRUE;
706              
707 0         0 return TRUE;
708             }
709              
710             sub _auth_key
711             {
712 0     0   0 my ($this, $auth_key) = @_;
713              
714 0 0       0 if (@_ == 2) {
715 0 0       0 if ($auth_key =~ m/^(?:0x)?([A-F0-9]+)$/i) {
716 0 0       0 $this->{_auth_key} = pack 'H*', length($1) % 2 ? '0'.$1 : $1;
717 0 0       0 if (!defined $this->_auth_key_validate()) {
718 0         0 return $this->_error();
719             }
720             } else {
721 0         0 return $this->_error(
722             'The authKey "%s" is expected in hexadecimal format', $auth_key
723             );
724             }
725             }
726              
727 0         0 return $this->{_auth_key};
728             }
729              
730             sub _auth_password
731             {
732 2     2   3 my ($this, $auth_password) = @_;
733              
734 2 50       5 if (@_ == 2) {
735 2 50       5 if ($auth_password eq q{}) {
736 0         0 return $this->_error('An empty authentication password was specified');
737             }
738 2         3 $this->{_auth_password} = $auth_password;
739             }
740              
741 2         4 return $this->{_auth_password};
742             }
743              
744             {
745             my $protocols = {
746             '(?:hmac-)?md5(?:-96)?', AUTH_PROTOCOL_HMACMD5,
747             quotemeta AUTH_PROTOCOL_HMACMD5, AUTH_PROTOCOL_HMACMD5,
748             '(?:hmac-)?sha(?:-?1|-96)?', AUTH_PROTOCOL_HMACSHA,
749             quotemeta AUTH_PROTOCOL_HMACSHA, AUTH_PROTOCOL_HMACSHA,
750             };
751              
752             sub _auth_protocol
753             {
754 1     1   3 my ($this, $proto) = @_;
755              
756 1 50       4 if (@_ < 2) {
757 0         0 return $this->{_auth_protocol};
758             }
759              
760 1 50       4 if ($proto eq q{}) {
761 0         0 return $this->_error('An empty authProtocol was specified');
762             }
763              
764 1         3 for (keys %{$protocols}) {
  1         4  
765 2 100       59 if ($proto =~ /^$_$/i) {
766 1         4 return $this->{_auth_protocol} = $protocols->{$_};
767             }
768             }
769              
770 0         0 return $this->_error('The authProtocol "%s" is unknown', $proto);
771             }
772              
773             }
774              
775             sub _priv_key
776             {
777 0     0   0 my ($this, $priv_key) = @_;
778              
779 0 0       0 if (@_ == 2) {
780 0 0       0 if ($priv_key =~ m/^(?:0x)?([A-F0-9]+)$/i) {
781 0 0       0 $this->{_priv_key} = pack 'H*', length($1) % 2 ? '0'.$1 : $1;
782 0 0       0 if (!defined $this->_priv_key_validate()) {
783 0         0 return $this->_error();
784             }
785             } else {
786 0         0 return $this->_error(
787             'The privKey "%s" is expected in hexadecimal format', $priv_key
788             );
789             }
790             }
791              
792 0         0 return $this->{_priv_key};
793             }
794              
795             sub _priv_password
796             {
797 2     2   4 my ($this, $priv_password) = @_;
798              
799 2 50       6 if (@_ == 2) {
800 2 50       4 if ($priv_password eq q{}) {
801 0         0 return $this->_error('An empty privacy password was specified');
802             }
803 2         3 $this->{_priv_password} = $priv_password;
804             }
805              
806 2         5 return $this->{_priv_password};
807             }
808              
809             {
810             my $protocols = {
811             '(?:cbc-)?des', PRIV_PROTOCOL_DES,
812             quotemeta PRIV_PROTOCOL_DES, PRIV_PROTOCOL_DES,
813             '(?:cbc-)?(?:3|triple-)des(?:-?ede)?', PRIV_PROTOCOL_DRAFT_3DESEDE,
814             quotemeta PRIV_PROTOCOL_DRAFT_3DESEDE, PRIV_PROTOCOL_DRAFT_3DESEDE,
815             '(?:(?:cfb)?128-?)?aes(?:-?128)?', PRIV_PROTOCOL_AESCFB128,
816             quotemeta PRIV_PROTOCOL_AESCFB128, PRIV_PROTOCOL_AESCFB128,
817             quotemeta PRIV_PROTOCOL_DRAFT_AESCFB128, PRIV_PROTOCOL_AESCFB128,
818             '(?:(?:cfb)?192-?)aes(?:-?128)?', PRIV_PROTOCOL_DRAFT_AESCFB192,
819             quotemeta PRIV_PROTOCOL_DRAFT_AESCFB192, PRIV_PROTOCOL_DRAFT_AESCFB192,
820             '(?:(?:cfb)?256-?)aes(?:-?128)?', PRIV_PROTOCOL_DRAFT_AESCFB256,
821             quotemeta PRIV_PROTOCOL_DRAFT_AESCFB256, PRIV_PROTOCOL_DRAFT_AESCFB256,
822             };
823              
824             sub _priv_protocol
825             {
826 2     2   4 my ($this, $proto) = @_;
827              
828 2 50       6 if (@_ < 2) {
829 0         0 return $this->{_priv_protocol};
830             }
831              
832 2 50       10 if ($proto eq q{}) {
833 0         0 return $this->_error('An empty privProtocol was specified');
834             }
835              
836 2         4 my $priv_proto;
837              
838 2         4 for (keys %{$protocols}) {
  2         9  
839 18 100       269 if ($proto =~ /^$_$/i) {
840 2         4 $priv_proto = $protocols->{$_};
841 2         5 last;
842             }
843             }
844              
845 2 50       8 if (!defined $priv_proto) {
846 0         0 return $this->_error('The privProtocol "%s" is unknown', $proto);
847             }
848              
849             # Validate the support of the AES cipher algorithm. Attempt to
850             # load the Crypt::Rijndael module. If this module is not found,
851             # do not provide support for the AES Cipher Algorithm.
852              
853 2 50 33     3 if (($priv_proto eq PRIV_PROTOCOL_AESCFB128) ||
      33        
854             ($priv_proto eq PRIV_PROTOCOL_DRAFT_AESCFB192) ||
855             ($priv_proto eq PRIV_PROTOCOL_DRAFT_AESCFB256))
856             {
857 0 0       0 if (defined (my $error = load_module('Crypt::Rijndael'))) {
858 0         0 return $this->_error(
859             'Support for privProtocol "%s" is unavailable %s', $proto, $error
860             );
861             }
862             }
863              
864 2         11 return $this->{_priv_protocol} = $priv_proto;
865             }
866              
867             }
868              
869             sub _engine_boots
870             {
871 2 50   2   10 return ($_[0]->{_synchronized}) ? $_[0]->{_engine_boots} : 0;
872             }
873              
874             sub _engine_time
875             {
876 0     0   0 my ($this) = @_;
877              
878 0 0       0 return 0 if (!$this->{_synchronized});
879              
880 0         0 $this->{_engine_time} = time() - $this->{_time_epoc};
881              
882 0 0       0 if ($this->{_engine_time} > 2147483647) {
883 0         0 DEBUG_INFO('snmpEngineTime rollover');
884 0 0       0 if (++$this->{_engine_boots} == 2147483647) {
885 0         0 die 'FATAL: Unable to handle snmpEngineBoots value';
886             }
887 0         0 $this->{_engine_time} -= 2147483647;
888 0         0 $this->{_time_epoc} = time() - $this->{_engine_time};
889 0 0       0 if (!$this->{_authoritative}) {
890 0         0 $this->{_synchronized} = FALSE;
891 0         0 return $this->{_latest_engine_time} = 0;
892             }
893             }
894              
895 0 0       0 if ($this->{_engine_time} < 0) {
896 0         0 die 'FATAL: Unable to handle negative snmpEngineTime value';
897             }
898              
899 0         0 return $this->{_engine_time};
900             }
901              
902             sub _security_params
903             {
904 6     6   7 my ($this) = @_;
905              
906             # Clear any previous error messages
907 6         25 $this->_error_clear();
908              
909             # We must have an usmUserName
910 6 50       15 if ($this->{_user_name} eq q{}) {
911 0         0 return $this->_error('The required userName was not specified');
912             }
913              
914             # Define the authentication parameters
915              
916 6 100 100     26 if ((defined $this->{_auth_password}) && ($this->{_discovered})) {
917 2 50       43 if (!defined $this->{_auth_key}) {
918 2 50       5 return $this->_error() if !defined $this->_auth_key_generate();
919             }
920 2         6 $this->{_auth_password} = undef;
921             }
922              
923 6 100       14 if (defined $this->{_auth_key}) {
924              
925             # Validate the key based on the protocol
926 4 50       16 if (!defined $this->_auth_key_validate()) {
927 0         0 return $this->_error('The authKey is invalid');
928             }
929              
930             # Initialize the authentication data
931 4 50       15 if (!defined $this->_auth_data_init()) {
932 0         0 return $this->_error('Failed to initialize the authentication data');
933             }
934              
935 4 50       10 if ($this->{_discovered}) {
936 4         14 $this->{_security_level} = SECURITY_LEVEL_AUTHNOPRIV;
937             }
938              
939             }
940              
941             # You must have authentication to have privacy
942              
943 6 50 66     19 if (!defined ($this->{_auth_key}) && !defined $this->{_auth_password}) {
944 0 0 0     0 if (defined ($this->{_priv_key}) || defined $this->{_priv_password}) {
945 0         0 return $this->_error(
946             'The securityLevel is unsupported (privacy requires authentication)'
947             );
948             }
949             }
950              
951             # Define the privacy parameters
952              
953 6 100 100     28 if ((defined $this->{_priv_password}) && ($this->{_discovered})) {
954 2 50       7 if (!defined $this->{_priv_key}) {
955 2 50       6 return $this->_error() if !defined $this->_priv_key_generate();
956             }
957 2         6 $this->{_priv_password} = undef;
958             }
959              
960 6 100       14 if (defined $this->{_priv_key}) {
961              
962             # Validate the key based on the protocol
963 4 50       52 if (!defined $this->_priv_key_validate()) {
964 0         0 return $this->_error('The privKey is invalid');
965             }
966              
967             # Initialize the privacy data
968 4 50       13 if (!defined $this->_priv_data_init()) {
969 0         0 return $this->_error('Failed to initialize the privacy data');
970             }
971              
972 4 50       13 if ($this->{_discovered}) {
973 4         13 $this->{_security_level} = SECURITY_LEVEL_AUTHPRIV;
974             }
975              
976             }
977              
978 6         20 DEBUG_INFO('securityLevel = %d', $this->{_security_level});
979              
980 6         22 return $this->{_security_level};
981             }
982              
983             sub _engine_id_discovery
984             {
985 2     2   33 my ($this, $engine_id) = @_;
986              
987 2 50       7 return TRUE if ($this->{_authoritative});
988              
989 2   50     12 DEBUG_INFO('engineID = 0x%s', unpack 'H*', $engine_id || q{});
990              
991 2 50 33     18 if (length($engine_id) < 5 || length($engine_id) > 32) {
992 0         0 return $this->_error(
993             'The msgAuthoritativeEngineID length of %d is out of range (5..32)',
994             length $engine_id
995             );
996             }
997              
998 2         3 $this->{_engine_id} = $engine_id;
999 2         9 $this->{_discovered} = TRUE;
1000              
1001 2 50       5 if (!defined $this->_security_params()) {
1002 0         0 $this->{_discovered} = FALSE;
1003 0         0 return $this->_error();
1004             }
1005              
1006 2         4 return TRUE;
1007             }
1008              
1009             sub _synchronize
1010             {
1011 2     2   14 my ($this, $msg_boots, $msg_time) = @_;
1012              
1013 2 50       6 return TRUE if ($this->{_authoritative});
1014 2 50       7 return TRUE if ($this->{_security_level} < SECURITY_LEVEL_AUTHNOPRIV);
1015              
1016 2 50 0     8 if (($msg_boots > $this->_engine_boots()) ||
      33        
1017             (($msg_boots == $this->_engine_boots()) &&
1018             ($msg_time > $this->{_latest_engine_time})))
1019             {
1020 2         6 DEBUG_INFO(
1021             'update: engineBoots = %d, engineTime = %d', $msg_boots, $msg_time
1022             );
1023              
1024 2         3 $this->{_engine_boots} = $msg_boots;
1025 2         8 $this->{_latest_engine_time} = $this->{_engine_time} = $msg_time;
1026 2         6 $this->{_time_epoc} = time() - $this->{_engine_time};
1027              
1028 2 50       7 if (!$this->{_synchronized}) {
1029 2         5 $this->{_synchronized} = TRUE;
1030 2 50       7 if (!defined $this->_security_params()) {
1031 0         0 return ($this->{_synchronized} = FALSE);
1032             }
1033             }
1034              
1035 2         5 return TRUE;
1036             }
1037              
1038             DEBUG_INFO(
1039 0         0 'no update: engineBoots = %d, msgBoots = %d; ' .
1040             'latestTime = %d, msgTime = %d',
1041             $this->_engine_boots(), $msg_boots,
1042             $this->{_latest_engine_time}, $msg_time
1043             );
1044              
1045 0         0 return TRUE;
1046             }
1047              
1048             sub _timeliness
1049             {
1050 0     0   0 my ($this, $msg_boots, $msg_time) = @_;
1051              
1052 0 0       0 return TRUE if ($this->{_security_level} < SECURITY_LEVEL_AUTHNOPRIV);
1053              
1054             # Retrieve a local copy of our snmpEngineBoots and snmpEngineTime
1055             # to avoid the possibilty of using different values in each of
1056             # the comparisons.
1057              
1058 0         0 my $engine_time = $this->_engine_time();
1059 0         0 my $engine_boots = $this->_engine_boots();
1060              
1061 0 0       0 if ($engine_boots == 2147483647) {
1062 0         0 $this->{_synchronized} = FALSE;
1063 0         0 return $this->_error('The system is not in the time window');
1064             }
1065              
1066 0 0       0 if (!$this->{_authoritative}) {
1067              
1068 0 0       0 if ($msg_boots < $engine_boots) {
1069 0         0 return $this->_error('The message is not in the time window');
1070             }
1071 0 0 0     0 if (($msg_boots == $engine_boots) && ($msg_time < ($engine_time - 150))) {
1072 0         0 return $this->_error('The message is not in the time window');
1073             }
1074              
1075             } else {
1076              
1077 0 0       0 if ($msg_boots != $engine_boots) {
1078 0         0 return $this->_error('The message is not in the time window');
1079             }
1080 0 0 0     0 if (($msg_time < ($engine_time - 150)) ||
1081             ($msg_time > ($engine_time + 150)))
1082             {
1083 0         0 return $this->_error('The message is not in the time window');
1084             }
1085              
1086             }
1087              
1088 0         0 return TRUE;
1089             }
1090              
1091             sub _authenticate_outgoing_msg
1092             {
1093 0     0   0 my ($this, $msg, $auth_location) = @_;
1094              
1095 0 0       0 if (!$auth_location) {
1096 0         0 return $this->_error(
1097             'Authentication failure (Unable to set msgAuthenticationParameters)'
1098             );
1099             }
1100              
1101             # Set the msgAuthenticationParameters
1102 0         0 substr ${$msg->reference}, -$auth_location, 12, $this->_auth_hmac($msg);
  0         0  
1103              
1104 0         0 return TRUE;
1105             }
1106              
1107             sub _authenticate_incoming_msg
1108             {
1109 0     0   0 my ($this, $msg, $auth_params) = @_;
1110              
1111             # Authenticate the message
1112 0 0       0 if ($auth_params ne $this->_auth_hmac($msg)) {
1113 0         0 return $this->_error('Authentication failure');
1114             }
1115              
1116 0         0 DEBUG_INFO('authentication passed');
1117              
1118 0         0 return TRUE;
1119             }
1120              
1121             sub _auth_hmac
1122             {
1123 4     4   207 my ($this, $msg) = @_;
1124              
1125 4 50 33     18 return q{} if (!defined($this->{_auth_data}) || !defined $msg);
1126              
1127 4         60 return substr
1128 4         15 $this->{_auth_data}->reset()->add(${$msg->reference()})->digest(), 0, 12;
1129             }
1130              
1131             sub _auth_data_init
1132             {
1133 4     4   7 my ($this) = @_;
1134              
1135 4 50       10 if (!defined $this->{_auth_key}) {
1136 0         0 return $this->_error('The required authKey is not defined');
1137             }
1138              
1139 4 100       16 return TRUE if defined $this->{_auth_data};
1140              
1141 2 100       4 if ($this->{_auth_protocol} eq AUTH_PROTOCOL_HMACMD5) {
    50          
1142              
1143 1         9 $this->{_auth_data} =
1144             Digest::HMAC->new($this->{_auth_key}, 'Digest::MD5');
1145              
1146             } elsif ($this->{_auth_protocol} eq AUTH_PROTOCOL_HMACSHA) {
1147              
1148 1         9 $this->{_auth_data} =
1149             Digest::HMAC->new($this->{_auth_key}, 'Digest::SHA1');
1150              
1151             } else {
1152              
1153 0         0 return $this->_error(
1154             'The authProtocol "%s" is unknown', $this->{_auth_protocol}
1155             );
1156              
1157             }
1158              
1159 2         59 return TRUE;
1160             }
1161              
1162             {
1163             my $encrypt =
1164             {
1165             PRIV_PROTOCOL_DES, \&_priv_encrypt_des,
1166             PRIV_PROTOCOL_DRAFT_3DESEDE, \&_priv_encrypt_3desede,
1167             PRIV_PROTOCOL_AESCFB128, \&_priv_encrypt_aescfbxxx,
1168             PRIV_PROTOCOL_DRAFT_AESCFB192, \&_priv_encrypt_aescfbxxx,
1169             PRIV_PROTOCOL_DRAFT_AESCFB256, \&_priv_encrypt_aescfbxxx
1170             };
1171              
1172             sub _encrypt_data
1173             {
1174             # my ($this, $msg, $priv_params, $plain) = @_;
1175              
1176 2 50   2   16 if (!exists $encrypt->{$_[0]->{_priv_protocol}}) {
1177 0         0 return $_[0]->_error('Encryption error (Unknown protocol)');
1178             }
1179              
1180 2 50       17 if (!defined
1181 2         9 $_[1]->prepare(
1182             OCTET_STRING,
1183             $_[0]->${\$encrypt->{$_[0]->{_priv_protocol}}}($_[2], $_[3])
1184             )
1185             )
1186             {
1187 0         0 return $_[0]->_error('Encryption error');
1188             }
1189              
1190             # Set the PDU buffer equal to the encryptedPDU
1191 2         8 return $_[3] = $_[1]->clear();
1192             }
1193             }
1194              
1195             {
1196             my $decrypt =
1197             {
1198             PRIV_PROTOCOL_DES, \&_priv_decrypt_des,
1199             PRIV_PROTOCOL_DRAFT_3DESEDE, \&_priv_decrypt_3desede,
1200             PRIV_PROTOCOL_AESCFB128, \&_priv_decrypt_aescfbxxx,
1201             PRIV_PROTOCOL_DRAFT_AESCFB192, \&_priv_decrypt_aescfbxxx,
1202             PRIV_PROTOCOL_DRAFT_AESCFB256, \&_priv_decrypt_aescfbxxx
1203             };
1204              
1205             sub _decrypt_data
1206             {
1207             # my ($this, $msg, $priv_params, $cipher) = @_;
1208              
1209             # Make sure there is data to decrypt.
1210 2 50   2   7 if (!defined $_[3]) {
1211 0   0     0 return $_[0]->_error($_[1]->error() || 'Decryption error (No data)');
1212             }
1213              
1214 2 50       9 if (!exists $decrypt->{$_[0]->{_priv_protocol}}) {
1215 0         0 return $_[0]->_error('Decryption error (Unknown protocol)');
1216             }
1217              
1218             # Clear the Message buffer
1219 2         6 $_[1]->clear();
1220              
1221             # Put the decrypted data back into the Message buffer
1222 2 50       6 if (!defined
1223 2         8 $_[1]->prepend(
1224             $_[0]->${\$decrypt->{$_[0]->{_priv_protocol}}}($_[2], $_[3])
1225             )
1226             )
1227             {
1228 0         0 return $_[0]->_error($_[1]->error());
1229             }
1230 2 50       7 return $_[0]->_error($_[1]->error()) if (!$_[1]->length());
1231              
1232             # See if the decrypted data starts with a SEQUENCE
1233             # and has a reasonable length.
1234              
1235 2         7 my $msglen = $_[1]->process(SEQUENCE);
1236 2 50 33     14 if ((!defined $msglen) || ($msglen > $_[1]->length())) {
1237 0         0 return $_[0]->_error('Decryption error');
1238             }
1239 2         8 $_[1]->index(0); # Reset the index
1240              
1241 2         6 DEBUG_INFO('privacy passed');
1242              
1243 2         5 return TRUE;
1244             }
1245             }
1246              
1247             sub _priv_data_init
1248             {
1249 4     4   7 my ($this) = @_;
1250              
1251 4 50       12 if (!defined $this->{_priv_key}) {
1252 0         0 return $this->_error('The required privKey is not defined');
1253             }
1254              
1255 4 100       13 return TRUE if defined $this->{_priv_data};
1256              
1257 2         6 my $init =
1258             {
1259             PRIV_PROTOCOL_DES, \&_priv_data_init_des,
1260             PRIV_PROTOCOL_DRAFT_3DESEDE, \&_priv_data_init_3desede,
1261             PRIV_PROTOCOL_AESCFB128, \&_priv_data_init_aescfbxxx,
1262             PRIV_PROTOCOL_DRAFT_AESCFB192, \&_priv_data_init_aescfbxxx,
1263             PRIV_PROTOCOL_DRAFT_AESCFB256, \&_priv_data_init_aescfbxxx
1264             };
1265              
1266 2 50       9 if (!exists $init->{$this->{_priv_protocol}}) {
1267 0         0 return $this->_error(
1268             'The privProtocol "%s" is unknown', $this->{_priv_protocol}
1269             );
1270             }
1271              
1272 2         4 return $this->${\$init->{$this->{_priv_protocol}}}();
  2         9  
1273             }
1274              
1275             sub _priv_data_init_des
1276             {
1277 2     2   4 my ($this) = @_;
1278              
1279 2 50       7 if (!defined $this->{_priv_key}) {
1280 0         0 return $this->_error('The required privKey is not defined');
1281             }
1282              
1283             # Create the DES object
1284 2         19 $this->{_priv_data}->{des} =
1285             Crypt::DES->new(substr $this->{_priv_key}, 0, 8);
1286              
1287             # Extract the pre-IV
1288 2         59 $this->{_priv_data}->{pre_iv} = substr $this->{_priv_key}, 8, 8;
1289              
1290             # Initialize the salt
1291 2         10 $this->{_priv_data}->{salt} = int rand ~0;
1292              
1293 2         6 return TRUE;
1294             }
1295              
1296             sub _priv_encrypt_des
1297             {
1298             # my ($this, $priv_params, $plain) = @_;
1299              
1300 2 50   2   8 if (!defined $_[0]->{_priv_data}) {
1301 0         0 return $_[0]->_error('The required privacy data is not defined');
1302             }
1303              
1304             # Always pad the plain text data. "The actual pad value is
1305             # irrelevant..." according RFC 3414 Section 8.1.1.2. However,
1306             # there are some agents out there that expect "standard block
1307             # padding" where each of the padding byte(s) are set to the size
1308             # of the padding (even for data that is a multiple of block size).
1309              
1310 2         7 my $pad = 8 - (length($_[2]) % 8);
1311 2         8 $_[2] .= pack('C', $pad) x $pad;
1312              
1313             # Create and set the salt
1314 2 50       7 if ($_[0]->{_priv_data}->{salt}++ == ~0) {
1315 0         0 $_[0]->{_priv_data}->{salt} = 0;
1316             }
1317 2         10 $_[1] = pack 'NN', $_[0]->{_engine_boots}, $_[0]->{_priv_data}->{salt};
1318              
1319             # Create the initial vector (IV)
1320 2         7 my $iv = $_[0]->{_priv_data}->{pre_iv} ^ $_[1];
1321              
1322 2         3 my $cipher = q{};
1323              
1324             # Perform Cipher Block Chaining (CBC)
1325 2         18 while ($_[2] =~ /(.{8})/gs) {
1326 10         104 $cipher .= $iv = $_[0]->{_priv_data}->{des}->encrypt($1 ^ $iv);
1327             }
1328              
1329 2         19 return $cipher;
1330             }
1331              
1332             sub _priv_decrypt_des
1333             {
1334             # my ($this, $priv_params, $cipher) = @_;
1335              
1336 2 50   2   6 if (!defined $_[0]->{_priv_data}) {
1337 0         0 return $_[0]->_error('The required privacy data is not defined');
1338             }
1339              
1340 2 50       7 if (length($_[1]) != 8) {
1341 0         0 return $_[0]->_error(
1342             'The msgPrivParameters length of %d is invalid', length $_[1]
1343             );
1344             }
1345              
1346 2 50       7 if (length($_[2]) % 8) {
1347 0         0 return $_[0]->_error(
1348             'The DES cipher length is not a multiple of the block size'
1349             );
1350             }
1351              
1352             # Create the initial vector (IV)
1353 2         4 my $iv = $_[0]->{_priv_data}->{pre_iv} ^ $_[1];
1354              
1355 2         3 my $plain = q{};
1356              
1357             # Perform Cipher Block Chaining (CBC)
1358 2         10 while ($_[2] =~ /(.{8})/gs) {
1359 10         33 $plain .= $iv ^ $_[0]->{_priv_data}->{des}->decrypt($1);
1360 10         74 $iv = $1;
1361             }
1362              
1363 2         10 return $plain;
1364             }
1365              
1366             sub _priv_data_init_3desede
1367             {
1368 0     0   0 my ($this) = @_;
1369              
1370 0 0       0 if (!defined $this->{_priv_key}) {
1371 0         0 return $this->_error('The required privKey is not defined');
1372             }
1373              
1374             # Create the 3 DES objects
1375              
1376 0         0 $this->{_priv_data}->{des1} =
1377             Crypt::DES->new(substr $this->{_priv_key}, 0, 8);
1378 0         0 $this->{_priv_data}->{des2} =
1379             Crypt::DES->new(substr $this->{_priv_key}, 8, 8);
1380 0         0 $this->{_priv_data}->{des3} =
1381             Crypt::DES->new(substr $this->{_priv_key}, 16, 8);
1382              
1383             # Extract the pre-IV
1384 0         0 $this->{_priv_data}->{pre_iv} = substr $this->{_priv_key}, 24, 8;
1385              
1386             # Initialize the salt
1387 0         0 $this->{_priv_data}->{salt} = int rand ~0;
1388              
1389             # Assign a hash algorithm to "bit spread" the salt
1390              
1391 0 0       0 if ($this->{_auth_protocol} eq AUTH_PROTOCOL_HMACMD5) {
    0          
1392 0         0 $this->{_priv_data}->{hash} = Digest::MD5->new();
1393             } elsif ($this->{_auth_protocol} eq AUTH_PROTOCOL_HMACSHA) {
1394 0         0 $this->{_priv_data}->{hash} = Digest::SHA1->new();
1395             }
1396              
1397 0         0 return TRUE;
1398             }
1399              
1400             sub _priv_encrypt_3desede
1401             {
1402             # my ($this, $priv_params, $plain) = @_;
1403              
1404 0 0   0   0 if (!defined $_[0]->{_priv_data}) {
1405 0         0 return $_[0]->_error('The required privacy data is not defined');
1406             }
1407              
1408             # Pad the plain text data using "standard block padding".
1409 0         0 my $pad = 8 - (length($_[2]) % 8);
1410 0         0 $_[2] .= pack('C', $pad) x $pad;
1411              
1412             # Create and set the salt
1413 0 0       0 if ($_[0]->{_priv_data}->{salt}++ == ~0) {
1414 0         0 $_[0]->{_priv_data}->{salt} = 0;
1415             }
1416 0         0 $_[1] = pack 'NN', $_[0]->{_engine_boots}, $_[0]->{_priv_data}->{salt};
1417              
1418             # Draft 3DES-EDE for USM Section 5.1.1.1.2 - "To achieve effective
1419             # bit spreading, the complete 8-octet 'salt' value SHOULD be
1420             # hashed using the usmUserAuthProtocol."
1421              
1422 0 0       0 if (exists $_[0]->{_priv_data}->{hash}) {
1423 0         0 $_[1] = substr $_[0]->{_priv_data}->{hash}->add($_[1])->digest(), 0, 8;
1424             }
1425              
1426             # Create the initial vector (IV)
1427 0         0 my $iv = $_[0]->{_priv_data}->{pre_iv} ^ $_[1];
1428              
1429 0         0 my $cipher = q{};
1430              
1431             # Perform Cipher Block Chaining (CBC)
1432 0         0 while ($_[2] =~ /(.{8})/gs) {
1433 0         0 $cipher .= $iv =
1434             $_[0]->{_priv_data}->{des3}->encrypt(
1435             $_[0]->{_priv_data}->{des2}->decrypt(
1436             $_[0]->{_priv_data}->{des1}->encrypt($1 ^ $iv)
1437             )
1438             );
1439             }
1440              
1441 0         0 return $cipher;
1442             }
1443              
1444             sub _priv_decrypt_3desede
1445             {
1446             # my ($this, $priv_params, $cipher) = @_;
1447              
1448 0 0   0   0 if (!defined $_[0]->{_priv_data}) {
1449 0         0 return $_[0]->_error('The required privacy data is not defined');
1450             }
1451              
1452 0 0       0 if (length($_[1]) != 8) {
1453 0         0 return $_[0]->_error(
1454             'The msgPrivParameters length of %d is invalid', length $_[1]
1455             );
1456             }
1457              
1458 0 0       0 if (length($_[2]) % 8) {
1459 0         0 return $_[0]->_error(
1460             'The CBC-3DES-EDE cipher length is not a multiple of the block size'
1461             );
1462             }
1463              
1464             # Create the initial vector (IV)
1465 0         0 my $iv = $_[0]->{_priv_data}->{pre_iv} ^ $_[1];
1466              
1467 0         0 my $plain = q{};
1468              
1469             # Perform Cipher Block Chaining (CBC)
1470 0         0 while ($_[2] =~ /(.{8})/gs) {
1471 0         0 $plain .=
1472             $iv ^ $_[0]->{_priv_data}->{des1}->decrypt(
1473             $_[0]->{_priv_data}->{des2}->encrypt(
1474             $_[0]->{_priv_data}->{des3}->decrypt($1)
1475             )
1476             );
1477 0         0 $iv = $1;
1478             }
1479              
1480 0         0 return $plain;
1481             }
1482              
1483             sub _priv_data_init_aescfbxxx
1484             {
1485 0     0   0 my ($this) = @_;
1486              
1487 0 0       0 if (!defined $this->{_priv_key}) {
1488 0         0 return $this->_error('The required privKey is not defined');
1489             }
1490              
1491             {
1492             # Avoid a "strict subs" error if Crypt::Rijndael is not loaded.
1493 1     1   15 no strict 'subs';
  1         3  
  1         1646  
  0         0  
1494              
1495             # Create the AES (Rijndael) object with a 128, 192, or 256 bit key.
1496              
1497 0         0 $this->{_priv_data}->{aes} =
1498             Crypt::Rijndael->new($this->{_priv_key}, Crypt::Rijndael::MODE_CFB());
1499             }
1500              
1501             # Initialize the salt
1502 0         0 $this->{_priv_data}->{salt1} = int rand ~0;
1503 0         0 $this->{_priv_data}->{salt2} = int rand ~0;
1504              
1505 0         0 return TRUE;
1506             }
1507              
1508             sub _priv_encrypt_aescfbxxx
1509             {
1510             # my ($this, $priv_params, $plain) = @_;
1511              
1512 0 0   0   0 if (!defined $_[0]->{_priv_data}) {
1513 0         0 return $_[0]->_error('The required privacy data is not defined');
1514             }
1515              
1516             # Validate the plain text length
1517 0         0 my $length = length $_[2];
1518 0 0       0 if ($length <= 16) {
1519 0         0 return $_[0]->_error(
1520             'The AES plain text length is not greater than the block size'
1521             );
1522             }
1523              
1524             # Create and set the salt
1525 0 0       0 if ($_[0]->{_priv_data}->{salt1}++ == ~0) {
1526 0         0 $_[0]->{_priv_data}->{salt1} = 0;
1527 0 0       0 if ($_[0]->{_priv_data}->{salt2}++ == ~0) {
1528 0         0 $_[0]->{_priv_data}->{salt2} = 0;
1529             }
1530             }
1531 0         0 $_[1] = pack 'NN', $_[0]->{_priv_data}->{salt2},
1532             $_[0]->{_priv_data}->{salt1};
1533              
1534             # AES in the USM Section - Section 3.1.3 "The last ciphertext
1535             # block is produced by exclusive-ORing the last plaintext segment
1536             # of r bits (r is less or equal to 128) with the segment of the r
1537             # most significant bits of the last output block."
1538              
1539             # This operation is identical to those performed on the previous
1540             # blocks except for the fact that the block can be less than the
1541             # block size. We can just pad the last block and operate on it as
1542             # usual and then ignore the padding after encrypting.
1543              
1544 0         0 $_[2] .= "\000" x (16 - ($length % 16));
1545              
1546             # Create the IV by concatenating "...the generating SNMP engine's
1547             # 32-bit snmpEngineBoots, the SNMP engine's 32-bit snmpEngineTime,
1548             # and a local 64-bit integer..."
1549              
1550 0         0 $_[0]->{_priv_data}->{aes}->set_iv(
1551             pack('NN', $_[0]->{_engine_boots}, $_[0]->{_engine_time}) . $_[1]
1552             );
1553              
1554             # Let the Crypt::Rijndael module perform 128 bit Cipher Feedback
1555             # (CFB) and return the result minus the "internal" padding.
1556              
1557 0         0 return substr $_[0]->{_priv_data}->{aes}->encrypt($_[2]), 0, $length;
1558             }
1559              
1560             sub _priv_decrypt_aescfbxxx
1561             {
1562             # my ($this, $priv_params, $cipher) = @_;
1563              
1564 0 0   0   0 if (!defined $_[0]->{_priv_data}) {
1565 0         0 return $_[0]->_error('The required privacy data is not defined');
1566             }
1567              
1568             # Validate the msgPrivParameters length. We assume that the
1569             # msgAuthoritativeEngineBoots and msgAuthoritativeEngineTime
1570             # have been prepended to the msgPrivParameters to create the
1571             # required 128 bit IV.
1572              
1573 0 0       0 if (length($_[1]) != 16) {
1574 0         0 return $_[0]->_error(
1575             'The AES IV length of %d is invalid', length $_[1]
1576             );
1577             }
1578              
1579             # Validate the cipher length
1580 0         0 my $length = length $_[2];
1581 0 0       0 if ($length <= 16) {
1582 0         0 return $_[0]->_error(
1583             'The AES cipher length is not greater than the block size'
1584             );
1585             }
1586              
1587             # AES in the USM Section - Section 3.1.4 "The last ciphertext
1588             # block (whose size r is less or equal to 128) is less or equal
1589             # to 128) is exclusive-ORed with the segment of the r most
1590             # significant bits of the last output block to recover the last
1591             # plaintext block of r bits."
1592              
1593             # This operation is identical to those performed on the previous
1594             # blocks except for the fact that the block can be less than the
1595             # block size. We can just pad the last block and operate on it as
1596             # usual and then ignore the padding after decrypting.
1597              
1598 0         0 $_[2] .= "\000" x (16 - ($length % 16));
1599              
1600             # Use the msgPrivParameters as the IV.
1601 0         0 $_[0]->{_priv_data}->{aes}->set_iv($_[1]);
1602              
1603             # Let the Crypt::Rijndael module perform 128 bit Cipher Feedback
1604             # (CFB) and return the result minus the "internal" padding.
1605              
1606 0         0 return substr $_[0]->{_priv_data}->{aes}->decrypt($_[2]), 0, $length;
1607             }
1608              
1609             sub _auth_key_generate
1610             {
1611 2     2   4 my ($this) = @_;
1612              
1613 2 50 33     11 if (!defined($this->{_engine_id}) || !defined $this->{_auth_password}) {
1614 0         0 return $this->_error('Unable to generate the authKey');
1615             }
1616              
1617 2         4 $this->{_auth_key} = $this->_password_localize($this->{_auth_password});
1618              
1619 2         10 return $this->{_auth_key};
1620             }
1621              
1622             sub _auth_key_validate
1623             {
1624 4     4   6 my ($this) = @_;
1625              
1626 4         11 my $key_len =
1627             {
1628             AUTH_PROTOCOL_HMACMD5, [ 16, 'HMAC-MD5' ],
1629             AUTH_PROTOCOL_HMACSHA, [ 20, 'HMAC-SHA1' ],
1630             };
1631              
1632 4 50       17 if (!exists $key_len->{$this->{_auth_protocol}}) {
1633 0         0 return $this->_error(
1634             'The authProtocol "%s" is unknown', $this->{_auth_protocol}
1635             );
1636             }
1637              
1638 4 50       18 if (length($this->{_auth_key}) != $key_len->{$this->{_auth_protocol}}->[0])
1639             {
1640 0         0 return $this->_error(
1641             'The %s authKey length of %d is invalid, expected %d',
1642             $key_len->{$this->{_auth_protocol}}->[1], length($this->{_auth_key}),
1643             $key_len->{$this->{_auth_protocol}}->[0]
1644             );
1645             }
1646              
1647 4         17 return TRUE;
1648             }
1649              
1650             sub _priv_key_generate
1651             {
1652 2     2   4 my ($this) = @_;
1653              
1654 2 50 33     10 if (!defined($this->{_engine_id}) || !defined $this->{_priv_password}) {
1655 0         0 return $this->_error('Unable to generate the privKey');
1656             }
1657              
1658 2         7 $this->{_priv_key} = $this->_password_localize($this->{_priv_password});
1659              
1660 2 50       10 return $this->_error() if !defined $this->{_priv_key};
1661              
1662 2 50 33     9 if ($this->{_priv_protocol} eq PRIV_PROTOCOL_DRAFT_3DESEDE) {
    50          
1663              
1664             # Draft 3DES-EDE for USM Section 2.1 - "To acquire the necessary
1665             # number of key bits, the password-to-key algorithm may be chained
1666             # using its output as further input in order to generate an
1667             # appropriate number of key bits."
1668              
1669 0         0 $this->{_priv_key} .= $this->_password_localize($this->{_priv_key});
1670              
1671             } elsif (($this->{_priv_protocol} eq PRIV_PROTOCOL_DRAFT_AESCFB192) ||
1672             ($this->{_priv_protocol} eq PRIV_PROTOCOL_DRAFT_AESCFB256))
1673             {
1674             # Draft AES in the USM Section 3.1.2.1 - "...if the size of the
1675             # localized key is not large enough to generate an encryption
1676             # key... ...set Kul = Kul || Hnnn(Kul) where Hnnn is the hash
1677             # function for the authentication protocol..."
1678              
1679 0         0 my $hnnn;
1680              
1681 0 0       0 if ($this->{_auth_protocol} eq AUTH_PROTOCOL_HMACMD5) {
    0          
1682 0         0 $hnnn = Digest::MD5->new();
1683             } elsif ($this->{_auth_protocol} eq AUTH_PROTOCOL_HMACSHA) {
1684 0         0 $hnnn = Digest::SHA1->new();
1685             } else {
1686 0         0 return $this->_error(
1687             'The authProtocol "%s" is unknown', $this->{_auth_protocol}
1688             );
1689             }
1690              
1691 0         0 $this->{_priv_key} .= $hnnn->add($this->{_priv_key})->digest();
1692              
1693             }
1694              
1695             # Truncate the privKey to the appropriate length.
1696              
1697 2         8 my $key_len =
1698             {
1699             PRIV_PROTOCOL_DES, 16, # RFC 3414 Section 8.2.1
1700             PRIV_PROTOCOL_DRAFT_3DESEDE, 32, # Draft 3DES for USM Section 5.2.1
1701             PRIV_PROTOCOL_AESCFB128, 16, # AES in the USM Section 3.2.1
1702             PRIV_PROTOCOL_DRAFT_AESCFB192, 24, # Draft AES in the USM Section 3.2.1
1703             PRIV_PROTOCOL_DRAFT_AESCFB256, 32 # Draft AES in the USM Section 3.2.1
1704             };
1705              
1706 2 50       10 if (!exists $key_len->{$this->{_priv_protocol}}) {
1707 0         0 return $this->_error(
1708             'The privProtocol "%s" is unknown', $this->{_priv_protocol}
1709             );
1710             }
1711              
1712 2         8 $this->{_priv_key} =
1713             substr $this->{_priv_key}, 0, $key_len->{$this->{_priv_protocol}};
1714              
1715 2         11 return $this->{_priv_key};
1716             }
1717              
1718             sub _priv_key_validate
1719             {
1720 4     4   7 my ($this) = @_;
1721              
1722 4         12 my $key_len =
1723             {
1724             PRIV_PROTOCOL_DES, [ 16, 'CBC-DES' ],
1725             PRIV_PROTOCOL_DRAFT_3DESEDE, [ 32, 'CBC-3DES-EDE' ],
1726             PRIV_PROTOCOL_AESCFB128, [ 16, 'CFB128-AES-128' ],
1727             PRIV_PROTOCOL_DRAFT_AESCFB192, [ 24, 'CFB128-AES-192' ],
1728             PRIV_PROTOCOL_DRAFT_AESCFB256, [ 32, 'CFB128-AES-256' ]
1729             };
1730              
1731 4 50       14 if (!exists $key_len->{$this->{_priv_protocol}}) {
1732 0         0 return $this->_error(
1733             'The privProtocol "%s" is unknown', $this->{_priv_protocol}
1734             );
1735             }
1736              
1737 4 50       16 if (length($this->{_priv_key}) != $key_len->{$this->{_priv_protocol}}->[0])
1738             {
1739 0         0 return $this->_error(
1740             'The %s privKey length of %d is invalid, expected %d',
1741             $key_len->{$this->{_priv_protocol}}->[1], length($this->{_priv_key}),
1742             $key_len->{$this->{_priv_protocol}}->[0]
1743             );
1744             }
1745              
1746 4 50       11 if ($this->{_priv_protocol} eq PRIV_PROTOCOL_DRAFT_3DESEDE) {
1747              
1748             # Draft 3DES-EDE for USM Section 5.1.1.1.1 "The checks for difference
1749             # and weakness... ...should be performed when the key is assigned.
1750             # If any of the mandated tests fail, then the whole key MUST be
1751             # discarded and an appropriate exception noted."
1752              
1753 0 0       0 if (substr($this->{_priv_key}, 0, 8) eq substr $this->{_priv_key}, 8, 8)
1754             {
1755 0         0 return $this->_error(
1756             'The CBC-3DES-EDE privKey is invalid (K1 equals K2)'
1757             );
1758             }
1759              
1760 0 0       0 if (substr($this->{_priv_key}, 8, 8) eq substr $this->{_priv_key}, 16, 8)
1761             {
1762 0         0 return $this->_error(
1763             'The CBC-3DES-EDE privKey is invalid (K2 equals K3)'
1764             );
1765             }
1766              
1767 0 0       0 if (substr($this->{_priv_key}, 0, 8) eq substr $this->{_priv_key}, 16, 8)
1768             {
1769 0         0 return $this->_error(
1770             'The CBC-3DES-EDE privKey is invalid (K1 equals K3)'
1771             );
1772             }
1773              
1774             }
1775              
1776 4         15 return TRUE;
1777             }
1778              
1779             sub _password_localize
1780             {
1781 4     4   15 my ($this, $password) = @_;
1782              
1783 4         7 my $digests =
1784             {
1785             AUTH_PROTOCOL_HMACMD5, 'Digest::MD5',
1786             AUTH_PROTOCOL_HMACSHA, 'Digest::SHA1',
1787             };
1788              
1789 4 50       13 if (!exists $digests->{$this->{_auth_protocol}}) {
1790 0         0 return $this->_error(
1791             'The authProtocol "%s" is unknown', $this->{_auth_protocol}
1792             );
1793             }
1794              
1795 4         24 my $digest = $digests->{$this->{_auth_protocol}}->new;
1796              
1797             # Create the initial digest using the password
1798              
1799 4         21 my $d = my $pad = $password x ((2048 / length $password) + 1);
1800              
1801 4         11 for (my $count = 0; $count < 2**20; $count += 2048) {
1802 2048         30206 $digest->add(substr $d, 0, 2048, q{});
1803 2048         5628 $d .= $pad;
1804             }
1805 4         36 $d = $digest->digest;
1806              
1807             # Localize the key with the authoritativeEngineID
1808              
1809 4         72 return $digest->add($d . $this->{_engine_id} . $d)->digest();
1810             }
1811              
1812             {
1813             my %modules;
1814              
1815             sub load_module
1816             {
1817 0     0 0   my ($module) = @_;
1818              
1819             # We attempt to load the required module under the protection of an
1820             # eval statement. If there is a failure, typically it is due to a
1821             # missing module required by the requested module and we attempt to
1822             # simplify the error message by just listing that module. We also
1823             # need to track failures since require() only produces an error on
1824             # the first attempt to load the module.
1825              
1826             # NOTE: Contrary to our typical convention, a return value of "undef"
1827             # actually means success and a defined value means error.
1828              
1829 0 0         return $modules{$module} if exists $modules{$module};
1830              
1831 0 0         if (!eval "require $module") {
1832 0 0         if ($@ =~ /locate (\S+\.pm)/) {
1833 0           $modules{$module} = sprintf '(Required module %s not found)', $1;
1834             } else {
1835 0           $modules{$module} = sprintf '(%s)', $@;
1836             }
1837             } else {
1838 0           $modules{$module} = undef;
1839             }
1840              
1841 0           return $modules{$module};
1842             }
1843             }
1844              
1845             # ============================================================================
1846             1; # [end Net::SNMP::Security::USM]
1847