Class: NETSNMP::SecurityParameters

Inherits:
Object
  • Object
show all
Includes:
Loggable
Defined in:
lib/netsnmp/security_parameters.rb

Overview

This module encapsulates the public API for encrypting/decrypting and signing/verifying.

It doesn’t interact with other layers from the library, rather it is used and passed all the arguments (consisting mostly of primitive types). It also provides validation of the security options passed with a client is initialized in v3 mode.

Constant Summary collapse

IPAD =
"\x36" * 64
OPAD =
"\x5c" * 64
TIMELINESS_THRESHOLD =

Timeliness is part of SNMP V3 Security The topic is described very nice here www.snmpsharpnet.com/?page_id=28 www.ietf.org/rfc/rfc2574.txt 1.4.1 Timeliness The probe is outdated after 150 seconds which results in a PDU Error, therefore it should expire before that and be renewed The 150 Seconds is specified in www.ietf.org/rfc/rfc2574.txt 2.2.3

150

Constants included from Loggable

Loggable::DEBUG, Loggable::DEBUG_LEVEL

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Loggable

#initialize_logger

Constructor Details

#initialize(username:, engine_id: "", security_level: nil, auth_protocol: nil, auth_password: nil, priv_protocol: nil, priv_password: nil, **options) ⇒ SecurityParameters

Note:

if security level is set to :no_auth_no_priv, all other parameters are optional; if :auth_no_priv, :auth_protocol will be coerced to :md5 (if not explicitly set), and :auth_password is mandatory; if :auth_priv, the sentence before applies, and :priv_protocol will be coerced to :des (if not explicitly set), and :priv_password becomes mandatory.

Returns a new instance of SecurityParameters.

Parameters:

  • username (String)

    the snmp v3 username

  • engine_id (String) (defaults to: "")

    the device engine id (initialized to ” for report)

  • security_level (Symbol, integer) (defaults to: nil)

    allowed snmp v3 security level (:auth_priv, :auth_no_priv, etc)

  • auth_protocol (Symbol, nil) (defaults to: nil)

    a supported authentication protocol (currently supported: :md5, :sha, :sha256)

  • priv_protocol (Symbol, nil) (defaults to: nil)

    a supported privacy protocol (currently supported: :des, :aes)

  • auth_password (String, nil) (defaults to: nil)

    the authentication password

  • priv_password (String, nil) (defaults to: nil)

    the privacy password



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/netsnmp/security_parameters.rb', line 40

def initialize(
  username:,
  engine_id: "",
  security_level: nil,
  auth_protocol: nil,
  auth_password: nil,
  priv_protocol: nil,
  priv_password: nil,
  **options
)
  @security_level = case security_level
                    when /no_?auth/         then 0
                    when /auth_?no_?priv/   then 1
                    when /auth_?priv/       then 3
                    when Integer then security_level
                    else 3 # rubocop:disable Lint/DuplicateBranch
                    end
  @username = username
  @engine_id = engine_id
  @auth_protocol = auth_protocol.to_sym unless auth_protocol.nil?
  @priv_protocol = priv_protocol.to_sym unless priv_protocol.nil?

  if @security_level.positive?
    @auth_protocol ||= :md5 # this is the default
    raise "security level requires an auth password" if auth_password.nil?
    raise "auth password must have between 8 to 32 characters" unless (8..32).cover?(auth_password.length)
  end

  if @security_level > 1
    @priv_protocol ||= :des
    raise "security level requires a priv password" if priv_password.nil?
    raise "priv password must have between 8 to 32 characters" unless (8..32).cover?(priv_password.length)
  end

  @auth_pass_key = passkey(auth_password) if auth_password
  @priv_pass_key = passkey(priv_password) if priv_password
  initialize_logger(**options)
end

Instance Attribute Details

#auth_protocolObject (readonly)

Returns the value of attribute auth_protocol.



25
26
27
# File 'lib/netsnmp/security_parameters.rb', line 25

def auth_protocol
  @auth_protocol
end

#engine_idObject

Returns the value of attribute engine_id.



25
26
27
# File 'lib/netsnmp/security_parameters.rb', line 25

def engine_id
  @engine_id
end

#security_levelObject (readonly)

Returns the value of attribute security_level.



25
26
27
# File 'lib/netsnmp/security_parameters.rb', line 25

def security_level
  @security_level
end

#usernameObject (readonly)

Returns the value of attribute username.



