diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a905e8f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,42 @@ +name: Lint + +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read + +jobs: + golangci: + name: golangci-lint + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.25.3" + cache: true + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + version: latest + # Optional: show only new issues if it's a pull request. + only-new-issues: true + # Optional: golangci-lint command line arguments. + args: --timeout=5m + # Skip the default install-mode since Go is already set up + skip-cache: false + skip-save-cache: false diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..7ff85c6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,103 @@ +version: "2" +run: + go: "1.25" + issues-exit-code: 1 + tests: true +linters: + enable: + # Critical Security & Correctness + - gosec # Security-focused linter (critical for crypto code) + - errcheck # Ensures errors aren't ignored + - govet # Go's official checker for suspicious constructs + - staticcheck # Comprehensive bug/performance/style checker + + # Bug Prevention + - ineffassign # Detects ineffectual assignments + - unused # Finds unused code + - nilnil # Detects returning nil with nil errors + - nilerr # Finds code returning nil even on error + - errchkjson # Checks for unchecked errors in JSON encoding/decoding + - bodyclose # Ensures HTTP response bodies are closed + - wastedassign # Detects wasted assignments + + # Code Quality & Maintainability + - gocyclo # Cyclomatic complexity + - gocognit # Cognitive complexity + - dupl # Code duplication detection + - unconvert # Unnecessary type conversions + - unparam # Unused function parameters + - misspell # Spelling mistakes + - goconst # Repeated strings that could be constants + + # Error Handling + - errorlint # Error wrapping and assertions + - errname # Error naming conventions + - nilnesserr # Nilness checks + + # Context & Concurrency + - fatcontext # Context.Context should be first parameter + - contextcheck # Context usage checks + + # Embedded & Struct Checks + - embeddedstructfieldcheck # Embedded struct field checks + - exhaustive # Exhaustive enum/switch checks + + # Style & Modernization + - asciicheck # ASCII character checks + - canonicalheader # Canonical header checks + - intrange # Integer range checks + - mirror # Mirror checks + - modernize # Modernization suggestions + - nolintlint # Nolint directive checks + - predeclared # Predeclared identifier checks + - usestdlibvars # Use standard library variables + + # Performance + - prealloc # Preallocation checks + - perfsprint # Sprint performance checks + + # Testing + - paralleltest # Parallel test checks + - testifylint # Testify assertions checks + - thelper # Test helper checks + - tparallel # t.Parallel() checks + + # Other + - exptostd # Experimental to standard library + - protogetter # Protobuf getter checks + - reassign # Reassignment checks + - sloglint # Structured logging checks + - godox # Highlight FIXMEs + + settings: + exhaustive: + default-signifies-exhaustive: true + godox: + keywords: + - FIXME + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + settings: + gofmt: + simplify: true + goimports: + local-prefixes: + - github.com/mycoria/crop + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..5d54def --- /dev/null +++ b/Justfile @@ -0,0 +1,53 @@ +# Justfile for CROP - Cryptographic Routines, Operations & Primitives + +# Default recipe: list all available commands +default: + @just --list + +# Install development tools +install-tools: + @echo "Installing golangci-lint..." + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest + @echo "golangci-lint installed successfully" + @golangci-lint --version + +# Run golangci-lint +lint: + @echo "Running golangci-lint..." + @golangci-lint run --timeout=5m + +# Run tests +test: + @echo "Running tests..." + @go test -v ./... + +# Run tests with coverage +test-coverage: + @echo "Running tests with coverage..." + @go test -v -coverprofile=coverage.out ./... + @go tool cover -html=coverage.out -o coverage.html + @echo "Coverage report generated: coverage.html" + +# Build the project +build: + @echo "Building..." + @go build ./... + +# Format code +fmt: + @echo "Formatting code..." + @go fmt ./... + +# Tidy dependencies +tidy: + @echo "Tidying dependencies..." + @go mod tidy + +# Clean build artifacts +clean: + @echo "Cleaning..." + @rm -f coverage.out coverage.html + +# Run all checks (format, lint, test) +check: fmt lint test + @echo "All checks passed!" diff --git a/challenge.go b/challenge.go index c8ded23..243200d 100644 --- a/challenge.go +++ b/challenge.go @@ -96,7 +96,7 @@ func (hcc *HashedContextChallenge) MakeResponse(challenge []byte) (response []by } func (hcc *HashedContextChallenge) makeHash(input []byte, reverse bool) []byte { - vh := NewValueHasher(hcc.hash) + vh := NewValueHasher(hcc.hash.New()) vh.AddString("hashed context challenge") // Fixed internal value. vh.AddString(hcc.purpose) // Add purpose. @@ -111,5 +111,5 @@ func (hcc *HashedContextChallenge) makeHash(input []byte, reverse bool) []byte { } vh.Add(input) - return vh.Sum() + return vh.Sum(nil) } diff --git a/challenge_test.go b/challenge_test.go index ad61336..63f757a 100644 --- a/challenge_test.go +++ b/challenge_test.go @@ -244,13 +244,13 @@ func TestHashedContextChallenge_ResponseMatchesIndependentComputation(t *testing // Independent recomputation matching the requester's expected order: // fixed string, purpose, requester, responder, challenge. - vh := NewValueHasher(BLAKE3) + vh := NewValueHasher(BLAKE3.New()) vh.AddString("hashed context challenge") vh.AddString(purpose) vh.AddString(reqCtx) vh.AddString(resCtx) vh.Add(chal) - resp2 := vh.Sum() + resp2 := vh.Sum(nil) if !bytes.Equal(resp1, resp2) { t.Fatalf("independent computation mismatch\n got: %x\nwant: %x", resp1, resp2) @@ -260,4 +260,4 @@ func TestHashedContextChallenge_ResponseMatchesIndependentComputation(t *testing if err := hReq.CheckResponse(resp1); err != nil { t.Fatalf("CheckResponse failed for valid response: %v", err) } -} \ No newline at end of file +} diff --git a/hash.go b/hash.go index 3820b8c..992ef51 100644 --- a/hash.go +++ b/hash.go @@ -125,9 +125,9 @@ func (h Hash) Verify(data, checksum []byte) error { } // NewValueHasher creates a structured hasher for multiple values. -func NewValueHasher(hash Hash) *ValueHasher { +func NewValueHasher(h hash.Hash) *ValueHasher { return &ValueHasher{ - hasher: hash.New(), + hasher: h, } } @@ -176,18 +176,32 @@ func (vh *ValueHasher) AddString(data string) { vh.Add([]byte(data)) } +// AddUint hashes an uint field. +func (vh *ValueHasher) AddUint(n uint64) { + var buf [8]byte + b := buf[:] + binary.BigEndian.PutUint64(b, n) + vh.Add(b) +} + // Sum finalizes and returns the hash result. -func (vh *ValueHasher) Sum() []byte { - // Write finisher. +func (vh *ValueHasher) Sum(dst []byte) []byte { + // Create finisher. finisher := [16]byte{ // Total field count. 0, 0, 0, 0, 0, 0, 0, 0, - // Max uint64 as the "field length". + // Use a max uint64 in the place of the "field length" of a next field as an otherwise impossible finalizer. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, } binary.BigEndian.PutUint64(finisher[:8], vh.fieldCnt) - return vh.hasher.Sum(finisher[:]) + // Write finisher. + _, err := vh.hasher.Write(finisher[:]) + if err != nil { + panic(err) + } + + return vh.hasher.Sum(dst) } diff --git a/hash_test.go b/hash_test.go index 770278f..d4b564f 100644 --- a/hash_test.go +++ b/hash_test.go @@ -157,11 +157,11 @@ func TestValueHasher_Sum_FormatAndDeterminism(t *testing.T) { for _, algo := range algos { algo := algo t.Run(string(algo), func(t *testing.T) { - vh := NewValueHasher(algo) + vh := NewValueHasher(algo.New()) for _, f := range fields { vh.Add(f) } - sum := vh.Sum() + sum := vh.Sum(nil) hasher := algo.New() if hasher == nil { @@ -198,11 +198,11 @@ func TestValueHasher_Sum_FormatAndDeterminism(t *testing.T) { } // Determinism: re-run and expect the same output. - vh2 := NewValueHasher(algo) + vh2 := NewValueHasher(algo.New()) for _, f := range fields { vh2.Add(f) } - sum2 := vh2.Sum() + sum2 := vh2.Sum(nil) if !bytes.Equal(sum, sum2) { t.Fatalf("non-deterministic result for ValueHasher\n1: %x\n2: %x", sum, sum2) } @@ -213,15 +213,15 @@ func TestValueHasher_Sum_FormatAndDeterminism(t *testing.T) { func TestValueHasher_AddString(t *testing.T) { algo := SHA2_256 - vh1 := NewValueHasher(algo) + vh1 := NewValueHasher(algo.New()) vh1.Add([]byte("hello")) vh1.Add([]byte("world")) - vh2 := NewValueHasher(algo) + vh2 := NewValueHasher(algo.New()) vh2.AddString("hello") vh2.AddString("world") - if got1, got2 := vh1.Sum(), vh2.Sum(); !bytes.Equal(got1, got2) { + if got1, got2 := vh1.Sum(nil), vh2.Sum(nil); !bytes.Equal(got1, got2) { t.Fatalf("AddString mismatch with Add\nAdd: %x\nAddString: %x", got1, got2) } } @@ -229,15 +229,15 @@ func TestValueHasher_AddString(t *testing.T) { func TestValueHasher_OrderMatters(t *testing.T) { algo := BLAKE2b_256 - vh1 := NewValueHasher(algo) + vh1 := NewValueHasher(algo.New()) vh1.Add([]byte("first")) vh1.Add([]byte("second")) - vh2 := NewValueHasher(algo) + vh2 := NewValueHasher(algo.New()) vh2.Add([]byte("second")) vh2.Add([]byte("first")) - if bytes.Equal(vh1.Sum(), vh2.Sum()) { + if bytes.Equal(vh1.Sum(nil), vh2.Sum(nil)) { t.Fatalf("expected different sums when field order differs") } } @@ -249,7 +249,7 @@ func TestNewValueHasher_WithInvalidAlgo_PanicsOnUse(t *testing.T) { } }() var invalid Hash = "NOPE" - vh := NewValueHasher(invalid) + vh := NewValueHasher(invalid.New()) // Should panic on first write due to nil hasher vh.Add([]byte("data")) } @@ -279,9 +279,9 @@ func preview(b []byte) string { if len(b) == 0 { return "" } - const max = 32 - if len(b) <= max { + const maxLen = 32 + if len(b) <= maxLen { return string(b) } - return string(b[:max]) + "..." + return string(b[:maxLen]) + "..." } diff --git a/key_maker_test.go b/key_maker_test.go index 169acff..63c19c9 100644 --- a/key_maker_test.go +++ b/key_maker_test.go @@ -77,8 +77,8 @@ func TestBlake3Keymaker_DeriveKeyInto_MinLength(t *testing.T) { } // Exactly min should succeed - min := make([]byte, keyMakerMinKeySize) - if err := km.DeriveKeyInto("", "", min); err != nil { + minBuf := make([]byte, keyMakerMinKeySize) + if err := km.DeriveKeyInto("", "", minBuf); err != nil { t.Fatalf("DeriveKeyInto(min) error: %v", err) } } diff --git a/keymaker.go b/keymaker.go index bb8d50b..7c6f46e 100644 --- a/keymaker.go +++ b/keymaker.go @@ -13,7 +13,7 @@ const ( // KeyMakerTypeBlake3 derives keys using BLAKE3. KeyMakerTypeBlake3 KeyMakerType = "BLAKE3" - keyMakerBaseContext = "nexufend key mkr" + keyMakerBaseContext = "_crop key maker_" keyMakerMinKeySize = 16 ) diff --git a/keypair_test.go b/keypair_test.go index 1746402..59be844 100644 --- a/keypair_test.go +++ b/keypair_test.go @@ -58,7 +58,7 @@ func TestKeyPair(t *testing.T) { if err != nil { t.Fatal(err) } - assert.EqualValues(t, privImportText, privImportBytes, "imports must match") + assert.Equal(t, privImportText, privImportBytes, "imports must match") importedPriv, err := LoadKeyPair(privImportText) if err != nil { t.Fatal(err) @@ -86,7 +86,7 @@ func TestKeyPair(t *testing.T) { if err != nil { t.Fatal(err) } - assert.EqualValues(t, pubImportText, pubImportBytes, "imports must match") + assert.Equal(t, pubImportText, pubImportBytes, "imports must match") importedpub, err := LoadKeyPair(pubImportText) if err != nil { t.Fatal(err) diff --git a/mac.go b/mac.go index e905413..5c012fa 100644 --- a/mac.go +++ b/mac.go @@ -21,8 +21,8 @@ const ( // MsgAuthCodeTypeBlake3 uses keyed BLAKE3. MsgAuthCodeTypeBlake3 MsgAuthCodeType = "BLAKE3" - macMinSaltSize = 8 - macSaltSize = 16 + macMinNonceSize = 8 + macNonceSize = 16 ) // IsValid returns whether this MAC type is supported. @@ -30,6 +30,8 @@ func (act MsgAuthCodeType) IsValid() bool { switch act { case MsgAuthCodeTypeHMACBlake3: return true + case MsgAuthCodeTypeBlake3: + return true } return false } @@ -84,9 +86,9 @@ type MsgAuthCodeHandler interface { // Type returns the MAC algorithm type. Type() MsgAuthCodeType // Sign generates an authentication code for the data. - Sign(data []byte) (mac []byte) + Sign(context string, data []byte) (mac []byte) // Verify checks that the MAC is valid for the data. - Verify(data []byte, mac []byte) error + Verify(context string, data []byte, mac []byte) error // Burn securely erases key material from memory. Burn() } @@ -107,56 +109,69 @@ func (hbm *HashBasedMAC) Type() MsgAuthCodeType { return hbm.handlerType } -func (hbm *HashBasedMAC) Sign(data []byte) (mac []byte) { +func (hbm *HashBasedMAC) Sign(context string, data []byte) (mac []byte) { hbm.signLock.Lock() defer hbm.signLock.Unlock() defer hbm.signer.Reset() // Create slice for the new MAC. - mac = make([]byte, 9+macSaltSize+hbm.signer.Size()) + mac = make([]byte, 9+macNonceSize+hbm.signer.Size()) + + // Create value hasher with signer. + vh := NewValueHasher(hbm.signer) + vh.AddString(context) // Increment and add sequence number for replay protection. sequence := hbm.seqChecker.NextOutSequence() + vh.AddUint(sequence) size := binary.PutUvarint(mac, sequence) - // Add random salt to prevent MAC reuse. - rand.Read(mac[size : size+macSaltSize]) - size += macSaltSize + // Add nonce to prevent MAC reuse. + //nolint:errcheck,gosec // crypto/rand.Read cannot fail + rand.Read(mac[size : size+macNonceSize]) + vh.Add(mac[size : size+macNonceSize]) + size += macNonceSize - // Generate checksum. - hbm.signer.Write(mac[:size]) - hbm.signer.Write(data) - copy(mac[size:], hbm.signer.Sum(nil)) + // Add data and generate checksum. + vh.Add(data) + vh.Sum(mac[size:]) size += hbm.signer.Size() // Return full MAC without extra bytes. return mac[:size] } -func (hbm *HashBasedMAC) Verify(data []byte, mac []byte) error { +func (hbm *HashBasedMAC) Verify(context string, data []byte, mac []byte) error { hbm.verifyLock.Lock() defer hbm.verifyLock.Unlock() defer hbm.verifier.Reset() + // Create value hasher with verifier. + vh := NewValueHasher(hbm.verifier) + vh.AddString(context) + // Extract sequence number (validated after MAC verification). seqNum, seqSize := binary.Uvarint(mac) if seqSize <= 0 { return fmt.Errorf("%w: too short", ErrAuthCodeInvalid) } + vh.AddUint(seqNum) - // Check salt size. - saltSize := len(mac) - seqSize - hbm.verifier.Size() - if saltSize < macMinSaltSize { + // Check nonce size. + nonceSize := len(mac) - seqSize - hbm.verifier.Size() + if nonceSize < macMinNonceSize { return fmt.Errorf("%w: too short", ErrAuthCodeInvalid) } + vh.Add(mac[seqSize : seqSize+nonceSize]) // Generate checksum. - hbm.verifier.Write(mac[:seqSize+saltSize]) - hbm.verifier.Write(data) - compareChecksum := hbm.verifier.Sum(nil) + vh.Add(data) + var compareChecksumBuf [64]byte + compareChecksum := compareChecksumBuf[:hbm.verifier.Size()] + vh.Sum(compareChecksum) // Compare checksum. - if subtle.ConstantTimeCompare(mac[seqSize+saltSize:], compareChecksum) != 1 { + if subtle.ConstantTimeCompare(mac[seqSize+nonceSize:], compareChecksum) != 1 { return ErrAuthCodeInvalid } diff --git a/mac_test.go b/mac_test.go index 0209427..e620f10 100644 --- a/mac_test.go +++ b/mac_test.go @@ -36,20 +36,20 @@ func TestAuthCode_SignVerify_Simple(t *testing.T) { // Sign with A, verify with B. msg1 := []byte("hello from A") - mac1 := a.Sign(msg1) - if err := b.Verify(msg1, mac1); err != nil { + mac1 := a.Sign("msg1", msg1) + if err := b.Verify("msg1", msg1, mac1); err != nil { t.Fatalf("verify failed for A->B: %v (mac: %x)", err, mac1) } // Sign with B, verify with A. msg2 := []byte("hello from B") - mac2 := b.Sign(msg2) - if err := a.Verify(msg2, mac2); err != nil { + mac2 := b.Sign("msg2", msg2) + if err := a.Verify("msg2", msg2, mac2); err != nil { t.Fatalf("verify failed for B->A: %v (mac: %x)", err, mac2) } // Cross-check that wrong message fails. - if err := a.Verify([]byte("tampered"), mac2); err == nil { + if err := a.Verify("msg2", []byte("tampered"), mac2); err == nil { t.Fatalf("expected verify to fail for tampered message but it succeeded") } }) @@ -95,14 +95,14 @@ func TestAuthCode_SignVerify_Randomized_BothDirections(t *testing.T) { // Sign for A->B. for i := 0; i < messages; i++ { data := []byte("A-msg-" + strconv.Itoa(i)) - mac := handlerA.Sign(data) + mac := handlerA.Sign("a-msg", data) AtoB = append(AtoB, &entry{id: string(data), data: data, mac: mac}) } // Sign for B->A. for i := 0; i < messages; i++ { data := []byte("B-msg-" + strconv.Itoa(i)) - mac := handlerB.Sign(data) + mac := handlerB.Sign("b-msg", data) BtoA = append(BtoA, &entry{id: string(data), data: data, mac: mac}) } @@ -112,7 +112,7 @@ func TestAuthCode_SignVerify_Randomized_BothDirections(t *testing.T) { // Verify for A->B. for _, entry := range AtoB { - if err := handlerB.Verify(entry.data, entry.mac); err != nil { + if err := handlerB.Verify("a-msg", entry.data, entry.mac); err != nil { t.Errorf("verify A->B failed at %s: %v", entry.id, err) } // fmt.Println(entry.id) @@ -120,7 +120,7 @@ func TestAuthCode_SignVerify_Randomized_BothDirections(t *testing.T) { // Verify for B->A. for _, entry := range BtoA { - if err := handlerA.Verify(entry.data, entry.mac); err != nil { + if err := handlerA.Verify("b-msg", entry.data, entry.mac); err != nil { t.Errorf("verify B->A failed at %s: %v", entry.id, err) } // fmt.Println(entry.id) @@ -130,6 +130,8 @@ func TestAuthCode_SignVerify_Randomized_BothDirections(t *testing.T) { } func TestAuthCode_ErrorCases(t *testing.T) { + t.Parallel() + acts := []MsgAuthCodeType{ MsgAuthCodeTypeHMACBlake3, } @@ -157,7 +159,7 @@ func TestAuthCode_ErrorCases(t *testing.T) { } // 1) too short (no uvarint) - err = verifier.Verify([]byte("data"), []byte{}) + err = verifier.Verify("", []byte("data"), []byte{}) if err == nil { t.Fatalf("expected error for too short mac, got nil") } @@ -166,14 +168,14 @@ func TestAuthCode_ErrorCases(t *testing.T) { } // 2) serial violation: sign two messages and verify the newer one first on the same verifier - mac1 := signer.Sign([]byte("first")) - mac2 := signer.Sign([]byte("second")) + mac1 := signer.Sign("", []byte("first")) + mac2 := signer.Sign("", []byte("second")) // verify second first -> ok - if err := verifier.Verify([]byte("second"), mac2); err != nil { + if err := verifier.Verify("", []byte("second"), mac2); err != nil { t.Fatalf("unexpected verify error for second: %v", err) } // verify first next -> serial violation - err = verifier.Verify([]byte("first"), mac1) + err = verifier.Verify("", []byte("first"), mac1) if err == nil { t.Fatalf("expected serial violation error but got nil") } @@ -182,29 +184,29 @@ func TestAuthCode_ErrorCases(t *testing.T) { } // 3) salt too short: craft mac with too-small salt by truncating a valid mac - orig := signer.Sign([]byte("x")) + orig := signer.Sign("", []byte("x")) _, serialSize := binary.Uvarint(orig) if serialSize <= 0 { t.Fatalf("failed to decode uvarint from mac") } // Determine hasher size based on the original mac and known macSaltSize - hasherSize := len(orig) - serialSize - macSaltSize + hasherSize := len(orig) - serialSize - macNonceSize if hasherSize <= 0 { t.Fatalf("unexpected hasher size computed: %d", hasherSize) } // Build truncated mac where saltSize = macMinSaltSize - 1 (too small) - newSaltSize := macMinSaltSize - 1 + newSaltSize := macMinNonceSize - 1 newLen := serialSize + newSaltSize + hasherSize if newLen >= len(orig) { // unexpected, but ensure we still create a too-short-salt mac by truncating to something smaller - newLen = serialSize + (macMinSaltSize - 1) + hasherSize + newLen = serialSize + (macMinNonceSize - 1) + hasherSize } if newLen <= 0 || newLen > len(orig) { t.Fatalf("unable to construct truncated mac for salt-too-short test") } trunc := make([]byte, newLen) copy(trunc, orig[:newLen]) - err = verifier.Verify([]byte("x"), trunc) + err = verifier.Verify("", []byte("x"), trunc) if err == nil { t.Fatalf("expected error for salt-too-short but got nil") } @@ -213,7 +215,7 @@ func TestAuthCode_ErrorCases(t *testing.T) { } // 4) checksum mismatch: tamper with the checksum bytes - valid := signer.Sign([]byte("payload")) + valid := signer.Sign("", []byte("payload")) // flip a byte in the checksum area (the tail) tampered := make([]byte, len(valid)) copy(tampered, valid) @@ -221,7 +223,7 @@ func TestAuthCode_ErrorCases(t *testing.T) { t.Fatalf("unexpected empty mac") } tampered[len(tampered)-1] ^= 0xFF - err = verifier.Verify([]byte("payload"), tampered) + err = verifier.Verify("", []byte("payload"), tampered) if err == nil { t.Fatalf("expected checksum mismatch to cause error but got nil") } @@ -230,8 +232,8 @@ func TestAuthCode_ErrorCases(t *testing.T) { } // 5) wrong message (data mismatch) - valid2 := signer.Sign([]byte("good")) - if err := verifier.Verify([]byte("bad"), valid2); err == nil { + valid2 := signer.Sign("", []byte("good")) + if err := verifier.Verify("", []byte("bad"), valid2); err == nil { t.Fatalf("expected verification failure for wrong data but got nil") } }) diff --git a/secret.go b/secret.go index 2f71aac..ce35c65 100644 --- a/secret.go +++ b/secret.go @@ -13,6 +13,7 @@ func NewSecret(length int) []byte { // Read random data into secret. secret := make([]byte, length) - rand.Read(secret) // Cannot fail. + //nolint:errcheck,gosec // crypto/rand.Read cannot fail + rand.Read(secret) return secret } diff --git a/sequence_test.go b/sequence_test.go index 0c98f4f..6493688 100644 --- a/sequence_test.go +++ b/sequence_test.go @@ -67,7 +67,7 @@ func TestStrictSequenceChecker_NextOutSequence_SequentialAndConcurrent(t *testin workers := runtime.GOMAXPROCS(0) per := N / workers - for w := 0; w < workers; w++ { + for w := range workers { wg.Add(1) count := per // last worker picks up remainder