Files
web/third_party/go-webauthn/webauthn/authenticator_test.go
T
gamertan bfe6cfd29e
verify / verify (push) Successful in 3m40s
auth: publish passkey foundations preview
2026-08-21 17:33:00 -04:00

278 lines
5.8 KiB
Go
Vendored

package webauthn
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinylib/msgp/msgp"
"github.com/go-webauthn/webauthn/protocol"
)
func TestAuthenticator_UpdateCounter(t *testing.T) {
type fields struct {
AAGUID []byte
SignCount uint32
CloneWarning bool
}
type args struct {
authDataCount uint32
}
testCases := []struct {
name string
fields fields
args args
expected bool
}{
{
"IncreasedCounter",
fields{
AAGUID: make([]byte, 16),
SignCount: 1,
CloneWarning: false,
},
args{
authDataCount: 2,
},
false,
},
{
"UnchangedCounter",
fields{
AAGUID: make([]byte, 16),
SignCount: 1,
CloneWarning: false,
},
args{
authDataCount: 1,
},
true,
},
{
"DecreasedCounter",
fields{
AAGUID: make([]byte, 16),
SignCount: 2,
CloneWarning: false,
},
args{
authDataCount: 1,
},
true,
},
{
"ZeroCounter",
fields{
AAGUID: make([]byte, 16),
SignCount: 0,
CloneWarning: false,
},
args{
authDataCount: 0,
},
false,
},
{
"CounterReturnedToZero",
fields{
AAGUID: make([]byte, 16),
SignCount: 1,
CloneWarning: false,
},
args{
authDataCount: 0,
},
true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
authenticator := &Authenticator{
AAGUID: tc.fields.AAGUID,
SignCount: tc.fields.SignCount,
CloneWarning: tc.fields.CloneWarning,
}
signCount := authenticator.SignCount
authenticator.UpdateCounter(tc.args.authDataCount)
assert.Equal(t, tc.expected, authenticator.CloneWarning)
if authenticator.CloneWarning {
assert.Equal(t, signCount, authenticator.SignCount)
} else {
assert.Equal(t, tc.args.authDataCount, authenticator.SignCount)
}
})
}
}
func TestSelectAuthenticator(t *testing.T) {
type args struct {
att string
rrk *bool
uv string
}
testCases := []struct {
name string
args args
expected protocol.AuthenticatorSelection
}{
{"GenerateCorrectAuthenticatorSelection",
args{
att: "platform",
rrk: protocol.ResidentKeyNotRequired(),
uv: "preferred",
},
protocol.AuthenticatorSelection{
AuthenticatorAttachment: protocol.Platform,
RequireResidentKey: protocol.ResidentKeyNotRequired(),
UserVerification: protocol.VerificationPreferred,
},
},
{"GenerateCorrectAuthenticatorSelection",
args{
att: "cross-platform",
rrk: protocol.ResidentKeyRequired(),
uv: "required",
},
protocol.AuthenticatorSelection{
AuthenticatorAttachment: protocol.CrossPlatform,
RequireResidentKey: protocol.ResidentKeyRequired(),
UserVerification: protocol.VerificationRequired,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, SelectAuthenticator(tc.args.att, tc.args.rrk, tc.args.uv))
})
}
}
func TestAuthenticator_MsgpRoundTrip(t *testing.T) {
testCases := []struct {
name string
original Authenticator
}{
{
"FullyPopulated",
Authenticator{
AAGUID: bytes.Repeat([]byte{0xAB}, 16),
SignCount: 1234,
CloneWarning: true,
Attachment: protocol.Platform,
},
},
{
"CrossPlatformNoCloneWarning",
Authenticator{
AAGUID: bytes.Repeat([]byte{0x01}, 16),
SignCount: 1,
CloneWarning: false,
Attachment: protocol.CrossPlatform,
},
},
{
"Zero",
Authenticator{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
data, err := tc.original.MarshalMsg(nil)
require.NoError(t, err)
var decoded Authenticator
left, err := decoded.UnmarshalMsg(data)
require.NoError(t, err)
assert.Empty(t, left, "UnmarshalMsg should consume all bytes")
assert.Equal(t, tc.original, decoded)
assert.LessOrEqual(t, len(data), tc.original.Msgsize())
var buf bytes.Buffer
require.NoError(t, msgp.Encode(&buf, &tc.original))
var streamDecoded Authenticator
require.NoError(t, msgp.Decode(&buf, &streamDecoded))
assert.Equal(t, tc.original, streamDecoded)
})
}
t.Run("UnmarshalSkipsUnknownKeys", func(t *testing.T) {
tiny := []byte{0x81, 0xa3, 'x', 'y', 'z', 0xc3}
var decoded Authenticator
left, err := decoded.UnmarshalMsg(tiny)
require.NoError(t, err)
assert.Empty(t, left)
assert.Equal(t, Authenticator{}, decoded)
})
}
func TestAuthenticator_MsgpEncodeErrorPaths(t *testing.T) {
v := Authenticator{
AAGUID: bytes.Repeat([]byte{0xAB}, 16),
SignCount: 1234,
CloneWarning: true,
Attachment: protocol.Platform,
}
data, err := v.MarshalMsg(nil)
require.NoError(t, err)
exerciseEncodeMsgErrorPaths(t, &v, data)
}
func TestAuthenticator_DecodeMsgInvalidTypes(t *testing.T) {
t.Run("NotAMap", func(t *testing.T) {
var a Authenticator
_, err := a.UnmarshalMsg(msgpString("not a map"))
require.Error(t, err)
var a2 Authenticator
require.Error(t, msgp.Decode(bytes.NewReader(msgpString("not a map")), &a2))
})
testCases := []struct {
name string
data []byte
wantSub string
}{
{"AAGUIDAsBool", msgpOneFieldMap("aaguid", msgpBool(true)), "AAGUID"},
{"SignCountAsString", msgpOneFieldMap("sc", msgpString("x")), "SignCount"},
{"CloneWarningAsInt", msgpOneFieldMap("cw", msgpInt64(42)), "CloneWarning"},
{"AttachmentAsBool", msgpOneFieldMap("aa", msgpBool(true)), "Attachment"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var a Authenticator
_, err := a.UnmarshalMsg(tc.data)
require.Error(t, err)
assert.Contains(t, err.Error(), tc.wantSub)
var a2 Authenticator
streamErr := msgp.Decode(bytes.NewReader(tc.data), &a2)
require.Error(t, streamErr)
assert.Contains(t, streamErr.Error(), tc.wantSub)
})
}
}