|
8 | 8 | "fmt" |
9 | 9 | "io" |
10 | 10 | "net/http" |
| 11 | + "strings" |
11 | 12 | "testing" |
12 | 13 | "time" |
13 | 14 |
|
@@ -789,6 +790,35 @@ func TestCreateCredential_Full(t *testing.T) { |
789 | 790 | } |
790 | 791 | } |
791 | 792 |
|
| 793 | +// TestCreateCredential_RejectsBackupStateWithoutBackupEligibility covers §7.1 step 18, which requires the BS bit to |
| 794 | +// be unset when the BE bit is unset. The equivalent assertion step is covered by the login validation. |
| 795 | +func TestCreateCredential_RejectsBackupStateWithoutBackupEligibility(t *testing.T) { |
| 796 | + body, challenge, _ := testRegistrationSpecVectorNoneES256Flags(t, protocol.FlagUserPresent|protocol.FlagBackupState|protocol.FlagAttestedCredentialData) |
| 797 | + |
| 798 | + parsedResponse, err := protocol.ParseCredentialCreationResponseBytes(body) |
| 799 | + require.NoError(t, err) |
| 800 | + |
| 801 | + userID := []byte(testUserID) |
| 802 | + |
| 803 | + w := &WebAuthn{ |
| 804 | + Config: &Config{ |
| 805 | + RPID: "example.org", |
| 806 | + RPOrigins: []string{"https://example.org"}, |
| 807 | + }, |
| 808 | + } |
| 809 | + |
| 810 | + session := SessionData{ |
| 811 | + Challenge: challenge, |
| 812 | + UserID: userID, |
| 813 | + CredParams: []protocol.CredentialParameter{{Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}}, |
| 814 | + } |
| 815 | + |
| 816 | + credential, err := w.CreateCredential(&defaultUser{id: userID}, session, parsedResponse) |
| 817 | + |
| 818 | + assert.Nil(t, credential) |
| 819 | + assert.EqualError(t, err, "Backup State Flag is true but Backup Eligible flag is false which is invalid") |
| 820 | +} |
| 821 | + |
792 | 822 | func TestFinishRegistration_Success(t *testing.T) { |
793 | 823 | body, challenge, credentialID := testRegistrationSpecVectorNoneES256(t) |
794 | 824 | credParams := []protocol.CredentialParameter{{Type: protocol.PublicKeyCredentialType, Algorithm: webauthncose.AlgES256}} |
@@ -975,20 +1005,38 @@ func TestValidateFilteredCredential(t *testing.T) { |
975 | 1005 | func testRegistrationSpecVectorNoneES256(t *testing.T) (body []byte, challenge string, credentialID []byte) { |
976 | 1006 | t.Helper() |
977 | 1007 |
|
| 1008 | + // The flags of the vector as published: UP, BE, BS, and AT. |
| 1009 | + return testRegistrationSpecVectorNoneES256Flags(t, protocol.FlagUserPresent|protocol.FlagBackupEligible|protocol.FlagBackupState|protocol.FlagAttestedCredentialData) |
| 1010 | +} |
| 1011 | + |
| 1012 | +// testRegistrationSpecVectorNoneES256Flags returns the spec test vector data for NoneES256 registration with the |
| 1013 | +// authenticator data flags byte replaced by flags. The none attestation statement format conveys no signature over |
| 1014 | +// the authenticator data, so the flags can be varied without invalidating the vector. |
| 1015 | +func testRegistrationSpecVectorNoneES256Flags(t *testing.T, flags protocol.AuthenticatorFlags) (body []byte, challenge string, credentialID []byte) { |
| 1016 | + t.Helper() |
| 1017 | + |
978 | 1018 | const ( |
979 | 1019 | attestationObjectHex = "a363666d74646e6f6e656761747453746d74a068617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b559000000008446ccb9ab1db374750b2367ff6f3a1f0020f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4a5010203262001215820afefa16f97ca9b2d23eb86ccb64098d20db90856062eb249c33a9b672f26df61225820930a56b87a2fca66334b03458abf879717c12cc68ed73290af2e2664796b9220" |
980 | 1020 | clientDataJSONHex = "7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a22414d4d507434557878475453746e63647134313759447742466938767049612d7077386f4f755657345441222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a20426b5165446a646354427258426941774a544c453551227d" |
981 | 1021 | credentialIDHex = "f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4" //nolint:gosec |
982 | 1022 | challengeHex = "00c30fb78531c464d2b6771dab8d7b603c01162f2fa486bea70f283ae556e130" |
| 1023 | + |
| 1024 | + // The flags byte of the authenticator data immediately follows the RP ID hash, which is what makes this |
| 1025 | + // prefix sufficient to locate it unambiguously within the attestation object. |
| 1026 | + rpIDHashHex = "bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b5" |
| 1027 | + vectorFlagHex = "59" |
983 | 1028 | ) |
984 | 1029 |
|
985 | 1030 | credentialID, err := hex.DecodeString(credentialIDHex) |
986 | 1031 | require.NoError(t, err) |
987 | 1032 |
|
988 | 1033 | challenge = base64.RawURLEncoding.EncodeToString(testRegDecodeHex(t, challengeHex)) |
989 | 1034 |
|
| 1035 | + attestationObjectFlagsHex := strings.Replace(attestationObjectHex, rpIDHashHex+vectorFlagHex, rpIDHashHex+hex.EncodeToString([]byte{byte(flags)}), 1) |
| 1036 | + require.Contains(t, attestationObjectFlagsHex, rpIDHashHex+hex.EncodeToString([]byte{byte(flags)})) |
| 1037 | + |
990 | 1038 | id := base64.RawURLEncoding.EncodeToString(credentialID) |
991 | | - attObj := base64.RawURLEncoding.EncodeToString(testRegDecodeHex(t, attestationObjectHex)) |
| 1039 | + attObj := base64.RawURLEncoding.EncodeToString(testRegDecodeHex(t, attestationObjectFlagsHex)) |
992 | 1040 | cdj := base64.RawURLEncoding.EncodeToString(testRegDecodeHex(t, clientDataJSONHex)) |
993 | 1041 |
|
994 | 1042 | response := map[string]any{ |
|
0 commit comments