etingof / pysnmp

Python SNMP library
http://snmplabs.com/pysnmp/
BSD 2-Clause "Simplified" License
576 stars 196 forks source link

How to change USM authKey and privKey for a user? #306

Open mreams13 opened 5 years ago

mreams13 commented 5 years ago

Using net-snmp's snmpusm, I can change a user's authKey and privKey by doing this: C:\net-snmp\bin\snmpusm -v 3 -u snmpadmin -l authPriv -a SHA -A "SekretSqwerrel" -x AES -X "SekretSqwerrel" fdc8:1437:b763:07eb::1 passwd "SekretSqwerrel" "changeme" normaluser

This command succeeds (normaluser is originally cloned from snmpadmin.)

I'm trying to do the same from pysnmp, but I'm running into the problem that I have no idea what to do with the passwords. (For this purpose authKey and privKey are the exact same.)

I see the process from RFC3414, but I can't find where anyone actually defines what makes up the kcValue. (I also found from Wireshark that net-snmp doesn't bother with the usmUserSpinLock or the usmUserPublic, so I can discard those.)

The recommended way to do a key change is as follows: 1) GET(usmUserSpinLock.0) and save in sValue. 2) generate the keyChange value based on the old (existing) secret key and the new secret key, let us call this kcValue.

             If you do the key change on behalf of another user:
               3) SET(usmUserSpinLock.0=sValue,
                      usmUserAuthKeyChange=kcValue
                      usmUserPublic=randomValue)

I'm want to do something like below...

        USM_TABLE_OID = "1.3.6.1.6.3.15.1.2.2.1"
    user_name = "normaluser"
    byte_encoded_name = "." + str(len(user_name)) + "." + ".".join(str(ord(f)) for f in user_name)
    auth_key_change_oid = USM_TABLE_OID + ".6." + engine_id + byte_encoded_name
    priv_key_change_oid = USM_TABLE_OID + ".9." + engine_id + byte_encoded_name

    error_indication, error_status, error_index, var_binds = next(
        pysnmp.hlapi.setCmd(
            pysnmp.hlapi.SnmpEngine(),
            usm_user_data,
            pysnmp.hlapi.Udp6TransportTarget((target_address, 161)),
            pysnmp.hlapi.ContextData(),
            pysnmp.hlapi.ObjectType(
                pysnmp.hlapi.ObjectIdentity(auth_key_change_oid),
                unknown_kcvalue1, # based on the old (existing) secret key and the new secret key, and the engine ID
            ),
            pysnmp.hlapi.ObjectType(
                pysnmp.hlapi.ObjectIdentity(priv_key_change_oid),
                unknown_kcvalue2, # based on the old (existing) secret key and the new secret key, and the engine ID
            ),
        )
    )

I tried playing around with the output of localizeKey, but I'm not sure that's how that is intended.

from pysnmp.proto.secmod.rfc3414.localkey import localizeKey
from pysnmp.hlapi import OctetString

localizeKey(OctetString("SekretSqwerrel") + OctetString("changeme"), OctetString("80005257800100000000000701000300000001030307"), sha1)

What is supposed to go there and is there already a helper function to generate it?

etingof commented 5 years ago

I think the KeyChange algorithm (the way to initialize kcvalue) is not readily implemented in pysnmp yet. If you have cycles to write one and contribute to pysnmp, I'd happily merge it. ;-)

