gosnmp
/
v3_usm.go
1068 строк · 30.2 Кб
1// Copyright 2012 The GoSNMP Authors. All rights reserved. Use of this
2// source code is governed by a BSD-style license that can be found in the
3// LICENSE file.
4
5// Copyright 2009 The Go Authors. All rights reserved.
6// Use of this source code is governed by a BSD-style
7// license that can be found in the LICENSE file.
8
9package gosnmp
10
11import (
12"bytes"
13"crypto"
14"crypto/aes"
15"crypto/cipher"
16"crypto/des" //nolint:gosec
17"crypto/hmac"
18"crypto/md5" //nolint:gosec
19crand "crypto/rand"
20"crypto/sha1" //nolint:gosec
21_ "crypto/sha256" // Register hash function #4 (SHA224), #5 (SHA256)
22_ "crypto/sha512" // Register hash function #6 (SHA384), #7 (SHA512)
23"encoding/binary"
24"encoding/hex"
25"errors"
26"fmt"
27"hash"
28"strings"
29"sync"
30"sync/atomic"
31)
32
33// SnmpV3AuthProtocol describes the authentication protocol in use by an authenticated SnmpV3 connection.
34type SnmpV3AuthProtocol uint8
35
36// NoAuth, MD5, and SHA are implemented
37const (
38NoAuth SnmpV3AuthProtocol = 1
39MD5 SnmpV3AuthProtocol = 2
40SHA SnmpV3AuthProtocol = 3
41SHA224 SnmpV3AuthProtocol = 4
42SHA256 SnmpV3AuthProtocol = 5
43SHA384 SnmpV3AuthProtocol = 6
44SHA512 SnmpV3AuthProtocol = 7
45)
46
47//go:generate stringer -type=SnmpV3AuthProtocol
48
49// HashType maps the AuthProtocol's hash type to an actual crypto.Hash object.
50func (authProtocol SnmpV3AuthProtocol) HashType() crypto.Hash {
51switch authProtocol {
52default:
53return crypto.MD5
54case SHA:
55return crypto.SHA1
56case SHA224:
57return crypto.SHA224
58case SHA256:
59return crypto.SHA256
60case SHA384:
61return crypto.SHA384
62case SHA512:
63return crypto.SHA512
64}
65}
66
67//nolint:gochecknoglobals
68var macVarbinds = [][]byte{
69{},
70{byte(OctetString), 0},
71{byte(OctetString), 12,
720, 0, 0, 0,
730, 0, 0, 0,
740, 0, 0, 0},
75{byte(OctetString), 12,
760, 0, 0, 0,
770, 0, 0, 0,
780, 0, 0, 0},
79{byte(OctetString), 16,
800, 0, 0, 0,
810, 0, 0, 0,
820, 0, 0, 0,
830, 0, 0, 0},
84{byte(OctetString), 24,
850, 0, 0, 0,
860, 0, 0, 0,
870, 0, 0, 0,
880, 0, 0, 0,
890, 0, 0, 0,
900, 0, 0, 0},
91{byte(OctetString), 32,
920, 0, 0, 0,
930, 0, 0, 0,
940, 0, 0, 0,
950, 0, 0, 0,
960, 0, 0, 0,
970, 0, 0, 0,
980, 0, 0, 0,
990, 0, 0, 0},
100{byte(OctetString), 48,
1010, 0, 0, 0,
1020, 0, 0, 0,
1030, 0, 0, 0,
1040, 0, 0, 0,
1050, 0, 0, 0,
1060, 0, 0, 0,
1070, 0, 0, 0,
1080, 0, 0, 0,
1090, 0, 0, 0,
1100, 0, 0, 0,
1110, 0, 0, 0,
1120, 0, 0, 0}}
113
114// SnmpV3PrivProtocol is the privacy protocol in use by an private SnmpV3 connection.
115type SnmpV3PrivProtocol uint8
116
117// NoPriv, DES implemented, AES planned
118// Changed: AES192, AES256, AES192C, AES256C added
119const (
120NoPriv SnmpV3PrivProtocol = 1
121DES SnmpV3PrivProtocol = 2
122AES SnmpV3PrivProtocol = 3
123AES192 SnmpV3PrivProtocol = 4 // Blumenthal-AES192
124AES256 SnmpV3PrivProtocol = 5 // Blumenthal-AES256
125AES192C SnmpV3PrivProtocol = 6 // Reeder-AES192
126AES256C SnmpV3PrivProtocol = 7 // Reeder-AES256
127)
128
129//go:generate stringer -type=SnmpV3PrivProtocol
130
131// UsmSecurityParameters is an implementation of SnmpV3SecurityParameters for the UserSecurityModel
132type UsmSecurityParameters struct {
133mu sync.Mutex
134// localAESSalt must be 64bit aligned to use with atomic operations.
135localAESSalt uint64
136localDESSalt uint32
137
138AuthoritativeEngineID string
139AuthoritativeEngineBoots uint32
140AuthoritativeEngineTime uint32
141UserName string
142AuthenticationParameters string
143PrivacyParameters []byte
144
145AuthenticationProtocol SnmpV3AuthProtocol
146PrivacyProtocol SnmpV3PrivProtocol
147
148AuthenticationPassphrase string
149PrivacyPassphrase string
150
151SecretKey []byte
152PrivacyKey []byte
153
154Logger Logger
155}
156
157func (sp *UsmSecurityParameters) getIdentifier() string {
158return sp.UserName
159}
160
161func (sp *UsmSecurityParameters) getLogger() Logger {
162return sp.Logger
163}
164
165func (sp *UsmSecurityParameters) setLogger(log Logger) {
166sp.Logger = log
167}
168
169// Description logs authentication paramater information to the provided GoSNMP Logger
170func (sp *UsmSecurityParameters) Description() string {
171var sb strings.Builder
172sb.WriteString("user=")
173sb.WriteString(sp.UserName)
174
175sb.WriteString(",engine=(")
176sb.WriteString(hex.EncodeToString([]byte(sp.AuthoritativeEngineID)))
177// sb.WriteString(sp.AuthoritativeEngineID)
178sb.WriteString(")")
179
180switch sp.AuthenticationProtocol {
181case NoAuth:
182sb.WriteString(",auth=noauth")
183case MD5:
184sb.WriteString(",auth=md5")
185case SHA:
186sb.WriteString(",auth=sha")
187case SHA224:
188sb.WriteString(",auth=sha224")
189case SHA256:
190sb.WriteString(",auth=sha256")
191case SHA384:
192sb.WriteString(",auth=sha384")
193case SHA512:
194sb.WriteString(",auth=sha512")
195}
196sb.WriteString(",authPass=")
197sb.WriteString(sp.AuthenticationPassphrase)
198
199switch sp.PrivacyProtocol {
200case NoPriv:
201sb.WriteString(",priv=NoPriv")
202case DES:
203sb.WriteString(",priv=DES")
204case AES:
205sb.WriteString(",priv=AES")
206case AES192:
207sb.WriteString(",priv=AES192")
208case AES256:
209sb.WriteString(",priv=AES256")
210case AES192C:
211sb.WriteString(",priv=AES192C")
212case AES256C:
213sb.WriteString(",priv=AES256C")
214}
215sb.WriteString(",privPass=")
216sb.WriteString(sp.PrivacyPassphrase)
217
218return sb.String()
219}
220
221// SafeString returns a logging safe (no secrets) string of the UsmSecurityParameters
222func (sp *UsmSecurityParameters) SafeString() string {
223return fmt.Sprintf("AuthoritativeEngineID:%s, AuthoritativeEngineBoots:%d, AuthoritativeEngineTimes:%d, UserName:%s, AuthenticationParameters:%s, PrivacyParameters:%v, AuthenticationProtocol:%s, PrivacyProtocol:%s",
224sp.AuthoritativeEngineID,
225sp.AuthoritativeEngineBoots,
226sp.AuthoritativeEngineTime,
227sp.UserName,
228sp.AuthenticationParameters,
229sp.PrivacyParameters,
230sp.AuthenticationProtocol,
231sp.PrivacyProtocol,
232)
233}
234
235// Log logs security paramater information to the provided GoSNMP Logger
236func (sp *UsmSecurityParameters) Log() {
237sp.mu.Lock()
238defer sp.mu.Unlock()
239sp.Logger.Printf("SECURITY PARAMETERS:%s", sp.SafeString())
240}
241
242// Copy method for UsmSecurityParameters used to copy a SnmpV3SecurityParameters without knowing it's implementation
243func (sp *UsmSecurityParameters) Copy() SnmpV3SecurityParameters {
244sp.mu.Lock()
245defer sp.mu.Unlock()
246return &UsmSecurityParameters{AuthoritativeEngineID: sp.AuthoritativeEngineID,
247AuthoritativeEngineBoots: sp.AuthoritativeEngineBoots,
248AuthoritativeEngineTime: sp.AuthoritativeEngineTime,
249UserName: sp.UserName,
250AuthenticationParameters: sp.AuthenticationParameters,
251PrivacyParameters: sp.PrivacyParameters,
252AuthenticationProtocol: sp.AuthenticationProtocol,
253PrivacyProtocol: sp.PrivacyProtocol,
254AuthenticationPassphrase: sp.AuthenticationPassphrase,
255PrivacyPassphrase: sp.PrivacyPassphrase,
256SecretKey: sp.SecretKey,
257PrivacyKey: sp.PrivacyKey,
258localDESSalt: sp.localDESSalt,
259localAESSalt: sp.localAESSalt,
260Logger: sp.Logger,
261}
262}
263
264func (sp *UsmSecurityParameters) getDefaultContextEngineID() string {
265return sp.AuthoritativeEngineID
266}
267
268// InitSecurityKeys initializes the Priv and Auth keys if needed
269func (sp *UsmSecurityParameters) InitSecurityKeys() error {
270sp.mu.Lock()
271defer sp.mu.Unlock()
272
273return sp.initSecurityKeysNoLock()
274}
275
276func (sp *UsmSecurityParameters) initSecurityKeysNoLock() error {
277var err error
278
279if sp.AuthenticationProtocol > NoAuth && len(sp.SecretKey) == 0 {
280sp.SecretKey, err = genlocalkey(sp.AuthenticationProtocol,
281sp.AuthenticationPassphrase,
282sp.AuthoritativeEngineID)
283if err != nil {
284return err
285}
286}
287if sp.PrivacyProtocol > NoPriv && len(sp.PrivacyKey) == 0 {
288switch sp.PrivacyProtocol {
289// Changed: The Output of SHA1 is a 20 octets array, therefore for AES128 (16 octets) either key extension algorithm can be used.
290case AES, AES192, AES256, AES192C, AES256C:
291// Use abstract AES key localization algorithms.
292sp.PrivacyKey, err = genlocalPrivKey(sp.PrivacyProtocol, sp.AuthenticationProtocol,
293sp.PrivacyPassphrase,
294sp.AuthoritativeEngineID)
295if err != nil {
296return err
297}
298default:
299sp.PrivacyKey, err = genlocalkey(sp.AuthenticationProtocol,
300sp.PrivacyPassphrase,
301sp.AuthoritativeEngineID)
302if err != nil {
303return err
304}
305}
306}
307return nil
308}
309
310func (sp *UsmSecurityParameters) setSecurityParameters(in SnmpV3SecurityParameters) error {
311var insp *UsmSecurityParameters
312var err error
313
314sp.mu.Lock()
315defer sp.mu.Unlock()
316
317if insp, err = castUsmSecParams(in); err != nil {
318return err
319}
320
321if sp.AuthoritativeEngineID != insp.AuthoritativeEngineID {
322sp.AuthoritativeEngineID = insp.AuthoritativeEngineID
323sp.SecretKey = nil
324sp.PrivacyKey = nil
325
326err = sp.initSecurityKeysNoLock()
327if err != nil {
328return err
329}
330}
331sp.AuthoritativeEngineBoots = insp.AuthoritativeEngineBoots
332sp.AuthoritativeEngineTime = insp.AuthoritativeEngineTime
333
334return nil
335}
336
337func (sp *UsmSecurityParameters) validate(flags SnmpV3MsgFlags) error {
338securityLevel := flags & AuthPriv // isolate flags that determine security level
339
340switch securityLevel {
341case AuthPriv:
342if sp.PrivacyProtocol <= NoPriv {
343return fmt.Errorf("securityParameters.PrivacyProtocol is required")
344}
345fallthrough
346case AuthNoPriv:
347if sp.AuthenticationProtocol <= NoAuth {
348return fmt.Errorf("securityParameters.AuthenticationProtocol is required")
349}
350fallthrough
351case NoAuthNoPriv:
352if sp.UserName == "" {
353return fmt.Errorf("securityParameters.UserName is required")
354}
355default:
356return fmt.Errorf("validate: MsgFlags must be populated with an appropriate security level")
357}
358
359if sp.PrivacyProtocol > NoPriv && len(sp.PrivacyKey) == 0 {
360if sp.PrivacyPassphrase == "" {
361return fmt.Errorf("securityParameters.PrivacyPassphrase is required when a privacy protocol is specified")
362}
363}
364
365if sp.AuthenticationProtocol > NoAuth && len(sp.SecretKey) == 0 {
366if sp.AuthenticationPassphrase == "" {
367return fmt.Errorf("securityParameters.AuthenticationPassphrase is required when an authentication protocol is specified")
368}
369}
370
371return nil
372}
373
374func (sp *UsmSecurityParameters) init(log Logger) error {
375var err error
376
377sp.Logger = log
378
379switch sp.PrivacyProtocol {
380case AES, AES192, AES256, AES192C, AES256C:
381salt := make([]byte, 8)
382_, err = crand.Read(salt)
383if err != nil {
384return fmt.Errorf("error creating a cryptographically secure salt: %w", err)
385}
386sp.localAESSalt = binary.BigEndian.Uint64(salt)
387case DES:
388salt := make([]byte, 4)
389_, err = crand.Read(salt)
390if err != nil {
391return fmt.Errorf("error creating a cryptographically secure salt: %w", err)
392}
393sp.localDESSalt = binary.BigEndian.Uint32(salt)
394}
395
396return nil
397}
398
399func castUsmSecParams(secParams SnmpV3SecurityParameters) (*UsmSecurityParameters, error) {
400s, ok := secParams.(*UsmSecurityParameters)
401if !ok || s == nil {
402return nil, fmt.Errorf("param SnmpV3SecurityParameters is not of type *UsmSecurityParameters")
403}
404return s, nil
405}
406
407var (
408passwordKeyHashCache = make(map[string][]byte) //nolint:gochecknoglobals
409passwordKeyHashMutex sync.RWMutex //nolint:gochecknoglobals
410passwordCacheDisable atomic.Bool //nolint:gochecknoglobals
411)
412
413// PasswordCaching is enabled by default for performance reason. If the cache was disabled then
414// re-enabled, the cache is reset.
415func PasswordCaching(enable bool) {
416oldCacheEnable := !passwordCacheDisable.Load()
417passwordKeyHashMutex.Lock()
418if !enable { // if off
419passwordKeyHashCache = nil
420} else if !oldCacheEnable && enable { // if off then on
421passwordKeyHashCache = make(map[string][]byte)
422}
423passwordCacheDisable.Store(!enable)
424passwordKeyHashMutex.Unlock()
425}
426
427func hashPassword(hash hash.Hash, password string) ([]byte, error) {
428if len(password) == 0 {
429return []byte{}, errors.New("hashPassword: password is empty")
430}
431var pi int // password index
432for i := 0; i < 1048576; i += 64 {
433var chunk []byte
434for e := 0; e < 64; e++ {
435chunk = append(chunk, password[pi%len(password)])
436pi++
437}
438if _, err := hash.Write(chunk); err != nil {
439return []byte{}, err
440}
441}
442hashed := hash.Sum(nil)
443return hashed, nil
444}
445
446// Common passwordToKey algorithm, "caches" the result to avoid extra computation each reuse
447func cachedPasswordToKey(hash hash.Hash, cacheKey string, password string) ([]byte, error) {
448cacheDisable := passwordCacheDisable.Load()
449if !cacheDisable {
450passwordKeyHashMutex.RLock()
451value := passwordKeyHashCache[cacheKey]
452passwordKeyHashMutex.RUnlock()
453
454if value != nil {
455return value, nil
456}
457}
458
459hashed, err := hashPassword(hash, password)
460if err != nil {
461return nil, err
462}
463
464if !cacheDisable {
465passwordKeyHashMutex.Lock()
466passwordKeyHashCache[cacheKey] = hashed
467passwordKeyHashMutex.Unlock()
468}
469
470return hashed, nil
471}
472
473func hMAC(hash crypto.Hash, cacheKey string, password string, engineID string) ([]byte, error) {
474hashed, err := cachedPasswordToKey(hash.New(), cacheKey, password)
475if err != nil {
476return []byte{}, nil
477}
478
479local := hash.New()
480_, err = local.Write(hashed)
481if err != nil {
482return []byte{}, err
483}
484
485_, err = local.Write([]byte(engineID))
486if err != nil {
487return []byte{}, err
488}
489
490_, err = local.Write(hashed)
491if err != nil {
492return []byte{}, err
493}
494
495final := local.Sum(nil)
496return final, nil
497}
498
499func cacheKey(authProtocol SnmpV3AuthProtocol, passphrase string) string {
500if passwordCacheDisable.Load() {
501return ""
502}
503var cacheKey = make([]byte, 1+len(passphrase))
504cacheKey = append(cacheKey, 'h'+byte(authProtocol))
505cacheKey = append(cacheKey, []byte(passphrase)...)
506return string(cacheKey)
507}
508
509// Extending the localized privacy key according to Reeder Key extension algorithm:
510// https://tools.ietf.org/html/draft-reeder-snmpv3-usm-3dese
511// Many vendors, including Cisco, use the 3DES key extension algorithm to extend the privacy keys that are too short when using AES,AES192 and AES256.
512// Previously implemented in net-snmp and pysnmp libraries.
513// Tested for AES128 and AES256
514func extendKeyReeder(authProtocol SnmpV3AuthProtocol, password string, engineID string) ([]byte, error) {
515var key []byte
516var err error
517
518key, err = hMAC(authProtocol.HashType(), cacheKey(authProtocol, password), password, engineID)
519
520if err != nil {
521return nil, err
522}
523
524newkey, err := hMAC(authProtocol.HashType(), cacheKey(authProtocol, string(key)), string(key), engineID)
525
526return append(key, newkey...), err
527}
528
529// Extending the localized privacy key according to Blumenthal key extension algorithm:
530// https://tools.ietf.org/html/draft-blumenthal-aes-usm-04#page-7
531// Not many vendors use this algorithm.
532// Previously implemented in the net-snmp and pysnmp libraries.
533// TODO: Not tested
534func extendKeyBlumenthal(authProtocol SnmpV3AuthProtocol, password string, engineID string) ([]byte, error) {
535var key []byte
536var err error
537
538key, err = hMAC(authProtocol.HashType(), cacheKey(authProtocol, password), password, engineID)
539
540if err != nil {
541return nil, err
542}
543
544newkey := authProtocol.HashType().New()
545_, _ = newkey.Write(key)
546return append(key, newkey.Sum(nil)...), err
547}
548
549// Changed: New function to calculate the Privacy Key for abstract AES
550func genlocalPrivKey(privProtocol SnmpV3PrivProtocol, authProtocol SnmpV3AuthProtocol, password string, engineID string) ([]byte, error) {
551var keylen int
552var localPrivKey []byte
553var err error
554
555switch privProtocol {
556case AES, DES:
557keylen = 16
558case AES192, AES192C:
559keylen = 24
560case AES256, AES256C:
561keylen = 32
562}
563
564switch privProtocol {
565case AES, AES192C, AES256C:
566localPrivKey, err = extendKeyReeder(authProtocol, password, engineID)
567
568case AES192, AES256:
569localPrivKey, err = extendKeyBlumenthal(authProtocol, password, engineID)
570
571default:
572localPrivKey, err = genlocalkey(authProtocol, password, engineID)
573}
574
575if err != nil {
576return nil, err
577}
578
579if len(localPrivKey) < keylen {
580return []byte{}, fmt.Errorf("genlocalPrivKey: privProtocol: %v len(localPrivKey): %d, keylen: %d",
581privProtocol, len(localPrivKey), keylen)
582}
583
584return localPrivKey[:keylen], nil
585}
586
587func genlocalkey(authProtocol SnmpV3AuthProtocol, passphrase string, engineID string) ([]byte, error) {
588var secretKey []byte
589var err error
590
591secretKey, err = hMAC(authProtocol.HashType(), cacheKey(authProtocol, passphrase), passphrase, engineID)
592
593if err != nil {
594return []byte{}, err
595}
596
597return secretKey, nil
598}
599
600// http://tools.ietf.org/html/rfc2574#section-8.1.1.1
601// localDESSalt needs to be incremented on every packet.
602func (sp *UsmSecurityParameters) usmAllocateNewSalt() interface{} {
603sp.mu.Lock()
604defer sp.mu.Unlock()
605var newSalt interface{}
606
607switch sp.PrivacyProtocol {
608case AES, AES192, AES256, AES192C, AES256C:
609newSalt = atomic.AddUint64(&(sp.localAESSalt), 1)
610default:
611newSalt = atomic.AddUint32(&(sp.localDESSalt), 1)
612}
613return newSalt
614}
615
616func (sp *UsmSecurityParameters) usmSetSalt(newSalt interface{}) error {
617sp.mu.Lock()
618defer sp.mu.Unlock()
619switch sp.PrivacyProtocol {
620case AES, AES192, AES256, AES192C, AES256C:
621aesSalt, ok := newSalt.(uint64)
622if !ok {
623return fmt.Errorf("salt provided to usmSetSalt is not the correct type for the AES privacy protocol")
624}
625var salt = make([]byte, 8)
626binary.BigEndian.PutUint64(salt, aesSalt)
627sp.PrivacyParameters = salt
628default:
629desSalt, ok := newSalt.(uint32)
630if !ok {
631return fmt.Errorf("salt provided to usmSetSalt is not the correct type for the DES privacy protocol")
632}
633var salt = make([]byte, 8)
634binary.BigEndian.PutUint32(salt, sp.AuthoritativeEngineBoots)
635binary.BigEndian.PutUint32(salt[4:], desSalt)
636sp.PrivacyParameters = salt
637}
638return nil
639}
640
641// InitPacket ensures the enc salt is incremented for packets marked for AuthPriv
642func (sp *UsmSecurityParameters) InitPacket(packet *SnmpPacket) error {
643// http://tools.ietf.org/html/rfc2574#section-8.1.1.1
644// localDESSalt needs to be incremented on every packet.
645newSalt := sp.usmAllocateNewSalt()
646if packet.MsgFlags&AuthPriv > AuthNoPriv {
647s, err := castUsmSecParams(packet.SecurityParameters)
648if err != nil {
649return err
650}
651return s.usmSetSalt(newSalt)
652}
653return nil
654}
655
656func (sp *UsmSecurityParameters) discoveryRequired() *SnmpPacket {
657if sp.AuthoritativeEngineID == "" {
658var emptyPdus []SnmpPDU
659
660// send blank packet to discover authoriative engine ID/boots/time
661blankPacket := &SnmpPacket{
662Version: Version3,
663MsgFlags: Reportable | NoAuthNoPriv,
664SecurityModel: UserSecurityModel,
665SecurityParameters: &UsmSecurityParameters{Logger: sp.Logger},
666PDUType: GetRequest,
667Logger: sp.Logger,
668Variables: emptyPdus,
669}
670
671return blankPacket
672}
673return nil
674}
675
676func (sp *UsmSecurityParameters) calcPacketDigest(packet []byte) ([]byte, error) {
677return calcPacketDigest(packet, sp)
678}
679
680// calcPacketDigest calculate authenticate digest for incoming messages (TRAP or
681// INFORM).
682// Support MD5, SHA1, SHA224, SHA256, SHA384, SHA512 protocols
683func calcPacketDigest(packetBytes []byte, secParams *UsmSecurityParameters) ([]byte, error) {
684var digest []byte
685var err error
686
687switch secParams.AuthenticationProtocol {
688case MD5, SHA:
689digest, err = digestRFC3414(
690secParams.AuthenticationProtocol,
691packetBytes,
692secParams.SecretKey)
693case SHA224, SHA256, SHA384, SHA512:
694digest, err = digestRFC7860(
695secParams.AuthenticationProtocol,
696packetBytes,
697secParams.SecretKey)
698}
699
700return digest, err
701}
702
703// digestRFC7860 calculate digest for incoming messages using HMAC-SHA2 protcols
704// according to RFC7860 4.2.2
705func digestRFC7860(h SnmpV3AuthProtocol, packet []byte, authKey []byte) ([]byte, error) {
706mac := hmac.New(h.HashType().New, authKey)
707_, err := mac.Write(packet)
708if err != nil {
709return []byte{}, err
710}
711msgDigest := mac.Sum(nil)
712return msgDigest, nil
713}
714
715// digestRFC3414 calculate digest for incoming messages using MD5 or SHA1
716// according to RFC3414 6.3.2 and 7.3.2
717func digestRFC3414(h SnmpV3AuthProtocol, packet []byte, authKey []byte) ([]byte, error) {
718var extkey [64]byte
719var err error
720var k1, k2 [64]byte
721var h1, h2 hash.Hash
722
723copy(extkey[:], authKey)
724
725switch h {
726case MD5:
727h1 = md5.New() //nolint:gosec
728h2 = md5.New() //nolint:gosec
729case SHA:
730h1 = sha1.New() //nolint:gosec
731h2 = sha1.New() //nolint:gosec
732}
733
734for i := 0; i < 64; i++ {
735k1[i] = extkey[i] ^ 0x36
736k2[i] = extkey[i] ^ 0x5c
737}
738
739_, err = h1.Write(k1[:])
740if err != nil {
741return []byte{}, err
742}
743
744_, err = h1.Write(packet)
745if err != nil {
746return []byte{}, err
747}
748
749d1 := h1.Sum(nil)
750
751_, err = h2.Write(k2[:])
752if err != nil {
753return []byte{}, err
754}
755
756_, err = h2.Write(d1)
757if err != nil {
758return []byte{}, err
759}
760
761return h2.Sum(nil)[:12], nil
762}
763
764func (sp *UsmSecurityParameters) authenticate(packet []byte) error {
765var msgDigest []byte
766var err error
767
768if msgDigest, err = sp.calcPacketDigest(packet); err != nil {
769return err
770}
771
772idx := bytes.Index(packet, macVarbinds[sp.AuthenticationProtocol])
773
774if idx < 0 {
775return fmt.Errorf("unable to locate the position in packet to write authentication key")
776}
777
778copy(packet[idx+2:idx+len(macVarbinds[sp.AuthenticationProtocol])], msgDigest)
779return nil
780}
781
782// determine whether a message is authentic
783func (sp *UsmSecurityParameters) isAuthentic(packetBytes []byte, packet *SnmpPacket) (bool, error) {
784var msgDigest []byte
785var packetSecParams *UsmSecurityParameters
786var err error
787
788if packetSecParams, err = castUsmSecParams(packet.SecurityParameters); err != nil {
789return false, err
790}
791// TODO: investigate call chain to determine if this is really the best spot for this
792if msgDigest, err = calcPacketDigest(packetBytes, packetSecParams); err != nil {
793return false, err
794}
795
796for k, v := range []byte(packetSecParams.AuthenticationParameters) {
797if msgDigest[k] != v {
798return false, nil
799}
800}
801return true, nil
802}
803
804func (sp *UsmSecurityParameters) encryptPacket(scopedPdu []byte) ([]byte, error) {
805var b []byte
806
807switch sp.PrivacyProtocol {
808case AES, AES192, AES256, AES192C, AES256C:
809var iv [16]byte
810binary.BigEndian.PutUint32(iv[:], sp.AuthoritativeEngineBoots)
811binary.BigEndian.PutUint32(iv[4:], sp.AuthoritativeEngineTime)
812copy(iv[8:], sp.PrivacyParameters)
813// aes.NewCipher(sp.PrivacyKey[:16]) changed to aes.NewCipher(sp.PrivacyKey)
814block, err := aes.NewCipher(sp.PrivacyKey)
815if err != nil {
816return nil, err
817}
818stream := cipher.NewCFBEncrypter(block, iv[:])
819ciphertext := make([]byte, len(scopedPdu))
820stream.XORKeyStream(ciphertext, scopedPdu)
821pduLen, err := marshalLength(len(ciphertext))
822if err != nil {
823return nil, err
824}
825b = append([]byte{byte(OctetString)}, pduLen...)
826scopedPdu = append(b, ciphertext...) //nolint:gocritic
827case DES:
828preiv := sp.PrivacyKey[8:]
829var iv [8]byte
830for i := 0; i < len(iv); i++ {
831iv[i] = preiv[i] ^ sp.PrivacyParameters[i]
832}
833block, err := des.NewCipher(sp.PrivacyKey[:8]) //nolint:gosec
834if err != nil {
835return nil, err
836}
837mode := cipher.NewCBCEncrypter(block, iv[:])
838
839pad := make([]byte, des.BlockSize-len(scopedPdu)%des.BlockSize)
840scopedPdu = append(scopedPdu, pad...)
841
842ciphertext := make([]byte, len(scopedPdu))
843mode.CryptBlocks(ciphertext, scopedPdu)
844pduLen, err := marshalLength(len(ciphertext))
845if err != nil {
846return nil, err
847}
848b = append([]byte{byte(OctetString)}, pduLen...)
849scopedPdu = append(b, ciphertext...) //nolint:gocritic
850}
851
852return scopedPdu, nil
853}
854
855func (sp *UsmSecurityParameters) decryptPacket(packet []byte, cursor int) ([]byte, error) {
856_, cursorTmp, err := parseLength(packet[cursor:])
857if err != nil {
858return nil, err
859}
860cursorTmp += cursor
861if cursorTmp > len(packet) {
862return nil, errors.New("error decrypting ScopedPDU: truncated packet")
863}
864
865switch sp.PrivacyProtocol {
866case AES, AES192, AES256, AES192C, AES256C:
867var iv [16]byte
868binary.BigEndian.PutUint32(iv[:], sp.AuthoritativeEngineBoots)
869binary.BigEndian.PutUint32(iv[4:], sp.AuthoritativeEngineTime)
870copy(iv[8:], sp.PrivacyParameters)
871
872block, err := aes.NewCipher(sp.PrivacyKey)
873if err != nil {
874return nil, err
875}
876stream := cipher.NewCFBDecrypter(block, iv[:])
877plaintext := make([]byte, len(packet[cursorTmp:]))
878stream.XORKeyStream(plaintext, packet[cursorTmp:])
879copy(packet[cursor:], plaintext)
880packet = packet[:cursor+len(plaintext)]
881case DES:
882if len(packet[cursorTmp:])%des.BlockSize != 0 {
883return nil, errors.New("error decrypting ScopedPDU: not multiple of des block size")
884}
885preiv := sp.PrivacyKey[8:]
886var iv [8]byte
887for i := 0; i < len(iv); i++ {
888iv[i] = preiv[i] ^ sp.PrivacyParameters[i]
889}
890block, err := des.NewCipher(sp.PrivacyKey[:8]) //nolint:gosec
891if err != nil {
892return nil, err
893}
894mode := cipher.NewCBCDecrypter(block, iv[:])
895
896plaintext := make([]byte, len(packet[cursorTmp:]))
897mode.CryptBlocks(plaintext, packet[cursorTmp:])
898copy(packet[cursor:], plaintext)
899// truncate packet to remove extra space caused by the
900// octetstring/length header that was just replaced
901packet = packet[:cursor+len(plaintext)]
902}
903return packet, nil
904}
905
906// marshal a snmp version 3 security parameters field for the User Security Model
907func (sp *UsmSecurityParameters) marshal(flags SnmpV3MsgFlags) ([]byte, error) {
908var buf bytes.Buffer
909var err error
910
911// msgAuthoritativeEngineID
912buf.Write([]byte{byte(OctetString), byte(len(sp.AuthoritativeEngineID))})
913buf.WriteString(sp.AuthoritativeEngineID)
914
915// msgAuthoritativeEngineBoots
916msgAuthoritativeEngineBoots, err := marshalUint32(sp.AuthoritativeEngineBoots)
917if err != nil {
918return nil, err
919}
920buf.Write([]byte{byte(Integer), byte(len(msgAuthoritativeEngineBoots))})
921buf.Write(msgAuthoritativeEngineBoots)
922
923// msgAuthoritativeEngineTime
924msgAuthoritativeEngineTime, err := marshalUint32(sp.AuthoritativeEngineTime)
925if err != nil {
926return nil, err
927}
928buf.Write([]byte{byte(Integer), byte(len(msgAuthoritativeEngineTime))})
929buf.Write(msgAuthoritativeEngineTime)
930
931// msgUserName
932buf.Write([]byte{byte(OctetString), byte(len(sp.UserName))})
933buf.WriteString(sp.UserName)
934
935// msgAuthenticationParameters
936if flags&AuthNoPriv > 0 {
937buf.Write(macVarbinds[sp.AuthenticationProtocol])
938} else {
939buf.Write([]byte{byte(OctetString), 0})
940}
941// msgPrivacyParameters
942if flags&AuthPriv > AuthNoPriv {
943privlen, err2 := marshalLength(len(sp.PrivacyParameters))
944if err2 != nil {
945return nil, err2
946}
947buf.Write([]byte{byte(OctetString)})
948buf.Write(privlen)
949buf.Write(sp.PrivacyParameters)
950} else {
951buf.Write([]byte{byte(OctetString), 0})
952}
953
954// wrap security parameters in a sequence
955paramLen, err := marshalLength(buf.Len())
956if err != nil {
957return nil, err
958}
959tmpseq := append([]byte{byte(Sequence)}, paramLen...)
960tmpseq = append(tmpseq, buf.Bytes()...)
961
962return tmpseq, nil
963}
964
965func (sp *UsmSecurityParameters) unmarshal(flags SnmpV3MsgFlags, packet []byte, cursor int) (int, error) {
966var err error
967
968if cursor >= len(packet) {
969return 0, errors.New("error parsing SNMPV3 User Security Model parameters: end of packet")
970}
971
972if PDUType(packet[cursor]) != Sequence {
973return 0, errors.New("error parsing SNMPV3 User Security Model parameters")
974}
975_, cursorTmp, err := parseLength(packet[cursor:])
976if err != nil {
977return 0, err
978}
979cursor += cursorTmp
980if cursorTmp > len(packet) {
981return 0, errors.New("error parsing SNMPV3 User Security Model parameters: truncated packet")
982}
983
984rawMsgAuthoritativeEngineID, count, err := parseRawField(sp.Logger, packet[cursor:], "msgAuthoritativeEngineID")
985if err != nil {
986return 0, fmt.Errorf("error parsing SNMPV3 User Security Model msgAuthoritativeEngineID: %w", err)
987}
988cursor += count
989if AuthoritativeEngineID, ok := rawMsgAuthoritativeEngineID.(string); ok {
990if sp.AuthoritativeEngineID != AuthoritativeEngineID {
991sp.AuthoritativeEngineID = AuthoritativeEngineID
992sp.SecretKey = nil
993sp.PrivacyKey = nil
994
995sp.Logger.Printf("Parsed authoritativeEngineID %0x", []byte(AuthoritativeEngineID))
996err = sp.initSecurityKeysNoLock()
997if err != nil {
998return 0, err
999}
1000}
1001}
1002
1003rawMsgAuthoritativeEngineBoots, count, err := parseRawField(sp.Logger, packet[cursor:], "msgAuthoritativeEngineBoots")
1004if err != nil {
1005return 0, fmt.Errorf("error parsing SNMPV3 User Security Model msgAuthoritativeEngineBoots: %w", err)
1006}
1007cursor += count
1008if AuthoritativeEngineBoots, ok := rawMsgAuthoritativeEngineBoots.(int); ok {
1009sp.AuthoritativeEngineBoots = uint32(AuthoritativeEngineBoots)
1010sp.Logger.Printf("Parsed authoritativeEngineBoots %d", AuthoritativeEngineBoots)
1011}
1012
1013rawMsgAuthoritativeEngineTime, count, err := parseRawField(sp.Logger, packet[cursor:], "msgAuthoritativeEngineTime")
1014if err != nil {
1015return 0, fmt.Errorf("error parsing SNMPV3 User Security Model msgAuthoritativeEngineTime: %w", err)
1016}
1017cursor += count
1018if AuthoritativeEngineTime, ok := rawMsgAuthoritativeEngineTime.(int); ok {
1019sp.AuthoritativeEngineTime = uint32(AuthoritativeEngineTime)
1020sp.Logger.Printf("Parsed authoritativeEngineTime %d", AuthoritativeEngineTime)
1021}
1022
1023rawMsgUserName, count, err := parseRawField(sp.Logger, packet[cursor:], "msgUserName")
1024if err != nil {
1025return 0, fmt.Errorf("error parsing SNMPV3 User Security Model msgUserName: %w", err)
1026}
1027cursor += count
1028if msgUserName, ok := rawMsgUserName.(string); ok {
1029sp.UserName = msgUserName
1030sp.Logger.Printf("Parsed userName %s", msgUserName)
1031}
1032
1033rawMsgAuthParameters, count, err := parseRawField(sp.Logger, packet[cursor:], "msgAuthenticationParameters")
1034if err != nil {
1035return 0, fmt.Errorf("error parsing SNMPV3 User Security Model msgAuthenticationParameters: %w", err)
1036}
1037if msgAuthenticationParameters, ok := rawMsgAuthParameters.(string); ok {
1038sp.AuthenticationParameters = msgAuthenticationParameters
1039sp.Logger.Printf("Parsed authenticationParameters %s", msgAuthenticationParameters)
1040}
1041// blank msgAuthenticationParameters to prepare for authentication check later
1042if flags&AuthNoPriv > 0 {
1043// In case if the authentication protocol is not configured or set to NoAuth, then the packet cannot
1044// be processed further
1045if sp.AuthenticationProtocol <= NoAuth {
1046return 0, errors.New("error parsing SNMPv3 User Security Model: authentication parameters are not configured to parse incoming authenticated message")
1047}
1048copy(packet[cursor+2:cursor+len(macVarbinds[sp.AuthenticationProtocol])], macVarbinds[sp.AuthenticationProtocol][2:])
1049}
1050cursor += count
1051
1052rawMsgPrivacyParameters, count, err := parseRawField(sp.Logger, packet[cursor:], "msgPrivacyParameters")
1053if err != nil {
1054return 0, fmt.Errorf("error parsing SNMPV3 User Security Model msgPrivacyParameters: %w", err)
1055}
1056cursor += count
1057if msgPrivacyParameters, ok := rawMsgPrivacyParameters.(string); ok {
1058sp.PrivacyParameters = []byte(msgPrivacyParameters)
1059sp.Logger.Printf("Parsed privacyParameters %s", msgPrivacyParameters)
1060if flags&AuthPriv >= AuthPriv {
1061if sp.PrivacyProtocol <= NoPriv {
1062return 0, errors.New("error parsing SNMPv3 User Security Model: privacy parameters are not configured to parse incoming encrypted message")
1063}
1064}
1065}
1066
1067return cursor, nil
1068}
1069