From 8e222e3489e6af837e7faf9a109413bf206dc26e Mon Sep 17 00:00:00 2001 From: johnabass Date: Wed, 28 Aug 2024 18:24:28 -0700 Subject: [PATCH 01/13] canonical Salter implementation --- salter.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ salter_test.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 salter.go create mode 100644 salter_test.go diff --git a/salter.go b/salter.go new file mode 100644 index 0000000..3464a27 --- /dev/null +++ b/salter.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package bascule + +import ( + "crypto/rand" + "fmt" + "io" + "math" +) + +const ( + // MaxSaltLength is the maximum number of bytes allowed + // for salt by this package. + MaxSaltLength = math.MaxUint8 // bytes +) + +var ( + // ErrMaxSaltLengthExceeded is returned to indicate that the value + // passed to Salter.Generate exceeded the maximum allowed value. + ErrMaxSaltLengthExceeded = fmt.Errorf( + "Salt length cannot exceed [%d] bytes", + MaxSaltLength, + ) +) + +// Salt is an initialization vector or just plain salt for a hash +// or other cryptographic algorithm. +type Salt []byte + +// Write writes a simple, binary representation of this salt. The raw +// salt is written to dst, prefixed with the length byte. +// +// If this salt is larger than MaxSaltLength, ErrMaxSaltLengthExceeded is returned. +func (s Salt) Write(dst io.Writer) (n int, err error) { + switch { + case len(s) > MaxSaltLength: + err = ErrMaxSaltLengthExceeded + + default: + // we only need (1) length byte + var length [1]byte + length[0] = uint8(len(s)) + + var c int + c, err = dst.Write(length[:]) + n += c + + if err == nil { + c, err = dst.Write([]byte(s)) + n += c + } + } + + return +} + +// Salter generates random salt. +type Salter interface { + // Generate generates n bytes of random salt. If the + // underlying source of randomness returned an error, that error + // is returned by this method. + // + // If n is nonpositive, returns an empty slice with no error. + // If n is larger than MaxSaltLength, an error is returned. + Generate(n int) (Salt, error) +} + +// defaultSalter is the default implementation of Salter. This +// implementation uses crypto/rand.Reader as the source of randomness. +type defaultSalter struct{} + +// Generate is the default implementation for this package. It uses +// crypto/rand.Reader to generate n bytes of salt, returning an +// error is n exceeds MaxSaltLength. +func (defaultSalter) Generate(n int) (s Salt, err error) { + switch { + case n < 1: + // do nothing + + case n > MaxSaltLength: + err = ErrMaxSaltLengthExceeded + + default: + s = make(Salt, n) + _, err = io.ReadFull(rand.Reader, []byte(s)) + } + + return +} + +// DefaultSalter returns the default Salter implementation. The returned +// Salter uses crypto/rand.Reader as the source of randomness. +func DefaultSalter() Salter { + return defaultSalter{} +} diff --git a/salter_test.go b/salter_test.go new file mode 100644 index 0000000..74eed2c --- /dev/null +++ b/salter_test.go @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package bascule + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/suite" +) + +type SalterTestSuite struct { + suite.Suite +} + +func (suite *SalterTestSuite) testSaltWrite() { + suite.Run("TooLarge", func() { + var ( + b bytes.Buffer + s = make(Salt, MaxSaltLength+1) + + n, err = s.Write(&b) + ) + + suite.ErrorIs(err, ErrMaxSaltLengthExceeded) + suite.Zero(n) + suite.Zero(b.Len()) + }) + + suite.Run("Success", func() { + var ( + b bytes.Buffer + s = Salt{10, 20, 30} + + n, err = s.Write(&b) + ) + + suite.NoError(err) + suite.Equal(4, n) + suite.Equal(4, b.Len()) + suite.Equal([]byte{3, 10, 20, 30}, b.Bytes()) + }) +} + +func (suite *SalterTestSuite) TestSalt() { + suite.Run("Write", suite.testSaltWrite) +} + +func (suite *SalterTestSuite) defaultSalter() Salter { + salter := DefaultSalter() + suite.Require().NotNil(salter) + return salter +} + +func (suite *SalterTestSuite) testDefaultSalterGenerate() { + suite.Run("Zero", func() { + salter := suite.defaultSalter() + + salt, err := salter.Generate(0) + suite.Empty(salt) + suite.NoError(err) + }) + + suite.Run("Negative", func() { + salter := suite.defaultSalter() + + salt, err := salter.Generate(-1) + suite.Empty(salt) + suite.NoError(err) + }) + + suite.Run("TooLarge", func() { + salter := suite.defaultSalter() + + salt, err := salter.Generate(MaxSaltLength + 1) + suite.Empty(salt) + suite.ErrorIs(err, ErrMaxSaltLengthExceeded) + }) + + suite.Run("Success", func() { + salter := suite.defaultSalter() + + salt, err := salter.Generate(5) + suite.NoError(err) + suite.Len(salt, 5) + }) +} + +func (suite *SalterTestSuite) TestDefaultSalter() { + suite.Run("Generate", suite.testDefaultSalterGenerate) +} + +func TestSalter(t *testing.T) { + suite.Run(t, new(SalterTestSuite)) +} From fc756f9c85d141e5a51ab7898926dadf30462d85 Mon Sep 17 00:00:00 2001 From: johnabass Date: Thu, 29 Aug 2024 12:02:52 -0700 Subject: [PATCH 02/13] rolled salting into hash algorithms --- doc.go | 3 +- go.mod | 4 +-- go.sum | 4 +++ salter.go | 97 -------------------------------------------------- salter_test.go | 96 ------------------------------------------------- 5 files changed, 8 insertions(+), 196 deletions(-) delete mode 100644 salter.go delete mode 100644 salter_test.go diff --git a/doc.go b/doc.go index 8380f6e..3bdcceb 100644 --- a/doc.go +++ b/doc.go @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 /* -Package bascule provides a configurable way to validate an auth token. +Package bascule implements authentication and authorization workflows, along +with commonly needed supporting infrastructure. */ package bascule diff --git a/go.mod b/go.mod index 9da656a..cbb0701 100644 --- a/go.mod +++ b/go.mod @@ -22,8 +22,8 @@ require ( github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.24.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 143d6b7..da88597 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,12 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/salter.go b/salter.go deleted file mode 100644 index 3464a27..0000000 --- a/salter.go +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC -// SPDX-License-Identifier: Apache-2.0 - -package bascule - -import ( - "crypto/rand" - "fmt" - "io" - "math" -) - -const ( - // MaxSaltLength is the maximum number of bytes allowed - // for salt by this package. - MaxSaltLength = math.MaxUint8 // bytes -) - -var ( - // ErrMaxSaltLengthExceeded is returned to indicate that the value - // passed to Salter.Generate exceeded the maximum allowed value. - ErrMaxSaltLengthExceeded = fmt.Errorf( - "Salt length cannot exceed [%d] bytes", - MaxSaltLength, - ) -) - -// Salt is an initialization vector or just plain salt for a hash -// or other cryptographic algorithm. -type Salt []byte - -// Write writes a simple, binary representation of this salt. The raw -// salt is written to dst, prefixed with the length byte. -// -// If this salt is larger than MaxSaltLength, ErrMaxSaltLengthExceeded is returned. -func (s Salt) Write(dst io.Writer) (n int, err error) { - switch { - case len(s) > MaxSaltLength: - err = ErrMaxSaltLengthExceeded - - default: - // we only need (1) length byte - var length [1]byte - length[0] = uint8(len(s)) - - var c int - c, err = dst.Write(length[:]) - n += c - - if err == nil { - c, err = dst.Write([]byte(s)) - n += c - } - } - - return -} - -// Salter generates random salt. -type Salter interface { - // Generate generates n bytes of random salt. If the - // underlying source of randomness returned an error, that error - // is returned by this method. - // - // If n is nonpositive, returns an empty slice with no error. - // If n is larger than MaxSaltLength, an error is returned. - Generate(n int) (Salt, error) -} - -// defaultSalter is the default implementation of Salter. This -// implementation uses crypto/rand.Reader as the source of randomness. -type defaultSalter struct{} - -// Generate is the default implementation for this package. It uses -// crypto/rand.Reader to generate n bytes of salt, returning an -// error is n exceeds MaxSaltLength. -func (defaultSalter) Generate(n int) (s Salt, err error) { - switch { - case n < 1: - // do nothing - - case n > MaxSaltLength: - err = ErrMaxSaltLengthExceeded - - default: - s = make(Salt, n) - _, err = io.ReadFull(rand.Reader, []byte(s)) - } - - return -} - -// DefaultSalter returns the default Salter implementation. The returned -// Salter uses crypto/rand.Reader as the source of randomness. -func DefaultSalter() Salter { - return defaultSalter{} -} diff --git a/salter_test.go b/salter_test.go deleted file mode 100644 index 74eed2c..0000000 --- a/salter_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC -// SPDX-License-Identifier: Apache-2.0 - -package bascule - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/suite" -) - -type SalterTestSuite struct { - suite.Suite -} - -func (suite *SalterTestSuite) testSaltWrite() { - suite.Run("TooLarge", func() { - var ( - b bytes.Buffer - s = make(Salt, MaxSaltLength+1) - - n, err = s.Write(&b) - ) - - suite.ErrorIs(err, ErrMaxSaltLengthExceeded) - suite.Zero(n) - suite.Zero(b.Len()) - }) - - suite.Run("Success", func() { - var ( - b bytes.Buffer - s = Salt{10, 20, 30} - - n, err = s.Write(&b) - ) - - suite.NoError(err) - suite.Equal(4, n) - suite.Equal(4, b.Len()) - suite.Equal([]byte{3, 10, 20, 30}, b.Bytes()) - }) -} - -func (suite *SalterTestSuite) TestSalt() { - suite.Run("Write", suite.testSaltWrite) -} - -func (suite *SalterTestSuite) defaultSalter() Salter { - salter := DefaultSalter() - suite.Require().NotNil(salter) - return salter -} - -func (suite *SalterTestSuite) testDefaultSalterGenerate() { - suite.Run("Zero", func() { - salter := suite.defaultSalter() - - salt, err := salter.Generate(0) - suite.Empty(salt) - suite.NoError(err) - }) - - suite.Run("Negative", func() { - salter := suite.defaultSalter() - - salt, err := salter.Generate(-1) - suite.Empty(salt) - suite.NoError(err) - }) - - suite.Run("TooLarge", func() { - salter := suite.defaultSalter() - - salt, err := salter.Generate(MaxSaltLength + 1) - suite.Empty(salt) - suite.ErrorIs(err, ErrMaxSaltLengthExceeded) - }) - - suite.Run("Success", func() { - salter := suite.defaultSalter() - - salt, err := salter.Generate(5) - suite.NoError(err) - suite.Len(salt, 5) - }) -} - -func (suite *SalterTestSuite) TestDefaultSalter() { - suite.Run("Generate", suite.testDefaultSalterGenerate) -} - -func TestSalter(t *testing.T) { - suite.Run(t, new(SalterTestSuite)) -} From 7119a55a1ee787cf6865a3d26171afd9f8a96808 Mon Sep 17 00:00:00 2001 From: johnabass Date: Thu, 29 Aug 2024 12:03:09 -0700 Subject: [PATCH 03/13] go mod tidy --- go.mod | 2 +- go.sum | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/go.mod b/go.mod index cbb0701..bf5a469 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/lestrrat-go/jwx/v2 v2.1.1 github.com/stretchr/testify v1.9.0 go.uber.org/multierr v1.11.0 + golang.org/x/crypto v0.26.0 ) require ( @@ -22,7 +23,6 @@ require ( github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/crypto v0.26.0 // indirect golang.org/x/sys v0.24.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index da88597..709057f 100644 --- a/go.sum +++ b/go.sum @@ -43,12 +43,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 12e3782d783555fbf2c6fd04b883c57ee5d4586a Mon Sep 17 00:00:00 2001 From: johnabass Date: Thu, 29 Aug 2024 12:03:33 -0700 Subject: [PATCH 04/13] hasher and comparer APIs for passwords or any plaintext --- basculehash/bcrypt.go | 38 +++++++++++++ basculehash/bcrypt_test.go | 109 +++++++++++++++++++++++++++++++++++++ basculehash/comparer.go | 19 +++++++ basculehash/doc.go | 8 +++ basculehash/hasher.go | 23 ++++++++ 5 files changed, 197 insertions(+) create mode 100644 basculehash/bcrypt.go create mode 100644 basculehash/bcrypt_test.go create mode 100644 basculehash/comparer.go create mode 100644 basculehash/doc.go create mode 100644 basculehash/hasher.go diff --git a/basculehash/bcrypt.go b/basculehash/bcrypt.go new file mode 100644 index 0000000..1e9b790 --- /dev/null +++ b/basculehash/bcrypt.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package basculehash + +import ( + "io" + + "golang.org/x/crypto/bcrypt" +) + +// Bcrypt is a Hasher and Comparer based around the bcrypt hashing +// algorithm. +type Bcrypt struct { + // Cost is the cost parameter for bcrypt. If unset, the internal + // bcrypt cost is used. If this value is higher than the max, + // Hash will return an error. + // + // See: https://pkg.go.dev/golang.org/x/crypto/bcrypt#pkg-constants + Cost int +} + +// Hash executes the bcrypt algorithm and write the output to dst. +func (b Bcrypt) Hash(dst io.Writer, plaintext []byte) (n int, err error) { + hashed, err := bcrypt.GenerateFromPassword(plaintext, b.Cost) + if err == nil { + n, err = dst.Write(hashed) + } + + return +} + +// Matches attempts to match a plaintext against its bcrypt hashed value. +func (b Bcrypt) Matches(plaintext, hash []byte) (ok bool, err error) { + err = bcrypt.CompareHashAndPassword(hash, plaintext) + ok = (err == nil) + return +} diff --git a/basculehash/bcrypt_test.go b/basculehash/bcrypt_test.go new file mode 100644 index 0000000..fd2f86f --- /dev/null +++ b/basculehash/bcrypt_test.go @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package basculehash + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/suite" + "golang.org/x/crypto/bcrypt" +) + +type BcryptTestSuite struct { + suite.Suite +} + +// plaintext returns a known, valid byte slice to hash +func (suite *BcryptTestSuite) plaintext() []byte { + return []byte("this is a password") +} + +// goodHash returns a hash that is expected to be successful. +// The plaintext() is hashed with the given cost. +func (suite *BcryptTestSuite) goodHash(cost int) []byte { + var ( + b bytes.Buffer + hasher = Bcrypt{Cost: cost} + _, err = hasher.Hash(&b, suite.plaintext()) + ) + + suite.Require().NoError(err) + return b.Bytes() +} + +func (suite *BcryptTestSuite) TestHash() { + suite.Run("DefaultCost", func() { + var ( + o strings.Builder + hasher = Bcrypt{} + + n, err = hasher.Hash(&o, suite.plaintext()) + ) + + suite.NoError(err) + suite.Equal(o.Len(), n) + }) + + suite.Run("CustomCost", func() { + var ( + o strings.Builder + hasher = Bcrypt{Cost: 12} + + n, err = hasher.Hash(&o, suite.plaintext()) + ) + + suite.NoError(err) + suite.Equal(o.Len(), n) + }) + + suite.Run("CostTooHigh", func() { + var ( + o strings.Builder + hasher = Bcrypt{Cost: bcrypt.MaxCost + 100} + + _, err = hasher.Hash(&o, suite.plaintext()) + ) + + suite.Error(err) + }) +} + +func (suite *BcryptTestSuite) TestMatches() { + suite.Run("Success", func() { + for _, cost := range []int{0 /* default */, 4, 8} { + suite.Run(fmt.Sprintf("cost=%d", cost), func() { + var ( + hashed = suite.goodHash(cost) + hasher = Bcrypt{Cost: cost} + ok, err = hasher.Matches(suite.plaintext(), hashed) + ) + + suite.True(ok) + suite.NoError(err) + }) + } + }) + + suite.Run("Fail", func() { + for _, cost := range []int{0 /* default */, 4, 8} { + suite.Run(fmt.Sprintf("cost=%d", cost), func() { + var ( + hashed = suite.goodHash(cost) + hasher = Bcrypt{Cost: cost} + ok, err = hasher.Matches([]byte("a different plaintext"), hashed) + ) + + suite.False(ok) + suite.Error(err) + }) + } + }) +} + +func TestBcrypt(t *testing.T) { + suite.Run(t, new(BcryptTestSuite)) +} diff --git a/basculehash/comparer.go b/basculehash/comparer.go new file mode 100644 index 0000000..7e27e55 --- /dev/null +++ b/basculehash/comparer.go @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package basculehash + +// Comparer is a strategy for comparing plaintext values with a +// hash value from a Hasher. +type Comparer interface { + // Matches tests if the given plaintext matches the given hash. + // For example, this method can test if a password matches the + // one-way hashed password from a config file or database. + // + // If this method returns true, the error will always be nil. + // If this method returns false, the error may be non-nil to + // indicate that the match failed due to a problem, such as + // the hash not being parseable. Client code that is just + // interested in a yes/no answer can disregard the error return. + Matches(plaintext, hash []byte) (bool, error) +} diff --git a/basculehash/doc.go b/basculehash/doc.go new file mode 100644 index 0000000..26460ef --- /dev/null +++ b/basculehash/doc.go @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +/* +Package basculehash provides basic hash support for things like passwords +or other sensitive data that needs to be stored externally to the application. +*/ +package basculehash diff --git a/basculehash/hasher.go b/basculehash/hasher.go new file mode 100644 index 0000000..a89656c --- /dev/null +++ b/basculehash/hasher.go @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package basculehash + +import ( + "io" +) + +// Hasher is a strategy for one-way hashing. +type Hasher interface { + // Hash writes the hash of a plaintext to a writer. The number of + // bytes written along with any error is returned. + // + // The format of the written hash must be ASCII. Typically, base64 + // encoding will be used to achieve this. + // + // This method should write out any hash parameters necessary to + // execute the same hash against a different plaintext. This allows + // a Comparer to work, for example. It also allows migration of + // hash parameters in a way that doesn't disturb already hashed values. + Hash(dst io.Writer, plaintext []byte) (int, error) +} From 83d45ee66295833232e067cb61bae228c6f3fb74 Mon Sep 17 00:00:00 2001 From: johnabass Date: Thu, 29 Aug 2024 14:38:30 -0700 Subject: [PATCH 05/13] CLI tool for hashing passwords (or other plaintext) --- .gitignore | 5 +---- cmd/hash/cli.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ cmd/hash/main.go | 41 +++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 8 ++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 cmd/hash/cli.go create mode 100644 cmd/hash/main.go diff --git a/.gitignore b/.gitignore index 69dfc9b..6b8d899 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,7 @@ *.so *.dylib *.swp - -# Example binaries -examples/acquirer/acquirer -examples/basculehttp/basculehttp +cmd/hash/hash # Test binary, build with `go test -c` *.test diff --git a/cmd/hash/cli.go b/cmd/hash/cli.go new file mode 100644 index 0000000..b6d21f6 --- /dev/null +++ b/cmd/hash/cli.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "io" + + "github.com/xmidt-org/bascule/basculehash" + "golang.org/x/crypto/bcrypt" +) + +// Context is the contextual information for all commands. +type Context struct { + Stdout io.Writer + Stderr io.Writer +} + +// Bcrypt is the subcommand for the bcrypt algorithm. +type Bcrypt struct { + Cost int `default:"10" help:"the cost parameter for bcrypt"` + Plaintext string `arg:"" required:""` +} + +func (cmd *Bcrypt) Validate() error { + switch { + case cmd.Cost < bcrypt.MinCost: + return fmt.Errorf("Cost cannot be less than %d", bcrypt.MinCost) + + case cmd.Cost > bcrypt.MaxCost: + return fmt.Errorf("Cost cannot be greater than %d", bcrypt.MaxCost) + + default: + return nil + } +} + +func (cmd *Bcrypt) Run(ctx *Context) error { + hasher := basculehash.Bcrypt{ + Cost: cmd.Cost, + } + + _, err := hasher.Hash(ctx.Stdout, []byte(cmd.Plaintext)) + return err +} + +// CLI is the top grammar node for the command-line tool. +type CLI struct { + // Bcrypt is the bcrypt subcommand. This is the only supported hash + // algorithm right now. + Bcrypt Bcrypt `cmd:""` +} diff --git a/cmd/hash/main.go b/cmd/hash/main.go new file mode 100644 index 0000000..64ff4f3 --- /dev/null +++ b/cmd/hash/main.go @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "os" + + "github.com/alecthomas/kong" +) + +func run(args []string, ctx *Context) (err error) { + var ( + grammar *kong.Kong + kongCtx *kong.Context + ) + + grammar, err = kong.New(new(CLI), kong.Bind(ctx)) + if err == nil { + kongCtx, err = grammar.Parse(args) + } + + if err == nil { + err = kongCtx.Run() + } + + return +} + +func main() { + err := run(os.Args[1:], &Context{ + Stdout: os.Stdout, + Stderr: os.Stderr, + }) + + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index bf5a469..07ea631 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/xmidt-org/bascule go 1.23 require ( + github.com/alecthomas/kong v0.9.0 github.com/lestrrat-go/jwx/v2 v2.1.1 github.com/stretchr/testify v1.9.0 go.uber.org/multierr v1.11.0 diff --git a/go.sum b/go.sum index 709057f..954729e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= +github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -6,6 +12,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= From f661208097aec4200fa83506492897774f39a3a4 Mon Sep 17 00:00:00 2001 From: johnabass Date: Thu, 29 Aug 2024 14:54:54 -0700 Subject: [PATCH 06/13] chore: restructured for clarity --- cmd/hash/cli.go | 27 +++++++++++++++++---------- cmd/hash/main.go | 31 +++++++++++++++---------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/cmd/hash/cli.go b/cmd/hash/cli.go index b6d21f6..62be675 100644 --- a/cmd/hash/cli.go +++ b/cmd/hash/cli.go @@ -1,23 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + package main import ( "fmt" - "io" + "github.com/alecthomas/kong" "github.com/xmidt-org/bascule/basculehash" "golang.org/x/crypto/bcrypt" ) -// Context is the contextual information for all commands. -type Context struct { - Stdout io.Writer - Stderr io.Writer -} +const ( + // MaxBcryptPlaintextLength is the maximum length of the input that + // bcrypt will operate on. This value isn't exposed via the + // golang.org/x/crypto/bcrypt package. + MaxBcryptPlaintextLength = 72 +) // Bcrypt is the subcommand for the bcrypt algorithm. type Bcrypt struct { - Cost int `default:"10" help:"the cost parameter for bcrypt"` - Plaintext string `arg:"" required:""` + Cost int `default:"10" short:"c" help:"the cost parameter for bcrypt. Must be between 4 and 31, inclusive."` + Plaintext string `arg:"" required:"" help:"the plaintext (e.g. password) to hash. This cannot exceed 72 bytes in length."` } func (cmd *Bcrypt) Validate() error { @@ -28,17 +32,20 @@ func (cmd *Bcrypt) Validate() error { case cmd.Cost > bcrypt.MaxCost: return fmt.Errorf("Cost cannot be greater than %d", bcrypt.MaxCost) + case len(cmd.Plaintext) > MaxBcryptPlaintextLength: + return fmt.Errorf("Plaintext length cannot exceed %d bytes", MaxBcryptPlaintextLength) + default: return nil } } -func (cmd *Bcrypt) Run(ctx *Context) error { +func (cmd *Bcrypt) Run(kong *kong.Kong) error { hasher := basculehash.Bcrypt{ Cost: cmd.Cost, } - _, err := hasher.Hash(ctx.Stdout, []byte(cmd.Plaintext)) + _, err := hasher.Hash(kong.Stdout, []byte(cmd.Plaintext)) return err } diff --git a/cmd/hash/main.go b/cmd/hash/main.go index 64ff4f3..392b231 100644 --- a/cmd/hash/main.go +++ b/cmd/hash/main.go @@ -4,38 +4,37 @@ package main import ( - "fmt" "os" "github.com/alecthomas/kong" ) -func run(args []string, ctx *Context) (err error) { - var ( - grammar *kong.Kong - kongCtx *kong.Context +func newKong() (*kong.Kong, error) { + return kong.New( + new(CLI), + kong.UsageOnError(), + kong.Description("hashes plaintext using bascule's infrastructure"), ) +} - grammar, err = kong.New(new(CLI), kong.Bind(ctx)) +func run(grammar *kong.Kong, args []string) (err error) { + var ctx *kong.Context if err == nil { - kongCtx, err = grammar.Parse(args) + ctx, err = grammar.Parse(args) } if err == nil { - err = kongCtx.Run() + err = ctx.Run() } return } func main() { - err := run(os.Args[1:], &Context{ - Stdout: os.Stdout, - Stderr: os.Stderr, - }) - - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + grammar, err := newKong() + if err == nil { + err = run(grammar, os.Args[1:]) } + + grammar.FatalIfErrorf(err) } From 58f8136c90f511299be690e3da9fefaf95525ced Mon Sep 17 00:00:00 2001 From: johnabass Date: Fri, 30 Aug 2024 11:01:24 -0700 Subject: [PATCH 07/13] chore: tests and refactoring --- cmd/hash/main.go | 25 +++++---- cmd/hash/main_test.go | 125 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 cmd/hash/main_test.go diff --git a/cmd/hash/main.go b/cmd/hash/main.go index 392b231..3fd52c1 100644 --- a/cmd/hash/main.go +++ b/cmd/hash/main.go @@ -9,32 +9,33 @@ import ( "github.com/alecthomas/kong" ) -func newKong() (*kong.Kong, error) { +func newKong(extra ...kong.Option) (*kong.Kong, error) { return kong.New( new(CLI), - kong.UsageOnError(), - kong.Description("hashes plaintext using bascule's infrastructure"), + append( + []kong.Option{ + kong.UsageOnError(), + kong.Description("hashes plaintext using bascule's infrastructure"), + }, + extra..., + )..., ) } -func run(grammar *kong.Kong, args []string) (err error) { +func run(args []string, extra ...kong.Option) { var ctx *kong.Context + k, err := newKong(extra...) if err == nil { - ctx, err = grammar.Parse(args) + ctx, err = k.Parse(args) } if err == nil { err = ctx.Run() } - return + k.FatalIfErrorf(err) } func main() { - grammar, err := newKong() - if err == nil { - err = run(grammar, os.Args[1:]) - } - - grammar.FatalIfErrorf(err) + run(os.Args[1:]) } diff --git a/cmd/hash/main_test.go b/cmd/hash/main_test.go new file mode 100644 index 0000000..9d41711 --- /dev/null +++ b/cmd/hash/main_test.go @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "bytes" + "strconv" + "testing" + + "github.com/alecthomas/kong" + "github.com/stretchr/testify/suite" + "golang.org/x/crypto/bcrypt" +) + +type RunTestSuite struct { + suite.Suite + + kongOptions []kong.Option + + stdout bytes.Buffer + stderr bytes.Buffer + exitCode int +} + +func (suite *RunTestSuite) exitFunc(code int) { + suite.exitCode = code +} + +func (suite *RunTestSuite) SetupSuite() { + suite.kongOptions = []kong.Option{ + kong.Writers(&suite.stdout, &suite.stderr), + kong.Exit(suite.exitFunc), + } +} + +func (suite *RunTestSuite) SetupTest() { + suite.stdout.Reset() + suite.stderr.Reset() + suite.exitCode = 0 +} + +func (suite *RunTestSuite) SetupSubTest() { + suite.SetupTest() +} + +func (suite *RunTestSuite) testBcryptSubcommandInvalidParameters() { + testCases := []struct { + args []string + }{ + { + args: []string{"bcrypt"}, + }, + { + args: []string{"bcrypt", "--cost", "123", "plaintext"}, + }, + { + args: []string{"bcrypt", "-c", "123", "plaintext"}, + }, + { + args: []string{"bcrypt", "--cost", "1", "plaintext"}, + }, + { + args: []string{"bcrypt", "-c", "1", "plaintext"}, + }, + { + args: []string{"bcrypt", "this plaintext is way to long ... asdfoiuwelrkjhsldkjfp983yu5pkljheflkajsodifuypwieuyrtplkahjsdflkajhsdf"}, + }, + } + + for i, testCase := range testCases { + suite.Run(strconv.Itoa(i), func() { + run(testCase.args, suite.kongOptions...) + + suite.NotEqual(0, suite.exitCode) + suite.Greater(suite.stdout.Len(), 0) // the usage on error goes to stdout + suite.Greater(suite.stderr.Len(), 0) + }) + } +} + +func (suite *RunTestSuite) testBcryptSubcommandSuccess() { + const plaintext string = "plaintext" + + testCases := []struct { + args []string + }{ + { + args: []string{"bcrypt", plaintext}, + }, + { + args: []string{"bcrypt", "-c", "5", plaintext}, + }, + { + args: []string{"bcrypt", "--cost", "9", plaintext}, + }, + } + + for i, testCase := range testCases { + suite.Run(strconv.Itoa(i), func() { + run(testCase.args, suite.kongOptions...) + + suite.Zero(suite.exitCode) + suite.Greater(suite.stdout.Len(), 0) + suite.Zero(suite.stderr.Len()) + + // what's written to stdout should be parseable as a bcrypt hash + suite.NoError( + bcrypt.CompareHashAndPassword( + suite.stdout.Bytes(), + []byte(plaintext), + ), + ) + }) + } +} + +func (suite *RunTestSuite) TestBcryptSubcommand() { + suite.Run("InvalidParameters", suite.testBcryptSubcommandInvalidParameters) + suite.Run("Success", suite.testBcryptSubcommandSuccess) +} + +func TestRun(t *testing.T) { + suite.Run(t, new(RunTestSuite)) +} From e5b39268a0e3ac35b1513b0b4f65e9989fcfee37 Mon Sep 17 00:00:00 2001 From: johnabass Date: Fri, 30 Aug 2024 12:07:08 -0700 Subject: [PATCH 08/13] chore: switched to a constant for the testing plaintext --- basculehash/bcrypt_test.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/basculehash/bcrypt_test.go b/basculehash/bcrypt_test.go index fd2f86f..3ff1939 100644 --- a/basculehash/bcrypt_test.go +++ b/basculehash/bcrypt_test.go @@ -13,22 +13,19 @@ import ( "golang.org/x/crypto/bcrypt" ) +const bcryptPlaintext string = "bcrypt plaintext" + type BcryptTestSuite struct { suite.Suite } -// plaintext returns a known, valid byte slice to hash -func (suite *BcryptTestSuite) plaintext() []byte { - return []byte("this is a password") -} - // goodHash returns a hash that is expected to be successful. // The plaintext() is hashed with the given cost. func (suite *BcryptTestSuite) goodHash(cost int) []byte { var ( b bytes.Buffer hasher = Bcrypt{Cost: cost} - _, err = hasher.Hash(&b, suite.plaintext()) + _, err = hasher.Hash(&b, []byte(bcryptPlaintext)) ) suite.Require().NoError(err) @@ -41,7 +38,7 @@ func (suite *BcryptTestSuite) TestHash() { o strings.Builder hasher = Bcrypt{} - n, err = hasher.Hash(&o, suite.plaintext()) + n, err = hasher.Hash(&o, []byte(bcryptPlaintext)) ) suite.NoError(err) @@ -53,7 +50,7 @@ func (suite *BcryptTestSuite) TestHash() { o strings.Builder hasher = Bcrypt{Cost: 12} - n, err = hasher.Hash(&o, suite.plaintext()) + n, err = hasher.Hash(&o, []byte(bcryptPlaintext)) ) suite.NoError(err) @@ -65,7 +62,7 @@ func (suite *BcryptTestSuite) TestHash() { o strings.Builder hasher = Bcrypt{Cost: bcrypt.MaxCost + 100} - _, err = hasher.Hash(&o, suite.plaintext()) + _, err = hasher.Hash(&o, []byte(bcryptPlaintext)) ) suite.Error(err) @@ -79,7 +76,7 @@ func (suite *BcryptTestSuite) TestMatches() { var ( hashed = suite.goodHash(cost) hasher = Bcrypt{Cost: cost} - ok, err = hasher.Matches(suite.plaintext(), hashed) + ok, err = hasher.Matches([]byte(bcryptPlaintext), hashed) ) suite.True(ok) From 5a09d300cc336fb443dfba1f432ede865b9bcbda Mon Sep 17 00:00:00 2001 From: johnabass Date: Fri, 30 Aug 2024 13:07:48 -0700 Subject: [PATCH 09/13] added a Passworder interface to access a token's password independent of the auth scheme --- basculehttp/basic.go | 2 +- passworder.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 passworder.go diff --git a/basculehttp/basic.go b/basculehttp/basic.go index f1704f8..7bc1b74 100644 --- a/basculehttp/basic.go +++ b/basculehttp/basic.go @@ -14,8 +14,8 @@ import ( // BasicToken is the interface that Basic Auth tokens implement. type BasicToken interface { + bascule.Passworder UserName() string - Password() string } // basicToken is the internal basic token struct that results from diff --git a/passworder.go b/passworder.go new file mode 100644 index 0000000..da9cb46 --- /dev/null +++ b/passworder.go @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package bascule + +// Passworder is an optional Token interface that provides access +// to any raw password contained within the token. +type Passworder interface { + // Password returns the password associated with this Token, if any. + Password() string +} From 292f498544ade4d913fc2608269e857d75258636 Mon Sep 17 00:00:00 2001 From: johnabass Date: Mon, 2 Sep 2024 18:22:38 -0700 Subject: [PATCH 10/13] provide a GetPassword API, since most clients won't care about the intermediate Passworder interface --- basculehttp/basic.go | 7 ++++++- mocks_test.go | 12 ++++++++++++ password.go | 28 ++++++++++++++++++++++++++++ password_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 password.go create mode 100644 password_test.go diff --git a/basculehttp/basic.go b/basculehttp/basic.go index 7bc1b74..9c926ff 100644 --- a/basculehttp/basic.go +++ b/basculehttp/basic.go @@ -14,8 +14,13 @@ import ( // BasicToken is the interface that Basic Auth tokens implement. type BasicToken interface { - bascule.Passworder + // UserName is the user name in the basic auth string and will + // be e the same as Principal(). UserName() string + + // Password returns the password from the basic auth string. + // This also permits a BasicToken to be used with bascule.GetPassword. + Password() string } // basicToken is the internal basic token struct that results from diff --git a/mocks_test.go b/mocks_test.go index 0f8be0c..6bae719 100644 --- a/mocks_test.go +++ b/mocks_test.go @@ -21,6 +21,18 @@ func (m *mockToken) ExpectPrincipal(v string) *mock.Call { return m.On("Principal").Return(v) } +type mockTokenWithPassword struct { + mockToken +} + +func (m *mockTokenWithPassword) Password() string { + return m.Called().String(0) +} + +func (m *mockTokenWithPassword) ExpectPassword(v string) *mock.Call { + return m.On("Password").Return(v) +} + type mockTokenWithCapabilities struct { mockToken } diff --git a/password.go b/password.go new file mode 100644 index 0000000..135eb85 --- /dev/null +++ b/password.go @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package bascule + +// Passworder is an optional interface that a Token may implement that +// provides access to an associated password. Tokens derived from +// basic authentication will implement this interface. +type Passworder interface { + // Password returns the password associated with this Token. + Password() string +} + +// GetPassword returns any password associated with the given Token. +// +// If the token implements Passworder, the result of the Password() +// method is returned along with true. Otherwise, this function returns +// the empty string and false to indicate that the Token did not carry +// an associated password. +func GetPassword(t Token) (password string, exists bool) { + var p Passworder + if TokenAs(t, &p) { + password = p.Password() + exists = true + } + + return +} diff --git a/password_test.go b/password_test.go new file mode 100644 index 0000000..6203cac --- /dev/null +++ b/password_test.go @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package bascule + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type PasswordTestSuite struct { + suite.Suite +} + +func (suite *PasswordTestSuite) TestGetPassword() { + suite.Run("NoPassword", func() { + token := new(mockToken) + password, exists := GetPassword(token) + + suite.Empty(password) + suite.False(exists) + token.AssertExpectations(suite.T()) + }) + + suite.Run("WithPassword", func() { + const expectedPassword = "this is an expected password" + token := new(mockTokenWithPassword) + token.ExpectPassword(expectedPassword) + + password, exists := GetPassword(token) + suite.Equal(expectedPassword, password) + suite.True(exists) + token.AssertExpectations(suite.T()) + }) +} + +func TestPassword(t *testing.T) { + suite.Run(t, new(PasswordTestSuite)) +} From 9c7710acf5a34ab56b9106e36ed562cd89129149 Mon Sep 17 00:00:00 2001 From: johnabass Date: Mon, 2 Sep 2024 18:23:37 -0700 Subject: [PATCH 11/13] chore: delint a false positive --- password_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/password_test.go b/password_test.go index 6203cac..2e9a5fc 100644 --- a/password_test.go +++ b/password_test.go @@ -24,7 +24,7 @@ func (suite *PasswordTestSuite) TestGetPassword() { }) suite.Run("WithPassword", func() { - const expectedPassword = "this is an expected password" + const expectedPassword = "this is an expected password" //nolint:gosec token := new(mockTokenWithPassword) token.ExpectPassword(expectedPassword) From 5107edd4d19109b6ab96d2ca8f9015282f6c22ba Mon Sep 17 00:00:00 2001 From: johnabass Date: Mon, 2 Sep 2024 18:25:01 -0700 Subject: [PATCH 12/13] chore: rename --- passworder.go | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 passworder.go diff --git a/passworder.go b/passworder.go deleted file mode 100644 index da9cb46..0000000 --- a/passworder.go +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC -// SPDX-License-Identifier: Apache-2.0 - -package bascule - -// Passworder is an optional Token interface that provides access -// to any raw password contained within the token. -type Passworder interface { - // Password returns the password associated with this Token, if any. - Password() string -} From a94899c593e93bd840d53e096fd0c4ac4132e06a Mon Sep 17 00:00:00 2001 From: johnabass Date: Tue, 3 Sep 2024 13:01:24 -0700 Subject: [PATCH 13/13] chore: clarified comment --- basculehash/hasher.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/basculehash/hasher.go b/basculehash/hasher.go index a89656c..baeebe7 100644 --- a/basculehash/hasher.go +++ b/basculehash/hasher.go @@ -12,12 +12,7 @@ type Hasher interface { // Hash writes the hash of a plaintext to a writer. The number of // bytes written along with any error is returned. // - // The format of the written hash must be ASCII. Typically, base64 - // encoding will be used to achieve this. - // - // This method should write out any hash parameters necessary to - // execute the same hash against a different plaintext. This allows - // a Comparer to work, for example. It also allows migration of - // hash parameters in a way that doesn't disturb already hashed values. + // The format of the written hash must be ASCII. The recommended + // format is the modular crypt format, which bcrypt uses. Hash(dst io.Writer, plaintext []byte) (int, error) }