Files
common/webauthn/credential_test.go

368 lines
14 KiB
Go
Raw Permalink Normal View History

2025-10-10 10:17:22 -04:00
package webauthn
import (
"bytes"
"encoding/base64"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/sonr-io/common/webauthn/webauthncbor"
"github.com/sonr-io/common/webauthn/webauthncose"
)
func TestParseCredentialCreationResponse(t *testing.T) {
type args struct {
responseName string
}
byteID, _ := base64.RawURLEncoding.DecodeString(
"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
)
byteAuthData, _ := base64.RawURLEncoding.DecodeString(
"dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
)
byteRPIDHash, _ := base64.RawURLEncoding.DecodeString(
"dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvA",
)
byteCredentialPubKey, _ := base64.RawURLEncoding.DecodeString(
"pSJYIMfCKfxl2SvnqJIiHQysHmpmITNgtCkQ5ESExSRjqrhXAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNc",
)
byteAttObject, _ := base64.RawURLEncoding.DecodeString(
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
)
byteClientDataJSON, _ := base64.RawURLEncoding.DecodeString(
"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ",
)
testCases := []struct {
name string
args args
expected *ParsedCredentialCreationData
errString string
errType string
errDetails string
errInfo string
}{
{
name: "ShouldParseCredentialRequest",
args: args{
responseName: "success",
},
expected: &ParsedCredentialCreationData{
ParsedPublicKeyCredential: ParsedPublicKeyCredential{
ParsedCredential: ParsedCredential{
ID: "6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
Type: string(PublicKeyCredentialType),
},
RawID: byteID,
ClientExtensionResults: AuthenticationExtensionsClientOutputs{
"appid": true,
},
AuthenticatorAttachment: Platform,
},
Response: ParsedAttestationResponse{
CollectedClientData: CollectedClientData{
Type: CeremonyType("webauthn.create"),
Challenge: "W8GzFU8pGjhoRbWrLDlamAfq_y4S1CZG1VuoeRLARrE",
Origin: "https://webauthn.io",
},
AttestationObject: AttestationObject{
Format: "none",
RawAuthData: byteAuthData,
AuthData: AuthenticatorData{
RPIDHash: byteRPIDHash,
Counter: 0,
Flags: 0x041,
AttData: AttestedCredentialData{
AAGUID: make([]byte, 16),
CredentialID: byteID,
CredentialPublicKey: byteCredentialPubKey,
},
},
},
Transports: []AuthenticatorTransport{USB, NFC, "fake"},
},
Raw: CredentialCreationResponse{
PublicKeyCredential: PublicKeyCredential{
Credential: Credential{
Type: string(PublicKeyCredentialType),
ID: "6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
},
RawID: byteID,
ClientExtensionResults: AuthenticationExtensionsClientOutputs{
"appid": true,
},
AuthenticatorAttachment: "platform",
},
AttestationResponse: AuthenticatorAttestationResponse{
AuthenticatorResponse: AuthenticatorResponse{
ClientDataJSON: byteClientDataJSON,
},
AttestationObject: byteAttObject,
Transports: []string{"usb", "nfc", "fake"},
},
},
},
errString: "",
},
{
name: "ShouldHandleTrailingData",
args: args{
responseName: "trailingData",
},
expected: nil,
errString: "Parse error for Registration",
errType: "invalid_request",
errDetails: "Parse error for Registration",
errInfo: "The body contains trailing data",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body := io.NopCloser(
bytes.NewReader([]byte(testCredentialRequestResponses[tc.args.responseName])),
)
actual, err := ParseCredentialCreationResponseBody(body)
if tc.errString != "" {
assert.EqualError(t, err, tc.errString)
AssertIsProtocolError(t, err, tc.errType, tc.errDetails, tc.errInfo)
return
}
assert.Equal(t, tc.expected.ClientExtensionResults, actual.ClientExtensionResults)
assert.Equal(t, tc.expected.ID, actual.ID)
assert.Equal(t, tc.expected.Type, actual.Type)
assert.Equal(t, tc.expected.ParsedCredential, actual.ParsedCredential)
assert.Equal(t, tc.expected.ParsedPublicKeyCredential, actual.ParsedPublicKeyCredential)
assert.Equal(t, tc.expected.ParsedPublicKeyCredential, actual.ParsedPublicKeyCredential)
assert.Equal(t, tc.expected.Raw, actual.Raw)
assert.Equal(t, tc.expected.RawID, actual.RawID)
assert.Equal(t, tc.expected.Response.Transports, actual.Response.Transports)
assert.Equal(
t,
tc.expected.Response.CollectedClientData,
actual.Response.CollectedClientData,
)
assert.Equal(
t,
tc.expected.Response.AttestationObject.AuthData.AttData.CredentialID,
actual.Response.AttestationObject.AuthData.AttData.CredentialID,
)
assert.Equal(
t,
tc.expected.Response.AttestationObject.Format,
actual.Response.AttestationObject.Format,
)
// Unmarshall CredentialPublicKey
var pkExpected, pkActual any
pkBytesExpected := tc.expected.Response.AttestationObject.AuthData.AttData.CredentialPublicKey
assert.NoError(t, webauthncbor.Unmarshal(pkBytesExpected, &pkExpected))
pkBytesActual := actual.Response.AttestationObject.AuthData.AttData.CredentialPublicKey
assert.NoError(t, webauthncbor.Unmarshal(pkBytesActual, &pkActual))
assert.Equal(t, pkExpected, pkActual)
})
}
}
func TestParsedCredentialCreationData_Verify(t *testing.T) {
byteID, _ := base64.RawURLEncoding.DecodeString(
"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
)
byteChallenge, _ := base64.RawURLEncoding.DecodeString(
"W8GzFU8pGjhoRbWrLDlamAfq_y4S1CZG1VuoeRLARrE",
)
byteAuthData, _ := base64.RawURLEncoding.DecodeString(
"dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
)
byteRPIDHash, _ := base64.RawURLEncoding.DecodeString(
"dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvA",
)
byteCredentialPubKey, _ := base64.RawURLEncoding.DecodeString(
"pSJYIMfCKfxl2SvnqJIiHQysHmpmITNgtCkQ5ESExSRjqrhXAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNc",
)
byteAttObject, _ := base64.RawURLEncoding.DecodeString(
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
)
byteClientDataJSON, _ := base64.RawURLEncoding.DecodeString(
"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ",
)
type fields struct {
ParsedPublicKeyCredential ParsedPublicKeyCredential
Response ParsedAttestationResponse
Raw CredentialCreationResponse
}
type args struct {
storedChallenge URLEncodedBase64
verifyUser bool
relyingPartyID string
relyingPartyOrigin []string
credParams []CredentialParameter
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "Successful Verification Test",
fields: fields{
ParsedPublicKeyCredential: ParsedPublicKeyCredential{
ParsedCredential: ParsedCredential{
ID: "6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
Type: string(PublicKeyCredentialType),
},
RawID: byteID,
},
Response: ParsedAttestationResponse{
CollectedClientData: CollectedClientData{
Type: CeremonyType("webauthn.create"),
Challenge: "W8GzFU8pGjhoRbWrLDlamAfq_y4S1CZG1VuoeRLARrE",
Origin: "https://webauthn.io",
},
AttestationObject: AttestationObject{
Format: "none",
RawAuthData: byteAuthData,
AuthData: AuthenticatorData{
RPIDHash: byteRPIDHash,
Counter: 0,
Flags: 0x041,
AttData: AttestedCredentialData{
AAGUID: make([]byte, 16),
CredentialID: byteID,
CredentialPublicKey: byteCredentialPubKey,
},
},
},
},
Raw: CredentialCreationResponse{
PublicKeyCredential: PublicKeyCredential{
Credential: Credential{
Type: string(PublicKeyCredentialType),
ID: "6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
},
RawID: byteID,
},
AttestationResponse: AuthenticatorAttestationResponse{
AuthenticatorResponse: AuthenticatorResponse{
ClientDataJSON: byteClientDataJSON,
},
AttestationObject: byteAttObject,
},
},
},
args: args{
storedChallenge: URLEncodedBase64(byteChallenge),
verifyUser: false,
relyingPartyID: `webauthn.io`,
relyingPartyOrigin: []string{`https://webauthn.io`},
credParams: []CredentialParameter{
{Type: "public-key", Algorithm: webauthncose.AlgES256},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pcc := &ParsedCredentialCreationData{
ParsedPublicKeyCredential: tt.fields.ParsedPublicKeyCredential,
Response: tt.fields.Response,
Raw: tt.fields.Raw,
}
if _, err := pcc.Verify(tt.args.storedChallenge.String(), tt.args.verifyUser, false, tt.args.relyingPartyID, tt.args.relyingPartyOrigin, nil, TopOriginIgnoreVerificationMode, nil, tt.args.credParams); (err != nil) != tt.wantErr {
t.Errorf(
"ParsedCredentialCreationData.Verify() error = %+v, wantErr %v",
err,
tt.wantErr,
)
}
})
}
}
var testCredentialRequestResponses = map[string]string{
`success`: `
{
"id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"type":"public-key",
"authenticatorAttachment":"platform",
"clientExtensionResults":{
"appid":true
},
"response":{
"attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ",
"transports":["usb","nfc","fake"]
}
}
`,
`trailingData`: `
{
"id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"type":"public-key",
"authenticatorAttachment":"platform",
"clientExtensionResults":{
"appid":true
},
"response":{
"attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ",
"transports":["usb","nfc","fake"]
}
}
trailing
`,
`successDeprecatedTransports`: `
{
"id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"type":"public-key",
"authenticatorAttachment":"not-valid",
"transports":["usb","nfc","fake"],
"clientExtensionResults":{
"appid":true
},
"response":{
"attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ"
}
}
`,
`successDeprecatedTransportsAndNew`: `
{
"id":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"rawId":"6xrtBhJQW6QU4tOaB4rrHaS2Ks0yDDL_q8jDC16DEjZ-VLVf4kCRkvl2xp2D71sTPYns-exsHQHTy3G-zJRK8g",
"type":"public-key",
"authenticatorAttachment":"cross-platform",
"transports":["usb","nfc","fake"],
"clientExtensionResults":{
"appid":true
},
"response":{
"attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOsa7QYSUFukFOLTmgeK6x2ktirNMgwy_6vIwwtegxI2flS1X-JAkZL5dsadg-9bEz2J7PnsbB0B08txvsyUSvKlAQIDJiABIVggLKF5xS0_BntttUIrm2Z2tgZ4uQDwllbdIfrrBMABCNciWCDHwin8Zdkr56iSIh0MrB5qZiEzYLQpEOREhMUkY6q4Vw",
"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXOEd6RlU4cEdqaG9SYldyTERsYW1BZnFfeTRTMUNaRzFWdW9lUkxBUnJFIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ",
"transports":["usb","nfc"]
}
}
`,
}