Add initial "pinniped alpha login oidc" partial implementation.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer
2020-10-06 12:42:29 -05:00
parent 7eed7ba19a
commit 38501ff763
13 changed files with 585 additions and 4 deletions
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package pkce
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"io"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
// Generate generates a new random PKCE code.
func Generate() (Code, error) { return generate(rand.Reader) }
func generate(rand io.Reader) (Code, error) {
var buf [32]byte
if _, err := io.ReadFull(rand, buf[:]); err != nil {
return "", errors.WithMessage(err, "could not generate PKCE code")
}
return Code(hex.EncodeToString(buf[:])), nil
}
// Code implements the basic options required for RFC 7636: Proof Key for Code Exchange (PKCE).
type Code string
// Challenge returns the OAuth2 auth code parameter for sending the PKCE code challenge.
func (p *Code) Challenge() oauth2.AuthCodeOption {
b := sha256.Sum256([]byte(*p))
return oauth2.SetAuthURLParam("code_challenge", base64.RawURLEncoding.EncodeToString(b[:]))
}
// Method returns the OAuth2 auth code parameter for sending the PKCE code challenge method.
func (p *Code) Method() oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("code_challenge_method", "S256")
}
// Verifier returns the OAuth2 auth code parameter for sending the PKCE code verifier.
func (p *Code) Verifier() oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("code_verifier", string(*p))
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package pkce
import (
"bytes"
"encoding/base64"
"net/url"
"testing"
"golang.org/x/oauth2"
"github.com/stretchr/testify/require"
)
func TestPKCE(t *testing.T) {
p, err := Generate()
require.NoError(t, err)
cfg := oauth2.Config{}
authCodeURL, err := url.Parse(cfg.AuthCodeURL("", p.Challenge(), p.Method()))
require.NoError(t, err)
// The code_challenge must be 256 bits (sha256) encoded as unpadded urlsafe base64.
chal, err := base64.RawURLEncoding.DecodeString(authCodeURL.Query().Get("code_challenge"))
require.NoError(t, err)
require.Len(t, chal, 32)
// The code_challenge_method must be a fixed value.
require.Equal(t, "S256", authCodeURL.Query().Get("code_challenge_method"))
// The code_verifier param should be 64 hex characters.
verifyURL, err := url.Parse(cfg.AuthCodeURL("", p.Verifier()))
require.NoError(t, err)
require.Regexp(t, `\A[0-9a-f]{64}\z`, verifyURL.Query().Get("code_verifier"))
var empty bytes.Buffer
p, err = generate(&empty)
require.EqualError(t, err, "could not generate PKCE code: EOF")
require.Empty(t, p)
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package state
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"io"
"github.com/pkg/errors"
)
// Generate generates a new random state parameter of an appropriate size.
func Generate() (State, error) { return generate(rand.Reader) }
func generate(rand io.Reader) (State, error) {
var buf [16]byte
if _, err := io.ReadFull(rand, buf[:]); err != nil {
return "", errors.WithMessage(err, "could not generate random state")
}
return State(hex.EncodeToString(buf[:])), nil
}
// State implements some utilities for working with OAuth2 state parameters.
type State string
// String returns the string encoding of this state value.
func (s *State) String() string {
return string(*s)
}
// Validate the returned state (from a callback parameter). Returns true iff the state is valid.
func (s *State) Valid(returnedState string) bool {
return subtle.ConstantTimeCompare([]byte(returnedState), []byte(*s)) == 1
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package state
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
func TestState(t *testing.T) {
s, err := Generate()
require.NoError(t, err)
require.Len(t, s, 32)
require.Len(t, s.String(), 32)
require.True(t, s.Valid(string(s)))
require.False(t, s.Valid(string(s)+"x"))
var empty bytes.Buffer
s, err = generate(&empty)
require.EqualError(t, err, "could not generate random state: EOF")
require.Empty(t, s)
}