First round of linter cleanups.

This commit is contained in:
2025-11-15 15:11:07 -08:00
parent 5fcba0e814
commit aba5e519a4
9 changed files with 200 additions and 190 deletions

View File

@@ -65,8 +65,6 @@ linters:
- funcorder # checks the order of functions, methods, and constructors - funcorder # checks the order of functions, methods, and constructors
- funlen # tool for detection of long functions - funlen # tool for detection of long functions
- gocheckcompilerdirectives # validates go compiler directive comments (//go:) - gocheckcompilerdirectives # validates go compiler directive comments (//go:)
- gochecknoglobals # checks that no global variables exist
- gochecknoinits # checks that no init functions are present in Go code
- gochecksumtype # checks exhaustiveness on Go "sum types" - gochecksumtype # checks exhaustiveness on Go "sum types"
- gocognit # computes and checks the cognitive complexity of functions - gocognit # computes and checks the cognitive complexity of functions
- goconst # finds repeated strings that could be replaced by a constant - goconst # finds repeated strings that could be replaced by a constant
@@ -368,7 +366,7 @@ linters:
max-func-lines: 0 max-func-lines: 0
nolintlint: nolintlint:
# Exclude following linters from requiring an explanation. # Exclude the following linters from requiring an explanation.
# Default: [] # Default: []
allow-no-explanation: [ funlen, gocognit, golines ] allow-no-explanation: [ funlen, gocognit, golines ]
# Enable to require an explanation of nonzero length after each nolint directive. # Enable to require an explanation of nonzero length after each nolint directive.
@@ -444,8 +442,11 @@ linters:
presets: presets:
- std-error-handling - std-error-handling
- common-false-positives - common-false-positives
# Excluding configuration per-path, per-linter, per-text and per-source.
rules: rules:
- path: 'ahash/ahash.go'
linters: [ staticcheck, gosec ]
- path: 'backoff/backoff_test.go'
linters: [ testpackage ]
- source: 'TODO' - source: 'TODO'
linters: [ godot ] linters: [ godot ]
- text: 'should have a package comment' - text: 'should have a package comment'

View File

