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