Files
web/third_party/go-webauthn/protocol/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

561 lines
13 KiB
Go
Vendored

package protocol
import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
noneAuthDataBase64 = "pkLSG3xtVeHOI8U5mCjSx0m/am7y/gPMnhDN9O1ttItBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQMAxl6G32ykWaLrv/ouCs5HoGsvONqBtOb7ZmyMs8K8PccnwyyqPzWn/yZuyQmQBguvjYSvH6gDBlFG65quUDCSlAQIDJiABIVggyJGP+ra/u/eVjqN4OeYXUShRWxrEeC6Sb5/bZmJ9q8MiWCCHIkRdg5oRb1RHoFVYUpogcjlObCKFsV1ls1T+uUc6rA=="
attAuthDataBase64 = "lWkIjx7O4yMpVANdvRDXyuORMFonUbVZu4/Xy7IpvdRBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIniszxcGnhupdPFOHJIm6dscrWCC2h8xHicBMu91THD0kdOdB0QQtkaEn+6KfsfT1o3NmmFT8YfXrG734WfVSmlAQIDJiABIVggyoHHeiUw5aSbt8/GsL9zaqZGRzV26A4y3CnCGUhVXu4iWCBMnc8za5xgPzIygngAv9W+vZTMGJwwZcM4sjiqkcb/1g=="
)
func TestAuthenticatorFlags_UserPresent(t *testing.T) {
testCases := []struct {
name string
flag AuthenticatorFlags
expected bool
}{
{
"Present",
AuthenticatorFlags(0x01),
true,
},
{
"Missing",
AuthenticatorFlags(0x10),
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.flag.UserPresent())
})
}
}
func TestAuthenticatorFlags_UserVerified(t *testing.T) {
testCases := []struct {
name string
flag AuthenticatorFlags
expected bool
}{
{
"Present",
AuthenticatorFlags(0x04),
true,
},
{
"Missing",
AuthenticatorFlags(0x02),
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.flag.UserVerified())
})
}
}
func TestAuthenticatorFlags_HasAttestedCredentialData(t *testing.T) {
testCases := []struct {
name string
flag AuthenticatorFlags
expected bool
}{
{
"Present",
AuthenticatorFlags(0x40),
true,
},
{
"Missing",
AuthenticatorFlags(0x01),
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.flag.HasAttestedCredentialData())
})
}
}
func TestAuthenticatorFlags_HasExtensions(t *testing.T) {
testCases := []struct {
name string
flag AuthenticatorFlags
expected bool
}{
{
"Present",
AuthenticatorFlags(0x80),
true,
},
{
"Missing",
AuthenticatorFlags(0x01),
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.flag.HasExtensions())
})
}
}
func TestAuthenticatorData_Unmarshal(t *testing.T) {
type fields struct {
RPIDHash []byte
Flags AuthenticatorFlags
Counter uint32
AttData AttestedCredentialData
ExtData []byte
}
type args struct {
rawAuthData []byte
}
noneAuthData, _ := base64.StdEncoding.DecodeString(noneAuthDataBase64)
attAuthData, _ := base64.StdEncoding.DecodeString(attAuthDataBase64)
// Empty data.
badAuthData1 := []byte{}
// Attested credential data missing.
badAuthData2 := make([]byte, minAttestedAuthLength-1)
copy(badAuthData2, attAuthData)
// Flags not set but data exists.
badAuthData3 := make([]byte, len(attAuthData))
copy(badAuthData3, attAuthData)
badAuthData3[32] &= 0b0011_1111
// Extensions data missing.
badAuthData4 := make([]byte, len(attAuthData))
copy(badAuthData4, attAuthData)
badAuthData4[32] |= 0b1000_0000
// Leftover bytes.
badAuthData5 := make([]byte, len(attAuthData)) //nolint:prealloc
copy(badAuthData5, attAuthData)
badAuthData5 = append(badAuthData5, []byte("Hello World")...)
testCases := []struct {
name string
fields fields
args args
err string
errType string
errDetails string
errInfo string
}{
{
name: "NoneMarshallSuccessfully",
fields: fields{},
args: args{
noneAuthData,
},
},
{
name: "AttDataMarshallSuccessfully",
fields: fields{},
args: args{
attAuthData,
},
},
{
name: "AuthenticatorDataTooShort",
fields: fields{},
args: args{
badAuthData1,
},
err: "Authenticator data length too short",
errType: "invalid_request",
errDetails: "Authenticator data length too short",
errInfo: fmt.Sprintf("Expected data greater than %d bytes. Got %d bytes", minAuthDataLength, len(badAuthData1)),
},
{
name: "AttestedCredentialMissing",
fields: fields{},
args: args{
badAuthData2,
},
err: "Attested credential flag set but data is missing",
errType: "invalid_request",
errDetails: "Attested credential flag set but data is missing",
errInfo: "",
},
{
name: "AttestedCredentialMissing",
fields: fields{},
args: args{
badAuthData3,
},
err: "Attested credential flag not set",
errType: "invalid_request",
errDetails: "Attested credential flag not set",
errInfo: "",
},
{
name: "ExtensionsDataMissing",
fields: fields{},
args: args{
badAuthData4,
},
err: "Extensions flag set but extensions data is missing",
errType: "invalid_request",
errDetails: "Extensions flag set but extensions data is missing",
errInfo: "",
},
{
name: "LeftoverBytes",
fields: fields{},
args: args{
badAuthData5,
},
err: "Leftover bytes decoding AuthenticatorData",
errType: "invalid_request",
errDetails: "Leftover bytes decoding AuthenticatorData",
errInfo: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &AuthenticatorData{
RPIDHash: tc.fields.RPIDHash,
Flags: tc.fields.Flags,
Counter: tc.fields.Counter,
AttData: tc.fields.AttData,
ExtData: tc.fields.ExtData,
}
err := a.Unmarshal(tc.args.rawAuthData)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
AssertIsProtocolError(t, err, tc.errType, tc.errDetails, tc.errInfo)
} else {
assert.NoError(t, err)
}
})
}
}
func TestAuthenticatorData_unmarshalAttestedData(t *testing.T) {
type fields struct {
RPIDHash []byte
Flags AuthenticatorFlags
Counter uint32
AttData AttestedCredentialData
ExtData []byte
}
type args struct {
rawAuthData []byte
}
noneAuthData, _ := base64.StdEncoding.DecodeString(noneAuthDataBase64)
attAuthData, _ := base64.StdEncoding.DecodeString(attAuthDataBase64)
// Data length too short.
badAuthData1 := make([]byte, len(attAuthData))
copy(badAuthData1, attAuthData)
binary.BigEndian.PutUint16(badAuthData1[53:], 256)
// ID length too long.
badAuthData2 := make([]byte, len(attAuthData)+maxCredentialIDLength+1)
copy(badAuthData2, attAuthData)
binary.BigEndian.PutUint16(badAuthData2[53:], maxCredentialIDLength+1)
// Malformed public key.
badAuthData3 := make([]byte, 119) //nolint:prealloc
copy(badAuthData3, attAuthData[:119])
badData, _ := hex.DecodeString("83FF20030102")
badAuthData3 = append(badAuthData3, badData...)
testCases := []struct {
name string
fields fields
args args
err string
errType string
errDetails string
errInfo string
}{
{
name: "None Marshall Successfully",
fields: fields{},
args: args{
noneAuthData,
},
},
{
name: "Att Data Marshall Successfully",
fields: fields{},
args: args{
attAuthData,
},
},
{
name: "Data length too short",
fields: fields{},
args: args{
badAuthData1,
},
err: "Authenticator attestation data length too short",
errType: "invalid_request",
errDetails: "Authenticator attestation data length too short",
errInfo: "",
},
{
name: "ID length too long",
fields: fields{},
args: args{
badAuthData2,
},
err: "Authenticator attestation data credential id length too long",
errType: "invalid_request",
errDetails: "Authenticator attestation data credential id length too long",
errInfo: "",
},
{
name: "Could not unmarshal Credential Public Key",
fields: fields{},
args: args{
badAuthData3,
},
err: "Could not unmarshal Credential Public Key: cbor: unexpected \"break\" code",
errType: "invalid_request",
errDetails: "Could not unmarshal Credential Public Key: cbor: unexpected \"break\" code",
errInfo: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := &AuthenticatorData{
RPIDHash: tc.fields.RPIDHash,
Flags: tc.fields.Flags,
Counter: tc.fields.Counter,
AttData: tc.fields.AttData,
ExtData: tc.fields.ExtData,
}
err := actual.unmarshalAttestedData(tc.args.rawAuthData)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
AssertIsProtocolError(t, err, tc.errType, tc.errDetails, tc.errInfo)
} else {
assert.NoError(t, err)
}
})
}
}
func TestAuthenticatorFlags_HasBackupEligible(t *testing.T) {
testCases := []struct {
name string
flag AuthenticatorFlags
expected bool
}{
{
name: "Present",
flag: FlagBackupEligible,
expected: true,
},
{
name: "PresentWithOtherFlags",
flag: FlagBackupEligible | FlagUserPresent,
expected: true,
},
{
name: "Missing",
flag: FlagUserPresent,
expected: false,
},
{
name: "Zero",
flag: 0,
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.flag.HasBackupEligible())
})
}
}
func TestAuthenticatorFlags_HasBackupState(t *testing.T) {
testCases := []struct {
name string
flag AuthenticatorFlags
expected bool
}{
{
name: "Present",
flag: FlagBackupState,
expected: true,
},
{
name: "PresentWithOtherFlags",
flag: FlagBackupState | FlagBackupEligible,
expected: true,
},
{
name: "Missing",
flag: FlagUserPresent,
expected: false,
},
{
name: "Zero",
flag: 0,
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.flag.HasBackupState())
})
}
}
func TestResidentKeyRequired(t *testing.T) {
result := ResidentKeyRequired()
require.NotNil(t, result)
assert.True(t, *result)
}
func TestResidentKeyNotRequired(t *testing.T) {
result := ResidentKeyNotRequired()
require.NotNil(t, result)
assert.False(t, *result)
}
func TestAuthenticatorData_Verify(t *testing.T) {
type fields struct {
RPIDHash []byte
Flags AuthenticatorFlags
Counter uint32
AttData AttestedCredentialData
ExtData []byte
}
type args struct {
rpIdHash []byte
userVerificationRequired bool
userPresenceRequired bool
}
testCases := []struct {
name string
fields fields
args args
err string
errType string
errDetails string
errInfo string
}{
{
name: "Success",
fields: fields{
RPIDHash: []byte{1, 2, 3},
Flags: AuthenticatorFlags(0x05),
},
args: args{
rpIdHash: []byte{1, 2, 3},
},
err: "",
},
{
name: "RP hash mismatch",
fields: fields{
RPIDHash: []byte{0xff},
},
args: args{
rpIdHash: []byte{0xaa},
},
err: "Error validating the authenticator response",
errType: "verification_error",
errDetails: "Error validating the authenticator response",
errInfo: "RP Hash mismatch. Expected ff and Received aa",
},
{
name: "UP flag not set",
fields: fields{
RPIDHash: []byte{1, 2, 3},
Flags: AuthenticatorFlags(0x04),
},
args: args{
rpIdHash: []byte{1, 2, 3},
userPresenceRequired: true,
},
err: "Error validating the authenticator response",
errType: "verification_error",
errDetails: "Error validating the authenticator response",
errInfo: "User presence required but flag not set by authenticator",
},
{
name: "User verification required",
fields: fields{
RPIDHash: []byte{1, 2, 3},
Flags: AuthenticatorFlags(0x01),
},
args: args{
rpIdHash: []byte{1, 2, 3},
userVerificationRequired: true,
userPresenceRequired: true,
},
err: "Error validating the authenticator response",
errType: "verification_error",
errDetails: "Error validating the authenticator response",
errInfo: "User verification required but flag not set by authenticator",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &AuthenticatorData{
RPIDHash: tc.fields.RPIDHash,
Flags: tc.fields.Flags,
Counter: tc.fields.Counter,
AttData: tc.fields.AttData,
ExtData: tc.fields.ExtData,
}
err := a.Verify(tc.args.rpIdHash, nil, tc.args.userVerificationRequired, tc.args.userPresenceRequired)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
AssertIsProtocolError(t, err, tc.errType, tc.errDetails, tc.errInfo)
} else {
assert.NoError(t, err)
}
})
}
}