Skip to content

Commit

Permalink
Merge branch 'release/v0.1.2' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhartstonge committed Jan 26, 2022
2 parents 6006cf1 + de6c77f commit d2c0c32
Show file tree
Hide file tree
Showing 7 changed files with 1,283 additions and 31 deletions.
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [v0.1.2] - 2022-01-27
### Added
- :white_check_mark: pkce: adds tests.

### Changed
- :recycle: options: orders options alphabetically.

### Fixed
- :bug: options: fixes challenge method being able to be set with invalid values.
- :bug: pkce: stops invalid methods from being able to be set.
- :bug: pkce: stops the code verifier length being overwritten if code verifier is set.
- :bug: pkce: ensures getCodeVerifier checks against nilness and emptiness.

## [v0.1.1] - 2022-01-25
### Added
- :memo: README: adds code verifier verification examples.
Expand All @@ -25,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Verification for an incoming code verifier.
- URL parameter key constants.

[Unreleased]: https://github.com/matthewhartstonge/pkce/compare/v0.1.1...HEAD
[v0.1.1]: https://github.com/matthewhartstonge/pkce/releases/tag/v0.1.1
[Unreleased]: https://github.com/matthewhartstonge/pkce/compare/v0.1.2...HEAD
[v0.1.2]: https://github.com/matthewhartstonge/pkce/compare/v0.1.1...v0.1.2
[v0.1.1]: https://github.com/matthewhartstonge/pkce/compare/v0.1.0...v0.1.1
[v0.1.0]: https://github.com/matthewhartstonge/pkce/releases/tag/v0.1.0
3 changes: 3 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var (
// trying a downgrade attack.
ErrMethodDowngrade = errors.New("clients must not downgrade to 'plain' after trying the 'S256' method")

// ErrMethodNotSupported enforces the use of compliant transform methods
ErrMethodNotSupported = errors.New("clients must use either 'plain' or 'S256' as a transform method")

// ErrVerifierCharacters enforces character compliance with the unreserved
// character set as specified in RFC 7636, 4.1.
ErrVerifierCharacters = fmt.Errorf(
Expand Down
26 changes: 16 additions & 10 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ package pkce
// Option enables variadic PKCE Key options to be configured.
type Option func(*Key) error

// WithCodeVerifierLength enables specifying the length of the code verifier
// to be generated.
func WithCodeVerifierLength(n int) Option {
// WithChallengeMethod enables specifying the challenge transformation method.
// Should only be used to downgrade to plain if required.
func WithChallengeMethod(method Method) Option {
return func(key *Key) (err error) {
err = key.setCodeVerifierLength(n)
switch method {
case Plain, S256:
key.challengeMethod = method

return
default:
return ErrMethodNotSupported
}

return nil
}
}

Expand All @@ -24,12 +30,12 @@ func WithCodeVerifier(codeVerifier []byte) Option {
}
}

// WithChallengeMethod enables specifying the challenge transformation method.
// Should only be used to downgrade to plain if required.
func WithChallengeMethod(method Method) Option {
// WithCodeVerifierLength enables specifying the length of the code verifier
// to be generated.
func WithCodeVerifierLength(n int) Option {
return func(key *Key) (err error) {
key.challengeMethod = method
err = key.setCodeVerifierLength(n)

return nil
return
}
}
81 changes: 81 additions & 0 deletions options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package pkce

import (
"reflect"
"testing"
)

func TestWithChallengeMethod(t *testing.T) {
tests := setChallengeMethodTests()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opt := WithChallengeMethod(tt.method)

err := opt(tt.gotKey)
if (err != nil) != tt.shouldErr {
t.Errorf("WithChallengeMethod() should error\ngot: %v, want: %v\n", err, tt.shouldErr)
}

if tt.shouldErr {
if tt.wantErr != err {
t.Errorf("WithChallengeMethod() error type not expected\ngot: %v, want: %v\n", err, tt.wantErr)
}
} else {
if !reflect.DeepEqual(tt.gotKey, tt.wantKey) {
t.Errorf("WithChallengeMethod() key\ngot: %v\nwant %v\n", tt.gotKey, tt.wantKey)
}
}
})
}
}

func TestWithCodeVerifier(t *testing.T) {
tests := setCodeVerifierTests()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opt := WithCodeVerifier(tt.codeVerifier)

err := opt(tt.gotKey)
if (err != nil) != tt.shouldErr {
t.Errorf("WithCodeVerifier() should error\ngot: %v, want: %v\n", err, tt.shouldErr)
}

if tt.shouldErr {
if tt.wantErr != err {
t.Errorf("WithCodeVerifier() error type not expected\ngot: %v, want: %v\n", err, tt.wantErr)
}
} else {
if !reflect.DeepEqual(tt.gotKey, tt.wantKey) {
t.Errorf("WithCodeVerifier() key\ngot: %v\nwant %v\n", tt.gotKey, tt.wantKey)
}
}
})
}
}

func TestWithCodeVerifierLength(t *testing.T) {
tests := setCodeVerifierLengthTests()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opt := WithCodeVerifierLength(tt.n)

err := opt(tt.gotKey)
if (err != nil) != tt.shouldErr {
t.Errorf("WithCodeVerifierLength() should error\ngot: %v, want: %v\n", err, tt.shouldErr)
}

if tt.shouldErr {
if tt.wantErr != err {
t.Errorf("WithCodeVerifierLength() error type not expected\ngot: %v, want: %v\n", err, tt.wantErr)
}
} else {
if !reflect.DeepEqual(tt.gotKey, tt.wantKey) {
t.Errorf("WithCodeVerifierLength() key\ngot: %v\nwant %v\n", tt.gotKey, tt.wantKey)
}
}
})
}
}
27 changes: 19 additions & 8 deletions pkce.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,17 @@ type Key struct {

// SetChallengeMethod enables upgrading code challenge generation method.
func (k *Key) SetChallengeMethod(method Method) error {
if k.challengeMethod == S256 && method == Plain {
return ErrMethodDowngrade
}
switch method {
case Plain, S256:
if k.challengeMethod == S256 && method == Plain {
return ErrMethodDowngrade
}

k.challengeMethod = method

k.challengeMethod = method
default:
return ErrMethodNotSupported
}

return nil
}
Expand All @@ -186,6 +192,11 @@ func (k *Key) ChallengeMethod() Method {
// If a code verifier is supplied, this setting will be ignored in favour of
// using the supplied verifier.
func (k *Key) setCodeVerifierLength(n int) error {
if len(k.codeVerifier) > 0 {
// Don't overwrite the set length.
return nil
}

if err := validateVerifierLen(n); err != nil {
return err
}
Expand Down Expand Up @@ -215,7 +226,7 @@ func (k *Key) CodeVerifier() string {
// getCodeVerifier returns a code verifier. If one has not been set, it will
// generate one based on the configured verifier length.
func (k *Key) getCodeVerifier() []byte {
if k.codeVerifier == nil {
if len(k.codeVerifier) == 0 {
k.codeVerifier = generateCodeVerifier(k.codeVerifierLen)
}

Expand Down Expand Up @@ -251,13 +262,13 @@ func generateCodeVerifier(n int) (out []byte) {

// generateCodeChallenge performs the transform required by the specified
// method.
func generateCodeChallenge(method Method, v []byte) (out string) {
func generateCodeChallenge(method Method, codeVerifier []byte) (out string) {
if method == Plain {
return string(v)
return string(codeVerifier)
}

s256 := sha256.New()
s256.Write(v)
s256.Write(codeVerifier)

return base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
}
Loading

0 comments on commit d2c0c32

Please sign in to comment.