mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-23 11:39:32 +02:00
proxy: add userinfo and webauthn endpoints (#3755)
* proxy: add userinfo and webauthn endpoints * use TLD for RP id * use EffectiveTLDPlusOne * upgrade webauthn * fix test * Update internal/handlers/jwks.go Co-authored-by: bobby <1544881+desimone@users.noreply.github.com> Co-authored-by: bobby <1544881+desimone@users.noreply.github.com>
This commit is contained in:
parent
81053ac8ef
commit
c1a522cd82
33 changed files with 498 additions and 216 deletions
|
@ -3,6 +3,7 @@ package webauthnutil
|
|||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/webauthn"
|
||||
|
@ -25,12 +26,14 @@ func GenerateChallenge(key []byte, expiry time.Time) cryptutil.SecureToken {
|
|||
|
||||
// GenerateCreationOptions generates creation options for WebAuthn.
|
||||
func GenerateCreationOptions(
|
||||
r *http.Request,
|
||||
key []byte,
|
||||
deviceType *device.Type,
|
||||
user *user.User,
|
||||
) *webauthn.PublicKeyCredentialCreationOptions {
|
||||
expiry := time.Now().Add(ceremonyTimeout)
|
||||
return newCreationOptions(
|
||||
r,
|
||||
GenerateChallenge(key, expiry).Bytes(),
|
||||
deviceType,
|
||||
user,
|
||||
|
@ -39,12 +42,14 @@ func GenerateCreationOptions(
|
|||
|
||||
// GenerateRequestOptions generates request options for WebAuthn.
|
||||
func GenerateRequestOptions(
|
||||
r *http.Request,
|
||||
key []byte,
|
||||
deviceType *device.Type,
|
||||
knownDeviceCredentials []*device.Credential,
|
||||
) *webauthn.PublicKeyCredentialRequestOptions {
|
||||
expiry := time.Now().Add(ceremonyTimeout)
|
||||
return newRequestOptions(
|
||||
r,
|
||||
GenerateChallenge(key, expiry).Bytes(),
|
||||
deviceType,
|
||||
knownDeviceCredentials,
|
||||
|
@ -54,6 +59,7 @@ func GenerateRequestOptions(
|
|||
// GetCreationOptionsForCredential gets the creation options for the public key creation credential. An error may be
|
||||
// returned if the challenge used to generate the credential is invalid.
|
||||
func GetCreationOptionsForCredential(
|
||||
r *http.Request,
|
||||
key []byte,
|
||||
deviceType *device.Type,
|
||||
user *user.User,
|
||||
|
@ -76,12 +82,13 @@ func GetCreationOptionsForCredential(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return newCreationOptions(challenge.Bytes(), deviceType, user), nil
|
||||
return newCreationOptions(r, challenge.Bytes(), deviceType, user), nil
|
||||
}
|
||||
|
||||
// GetRequestOptionsForCredential gets the request options for the public key request credential. An error may be
|
||||
// returned if the challenge used to generate the credential is invalid.
|
||||
func GetRequestOptionsForCredential(
|
||||
r *http.Request,
|
||||
key []byte,
|
||||
deviceType *device.Type,
|
||||
knownDeviceCredentials []*device.Credential,
|
||||
|
@ -104,11 +111,12 @@ func GetRequestOptionsForCredential(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return newRequestOptions(challenge.Bytes(), deviceType, knownDeviceCredentials), nil
|
||||
return newRequestOptions(r, challenge.Bytes(), deviceType, knownDeviceCredentials), nil
|
||||
}
|
||||
|
||||
// newCreationOptions gets the creation options for WebAuthn with the provided challenge.
|
||||
func newCreationOptions(
|
||||
r *http.Request,
|
||||
challenge []byte,
|
||||
deviceType *device.Type,
|
||||
user *user.User,
|
||||
|
@ -116,6 +124,7 @@ func newCreationOptions(
|
|||
options := &webauthn.PublicKeyCredentialCreationOptions{
|
||||
RP: webauthn.PublicKeyCredentialRPEntity{
|
||||
Name: rpName,
|
||||
ID: GetEffectiveDomain(r),
|
||||
},
|
||||
User: GetUserEntity(user),
|
||||
Challenge: challenge,
|
||||
|
@ -133,6 +142,7 @@ func newCreationOptions(
|
|||
|
||||
// newRequestOptions gets the request options for WebAuthn with the provided challenge.
|
||||
func newRequestOptions(
|
||||
r *http.Request,
|
||||
challenge []byte,
|
||||
deviceType *device.Type,
|
||||
knownDeviceCredentials []*device.Credential,
|
||||
|
@ -140,6 +150,7 @@ func newRequestOptions(
|
|||
options := &webauthn.PublicKeyCredentialRequestOptions{
|
||||
Challenge: challenge,
|
||||
Timeout: ceremonyTimeout,
|
||||
RPID: GetEffectiveDomain(r),
|
||||
}
|
||||
fillRequestUserVerificationRequirement(
|
||||
options,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package webauthnutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pomerium/pomerium/pkg/grpc/device"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/user"
|
||||
|
@ -12,14 +14,17 @@ import (
|
|||
)
|
||||
|
||||
func TestGenerateCreationOptions(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "https://www.example.com", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("random challenge", func(t *testing.T) {
|
||||
key := []byte{1, 2, 3}
|
||||
options1 := GenerateCreationOptions(key, predefinedDeviceTypes[DefaultDeviceType], &user.User{
|
||||
options1 := GenerateCreationOptions(r, key, predefinedDeviceTypes[DefaultDeviceType], &user.User{
|
||||
Id: "example",
|
||||
Email: "test@example.com",
|
||||
Name: "Test User",
|
||||
})
|
||||
options2 := GenerateCreationOptions(key, predefinedDeviceTypes[DefaultDeviceType], &user.User{
|
||||
options2 := GenerateCreationOptions(r, key, predefinedDeviceTypes[DefaultDeviceType], &user.User{
|
||||
Id: "example",
|
||||
Email: "test@example.com",
|
||||
Name: "Test User",
|
||||
|
@ -28,7 +33,7 @@ func TestGenerateCreationOptions(t *testing.T) {
|
|||
})
|
||||
t.Run(DefaultDeviceType, func(t *testing.T) {
|
||||
key := []byte{1, 2, 3}
|
||||
options := GenerateCreationOptions(key, predefinedDeviceTypes[DefaultDeviceType], &user.User{
|
||||
options := GenerateCreationOptions(r, key, predefinedDeviceTypes[DefaultDeviceType], &user.User{
|
||||
Id: "example",
|
||||
Email: "test@example.com",
|
||||
Name: "Test User",
|
||||
|
@ -37,6 +42,7 @@ func TestGenerateCreationOptions(t *testing.T) {
|
|||
assert.Equal(t, &webauthn.PublicKeyCredentialCreationOptions{
|
||||
RP: webauthn.PublicKeyCredentialRPEntity{
|
||||
Name: "Pomerium",
|
||||
ID: "example.com",
|
||||
},
|
||||
User: webauthn.PublicKeyCredentialUserEntity{
|
||||
ID: []byte{
|
||||
|
@ -63,15 +69,18 @@ func TestGenerateCreationOptions(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGenerateRequestOptions(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "https://www.example.com", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("random challenge", func(t *testing.T) {
|
||||
key := []byte{1, 2, 3}
|
||||
options1 := GenerateRequestOptions(key, predefinedDeviceTypes[DefaultDeviceType], nil)
|
||||
options2 := GenerateRequestOptions(key, predefinedDeviceTypes[DefaultDeviceType], nil)
|
||||
options1 := GenerateRequestOptions(r, key, predefinedDeviceTypes[DefaultDeviceType], nil)
|
||||
options2 := GenerateRequestOptions(r, key, predefinedDeviceTypes[DefaultDeviceType], nil)
|
||||
assert.NotEqual(t, options1.Challenge, options2.Challenge)
|
||||
})
|
||||
t.Run(DefaultDeviceType, func(t *testing.T) {
|
||||
key := []byte{1, 2, 3}
|
||||
options := GenerateRequestOptions(key, predefinedDeviceTypes[DefaultDeviceType], []*device.Credential{
|
||||
options := GenerateRequestOptions(r, key, predefinedDeviceTypes[DefaultDeviceType], []*device.Credential{
|
||||
{Id: "device1", Specifier: &device.Credential_Webauthn{Webauthn: &device.Credential_WebAuthn{
|
||||
Id: []byte{4, 5, 6},
|
||||
}}},
|
||||
|
@ -79,6 +88,7 @@ func TestGenerateRequestOptions(t *testing.T) {
|
|||
options.Challenge = nil
|
||||
assert.Equal(t, &webauthn.PublicKeyCredentialRequestOptions{
|
||||
Timeout: 900000000000,
|
||||
RPID: "example.com",
|
||||
AllowCredentials: []webauthn.PublicKeyCredentialDescriptor{
|
||||
{Type: "public-key", ID: []byte{4, 5, 6}},
|
||||
},
|
||||
|
@ -129,7 +139,8 @@ func TestFillPublicKeyCredentialParameters(t *testing.T) {
|
|||
}{
|
||||
{"", 0, nil},
|
||||
{"public-key", -7, &device.WebAuthnOptions_PublicKeyCredentialParameters{
|
||||
Type: device.WebAuthnOptions_PUBLIC_KEY, Alg: -7}},
|
||||
Type: device.WebAuthnOptions_PUBLIC_KEY, Alg: -7,
|
||||
}},
|
||||
} {
|
||||
params := new(webauthn.PublicKeyCredentialParameters)
|
||||
fillPublicKeyCredentialParameters(params, testCase.in)
|
||||
|
|
|
@ -1,2 +1,32 @@
|
|||
// Package webauthnutil contains types and functions for working with the webauthn package.
|
||||
package webauthnutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
|
||||
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||
"github.com/pomerium/webauthn"
|
||||
)
|
||||
|
||||
// GetRelyingParty gets a RelyingParty for the given request and databroker client.
|
||||
func GetRelyingParty(r *http.Request, client databroker.DataBrokerServiceClient) *webauthn.RelyingParty {
|
||||
return webauthn.NewRelyingParty(
|
||||
"https://"+GetEffectiveDomain(r),
|
||||
NewCredentialStorage(client),
|
||||
)
|
||||
}
|
||||
|
||||
// GetEffectiveDomain returns the effective domain for an HTTP request.
|
||||
func GetEffectiveDomain(r *http.Request) string {
|
||||
h, _, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
h = r.Host
|
||||
}
|
||||
if tld, err := publicsuffix.EffectiveTLDPlusOne(h); err == nil {
|
||||
return tld
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
|
31
pkg/webauthnutil/webauthnutil_test.go
Normal file
31
pkg/webauthnutil/webauthnutil_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package webauthnutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetEffectiveDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, tc := range []struct {
|
||||
in string
|
||||
expect string
|
||||
}{
|
||||
{"https://www.example.com/some/path", "example.com"},
|
||||
{"https://www.example.com:8080/some/path", "example.com"},
|
||||
{"https://www.subdomain.example.com/some/path", "example.com"},
|
||||
{"https://example.com/some/path", "example.com"},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.expect, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, tc.in, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expect, GetEffectiveDomain(r))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue