Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions engine_v3_fips_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2026 Netdata Inc. All rights reserved. Use of this
// source code is governed by a BSD-style license that can be found in the
// LICENSE file.

package gosnmp

import (
"crypto/fips140"
"maps"
"slices"
"testing"
"testing/synctest"
)

// getFirstOf2 runs two Gets, records the first one's result and returns the
// second's.
func getFirstOf2(x *GoSNMP, a *fakeV3Agent) (*SnmpPacket, error) {
res, err := x.Get([]string{engineOID})
a.tr.addf("first Get: %s", describeEngineResult(res, err))
return x.Get([]string{engineOID})
}

// v3FIPSScenarios are the scenarios of TestEngineV3FIPS140Only.
func v3FIPSScenarios() map[string]v3Scenario {
const cfbPanic = "AES-CFB panics in FIPS 140-only mode; send recovers it into an error that carries the stack"
const partialState = "a failed key derivation leaves the agent's engine ID adopted without keys, boots or time, so the next request skips the discovery"
return map[string]v3Scenario{
"discovery/noauth": {user: "codec-noauth"},
"discovery/md5": {user: "codec-md5", run: getFirstOf2, knownBug: partialState},
"discovery/sha-aes": {user: "codec-sha-aes", run: getFirstOf2, knownBug: partialState},
"discovery/sha256-des": {user: "codec-sha256-des"},
"discovery/sha256-aes": {user: "codec-sha256-aes", knownBug: cfbPanic},
"discovery/sha512-aes256c": {user: "codec-sha512-aes256c", knownBug: cfbPanic},
"known-engine/md5": {
user: "codec-md5", setup: func(_ *GoSNMP, sp *UsmSecurityParameters) { sp.AuthoritativeEngineID = agentEngineID },
knownBug: "the key derivation error is ignored when the engine ID is known",
},
}
}

// TestEngineV3FIPS140Only pins, when run with GODEBUG=fips140=only, what the
// request engine does when FIPS 140-only mode refuses an algorithm: MD5 and
// SHA-1 for the key derivation and digests, DES and AES-CFB for privacy.
// The agent runs without enforcement. testdata/engine/v3-fips.golden holds
// the transcripts; regenerate it with
// GODEBUG=fips140=only go test -run TestEngineV3FIPS140Only -update .
func TestEngineV3FIPS140Only(t *testing.T) {
if !fips140.Enforced() {
t.Skip("run with GODEBUG=fips140=only")
}
scenarios := v3FIPSScenarios()
var results []goldenCase
for _, name := range slices.Sorted(maps.Keys(scenarios)) {
sc := scenarios[name]
var dump string
synctest.Test(t, func(t *testing.T) {
dump = runV3Scenario(t, sc)
})
results = append(results, goldenCase{name: "v3-fips/" + name, dump: dump})
}
engineGolden.check(t, "v3-fips", results)
}
17 changes: 13 additions & 4 deletions engine_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package gosnmp