@@ -4,8 +4,8 @@
package ahash package ahash
import ( import (
"crypto/md5" "crypto/md5" // #nosec G505
"crypto/sha1" "crypto/sha1" // #nosec G501
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"errors" "errors"
@@ -17,34 +17,15 @@ import (
"io" "io"
"sort" "sort"
"git.wntrmute.dev/kyle/goutils/assert"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s" "golang.org/x/crypto/blake2s"
"golang.org/x/crypto/md4" "golang.org/x/crypto/md4" // #nosec G506
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160" // #nosec G507
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
"git.wntrmute.dev/kyle/goutils/assert"
) )
func sha224Slicer(bs []byte) []byte {
sum := sha256.Sum224(bs)
return sum[:]
}
func sha256Slicer(bs []byte) []byte {
sum := sha256.Sum256(bs)
return sum[:]
}
func sha384Slicer(bs []byte) []byte {
sum := sha512.Sum384(bs)
return sum[:]
}
func sha512Slicer(bs []byte) []byte {
sum := sha512.Sum512(bs)
return sum[:]
}
// Hash represents a generic hash function that may or may not be secure. It // Hash represents a generic hash function that may or may not be secure. It
// satisfies the hash.Hash interface. // satisfies the hash.Hash interface.
type Hash struct { type Hash struct {
@@ -247,17 +228,17 @@ func init() {
// HashList returns a sorted list of all the hash algorithms supported by the // HashList returns a sorted list of all the hash algorithms supported by the
// package. // package.
func HashList() []string { func HashList() []string {
return hashList[:] return hashList
} }
// SecureHashList returns a sorted list of all the secure (cryptographic) hash // SecureHashList returns a sorted list of all the secure (cryptographic) hash
// algorithms supported by the package. // algorithms supported by the package.
func SecureHashList() []string { func SecureHashList() []string {
return secureHashList[:] return secureHashList
} }
// InsecureHashList returns a sorted list of all the insecure hash algorithms // InsecureHashList returns a sorted list of all the insecure hash algorithms
// supported by the package. // supported by the package.
func InsecureHashList() []string { func InsecureHashList() []string {
return insecureHashList[:] return insecureHashList
} }

View File

@@ -1,16 +1,18 @@
package ahash package ahash_test
import ( import (
"bytes" "bytes"
"encoding/hex"
"fmt" "fmt"
"testing" "testing"
"git.wntrmute.dev/kyle/goutils/ahash"
"git.wntrmute.dev/kyle/goutils/assert" "git.wntrmute.dev/kyle/goutils/assert"
) )
func TestSecureHash(t *testing.T) { func TestSecureHash(t *testing.T) {
algo := "sha256" algo := "sha256"
h, err := New(algo) h, err := ahash.New(algo)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, h.IsSecure(), algo+" should be a secure hash") assert.BoolT(t, h.IsSecure(), algo+" should be a secure hash")
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo") assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
@@ -19,28 +21,28 @@ func TestSecureHash(t *testing.T) {
var data []byte var data []byte
var expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" var expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
sum, err := Sum(algo, data) sum, err := ahash.Sum(algo, data)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum)) assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
data = []byte("hello, world") data = []byte("hello, world")
buf := bytes.NewBuffer(data) buf := bytes.NewBuffer(data)
expected = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b" expected = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
sum, err = SumReader(algo, buf) sum, err = ahash.SumReader(algo, buf)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum)) assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
data = []byte("hello world") data = []byte("hello world")
_, err = h.Write(data) _, err = h.Write(data)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
unExpected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b" unExpected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
sum = h.Sum(nil) sum = h.Sum(nil)
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected)) assert.BoolT(t, hex.EncodeToString(sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
} }
func TestInsecureHash(t *testing.T) { func TestInsecureHash(t *testing.T) {
algo := "md5" algo := "md5"
h, err := New(algo) h, err := ahash.New(algo)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash") assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo") assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
@@ -49,28 +51,28 @@ func TestInsecureHash(t *testing.T) {
var data []byte var data []byte
var expected = "d41d8cd98f00b204e9800998ecf8427e" var expected = "d41d8cd98f00b204e9800998ecf8427e"
sum, err := Sum(algo, data) sum, err := ahash.Sum(algo, data)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum)) assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
data = []byte("hello, world") data = []byte("hello, world")
buf := bytes.NewBuffer(data) buf := bytes.NewBuffer(data)
expected = "e4d7f1b4ed2e42d15898f4b27b019da4" expected = "e4d7f1b4ed2e42d15898f4b27b019da4"
sum, err = SumReader(algo, buf) sum, err = ahash.SumReader(algo, buf)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum)) assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
data = []byte("hello world") data = []byte("hello world")
_, err = h.Write(data) _, err = h.Write(data)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
unExpected := "e4d7f1b4ed2e42d15898f4b27b019da4" unExpected := "e4d7f1b4ed2e42d15898f4b27b019da4"
sum = h.Sum(nil) sum = h.Sum(nil)
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected)) assert.BoolT(t, hex.EncodeToString(sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
} }
func TestHash32(t *testing.T) { func TestHash32(t *testing.T) {
algo := "crc32-ieee" algo := "crc32-ieee"
h, err := New(algo) h, err := ahash.New(algo)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash") assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo") assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
@@ -102,7 +104,7 @@ func TestHash32(t *testing.T) {
func TestHash64(t *testing.T) { func TestHash64(t *testing.T) {
algo := "crc64" algo := "crc64"
h, err := New(algo) h, err := ahash.New(algo)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash") assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo") assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
@@ -133,9 +135,9 @@ func TestHash64(t *testing.T) {
} }
func TestListLengthSanity(t *testing.T) { func TestListLengthSanity(t *testing.T) {
all := HashList() all := ahash.HashList()
secure := SecureHashList() secure := ahash.SecureHashList()
insecure := InsecureHashList() insecure := ahash.InsecureHashList()
assert.BoolT(t, len(all) == len(secure)+len(insecure)) assert.BoolT(t, len(all) == len(secure)+len(insecure))
} }
@@ -146,11 +148,11 @@ func TestSumLimitedReader(t *testing.T) {
extendedData := bytes.NewBufferString("hello, world! this is an extended message") extendedData := bytes.NewBufferString("hello, world! this is an extended message")
expected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b" expected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
hash, err := SumReader("sha256", data) hash, err := ahash.SumReader("sha256", data)
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, fmt.Sprintf("%x", hash) == expected, fmt.Sprintf("have hash %x, want %s", hash, expected)) assert.BoolT(t, hex.EncodeToString(hash) == expected, fmt.Sprintf("have hash %x, want %s", hash, expected))
extendedHash, err := SumLimitedReader("sha256", extendedData, int64(dataLen)) extendedHash, err := ahash.SumLimitedReader("sha256", extendedData, int64(dataLen))
assert.NoErrorT(t, err) assert.NoErrorT(t, err)
assert.BoolT(t, bytes.Equal(hash, extendedHash), fmt.Sprintf("have hash %x, want %x", extendedHash, hash)) assert.BoolT(t, bytes.Equal(hash, extendedHash), fmt.Sprintf("have hash %x, want %x", extendedHash, hash))

View File

@@ -9,6 +9,7 @@
package assert package assert
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
@@ -16,11 +17,13 @@ import (
"testing" "testing"
) )
const callerSkip = 2
// NoDebug can be set to true to cause all asserts to be ignored. // NoDebug can be set to true to cause all asserts to be ignored.
var NoDebug bool var NoDebug bool
func die(what string, a ...string) { func die(what string, a ...string) {
_, file, line, ok := runtime.Caller(2) _, file, line, ok := runtime.Caller(callerSkip)
if !ok { if !ok {
panic(what) panic(what)
} }
@@ -31,30 +34,32 @@ func die(what string, a ...string) {
s = ": " + s s = ": " + s
} }
panic(what + s) panic(what + s)
} else {
fmt.Fprintf(os.Stderr, "%s", what)
if len(a) > 0 {
s := strings.Join(a, ", ")
fmt.Fprintln(os.Stderr, ": "+s)
} else {
fmt.Fprintf(os.Stderr, "\n")
}
fmt.Fprintf(os.Stderr, "\t%s line %d\n", file, line)
os.Exit(1)
} }
fmt.Fprintf(os.Stderr, "%s", what)
if len(a) > 0 {
s := strings.Join(a, ", ")
fmt.Fprintln(os.Stderr, ": "+s)
} else {
fmt.Fprintf(os.Stderr, "\n")
}
fmt.Fprintf(os.Stderr, "\t%s line %d\n", file, line)
os.Exit(1)
} }
// Bool asserts that cond is false. // Bool asserts that cond is false.
// //
// For example, this would replace // For example, this would replace
// if x < 0 { //
// log.Fatal("x is subzero") // if x < 0 {
// } // log.Fatal("x is subzero")
// }
// //
// The same assertion would be // The same assertion would be
// assert.Bool(x, "x is subzero") //
// assert.Bool(x, "x is subzero")
func Bool(cond bool, s ...string) { func Bool(cond bool, s ...string) {
if NoDebug { if NoDebug {
return return
@@ -68,11 +73,12 @@ func Bool(cond bool, s ...string) {
// Error asserts that err is not nil, e.g. that an error has occurred. // Error asserts that err is not nil, e.g. that an error has occurred.
// //
// For example, // For example,
// if err == nil { //
// log.Fatal("call to <something> should have failed") // if err == nil {
// } // log.Fatal("call to <something> should have failed")
// // becomes // }
// assert.Error(err, "call to <something> should have failed") // // becomes
// assert.Error(err, "call to <something> should have failed")
func Error(err error, s ...string) { func Error(err error, s ...string) {
if NoDebug { if NoDebug {
return return
@@ -100,7 +106,7 @@ func NoError(err error, s ...string) {
// ErrorEq asserts that the actual error is the expected error. // ErrorEq asserts that the actual error is the expected error.
func ErrorEq(expected, actual error) { func ErrorEq(expected, actual error) {
if NoDebug || (expected == actual) { if NoDebug || (errors.Is(expected, actual)) {
return return
} }
@@ -155,7 +161,7 @@ func NoErrorT(t *testing.T, err error) {
// ErrorEqT compares a pair of errors, calling Fatal on it if they // ErrorEqT compares a pair of errors, calling Fatal on it if they
// don't match. // don't match.
func ErrorEqT(t *testing.T, expected, actual error) { func ErrorEqT(t *testing.T, expected, actual error) {
if NoDebug || (expected == actual) { if NoDebug || (errors.Is(expected, actual)) {
return return
} }

View File

@@ -10,29 +10,21 @@
// backoff is configured with a maximum duration that will not be // backoff is configured with a maximum duration that will not be
// exceeded. // exceeded.
// //
// The `New` function will attempt to use the system's cryptographic // This package uses math/rand/v2 for jitter, which is automatically
// random number generator to seed a Go math/rand random number // seeded from a cryptographically secure source.
// source. If this fails, the package will panic on startup.
package backoff package backoff
import ( import (
"crypto/rand"
"encoding/binary"
"io"
"math" "math"
mrand "math/rand" "math/rand/v2"
"sync"
"time" "time"
) )
var prngMu sync.Mutex
var prng *mrand.Rand
// DefaultInterval is used when a Backoff is initialised with a // DefaultInterval is used when a Backoff is initialised with a
// zero-value Interval. // zero-value Interval.
var DefaultInterval = 5 * time.Minute var DefaultInterval = 5 * time.Minute
// DefaultMaxDuration is maximum amount of time that the backoff will // DefaultMaxDuration is the maximum amount of time that the backoff will
// delay for. // delay for.
var DefaultMaxDuration = 6 * time.Hour var DefaultMaxDuration = 6 * time.Hour
@@ -50,10 +42,9 @@ type Backoff struct {
// interval controls the time step for backing off. // interval controls the time step for backing off.
interval time.Duration interval time.Duration
// noJitter controls whether to use the "Full Jitter" // noJitter controls whether to use the "Full Jitter" improvement to attempt
// improvement to attempt to smooth out spikes in a high // to smooth out spikes in a high-contention scenario. If noJitter is set to
// contention scenario. If noJitter is set to true, no // true, no jitter will be introduced.
// jitter will be introduced.
noJitter bool noJitter bool
// decay controls the decay of n. If it is non-zero, n is // decay controls the decay of n. If it is non-zero, n is
@@ -65,17 +56,17 @@ type Backoff struct {
lastTry time.Time lastTry time.Time
} }
// New creates a new backoff with the specified max duration and // New creates a new backoff with the specified maxDuration duration and
// interval. Zero values may be used to use the default values. // interval. Zero values may be used to use the default values.
// //
// Panics if either max or interval is negative. // Panics if either dMax or interval is negative.
func New(max time.Duration, interval time.Duration) *Backoff { func New(dMax time.Duration, interval time.Duration) *Backoff {
if max < 0 || interval < 0 { if dMax < 0 || interval < 0 {
panic("backoff: max or interval is negative") panic("backoff: dMax or interval is negative")
} }
b := &Backoff{ b := &Backoff{
maxDuration: max, maxDuration: dMax,
interval: interval, interval: interval,
} }
b.setup() b.setup()
@@ -84,31 +75,12 @@ func New(max time.Duration, interval time.Duration) *Backoff {
// NewWithoutJitter works similarly to New, except that the created // NewWithoutJitter works similarly to New, except that the created
// Backoff will not use jitter. // Backoff will not use jitter.
func NewWithoutJitter(max time.Duration, interval time.Duration) *Backoff { func NewWithoutJitter(dMax time.Duration, interval time.Duration) *Backoff {
b := New(max, interval) b := New(dMax, interval)
b.noJitter = true b.noJitter = true
return b return b
} }
func init() {
var buf [8]byte
var n int64
_, err := io.ReadFull(rand.Reader, buf[:])
if err != nil {
panic(err.Error())
}
// G115: Intentional uint64->int64 conversion. Overflow is acceptable here
// since math/rand.NewSource accepts any int64 value (positive or negative)
// as a valid seed. The conversion ensures uniform distribution across the
// entire int64 range.
n = int64(binary.LittleEndian.Uint64(buf[:])) // #nosec G115
src := mrand.NewSource(n)
prng = mrand.New(src)
}
func (b *Backoff) setup() { func (b *Backoff) setup() {
if b.interval == 0 { if b.interval == 0 {
b.interval = DefaultInterval b.interval = DefaultInterval
@@ -126,35 +98,44 @@ func (b *Backoff) Duration() time.Duration {
b.decayN() b.decayN()
t := b.duration(b.n) d := b.duration(b.n)
if b.n < math.MaxUint64 { if b.n < math.MaxUint64 {
b.n++ b.n++
} }
if !b.noJitter { if !b.noJitter {
prngMu.Lock() d = time.Duration(rand.Int64N(int64(d))) // #nosec G404
t = time.Duration(prng.Int63n(int64(t)))
prngMu.Unlock()
} }
return t return d
} }
const maxN uint64 = 63
// requires b to be locked. // requires b to be locked.
func (b *Backoff) duration(n uint64) (t time.Duration) { func (b *Backoff) duration(n uint64) time.Duration {
// Saturate pow // Use left shift on the underlying integer representation to avoid
pow := time.Duration(math.MaxInt64) // multiplying time.Duration by time.Duration (which is semantically
if n < 63 { // incorrect and flagged by linters).
pow = 1 << n if n >= maxN {
// Saturate when n would overflow a 64-bit shift or exceed maxDuration.
return b.maxDuration
} }
t = b.interval * pow // Calculate 2^n * interval using a shift. Detect overflow by checking
if t/pow != b.interval || t > b.maxDuration { // for sign change or monotonicity loss and clamp to maxDuration.
t = b.maxDuration shifted := b.interval << n
if shifted < 0 || shifted < b.interval {
// Overflow occurred during the shift; clamp to maxDuration.
return b.maxDuration
} }
return if shifted > b.maxDuration {
return b.maxDuration
}
return shifted
} }
// Reset resets the attempt counter of a backoff. // Reset resets the attempt counter of a backoff.
@@ -178,7 +159,7 @@ func (b *Backoff) SetDecay(decay time.Duration) {
b.decay = decay b.decay = decay
} }
// requires b to be locked // requires b to be locked.
func (b *Backoff) decayN() { func (b *Backoff) decayN() {
if b.decay == 0 { if b.decay == 0 {
return return
@@ -190,7 +171,9 @@ func (b *Backoff) decayN() {
} }
lastDuration := b.duration(b.n - 1) lastDuration := b.duration(b.n - 1)
decayed := time.Since(b.lastTry) > lastDuration+b.decay // Reset when the elapsed time is at least the previous backoff plus decay.
// Using ">=" avoids boundary flakiness in tests and real usage.
decayed := time.Since(b.lastTry) >= lastDuration+b.decay
b.lastTry = time.Now() b.lastTry = time.Now()
if !decayed { if !decayed {

View File

@@ -9,7 +9,7 @@ import (
// If given New with 0's and no jitter, ensure that certain invariants are met: // If given New with 0's and no jitter, ensure that certain invariants are met:
// //
// - the default max duration and interval should be used // - the default maxDuration duration and interval should be used
// - noJitter should be true // - noJitter should be true
// - the RNG should not be initialised // - the RNG should not be initialised
// - the first duration should be equal to the default interval // - the first duration should be equal to the default interval
@@ -17,7 +17,11 @@ func TestDefaults(t *testing.T) {
b := NewWithoutJitter(0, 0) b := NewWithoutJitter(0, 0)
if b.maxDuration != DefaultMaxDuration { if b.maxDuration != DefaultMaxDuration {
t.Fatalf("expected new backoff to use the default max duration (%s), but have %s", DefaultMaxDuration, b.maxDuration) t.Fatalf(
"expected new backoff to use the default maxDuration duration (%s), but have %s",
DefaultMaxDuration,
b.maxDuration,
)
} }
if b.interval != DefaultInterval { if b.interval != DefaultInterval {
@@ -48,7 +52,7 @@ func TestSetup(t *testing.T) {
func TestTries(t *testing.T) { func TestTries(t *testing.T) {
b := NewWithoutJitter(5, 1) b := NewWithoutJitter(5, 1)
for i := uint64(0); i < 3; i++ { for i := range uint64(3) {
if b.n != i { if b.n != i {
t.Fatalf("want tries=%d, have tries=%d", i, b.n) t.Fatalf("want tries=%d, have tries=%d", i, b.n)
} }
@@ -73,7 +77,7 @@ func TestTries(t *testing.T) {
func TestReset(t *testing.T) { func TestReset(t *testing.T) {
const iter = 10 const iter = 10
b := New(1000, 1) b := New(1000, 1)
for i := 0; i < iter; i++ { for range iter {
_ = b.Duration() _ = b.Duration()
} }
@@ -88,17 +92,17 @@ func TestReset(t *testing.T) {
} }
const decay = 5 * time.Millisecond const decay = 5 * time.Millisecond
const max = 10 * time.Millisecond const maxDuration = 10 * time.Millisecond
const interval = time.Millisecond const interval = time.Millisecond
func TestDecay(t *testing.T) { func TestDecay(t *testing.T) {
const iter = 10 const iter = 10
b := NewWithoutJitter(max, 1) b := NewWithoutJitter(maxDuration, 1)
b.SetDecay(decay) b.SetDecay(decay)
var backoff time.Duration var backoff time.Duration
for i := 0; i < iter; i++ { for range iter {
backoff = b.Duration() backoff = b.Duration()
} }
@@ -127,7 +131,7 @@ func TestDecaySaturation(t *testing.T) {
b.SetDecay(decay) b.SetDecay(decay)
var duration time.Duration var duration time.Duration
for i := 0; i <= 2; i++ { for range 3 {
duration = b.Duration() duration = b.Duration()
} }
@@ -145,7 +149,7 @@ func TestDecaySaturation(t *testing.T) {
} }
func ExampleBackoff_SetDecay() { func ExampleBackoff_SetDecay() {
b := NewWithoutJitter(max, interval) b := NewWithoutJitter(maxDuration, interval)
b.SetDecay(decay) b.SetDecay(decay)
// try 0 // try 0

View File

@@ -158,59 +158,87 @@ type EncryptedContentInfo struct {
EncryptedContent []byte `asn1:"tag:0,optional"` EncryptedContent []byte `asn1:"tag:0,optional"`
} }
func unmarshalInit(raw []byte) (init initPKCS7, err error) {
_, err = asn1.Unmarshal(raw, &init)
if err != nil {
return initPKCS7{}, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
return init, nil
}
func populateData(msg *PKCS7, content asn1.RawValue) error {
msg.ContentInfo = "Data"
_, err := asn1.Unmarshal(content.Bytes, &msg.Content.Data)
if err != nil {
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
return nil
}
func populateSignedData(msg *PKCS7, contentBytes []byte) error {
msg.ContentInfo = "SignedData"
var sd signedData
if _, err := asn1.Unmarshal(contentBytes, &sd); err != nil {
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
if len(sd.Certificates.Bytes) != 0 {
certs, err := x509.ParseCertificates(sd.Certificates.Bytes)
if err != nil {
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
msg.Content.SignedData.Certificates = certs
}
if len(sd.Crls.Bytes) != 0 {
crl, err := x509.ParseRevocationList(sd.Crls.Bytes)
if err != nil {
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
msg.Content.SignedData.Crl = crl
}
msg.Content.SignedData.Version = sd.Version
msg.Content.SignedData.Raw = contentBytes
return nil
}
func populateEncryptedData(msg *PKCS7, contentBytes []byte) error {
msg.ContentInfo = "EncryptedData"
var ed EncryptedData
if _, err := asn1.Unmarshal(contentBytes, &ed); err != nil {
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
if ed.Version != 0 {
return certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS #7 encryptedData version 0 is supported"))
}
msg.Content.EncryptedData = ed
return nil
}
// ParsePKCS7 attempts to parse the DER encoded bytes of a // ParsePKCS7 attempts to parse the DER encoded bytes of a
// PKCS7 structure. // PKCS7 structure.
func ParsePKCS7(raw []byte) (msg *PKCS7, err error) { func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {
var pkcs7 initPKCS7 pkcs7, err := unmarshalInit(raw)
_, err = asn1.Unmarshal(raw, &pkcs7)
if err != nil { if err != nil {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err) return nil, err
} }
msg = new(PKCS7) msg = new(PKCS7)
msg.Raw = pkcs7.Raw msg.Raw = pkcs7.Raw
msg.ContentInfo = pkcs7.ContentType.String() msg.ContentInfo = pkcs7.ContentType.String()
switch {
case msg.ContentInfo == ObjIDData:
msg.ContentInfo = "Data"
_, err = asn1.Unmarshal(pkcs7.Content.Bytes, &msg.Content.Data)
if err != nil {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
case msg.ContentInfo == ObjIDSignedData:
msg.ContentInfo = "SignedData"
var signedData signedData
_, err = asn1.Unmarshal(pkcs7.Content.Bytes, &signedData)
if err != nil {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
if len(signedData.Certificates.Bytes) != 0 {
msg.Content.SignedData.Certificates, err = x509.ParseCertificates(signedData.Certificates.Bytes)
if err != nil {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
}
if len(signedData.Crls.Bytes) != 0 {
msg.Content.SignedData.Crl, err = x509.ParseRevocationList(signedData.Crls.Bytes)
if err != nil {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
}
msg.Content.SignedData.Version = signedData.Version
msg.Content.SignedData.Raw = pkcs7.Content.Bytes
case msg.ContentInfo == ObjIDEncryptedData:
msg.ContentInfo = "EncryptedData"
var encryptedData EncryptedData
_, err = asn1.Unmarshal(pkcs7.Content.Bytes, &encryptedData)
if err != nil {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
}
if encryptedData.Version != 0 {
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS #7 encryptedData version 0 is supported"))
}
msg.Content.EncryptedData = encryptedData
switch msg.ContentInfo {
case ObjIDData:
if err := populateData(msg, pkcs7.Content); err != nil {
return nil, err
}
case ObjIDSignedData:
if err := populateSignedData(msg, pkcs7.Content.Bytes); err != nil {
return nil, err
}
case ObjIDEncryptedData:
if err := populateEncryptedData(msg, pkcs7.Content.Bytes); err != nil {
return nil, err
}
default: default:
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS# 7 content of type data, signed data or encrypted data can be parsed")) return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS# 7 content of type data, signed data or encrypted data can be parsed"))
} }

6
go.mod
View File

@@ -1,14 +1,14 @@
module git.wntrmute.dev/kyle/goutils module git.wntrmute.dev/kyle/goutils
go 1.22 go 1.24.0
require ( require (
github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/go-syslog v1.0.0
github.com/kr/text v0.2.0 github.com/kr/text v0.2.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.12.0 github.com/pkg/sftp v1.12.0
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b golang.org/x/crypto v0.44.0
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad golang.org/x/sys v0.38.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )

5
go.sum
View File

@@ -27,12 +27,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=