This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user