This commit is contained in:
+314
@@ -0,0 +1,314 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol/webauthncose"
|
||||
)
|
||||
|
||||
func TestPublicKeyCredentialRequestOptions_GetAllowedCredentialIDs(t *testing.T) {
|
||||
type fields struct {
|
||||
Challenge URLEncodedBase64
|
||||
Timeout int
|
||||
RelyingPartyID string
|
||||
AllowedCredentials []CredentialDescriptor
|
||||
UserVerification UserVerificationRequirement
|
||||
Extensions AuthenticationExtensions
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fields fields
|
||||
expected [][]byte
|
||||
}{
|
||||
{
|
||||
"CorrectCredentialIDs",
|
||||
fields{
|
||||
Challenge: URLEncodedBase64([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
|
||||
Timeout: 60,
|
||||
AllowedCredentials: []CredentialDescriptor{
|
||||
{
|
||||
Type: PublicKeyCredentialType, CredentialID: []byte("1234"), Transport: []AuthenticatorTransport{"usb"},
|
||||
},
|
||||
},
|
||||
RelyingPartyID: "test.org",
|
||||
UserVerification: VerificationPreferred,
|
||||
Extensions: AuthenticationExtensions{},
|
||||
},
|
||||
[][]byte{
|
||||
[]byte("1234"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
options := &PublicKeyCredentialRequestOptions{
|
||||
Challenge: tc.fields.Challenge,
|
||||
Timeout: tc.fields.Timeout,
|
||||
RelyingPartyID: tc.fields.RelyingPartyID,
|
||||
AllowedCredentials: tc.fields.AllowedCredentials,
|
||||
UserVerification: tc.fields.UserVerification,
|
||||
Extensions: tc.fields.Extensions,
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expected, options.GetAllowedCredentialIDs())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCredentialDescriptor_SignalUnknownCredential(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
rpid string
|
||||
have *CredentialDescriptor
|
||||
expected *SignalUnknownCredential
|
||||
expectedJSON string
|
||||
}{
|
||||
{
|
||||
"ShouldHandleStandard",
|
||||
"example.com",
|
||||
&CredentialDescriptor{
|
||||
CredentialID: URLEncodedBase64("1234"),
|
||||
},
|
||||
&SignalUnknownCredential{
|
||||
CredentialID: URLEncodedBase64("1234"),
|
||||
RPID: "example.com",
|
||||
},
|
||||
`{"credentialId":"MTIzNA","rpId":"example.com"}`,
|
||||
},
|
||||
{
|
||||
"ShouldHandleNoID",
|
||||
"example.com",
|
||||
&CredentialDescriptor{},
|
||||
&SignalUnknownCredential{
|
||||
RPID: "example.com",
|
||||
},
|
||||
`{"credentialId":null,"rpId":"example.com"}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := tc.have.SignalUnknownCredential(tc.rpid)
|
||||
|
||||
assert.Equal(t, tc.expected, actual)
|
||||
|
||||
data, err := json.Marshal(actual)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.expectedJSON, string(data))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCredentialParameter_MsgpRoundTrip(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
original CredentialParameter
|
||||
}{
|
||||
{"BothFieldsSet", CredentialParameter{Type: PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}},
|
||||
{"RS256", CredentialParameter{Type: PublicKeyCredentialType, Algorithm: webauthncose.AlgRS256}},
|
||||
{"Ed25519", CredentialParameter{Type: PublicKeyCredentialType, Algorithm: webauthncose.AlgEdDSA}},
|
||||
{"TypeOnly", CredentialParameter{Type: PublicKeyCredentialType}},
|
||||
{"AlgorithmOnly", CredentialParameter{Algorithm: webauthncose.AlgES256}},
|
||||
{"BothOmitted", CredentialParameter{}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data, err := tc.original.MarshalMsg(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var decoded CredentialParameter
|
||||
|
||||
left, err := decoded.UnmarshalMsg(data)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, left)
|
||||
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 CredentialParameter
|
||||
|
||||
require.NoError(t, msgp.Decode(&buf, &streamDecoded))
|
||||
assert.Equal(t, tc.original, streamDecoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCredentialParameter_MsgpOmitEmpty(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value CredentialParameter
|
||||
wantLen int
|
||||
}{
|
||||
{"BothPresent", CredentialParameter{Type: PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}, 2},
|
||||
{"TypeOnly", CredentialParameter{Type: PublicKeyCredentialType}, 1},
|
||||
{"AlgorithmOnly", CredentialParameter{Algorithm: webauthncose.AlgES256}, 1},
|
||||
{"BothOmitted", CredentialParameter{}, 0},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data, err := tc.value.MarshalMsg(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
size, _, err := msgp.ReadMapHeaderBytes(data)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint32(tc.wantLen), size)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCredentialParameter_MsgpUnmarshalSkipsUnknownKeys(t *testing.T) {
|
||||
t.Run("AlongsideKnown", func(t *testing.T) {
|
||||
original := CredentialParameter{Type: PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}
|
||||
|
||||
data, err := original.MarshalMsg(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
size, rest, err := msgp.ReadMapHeaderBytes(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
spliced := msgp.AppendMapHeader(nil, size+1)
|
||||
spliced = msgp.AppendString(spliced, "xyz")
|
||||
spliced = msgp.AppendBool(spliced, true)
|
||||
spliced = append(spliced, rest...)
|
||||
|
||||
var decoded CredentialParameter
|
||||
|
||||
left, err := decoded.UnmarshalMsg(spliced)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, left)
|
||||
assert.Equal(t, original, decoded)
|
||||
})
|
||||
|
||||
t.Run("OnlyUnknown", func(t *testing.T) {
|
||||
tiny := []byte{0x81, 0xa3, 'x', 'y', 'z', 0xc3}
|
||||
|
||||
var decoded CredentialParameter
|
||||
|
||||
left, err := decoded.UnmarshalMsg(tiny)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, left)
|
||||
assert.Equal(t, CredentialParameter{}, decoded)
|
||||
|
||||
var streamDecoded CredentialParameter
|
||||
|
||||
require.NoError(t, msgp.Decode(bytes.NewReader(tiny), &streamDecoded))
|
||||
assert.Equal(t, CredentialParameter{}, streamDecoded)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentialParameter_DecodeMsgInvalidTypes(t *testing.T) {
|
||||
t.Run("NotAMap", func(t *testing.T) {
|
||||
var c CredentialParameter
|
||||
|
||||
_, err := c.UnmarshalMsg(msgpString("not a map"))
|
||||
require.Error(t, err)
|
||||
|
||||
var c2 CredentialParameter
|
||||
|
||||
require.Error(t, msgp.Decode(bytes.NewReader(msgpString("not a map")), &c2))
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantSub string
|
||||
}{
|
||||
{"TypeAsInt", msgpOneFieldMap("typ", msgpInt64(42)), "Type"},
|
||||
{"TypeAsBool", msgpOneFieldMap("typ", msgpBool(true)), "Type"},
|
||||
{"AlgorithmAsString", msgpOneFieldMap("alg", msgpString("not an int")), "Algorithm"},
|
||||
{"AlgorithmAsBool", msgpOneFieldMap("alg", msgpBool(true)), "Algorithm"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var c CredentialParameter
|
||||
|
||||
_, err := c.UnmarshalMsg(tc.data)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.wantSub)
|
||||
|
||||
var c2 CredentialParameter
|
||||
|
||||
streamErr := msgp.Decode(bytes.NewReader(tc.data), &c2)
|
||||
require.Error(t, streamErr)
|
||||
assert.Contains(t, streamErr.Error(), tc.wantSub)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCredentialParameter_MsgpEncodeErrorPaths(t *testing.T) {
|
||||
v := CredentialParameter{Type: PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}
|
||||
|
||||
data, err := v.MarshalMsg(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
exerciseEncodeMsgErrorPaths(t, v, data)
|
||||
}
|
||||
|
||||
type failingWriter struct {
|
||||
limit int
|
||||
count int
|
||||
}
|
||||
|
||||
func (w *failingWriter) Write(p []byte) (int, error) {
|
||||
remaining := w.limit - w.count
|
||||
if remaining <= 0 {
|
||||
return 0, errors.New("failingWriter: exhausted")
|
||||
}
|
||||
|
||||
if len(p) > remaining {
|
||||
w.count = w.limit
|
||||
|
||||
return remaining, errors.New("failingWriter: exhausted")
|
||||
}
|
||||
|
||||
w.count += len(p)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func exerciseEncodeMsgErrorPaths(t *testing.T, enc msgp.Encodable, marshalled []byte) {
|
||||
t.Helper()
|
||||
|
||||
for limit := 0; limit <= len(marshalled); limit++ {
|
||||
fw := &failingWriter{limit: limit}
|
||||
wr := msgp.NewWriterSize(fw, 18)
|
||||
|
||||
err := enc.EncodeMsg(wr)
|
||||
if err == nil {
|
||||
err = wr.Flush()
|
||||
}
|
||||
|
||||
if limit < len(marshalled) {
|
||||
require.Errorf(t, err, "EncodeMsg should fail when underlying writer errors after %d bytes", limit)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func msgpOneFieldMap(key string, value []byte) []byte {
|
||||
b := msgp.AppendMapHeader(nil, 1)
|
||||
b = msgp.AppendString(b, key)
|
||||
|
||||
return append(b, value...)
|
||||
}
|
||||
|
||||
func msgpBool(v bool) []byte { return msgp.AppendBool(nil, v) }
|
||||
func msgpInt64(v int64) []byte { return msgp.AppendInt64(nil, v) }
|
||||
func msgpString(v string) []byte { return msgp.AppendString(nil, v) }
|
||||
Reference in New Issue
Block a user