mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 05:27:24 +08:00
292 lines
9.6 KiB
Go
292 lines
9.6 KiB
Go
// Copyright 2022 Gravitational, Inc
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package webauthnwin
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol"
|
|
"github.com/go-webauthn/webauthn/protocol/webauthncose"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gravitational/teleport/api/types/webauthn"
|
|
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
|
)
|
|
|
|
func init() {
|
|
// Make tests silent.
|
|
PromptWriter = io.Discard
|
|
}
|
|
|
|
func TestRegister(t *testing.T) {
|
|
resetNativeAfterTests(t)
|
|
|
|
const origin = "https://example.com"
|
|
okCC := &wanlib.CredentialCreation{
|
|
Response: protocol.PublicKeyCredentialCreationOptions{
|
|
Challenge: make([]byte, 32),
|
|
RelyingParty: protocol.RelyingPartyEntity{
|
|
ID: "example.com",
|
|
CredentialEntity: protocol.CredentialEntity{
|
|
Name: "Teleport",
|
|
},
|
|
},
|
|
User: protocol.UserEntity{
|
|
ID: []byte{1, 2, 3, 4},
|
|
DisplayName: "display name",
|
|
CredentialEntity: protocol.CredentialEntity{
|
|
Name: "user name",
|
|
},
|
|
},
|
|
Parameters: []protocol.CredentialParameter{
|
|
{Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgES256},
|
|
{Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgRS256},
|
|
},
|
|
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
|
UserVerification: protocol.VerificationDiscouraged,
|
|
},
|
|
Attestation: protocol.PreferNoAttestation,
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
origin string
|
|
createCC func() *wanlib.CredentialCreation
|
|
assertFn func(t *testing.T, ccr *webauthn.CredentialCreationResponse, req *makeCredentialRequest)
|
|
}{
|
|
{
|
|
name: "flow with auto attachment and discouraged UV",
|
|
origin: origin,
|
|
createCC: func() *wanlib.CredentialCreation { return okCC },
|
|
assertFn: func(t *testing.T, ccr *webauthn.CredentialCreationResponse, req *makeCredentialRequest) {
|
|
assert.Equal(t, webauthnAttachmentAny, req.opts.dwAuthenticatorAttachment)
|
|
|
|
assert.Equal(t, webauthnUserVerificationDiscouraged, req.opts.dwUserVerificationRequirement)
|
|
|
|
assert.Equal(t, uint32(0), req.opts.bRequireResidentKey)
|
|
},
|
|
},
|
|
{
|
|
name: "with UV required and cross-platform and RRK",
|
|
origin: origin,
|
|
createCC: func() *wanlib.CredentialCreation {
|
|
cc := *okCC
|
|
cc.Response.User.DisplayName = "display name"
|
|
cc.Response.AuthenticatorSelection.UserVerification = protocol.VerificationRequired
|
|
cc.Response.AuthenticatorSelection.AuthenticatorAttachment = protocol.CrossPlatform
|
|
cc.Response.AuthenticatorSelection.ResidentKey = protocol.ResidentKeyRequirementRequired
|
|
return &cc
|
|
},
|
|
assertFn: func(t *testing.T, ccr *webauthn.CredentialCreationResponse, req *makeCredentialRequest) {
|
|
assert.Equal(t, webauthnUserVerificationRequired, req.opts.dwUserVerificationRequirement)
|
|
|
|
assert.Equal(t, webauthnAttachmentCrossPlatform, req.opts.dwAuthenticatorAttachment)
|
|
|
|
assert.Equal(t, uint32(1), req.opts.bRequireResidentKey)
|
|
},
|
|
},
|
|
{
|
|
name: "with UV preferred and platform",
|
|
origin: origin,
|
|
createCC: func() *wanlib.CredentialCreation {
|
|
cc := *okCC
|
|
cc.Response.AuthenticatorSelection.UserVerification = protocol.VerificationPreferred
|
|
cc.Response.AuthenticatorSelection.AuthenticatorAttachment = protocol.Platform
|
|
return &cc
|
|
},
|
|
assertFn: func(t *testing.T, ccr *webauthn.CredentialCreationResponse, req *makeCredentialRequest) {
|
|
assert.Equal(t, webauthnUserVerificationPreferred, req.opts.dwUserVerificationRequirement)
|
|
|
|
assert.Equal(t, webauthnAttachmentPlatform, req.opts.dwAuthenticatorAttachment)
|
|
},
|
|
},
|
|
{
|
|
name: "with UV discouraged and platform",
|
|
origin: origin,
|
|
createCC: func() *wanlib.CredentialCreation {
|
|
cc := *okCC
|
|
cc.Response.AuthenticatorSelection.UserVerification = protocol.VerificationDiscouraged
|
|
return &cc
|
|
},
|
|
assertFn: func(t *testing.T, ccr *webauthn.CredentialCreationResponse, req *makeCredentialRequest) {
|
|
assert.Equal(t, webauthnUserVerificationDiscouraged, req.opts.dwUserVerificationRequirement)
|
|
},
|
|
},
|
|
{
|
|
name: "RRK from RequireResidentKey if is empty ResidentKey",
|
|
origin: origin,
|
|
createCC: func() *wanlib.CredentialCreation {
|
|
cc := *okCC
|
|
cc.Response.AuthenticatorSelection.RequireResidentKey = protocol.ResidentKeyRequired()
|
|
return &cc
|
|
},
|
|
assertFn: func(t *testing.T, ccr *webauthn.CredentialCreationResponse, req *makeCredentialRequest) {
|
|
assert.Equal(t, uint32(1), req.opts.bRequireResidentKey)
|
|
},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
mock := &mockNative{}
|
|
*Native = mock
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
|
defer cancel()
|
|
|
|
resp, err := Register(ctx, test.origin, test.createCC())
|
|
require.NoError(t, err, "Register failed")
|
|
if test.assertFn != nil {
|
|
test.assertFn(t, resp.GetWebauthn(), mock.makeCredentialReq)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogin(t *testing.T) {
|
|
resetNativeAfterTests(t)
|
|
|
|
const origin = "https://example.com"
|
|
okAssertion := &wanlib.CredentialAssertion{
|
|
Response: protocol.PublicKeyCredentialRequestOptions{
|
|
Challenge: make([]byte, 32),
|
|
RelyingPartyID: "example.com",
|
|
AllowedCredentials: []protocol.CredentialDescriptor{
|
|
{Type: protocol.PublicKeyCredentialType, CredentialID: []byte{1, 2, 3, 4, 5}},
|
|
},
|
|
UserVerification: protocol.VerificationDiscouraged,
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
origin string
|
|
assertionIn func() *wanlib.CredentialAssertion
|
|
opts LoginOpts
|
|
wantErr string
|
|
assertFn func(t *testing.T, car *webauthn.CredentialAssertionResponse, req *getAssertionRequest)
|
|
}{
|
|
{
|
|
name: "uv discouraged, attachment auto",
|
|
origin: origin,
|
|
assertionIn: func() *wanlib.CredentialAssertion { return okAssertion },
|
|
assertFn: func(t *testing.T, car *webauthn.CredentialAssertionResponse, req *getAssertionRequest) {
|
|
assert.Equal(t, uint32(6), req.opts.dwVersion)
|
|
|
|
assert.Equal(t, webauthnUserVerificationDiscouraged, req.opts.dwUserVerificationRequirement)
|
|
|
|
assert.Equal(t, webauthnAttachmentAny, req.opts.dwAuthenticatorAttachment)
|
|
},
|
|
},
|
|
{
|
|
name: "uv required, attachment platform",
|
|
origin: origin,
|
|
assertionIn: func() *wanlib.CredentialAssertion {
|
|
out := *okAssertion
|
|
out.Response.UserVerification = protocol.VerificationRequired
|
|
return &out
|
|
},
|
|
opts: LoginOpts{AuthenticatorAttachment: AttachmentPlatform},
|
|
assertFn: func(t *testing.T, car *webauthn.CredentialAssertionResponse, req *getAssertionRequest) {
|
|
assert.Equal(t, uint32(6), req.opts.dwVersion)
|
|
|
|
assert.Equal(t, webauthnUserVerificationRequired, req.opts.dwUserVerificationRequirement)
|
|
|
|
assert.Equal(t, webauthnAttachmentPlatform, req.opts.dwAuthenticatorAttachment)
|
|
},
|
|
},
|
|
{
|
|
name: "uv preferred, attachment cross-platform",
|
|
origin: origin,
|
|
assertionIn: func() *wanlib.CredentialAssertion {
|
|
out := *okAssertion
|
|
out.Response.UserVerification = protocol.VerificationPreferred
|
|
return &out
|
|
},
|
|
opts: LoginOpts{AuthenticatorAttachment: AttachmentCrossPlatform},
|
|
assertFn: func(t *testing.T, car *webauthn.CredentialAssertionResponse, req *getAssertionRequest) {
|
|
assert.Equal(t, uint32(6), req.opts.dwVersion)
|
|
|
|
assert.Equal(t, webauthnUserVerificationPreferred, req.opts.dwUserVerificationRequirement)
|
|
|
|
assert.Equal(t, webauthnAttachmentCrossPlatform, req.opts.dwAuthenticatorAttachment)
|
|
},
|
|
},
|
|
{
|
|
name: "uv discouraged",
|
|
origin: origin,
|
|
assertionIn: func() *wanlib.CredentialAssertion {
|
|
out := *okAssertion
|
|
out.Response.UserVerification = protocol.VerificationDiscouraged
|
|
return &out
|
|
},
|
|
opts: LoginOpts{AuthenticatorAttachment: AttachmentCrossPlatform},
|
|
assertFn: func(t *testing.T, car *webauthn.CredentialAssertionResponse, req *getAssertionRequest) {
|
|
assert.Equal(t, uint32(6), req.opts.dwVersion)
|
|
|
|
assert.Equal(t, webauthnUserVerificationDiscouraged, req.opts.dwUserVerificationRequirement)
|
|
},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
mock := &mockNative{}
|
|
*Native = mock
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
|
defer cancel()
|
|
|
|
resp, _, err := Login(ctx, test.origin, test.assertionIn(), &test.opts)
|
|
require.NoError(t, err, "Login failed")
|
|
if test.assertFn != nil {
|
|
test.assertFn(t, resp.GetWebauthn(), mock.getAssersionReq)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func resetNativeAfterTests(t *testing.T) {
|
|
n := *Native
|
|
t.Cleanup(func() {
|
|
*Native = n
|
|
})
|
|
}
|
|
|
|
type mockNative struct {
|
|
getAssersionReq *getAssertionRequest
|
|
makeCredentialReq *makeCredentialRequest
|
|
}
|
|
|
|
func (m *mockNative) CheckSupport() CheckSupportResult {
|
|
return CheckSupportResult{
|
|
HasCompileSupport: true,
|
|
IsAvailable: true,
|
|
}
|
|
}
|
|
|
|
func (m *mockNative) GetAssertion(origin string, in *getAssertionRequest) (*wanlib.CredentialAssertionResponse, error) {
|
|
m.getAssersionReq = in
|
|
return &wanlib.CredentialAssertionResponse{}, nil
|
|
}
|
|
|
|
func (m *mockNative) MakeCredential(origin string, in *makeCredentialRequest) (*wanlib.CredentialCreationResponse, error) {
|
|
m.makeCredentialReq = in
|
|
return &wanlib.CredentialCreationResponse{}, nil
|
|
}
|