diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c886c3e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,16 @@ +--- +name: Build + +on: + pull_request: + branches: + - main + +permissions: + contents: read + pull-requests: read + +jobs: + golang-checks: + name: Golang checks + uses: bodgit/workflows/.github/workflows/golang-checks.yml@f2ad91a9e0ba58982cdc180b62f69555ddf17d06 # v1.0.0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 93a6954..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: build -on: - push: - tags: - - v* - branches: - - main - - master - pull_request: -permissions: - contents: write - pull-requests: read -jobs: - build: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - go: - - '1.18' - - '1.19' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - name: golangci-lint - uses: golangci/golangci-lint-action@v5 - with: - only-new-issues: true - - name: Test - run: go test -v -coverprofile=cover.out ./... - - name: Send coverage - uses: shogo82148/actions-goveralls@v1 - with: - path-to-profile: cover.out - flag-name: Go-${{ matrix.go }} - parallel: true - finish: - needs: build - runs-on: ubuntu-latest - steps: - - uses: shogo82148/actions-goveralls@v1 - with: - parallel-finished: true - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.19' - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5.0.0 - if: startsWith(github.ref, 'refs/tags/') - with: - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.golangci.yaml b/.golangci.yaml index e62baae..52b7a92 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,10 +1,124 @@ ---- +version: "2" linters: - enable-all: true - disable: - - exhaustivestruct - - exhaustruct + default: none + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - containedctx + - contextcheck + - copyloopvar + - cyclop + - decorder + - dogsled + - dupl + - dupword + - durationcheck + - err113 + - errcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exptostd + - fatcontext + - forbidigo + - forcetypeassert + - funcorder + - funlen + - ginkgolinter + - gocheckcompilerdirectives - gochecknoglobals - gochecknoinits - - nonamedreturns - - varnamelen + - gochecksumtype + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - goheader + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosmopolitan + - govet + - grouper + - iface + - importas + - inamedparam + - ineffassign + - interfacebloat + - intrange + - lll + - loggercheck + - maintidx + - makezero + - mirror + - misspell + - musttag + - nakedret + - nestif + - nilerr + - nilnesserr + - nilnil + - nlreturn + - noctx + - nolintlint + - nosprintfhostport + - paralleltest + - perfsprint + - prealloc + - predeclared + - promlinter + - protogetter + - reassign + - recvcheck + - revive + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - staticcheck + - tagalign + - tagliatelle + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - wastedassign + - whitespace + - wrapcheck + - wsl + - zerologlint + exclusions: + rules: + - linters: + - revive + path: 'internal/util/*' + text: 'var-naming: avoid meaningless package names' + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/cognito/cognito.go b/cognito/cognito.go index 4adaf40..af32f65 100644 --- a/cognito/cognito.go +++ b/cognito/cognito.go @@ -11,8 +11,9 @@ import ( "github.com/bodgit/srp/internal/util" ) +//nolint:gochecknoglobals var cognitoGroups = map[int]*srp.Group{ - 3072: util.Must(srp.NewGroup(2, 3072, rfc5054.Hex3072)), //nolint:gomnd + 3072: util.Must(srp.NewGroup(2, 3072, rfc5054.Hex3072)), } // GetGroup returns the AWS Cognito group for the prime of n bits. @@ -28,14 +29,14 @@ func GetGroup(n int) (*srp.Group, error) { // NewSRP returns a new srp.SRP struct with the Cognito-specific options // already set. func NewSRP() (*srp.SRP, error) { - //nolint:gomnd,wrapcheck + //nolint:wrapcheck return srp.NewSRP(crypto.SHA256, util.Must(GetGroup(3072)), srp.K(Multiplier), srp.U(ComputeU), srp.X(ComputeX)) } // Pad prepends a zero byte to slice b if the first byte is greater than or // equal to 0x80. func Pad(b []byte) []byte { - if b[0] >= 0x80 { //nolint:gomnd + if b[0] >= 0x80 { b = append([]byte{0x00}, b...) } diff --git a/group.go b/group.go index 9e1d5ea..330377b 100644 --- a/group.go +++ b/group.go @@ -15,14 +15,15 @@ type Group struct { Size int } +//nolint:gochecknoglobals var rfcGroups = map[int]*Group{ - 1024: util.Must(NewGroup(2, 1024, rfc5054.Hex1024)), //nolint:gomnd - 1536: util.Must(NewGroup(2, 1536, rfc5054.Hex1536)), //nolint:gomnd - 2048: util.Must(NewGroup(2, 2048, rfc5054.Hex2048)), //nolint:gomnd - 3072: util.Must(NewGroup(5, 3072, rfc5054.Hex3072)), //nolint:gomnd - 4096: util.Must(NewGroup(5, 4096, rfc5054.Hex4096)), //nolint:gomnd - 6144: util.Must(NewGroup(5, 6144, rfc5054.Hex6144)), //nolint:gomnd - 8192: util.Must(NewGroup(19, 8192, rfc5054.Hex8192)), //nolint:gomnd + 1024: util.Must(NewGroup(2, 1024, rfc5054.Hex1024)), + 1536: util.Must(NewGroup(2, 1536, rfc5054.Hex1536)), + 2048: util.Must(NewGroup(2, 2048, rfc5054.Hex2048)), + 3072: util.Must(NewGroup(5, 3072, rfc5054.Hex3072)), + 4096: util.Must(NewGroup(5, 4096, rfc5054.Hex4096)), + 6144: util.Must(NewGroup(5, 6144, rfc5054.Hex6144)), + 8192: util.Must(NewGroup(19, 8192, rfc5054.Hex8192)), } // NewGroup returns a Group with the generator g, and a prime of size bits set @@ -36,7 +37,7 @@ func NewGroup(g int64, size int, s string) (*Group, error) { group := &Group{ G: big.NewInt(g), N: new(big.Int).SetBytes(b), - Size: size >> 3, //nolint:gomnd + Size: size >> 3, } return group, nil diff --git a/internal/rfc5054/test.go b/internal/rfc5054/test.go index 87104c6..3b71398 100644 --- a/internal/rfc5054/test.go +++ b/internal/rfc5054/test.go @@ -1,3 +1,4 @@ +//nolint:gochecknoglobals package rfc5054 import "github.com/bodgit/srp/internal/util" diff --git a/internal/util/bytes.go b/internal/util/bytes.go index 3a06614..f397aac 100644 --- a/internal/util/bytes.go +++ b/internal/util/bytes.go @@ -7,6 +7,8 @@ import ( "regexp" ) +// BytesFromHexString removes any characters that are not hex values from s +// and then decodes the string to a byte slice. func BytesFromHexString(s string) ([]byte, error) { b, err := hex.DecodeString(regexp.MustCompile("[^0-9a-fA-F]").ReplaceAllString(s, "")) if err != nil { @@ -16,6 +18,7 @@ func BytesFromHexString(s string) ([]byte, error) { return b, nil } +// Pad returns x as a byte slice, padding it to n bytes. func Pad(x *big.Int, n int) []byte { b := x.Bytes() if len(b) < n { diff --git a/internal/util/doc.go b/internal/util/doc.go new file mode 100644 index 0000000..8f9c2d7 --- /dev/null +++ b/internal/util/doc.go @@ -0,0 +1,2 @@ +// Package util is a collection of utility functions shared between packages. +package util diff --git a/internal/util/group.go b/internal/util/group.go index d0f6ad0..00c9bfa 100644 --- a/internal/util/group.go +++ b/internal/util/group.go @@ -2,4 +2,5 @@ package util import "errors" +// ErrGroupNotFound is the error returned when an SRP group is not found. var ErrGroupNotFound = errors.New("group not found") diff --git a/internal/util/must.go b/internal/util/must.go index 39327ae..2fd58bd 100644 --- a/internal/util/must.go +++ b/internal/util/must.go @@ -1,5 +1,6 @@ package util +// Must will panic if err is non-nil. func Must[T any](v T, err error) T { //nolint:ireturn if err != nil { panic(err) diff --git a/isv_test.go b/isv_test.go index e09a2f4..812e583 100644 --- a/isv_test.go +++ b/isv_test.go @@ -7,8 +7,10 @@ import ( "github.com/bodgit/srp" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +//nolint:gochecknoglobals var isv = []byte{0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x01, 0x03} func TestISV_MarshalBinary(t *testing.T) { @@ -135,7 +137,7 @@ func TestISV_UnmarshalBinary(t *testing.T) { err := i.UnmarshalBinary(table.b) - assert.ErrorIs(t, err, table.err) + require.ErrorIs(t, err, table.err) if err == nil { assert.Equal(t, table.identity, i.Identity) diff --git a/server_test.go b/server_test.go index 9f104b5..2fede9e 100644 --- a/server_test.go +++ b/server_test.go @@ -26,7 +26,7 @@ func TestServer_MarshalBinary(t *testing.T) { b, err := server.MarshalBinary() assert.NotNil(t, b) - assert.Nil(t, err) + assert.NoError(t, err) } func TestServer_UnmarshalBinary(t *testing.T) { diff --git a/srp.go b/srp.go index 97e95b5..5553e7a 100644 --- a/srp.go +++ b/srp.go @@ -1,3 +1,8 @@ +// Package srp is an implementation of SRP-6a as documented in [RFC 5054] and +// [RFC 2945]. +// +// [RFC 5054]: https://www.rfc-editor.org/rfc/rfc5054 +// [RFC 2945]: https://www.rfc-editor.org/rfc/rfc2945 package srp import ( @@ -50,14 +55,57 @@ func NewSRP(hash crypto.Hash, group *Group, options ...func(*SRP) error) (*SRP, return s, nil } -func (s *SRP) setOption(options ...func(*SRP) error) error { - for _, option := range options { - if err := option(s); err != nil { - return err - } +// HashBytes hashes each passed byte slice and returns the digest. +func (s *SRP) HashBytes(a ...[]byte) []byte { + h := s.h.New() + + for _, z := range a { + _, _ = h.Write(z) } - return nil + return h.Sum(nil) +} + +// HashInt hashes each passed byte slice and returns the digest as a big.Int. +func (s *SRP) HashInt(a ...[]byte) *big.Int { + return new(big.Int).SetBytes(s.HashBytes(a...)) +} + +// NewISV creates a new ISV containing the identity, salt and verifier. +func (s *SRP) NewISV(identity, password []byte) (*ISV, error) { + salt, err := randBytes(s.Group().Size) + if err != nil { + return nil, err + } + + return &ISV{ + Identity: identity, + Salt: salt, + Verifier: s.computeV(s.computeX(identity, password, salt)).Bytes(), + }, nil +} + +// NewClient creates a new Client using the identity and password. +func (s *SRP) NewClient(identity, password []byte) (*Client, error) { + a, err := randBigInt(s.Group().Size) + if err != nil { + return nil, err + } + + return &Client{ + s: s, + identity: identity, + password: password, + a: a, + xA: s.computeA(a), + }, nil +} + +// NewServer creates a new Server using the ISV and the client public value. +func (s *SRP) NewServer(i *ISV, xA []byte) (*Server, error) { + server := new(Server) + + return server, server.Reset(s, i, xA) } // K overrides the default function for computing the multiplier. @@ -107,6 +155,16 @@ func (s *SRP) Group() *Group { return s.g } +func (s *SRP) setOption(options ...func(*SRP) error) error { + for _, option := range options { + if err := option(s); err != nil { + return err + } + } + + return nil +} + func (s *SRP) multiplier() *big.Int { if s.k != nil { return s.k(s) @@ -187,56 +245,3 @@ func (s *SRP) computeM2(xA *big.Int, m1, xK []byte) []byte { // M2 = H(A | M | K) return s.HashBytes(xA.Bytes(), m1, xK) } - -// HashBytes hashes each passed byte slice and returns the digest. -func (s *SRP) HashBytes(a ...[]byte) []byte { - h := s.h.New() - - for _, z := range a { - _, _ = h.Write(z) - } - - return h.Sum(nil) -} - -// HashInt hashes each passed byte slice and returns the digest as a big.Int. -func (s *SRP) HashInt(a ...[]byte) *big.Int { - return new(big.Int).SetBytes(s.HashBytes(a...)) -} - -// NewISV creates a new ISV containing the identity, salt and verifier. -func (s *SRP) NewISV(identity, password []byte) (*ISV, error) { - salt, err := randBytes(s.Group().Size) - if err != nil { - return nil, err - } - - return &ISV{ - Identity: identity, - Salt: salt, - Verifier: s.computeV(s.computeX(identity, password, salt)).Bytes(), - }, nil -} - -// NewClient creates a new Client using the identity and password. -func (s *SRP) NewClient(identity, password []byte) (*Client, error) { - a, err := randBigInt(s.Group().Size) - if err != nil { - return nil, err - } - - return &Client{ - s: s, - identity: identity, - password: password, - a: a, - xA: s.computeA(a), - }, nil -} - -// NewServer creates a new Server using the ISV and the client public value. -func (s *SRP) NewServer(i *ISV, xA []byte) (*Server, error) { - server := new(Server) - - return server, server.Reset(s, i, xA) -} diff --git a/srp_test.go b/srp_test.go index 0d7763c..607a5ff 100644 --- a/srp_test.go +++ b/srp_test.go @@ -10,6 +10,7 @@ import ( "github.com/bodgit/srp/internal/rfc5054" "github.com/bodgit/srp/internal/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func newSRP() *srp.SRP { @@ -26,9 +27,9 @@ func TestNewSRP(t *testing.T) { })) assert.NotNil(t, s) - assert.Nil(t, err) + require.NoError(t, err) - errTest := errors.New("test") //nolint:goerr113 + errTest := errors.New("test") //nolint:err113 s, err = srp.NewSRP(crypto.SHA1, util.Must(srp.GetGroup(1024)), func(*srp.SRP) error { return errTest @@ -44,9 +45,9 @@ func TestNewISV(t *testing.T) { s := newSRP() i := util.Must(s.NewISV(rfc5054.Identity, rfc5054.Password)) - assert.Equal(t, len(rfc5054.Identity), len(i.Identity)) - assert.Equal(t, s.Group().Size, len(i.Salt)) - assert.Equal(t, s.Group().Size, len(i.Verifier)) + assert.Len(t, i.Identity, len(rfc5054.Identity)) + assert.Len(t, i.Salt, s.Group().Size) + assert.Len(t, i.Verifier, s.Group().Size) } func TestNewClient(t *testing.T) { @@ -55,7 +56,7 @@ func TestNewClient(t *testing.T) { s := newSRP() client := util.Must(s.NewClient(rfc5054.Identity, rfc5054.Password)) - assert.Equal(t, s.Group().Size, len(client.A())) + assert.Len(t, client.A(), s.Group().Size) } func TestNewServer(t *testing.T) { @@ -64,7 +65,7 @@ func TestNewServer(t *testing.T) { s := newSRP() server := util.Must(s.NewServer(util.Must(s.NewISV(rfc5054.Identity, rfc5054.Password)), rfc5054.XA)) - assert.Equal(t, s.Group().Size, len(server.B())) + assert.Len(t, server.B(), s.Group().Size) } func TestHandshake(t *testing.T) { diff --git a/util.go b/util.go index 6f0a53c..dedaca1 100644 --- a/util.go +++ b/util.go @@ -11,9 +11,7 @@ import ( func randBytes(n int) ([]byte, error) { b := make([]byte, n) - - _, err := io.ReadFull(rand.Reader, b) - if err != nil { + if _, err := io.ReadFull(rand.Reader, b); err != nil { return nil, fmt.Errorf("unable to read random bytes: %w", err) } @@ -34,6 +32,7 @@ func writeBytes(w io.Writer, b []byte) error { return ErrTooBig } + //nolint:gosec if err := binary.Write(w, binary.BigEndian, uint16(len(b))); err != nil { return fmt.Errorf("unable to write length: %w", err) }