25
26
27
# File 'lib/netsnmp/security_parameters.rb', line 25

def username
  @username
end

Instance Method Details

#decode(der, salt:, engine_time:, engine_boots:, security_level: @security_level) ⇒ Object

Parameters:

  • der (String)

    the encoded der to be decoded

  • salt (String)

    the salt from the incoming der

  • engine_time (Integer)

    the reported engine time

  • engine_boots (Integer)

    the reported engine boots



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/netsnmp/security_parameters.rb', line 110

def decode(der, salt:, engine_time:, engine_boots:, security_level: @security_level)
  asn = OpenSSL::ASN1.decode(der)
  return asn if security_level < 3

  encryptor = encryption
  return asn unless encryptor

  encrypted_pdu = asn.value
  pdu_der = encryptor.decrypt(encrypted_pdu, salt: salt, engine_time: engine_time, engine_boots: engine_boots)
  log(level: 2) { "message has been decrypted" }
  OpenSSL::ASN1.decode(pdu_der)
end

#encode(pdu, salt:, engine_time:, engine_boots:) ⇒ Array

Returns a pair, where the first argument in the asn structure with the encoded pdu, and the second is the calculated salt (if it has been encrypted).

Parameters:

  • pdu (#to_asn, #to_der)

    the pdu to encode (must quack like a asn1 type)

  • salt (String)

    the salt to use

  • engine_time (Integer)

    the reported engine time

  • engine_boots (Integer)

    the reported boots time

Returns:

  • (Array)

    a pair, where the first argument in the asn structure with the encoded pdu, and the second is the calculated salt (if it has been encrypted)



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/netsnmp/security_parameters.rb', line 91

def encode(pdu, salt:, engine_time:, engine_boots:)
  encryptor = encryption

  if encryptor
    encrypted_pdu, salt = encryptor.encrypt(pdu.to_der, engine_boots: engine_boots,
                                                        engine_time: engine_time)
    [
      OpenSSL::ASN1::OctetString.new(encrypted_pdu).with_label(:encrypted_pdu),
      OpenSSL::ASN1::OctetString.new(salt).with_label(:salt)
    ]
  else
    [pdu.to_asn, salt]
  end
end

#must_revalidate?Boolean

Returns:

  • (Boolean)


166
167
168
169
170
171
# File 'lib/netsnmp/security_parameters.rb', line 166

def must_revalidate?
  return @engine_id.empty? unless authorizable?
  return true if @engine_id.empty? || @timeliness.nil?

  (Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) - @timeliness) >= TIMELINESS_THRESHOLD
end

#sign(message) ⇒ String

Note:

this method is used in the process of authenticating a message

Returns the digest signature of the message payload.

Parameters:

  • message (String)

    the already encoded snmp v3 message

Returns:

  • (String)

    the digest signature of the message payload



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/netsnmp/security_parameters.rb', line 127

def sign(message)
  # don't sign unless you have to
  return unless @auth_protocol

  key = auth_key.dup

  # SHA256 => https://datatracker.ietf.org/doc/html/rfc7860#section-4.2.2
  # The 24 first octets of HMAC are taken as the computed MAC value
  return OpenSSL::HMAC.digest("SHA256", key, message)[0, 24] if @auth_protocol == :sha256

  # MD5 => https://datatracker.ietf.org/doc/html/rfc3414#section-6.3.2
  # SHA1 => https://datatracker.ietf.org/doc/html/rfc3414#section-7.3.2
  key << ("\x00" * (@auth_protocol == :md5 ? 48 : 44))
  k1 = key.xor(IPAD)
  k2 = key.xor(OPAD)

  digest.reset
  digest << (k1 + message)
  d1 = digest.digest

  digest.reset
  digest << (k2 + d1)
  # The 12 first octets of the digest are taken as the computed MAC value
  digest.digest[0, 12]
end

#verify(stream, salt, security_level: @security_level) ⇒ Object

Parameters:

  • stream (String)

    the encoded incoming payload

  • salt (String)

    the incoming payload”s salt

Raises:

  • (NETSNMP::Error)

    if the message’s integration has been violated



157
158
159
160
161
162
163
164
# File 'lib/netsnmp/security_parameters.rb', line 157

def verify(stream, salt, security_level: @security_level)
  return if security_level.nil? || security_level < 1

  verisalt = sign(stream)
  raise Error, "invalid message authentication salt" unless verisalt == salt

  log(level: 2) { "message has been verified" }
end