The algorithm itself is defined in RFC2574 within the KeyChange TEXTUAL-CONVENTION:

          The object's value is a manager-generated, partially-random
          value which, when modified, causes the value of the secret
          key K, to be modified via a one-way function.

          The value of an instance of this object is the concatenation
          of two components: first a 'random' component and then a
          'delta' component.

          The lengths of the random and delta components
          are given by the corresponding value of the protocol P;
          if P requires K to be a fixed length, the length of both the
          random and delta components is that fixed length; if P
          allows the length of K to be variable up to a particular
          maximum length, the length of the random component is that
          maximum length and the length of the delta component is any
          length less than or equal to that maximum length.
          For example, usmHMACMD5AuthProtocol requires K to be a fixed
          length of 16 octets and L - of 16 octets.
          usmHMACSHAAuthProtocol requires K to be a fixed length of
          20 octets and L - of 20 octets. Other protocols may define
          other sizes, as deemed appropriate.

          When a requester wants to change the old key K to a new
          key keyNew on a remote entity, the 'random' component is
          obtained from either a true random generator, or from a
          pseudorandom generator, and the 'delta' component is
          computed as follows:

           - a temporary variable is initialized to the existing value
             of K;
           - if the length of the keyNew is greater than L octets,
             then:
              - the random component is appended to the value of the
                temporary variable, and the result is input to the
                the hash algorithm H to produce a digest value, and
                the temporary variable is set to this digest value;
              - the value of the temporary variable is XOR-ed with
                the first (next) L-octets (16 octets in case of MD5)
                of the keyNew to produce the first (next) L-octets
                (16 octets in case of MD5) of the 'delta' component.
              - the above two steps are repeated until the unused
                portion of the keyNew component is L octets or less,
           - the random component is appended to the value of the
             temporary variable, and the result is input to the
             hash algorithm H to produce a digest value;
           - this digest value, truncated if necessary to be the same
             length as the unused portion of the keyNew, is XOR-ed
             with the unused portion of the keyNew to produce the
             (final portion of the) 'delta' component.

           For example, using MD5 as the hash algorithm H:

              iterations = (lenOfDelta - 1)/16; /* integer division */
              temp = keyOld;
              for (i = 0; i < iterations; i++) {
                  temp = MD5 (temp || random);
                  delta[i*16 .. (i*16)+15] =
                         temp XOR keyNew[i*16 .. (i*16)+15];
              }
              temp = MD5 (temp || random);
              delta[i*16 .. lenOfDelta-1] =
                     temp XOR keyNew[i*16 .. lenOfDelta-1];

          The 'random' and 'delta' components are then concatenated as
          described above, and the resulting octet string is sent to
          the recipient as the new value of an instance of this object.

          At the receiver side, when an instance of this object is set
          to a new value, then a new value of K is computed as follows:

           - a temporary variable is initialized to the existing value
             of K;
           - if the length of the delta component is greater than L
             octets, then:
              - the random component is appended to the value of the
                temporary variable, and the result is input to the
                hash algorithm H to produce a digest value, and the
                temporary variable is set to this digest value;
              - the value of the temporary variable is XOR-ed with
                the first (next) L-octets (16 octets in case of MD5)
                of the delta component to produce the first (next)
                L-octets (16 octets in case of MD5) of the new value
                of K.
              - the above two steps are repeated until the unused
                portion of the delta component is L octets or less,
           - the random component is appended to the value of the
             temporary variable, and the result is input to the
             hash algorithm H to produce a digest value;
           - this digest value, truncated if necessary to be the same
             length as the unused portion of the delta component, is
             XOR-ed with the unused portion of the delta component to
             produce the (final portion of the) new value of K.

           For example, using MD5 as the hash algorithm H:

              iterations = (lenOfDelta - 1)/16; /* integer division */
              temp = keyOld;
              for (i = 0; i < iterations; i++) {
                  temp = MD5 (temp || random);
                  keyNew[i*16 .. (i*16)+15] =
                         temp XOR delta[i*16 .. (i*16)+15];
              }
              temp = MD5 (temp || random);
              keyNew[i*16 .. lenOfDelta-1] =
                     temp XOR delta[i*16 .. lenOfDelta-1];

          The value of an object with this syntax, whenever it is
          retrieved by the management protocol, is always the zero
          length string.

          Note that the keyOld and keyNew are the localized keys.
mreams13 commented 5 years ago

Thank you for the information. I'll see if I can implement that.