import (
"bytes"
"crypto/fips140"
"fmt"
"maps"
"slices"
Expand All @@ -23,6 +24,7 @@ var agentCreds = map[string]agentUser{
"codec-md5": {auth: MD5, authPass: "codec-md5-pass", priv: NoPriv},
"codec-sha-aes": {auth: SHA, authPass: "codec-sha-pass", priv: AES, privPass: "codec-aes-pass"},
"codec-sha256-des": {auth: SHA256, authPass: "codec-sha256-pass", priv: DES, privPass: "codec-des-pass"},
"codec-sha256-aes": {auth: SHA256, authPass: "codec-sha256-pass", priv: AES, privPass: "codec-aes-pass"},
"codec-sha512-aes256c": {auth: SHA512, authPass: "codec-sha512-pass", priv: AES256C, privPass: "codec-aes256c-pass"},
}

Expand Down Expand Up @@ -478,12 +480,17 @@ func TestEngineV3Characterization(t *testing.T) {
}

// runV3Scenario runs sc in the calling synctest bubble and returns its
// transcript.
// transcript. The agent and the key oracle run without FIPS 140-only
// enforcement: they stand for the other side, not the client.
func runV3Scenario(t *testing.T, sc v3Scenario) string {
tr := newEngineTranscript()
agent := newFakeV3Agent(tr, agentCreds, sysDescr)
var agent *fakeV3Agent
fips140.WithoutEnforcement(func() { agent = newFakeV3Agent(tr, agentCreds, sysDescr) })
agent.script = sc.script
c := newFakeTransport(tr, agent.handle)
c := newFakeTransport(tr, func(n int, data []byte) (replies []agentReply) {
fips140.WithoutEnforcement(func() { replies = agent.handle(n, data) })
return replies
})

creds, ok := agentCreds[sc.user]
if sc.creds != nil {
Expand Down Expand Up @@ -555,7 +562,9 @@ func describeKeys(sp *UsmSecurityParameters) string {
PrivacyPassphrase: sp.PrivacyPassphrase,
AuthoritativeEngineID: id,
}
if want.InitSecurityKeys() != nil || !bytes.Equal(want.SecretKey, sp.SecretKey) ||
var err error
fips140.WithoutEnforcement(func() { err = want.InitSecurityKeys() })
if err != nil || !bytes.Equal(want.SecretKey, sp.SecretKey) ||
!bytes.Equal(want.PrivacyKey, sp.PrivacyKey) {
continue
}
Expand Down
112 changes: 112 additions & 0 deletions testdata/engine/v3-fips.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Engine characterization results (v3-fips). Generated by go test -update; do not edit.

== v3-fips/discovery/md5
0s client: user="codec-md5" authNoPriv+reportable timeout=1s retries=2
0s deadline +1s
0s hook PreSend
0s write #1: 3 msgID=2001 noAuthNoPriv+reportable user="" engine="" boots=0 time=0 context=""/"" GetRequest id=1001 []
0s agent: answers #1 with Report id=1001 msgID=2001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1] at noAuthNoPriv (unknown engine ID)
0s agent answers #1 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2001 noAuthNoPriv user="" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" Report id=1001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1]
0s hook OnRecv
0s hook OnFinish
0s first Get: packet empty, error "crypto/md5: use of MD5 is not allowed in FIPS 140-only mode"
0s deadline +1s
0s result: packet nil, error "marshal: crypto/md5: use of MD5 is not allowed in FIPS 140-only mode"
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=0 time=0 keys=none context engine="\x80\x00\x1f\x88\x04codec-agent"
0s known bug: a failed key derivation leaves the agent's engine ID adopted without keys, boots or time, so the next request skips the discovery

== v3-fips/discovery/noauth
0s client: user="codec-noauth" noAuthNoPriv+reportable timeout=1s retries=2
0s deadline +1s
0s hook PreSend
0s write #1: 3 msgID=2001 noAuthNoPriv+reportable user="" engine="" boots=0 time=0 context=""/"" GetRequest id=1001 []
0s agent: answers #1 with Report id=1001 msgID=2001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1] at noAuthNoPriv (unknown engine ID)
0s agent answers #1 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2001 noAuthNoPriv user="" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" Report id=1001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1]
0s hook OnRecv
0s hook OnFinish
0s deadline +1s
0s hook PreSend
0s write #2: 3 msgID=2002 noAuthNoPriv+reportable user="codec-noauth" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" GetRequest id=1002 [.1.3.6.1.2.1.1.1.0]
0s agent: answers #2 with GetResponse id=1002 msgID=2002 [.1.3.6.1.2.1.1.1.0=OctetString:"codec agent"] at noAuthNoPriv (request accepted)
0s agent answers #2 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2002 noAuthNoPriv user="codec-noauth" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" GetResponse id=1002 [.1.3.6.1.2.1.1.1.0=OctetString:"codec agent"]
0s hook OnRecv
0s hook OnFinish
0s result: packet 3 msgID=2002 noAuthNoPriv GetResponse id=1002 context="\x80\x00\x1f\x88\x04codec-agent"/"" usm="codec-noauth"/"\x80\x00\x1f\x88\x04codec-agent"/7/1000 [.1.3.6.1.2.1.1.1.0=OctetString:"codec agent"], no error
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 keys=none context engine="\x80\x00\x1f\x88\x04codec-agent"

== v3-fips/discovery/sha-aes
0s client: user="codec-sha-aes" authPriv+reportable timeout=1s retries=2
0s deadline +1s
0s hook PreSend
0s write #1: 3 msgID=2001 noAuthNoPriv+reportable user="" engine="" boots=0 time=0 context=""/"" GetRequest id=1001 []
0s agent: answers #1 with Report id=1001 msgID=2001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1] at noAuthNoPriv (unknown engine ID)
0s agent answers #1 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2001 noAuthNoPriv user="" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" Report id=1001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1]
0s hook OnRecv
0s hook OnFinish
0s first Get: packet empty, error "crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode"
0s deadline +1s
0s result: packet nil, error "marshal: crypto/aes: invalid key size 0"
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=0 time=0 keys=none context engine="\x80\x00\x1f\x88\x04codec-agent"
0s known bug: a failed key derivation leaves the agent's engine ID adopted without keys, boots or time, so the next request skips the discovery

== v3-fips/discovery/sha256-aes
0s client: user="codec-sha256-aes" authPriv+reportable timeout=1s retries=2
0s deadline +1s
0s hook PreSend
0s write #1: 3 msgID=2001 noAuthNoPriv+reportable user="" engine="" boots=0 time=0 context=""/"" GetRequest id=1001 []
0s agent: answers #1 with Report id=1001 msgID=2001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1] at noAuthNoPriv (unknown engine ID)
0s agent answers #1 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2001 noAuthNoPriv user="" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" Report id=1001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1]
0s hook OnRecv
0s hook OnFinish
0s deadline +1s
0s result: packet nil, error "recover: crypto/cipher: use of CFB is not allowed in FIPS 140-only mode Stack: ..."
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 keys=current context engine="\x80\x00\x1f\x88\x04codec-agent"
0s known bug: AES-CFB panics in FIPS 140-only mode; send recovers it into an error that carries the stack

== v3-fips/discovery/sha256-des
0s client: user="codec-sha256-des" authPriv+reportable timeout=1s retries=2
0s deadline +1s
0s hook PreSend
0s write #1: 3 msgID=2001 noAuthNoPriv+reportable user="" engine="" boots=0 time=0 context=""/"" GetRequest id=1001 []
0s agent: answers #1 with Report id=1001 msgID=2001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1] at noAuthNoPriv (unknown engine ID)
0s agent answers #1 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2001 noAuthNoPriv user="" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" Report id=1001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1]
0s hook OnRecv
0s hook OnFinish
0s deadline +1s
0s result: packet nil, error "marshal: crypto/des: use of DES is not allowed in FIPS 140-only mode"
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 keys=current context engine="\x80\x00\x1f\x88\x04codec-agent"

== v3-fips/discovery/sha512-aes256c
0s client: user="codec-sha512-aes256c" authPriv+reportable timeout=1s retries=2
0s deadline +1s
0s hook PreSend
0s write #1: 3 msgID=2001 noAuthNoPriv+reportable user="" engine="" boots=0 time=0 context=""/"" GetRequest id=1001 []
0s agent: answers #1 with Report id=1001 msgID=2001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1] at noAuthNoPriv (unknown engine ID)
0s agent answers #1 after 0s
0s hook OnSent
0s read from 127.0.0.2:161: 3 msgID=2001 noAuthNoPriv user="" engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 context="\x80\x00\x1f\x88\x04codec-agent"/"" Report id=1001 [.1.3.6.1.6.3.15.1.1.4.0=Counter32:1]
0s hook OnRecv
0s hook OnFinish
0s deadline +1s
0s result: packet nil, error "recover: crypto/cipher: use of CFB is not allowed in FIPS 140-only mode Stack: ..."
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=7 time=1000 keys=current context engine="\x80\x00\x1f\x88\x04codec-agent"
0s known bug: AES-CFB panics in FIPS 140-only mode; send recovers it into an error that carries the stack

== v3-fips/known-engine/md5
0s client: user="codec-md5" authNoPriv+reportable timeout=1s retries=2
0s deadline +1s
0s result: packet nil, error "marshal: crypto/md5: use of MD5 is not allowed in FIPS 140-only mode"
0s client after: engine="\x80\x00\x1f\x88\x04codec-agent" boots=0 time=0 keys=none context engine=""
0s known bug: the key derivation error is ignored when the engine ID is known
Loading