561 lines
13 KiB
Go
Vendored
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)
|
|
}
|
|
})
|
|
}
|
|
}
|