Files
gamertan bfe6cfd29e
verify / verify (push) Successful in 3m40s
auth: publish passkey foundations preview
2026-08-21 17:33:00 -04:00

510 lines
20 KiB
Go
Vendored

package protocol
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-webauthn/webauthn/metadata"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)
func Test_VerifyPackedFormat(t *testing.T) {
successAttResponseES256 := attestationTestUnpackResponse(t, packedTestResponseES256["success"]).Response.AttestationObject
successClientDataHashES256 := sha256.Sum256(attestationTestUnpackResponse(t, packedTestResponseES256["success"]).Raw.AttestationResponse.ClientDataJSON)
successAttResponseES512 := attestationTestUnpackResponse(t, packedTestResponseES512["success"]).Response.AttestationObject
successClientDataHashES512 := sha256.Sum256(attestationTestUnpackResponse(t, packedTestResponseES512["success"]).Raw.AttestationResponse.ClientDataJSON)
successAttResponseSolo2 := attestationTestUnpackResponse(t, packedTestResponseSolo2["success"]).Response.AttestationObject
successClientDataHashSolo2 := sha256.Sum256(attestationTestUnpackResponse(t, packedTestResponseSolo2["success"]).Raw.AttestationResponse.ClientDataJSON)
testCases := []struct {
name string
att AttestationObject
clientDataHash []byte
attestationType string
err string
}{
{
name: "ShouldSuccessfullyVerifyES256",
att: successAttResponseES256,
clientDataHash: successClientDataHashES256[:],
attestationType: string(metadata.BasicFull),
},
{
name: "ShouldSuccessfullyVerifyES512SelfAttestation",
att: successAttResponseES512,
clientDataHash: successClientDataHashES512[:],
attestationType: string(metadata.BasicSurrogate),
},
{
name: "ShouldSuccessfullyVerifySolo2",
att: successAttResponseSolo2,
clientDataHash: successClientDataHashSolo2[:],
attestationType: string(metadata.BasicFull),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
attestationType, _, err := attestationFormatValidationHandlerPacked(tc.att, tc.clientDataHash, nil)
if tc.err != "" {
require.EqualError(t, err, tc.err)
} else {
require.NoError(t, err)
}
assert.Equal(t, tc.attestationType, attestationType)
})
}
}
func TestPackedFormat_HandlerErrors(t *testing.T) {
testCases := []struct {
name string
attStatement map[string]any
err string
}{
{
name: "ShouldFailMissingAlg",
attStatement: map[string]any{},
err: "Error retrieving alg value",
},
{
name: "ShouldFailAlgWrongType",
attStatement: map[string]any{stmtAlgorithm: "not-int"},
err: "Error retrieving alg value",
},
{
name: "ShouldFailMissingSig",
attStatement: map[string]any{stmtAlgorithm: int64(-7)},
err: "Error retrieving sig value",
},
{
name: "ShouldFailSigWrongType",
attStatement: map[string]any{stmtAlgorithm: int64(-7), stmtSignature: "not-bytes"},
err: "Error retrieving sig value",
},
{
name: "ShouldReturnECDAANotImplemented",
attStatement: map[string]any{stmtAlgorithm: int64(-7), stmtSignature: []byte("sig"), stmtECDAAKID: []byte("keyid")},
err: "This field is not yet supported by the WebAuthn spec",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
att := AttestationObject{
Format: "packed",
AttStatement: tc.attStatement,
}
_, _, err := attestationFormatValidationHandlerPacked(att, []byte("hash"), nil)
require.EqualError(t, err, tc.err)
})
}
}
func TestPackedFormat_BasicAttestationErrors(t *testing.T) {
testCases := []struct {
name string
x5c []any
alg int64
err string
}{
{
name: "ShouldFailX5CElementNotBytes",
x5c: []any{"not-bytes"},
alg: int64(-7),
err: "Error getting certificate from x5c cert chain",
},
{
name: "ShouldFailX5CInvalidCert",
x5c: []any{[]byte("not-a-cert")},
alg: int64(-7),
err: "Error parsing certificate from ASN.1 data: x509: malformed certificate",
},
{
name: "ShouldFailEmptyX5C",
x5c: []any{},
alg: int64(-7),
err: "Error getting certificate from x5c cert chain",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, _, err := handleBasicAttestation([]byte("sig"), []byte("hash"), []byte("auth"), []byte("aaguid"), tc.alg, tc.x5c, nil)
require.EqualError(t, err, tc.err)
})
}
}
func TestPackedFormat_BasicAttestationSignatureAndTimeErrors(t *testing.T) {
authData := []byte("fake-auth-data")
clientDataHash := []byte("fake-client-hash")
signatureData := append(authData, clientDataHash...) //nolint:gocritic
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
validTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Test"},
OrganizationalUnit: []string{"Authenticator Attestation"},
CommonName: "Test Cert",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
}
validCertDER, err := x509.CreateCertificate(rand.Reader, validTemplate, validTemplate, &key.PublicKey, key)
require.NoError(t, err)
h := sha256.Sum256(signatureData)
validSig, err := key.Sign(rand.Reader, h[:], nil)
require.NoError(t, err)
expiredTemplate := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: validTemplate.Subject,
NotBefore: time.Now().Add(-48 * time.Hour),
NotAfter: time.Now().Add(-24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
}
expiredCertDER, err := x509.CreateCertificate(rand.Reader, expiredTemplate, expiredTemplate, &key.PublicKey, key)
require.NoError(t, err)
futureTemplate := &x509.Certificate{
SerialNumber: big.NewInt(3),
Subject: validTemplate.Subject,
NotBefore: time.Now().Add(24 * time.Hour),
NotAfter: time.Now().Add(48 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
}
futureCertDER, err := x509.CreateCertificate(rand.Reader, futureTemplate, futureTemplate, &key.PublicKey, key)
require.NoError(t, err)
testCases := []struct {
name string
x5c []any
alg int64
sig []byte
err string
}{
{
name: "ShouldFailExpiredCert",
x5c: []any{expiredCertDER},
alg: int64(webauthncose.AlgES256),
sig: validSig,
err: "Cert in chain is either no longer valid or not yet valid",
},
{
name: "ShouldFailFutureCert",
x5c: []any{futureCertDER},
alg: int64(webauthncose.AlgES256),
sig: validSig,
err: "Cert in chain is either no longer valid or not yet valid",
},
{
name: "ShouldFailUnsupportedAlgorithm",
x5c: []any{validCertDER},
alg: int64(0),
sig: validSig,
err: "Unsupported COSE alg: 0",
},
{
name: "ShouldFailInvalidSignature",
x5c: []any{validCertDER},
alg: int64(webauthncose.AlgES256),
sig: []byte("bad-signature"),
err: "Signature validation error: x509: ECDSA verification failure",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, _, err := handleBasicAttestation(tc.sig, clientDataHash, authData, nil, tc.alg, tc.x5c, nil)
require.EqualError(t, err, tc.err)
})
}
}
func TestPackedFormat_SelfAttestationErrors(t *testing.T) {
pcc := attestationTestUnpackResponse(t, packedTestResponseES512["success"])
validPubKey := pcc.Response.AttestationObject.AuthData.AttData.CredentialPublicKey
testCases := []struct {
name string
alg int64
pub []byte
err string
}{
{
name: "ShouldFailInvalidPublicKey",
alg: int64(-7),
pub: []byte("not-cbor"),
err: "Error parsing the public key: Unsupported Public Key Type",
},
{
name: "ShouldFailAlgorithmMismatch",
alg: int64(-7),
pub: validPubKey,
err: "Public key algorithm does not equal att statement algorithm",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, _, err := handleSelfAttestation(tc.alg, tc.pub, []byte("auth"), []byte("hash"), []byte("sig"), nil)
require.EqualError(t, err, tc.err)
})
}
}
func TestVerifyKeyAlgorithm(t *testing.T) {
testCases := []struct {
name string
keyAlg int64
attAlg int64
err string
}{
{
name: "ShouldSucceedWhenMatch",
keyAlg: int64(-7),
attAlg: int64(-7),
},
{
name: "ShouldFailWhenMismatch",
keyAlg: int64(-7),
attAlg: int64(-257),
err: "Public key algorithm does not equal att statement algorithm",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := verifyKeyAlgorithm(tc.keyAlg, tc.attAlg)
if tc.err != "" {
require.EqualError(t, err, tc.err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestPackedFormat_BasicAttestationCertRequirements(t *testing.T) {
authData := []byte("fake-auth-data")
clientDataHash := []byte("fake-client-hash")
signatureData := append(authData, clientDataHash...) //nolint:gocritic
testCases := []struct {
name string
template *x509.Certificate
err string
}{
{
name: "ShouldFailMissingCountry",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Org"},
OrganizationalUnit: []string{"Authenticator Attestation"},
CommonName: "Test",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Country Code is invalid",
},
{
name: "ShouldFailUnassignedCountry",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"ZI"},
Organization: []string{"Test Org"},
OrganizationalUnit: []string{"Authenticator Attestation"},
CommonName: "Test",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Country Code is invalid",
},
{
name: "ShouldFailWrongCaseCountry",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"us"},
Organization: []string{"Test Org"},
OrganizationalUnit: []string{"Authenticator Attestation"},
CommonName: "Test",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Country Code is invalid",
},
{
name: "ShouldFailAlpha3Country",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"USA"},
Organization: []string{"Test Org"},
OrganizationalUnit: []string{"Authenticator Attestation"},
CommonName: "Test",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Country Code is invalid",
},
{
name: "ShouldFailMissingOrganization",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"US"},
OrganizationalUnit: []string{"Authenticator Attestation"},
CommonName: "Test",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Organization is invalid",
},
{
name: "ShouldFailWrongOrganizationalUnit",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Test Org"},
OrganizationalUnit: []string{"Wrong OU"},
CommonName: "Test",
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Organizational Unit is invalid",
},
{
name: "ShouldFailMissingCommonName",
template: &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Test Org"},
OrganizationalUnit: []string{"Authenticator Attestation"},
},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
},
err: "Attestation Certificate Common Name not set",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
certDER, err := x509.CreateCertificate(rand.Reader, tc.template, tc.template, &key.PublicKey, key)
require.NoError(t, err)
sigAlg := webauthncose.SigAlgFromCOSEAlg(webauthncose.AlgES256)
cert, err := x509.ParseCertificate(certDER)
require.NoError(t, err)
sig, err := key.Sign(rand.Reader, packedTestHashForSigAlg(t, sigAlg, signatureData), nil)
require.NoError(t, err)
x5c := []any{certDER}
_, _, err = handleBasicAttestation(sig, clientDataHash, authData, nil, int64(webauthncose.AlgES256), x5c, nil)
require.EqualError(t, err, tc.err)
_ = cert
})
}
}
// Supporting functions.
func packedTestHashForSigAlg(t *testing.T, alg x509.SignatureAlgorithm, data []byte) []byte {
t.Helper()
switch alg {
case x509.ECDSAWithSHA256:
h := sha256.Sum256(data)
return h[:]
default:
t.Fatalf("unsupported signature algorithm: %v", alg)
return nil
}
}
// Test data.
var packedTestResponseES256 = map[string]string{
`success`: `{
"rawId": "hUf7WI3IZmoLOzYhHFe7U-df4QD17lQBMi9iS-z3dWFlr79MXOoTR8dJzb_Y7sAstHBrcC1nv8pOr6aFz50K65juYXWt8k26bKu-Hu4CulPo53bIStJ4kpOr2Dlr6Z4D",
"id": "hUf7WI3IZmoLOzYhHFe7U-df4QD17lQBMi9iS-z3dWFlr79MXOoTR8dJzb_Y7sAstHBrcC1nv8pOr6aFz50K65juYXWt8k26bKu-Hu4CulPo53bIStJ4kpOr2Dlr6Z4D",
"response": {
"clientDataJSON": "ew0KCSJ0eXBlIiA6ICJ3ZWJhdXRobi5jcmVhdGUiLA0KCSJjaGFsbGVuZ2UiIDogIlBfSktRaWQxdHZzNEJsdGlaMUNzRWZYbDNHWjBJcG1MUFVRRmxZLW8weDlzZ3ZDS3lXNXpQUkpjTzc3M2VpOE93WEN5Rjl1Wk42X3B5elhOT0FKUjdBIiwNCgkib3JpZ2luIiA6ICJodHRwczovL2xvY2FsaG9zdDo0NDMyOSIsDQoJInRva2VuQmluZGluZyIgOiANCgl7DQoJCSJzdGF0dXMiIDogInN1cHBvcnRlZCINCgl9DQp9",
"attestationObject": "o2NmbXRmcGFja2VkaGF1dGhEYXRhWORJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0UAAChiQjgyRUQ3M0M4RkI0RTVBMgBghUf7WI3IZmoLOzYhHFe7U-df4QD17lQBMi9iS-z3dWFlr79MXOoTR8dJzb_Y7sAstHBrcC1nv8pOr6aFz50K65juYXWt8k26bKu-Hu4CulPo53bIStJ4kpOr2Dlr6Z4DpQECAyYgASFYIA9RHvpjfWoWN_Im7eYwG1Y8kA77s7QH9uf9TePknT3mIlggJ8tNsMrPPrewstqf65ItALMxBIi4VUoTIZEyAkXN6U1nYXR0U3RtdKNjYWxnJmNzaWdYRzBFAiBsbcx3U1xgYinrnczLOUDOlYGvYENDGzv77WdM1W3FTQIhAJ16HUK8XyG83cOVQFKkijdgHyDV97XylRMU_rWHAkP_Y3g1Y4NZAkUwggJBMIIB6KADAgECAhAVn3vCzYkY8Shrk0j6nzPiMAoGCCqGSM49BAMCMEkxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRGZWl0aWFuIFRlY2hub2xvZ2llczEbMBkGA1UEAwwSRmVpdGlhbiBGSURPMiBDQS0xMCAXDTE4MDQxMTAwMDAwMFoYDzIwMzMwNDEwMjM1OTU5WjBvMQswCQYDVQQGEwJDTjEdMBsGA1UECgwURmVpdGlhbiBUZWNobm9sb2dpZXMxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xHTAbBgNVBAMMFEZUIEJpb1Bhc3MgRklETzIgVVNCMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgAZ1XFn7yUmwFajSCpJYl76DCrLv6Cz4j-2gkJZj5UjHHxEnBTO0JEZ4nUz-4QFDipTpgz3iACwvKh3Xb03bXaOBiTCBhjAdBgNVHQ4EFgQUelSCQoBi2Irnr4SYJcSvkak0mPIwHwYDVR0jBBgwFoAUTTvYxGcVG7sT6POE2DBPnWkVwIMwDAYDVR0TAQH_BAIwADATBgsrBgEEAYLlHAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBBCODJFRDczQzhGQjRFNUEyMAoGCCqGSM49BAMCA0cAMEQCICRLRaO-iNy34CWixqMSz_uG7bwnSiLBBS4xSFHw6LCHAiA0Gr9OHCTyCxpz1T2swqn5FbQbsjprAW8f7_jg5_iQwFkB_zCCAfswggGgoAMCAQICEBWfe8LNiRjxKGuTSPqfM-EwCgYIKoZIzj0EAwIwSzELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFEZlaXRpYW4gVGVjaG5vbG9naWVzMR0wGwYDVQQDDBRGZWl0aWFuIEZJRE8gUm9vdCBDQTAgFw0xODA0MTAwMDAwMDBaGA8yMDM4MDQwOTIzNTk1OVowSTELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFEZlaXRpYW4gVGVjaG5vbG9naWVzMRswGQYDVQQDDBJGZWl0aWFuIEZJRE8yIENBLTEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASOfmAJ7MEWZcyg-sPpb-UIO5VtVyUR61sy9NZnOVfdZ9i2FzUd_0u5gOYLqbkzuZo0MPMX6iETB1a9agd03nWPo2YwZDAdBgNVHQ4EFgQUTTvYxGcVG7sT6POE2DBPnWkVwIMwHwYDVR0jBBgwFoAU0aGYTYF_w7lr9gdnvVAS_pBF8VQwEgYDVR0TAQH_BAgwBgEB_wIBADAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSQAwRgIhAPt_o9JAR6ERUMJ4Vm0hzJAWmOyhf087SDRTecpg5MJlAiEA6wpDwYjB172IPpEkYFbCsLlbWKJ0bwufPKkcKS0rWexZAdwwggHYMIIBfqADAgECAhAVn3vCzYkY8Shrk0j6nzPWMAoGCCqGSM49BAMCMEsxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRGZWl0aWFuIFRlY2hub2xvZ2llczEdMBsGA1UEAwwURmVpdGlhbiBGSURPIFJvb3QgQ0EwIBcNMTgwNDAxMDAwMDAwWhgPMjA0ODAzMzEyMzU5NTlaMEsxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRGZWl0aWFuIFRlY2hub2xvZ2llczEdMBsGA1UEAwwURmVpdGlhbiBGSURPIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASd8ApuO8xfUTLVvqT5ZBB01Uy30mAZbInc-8zgFIrlepN-j77SgCP_i2fDIgvQcUFH1K36S2OpJcN-OJcC6uzzo0IwQDAdBgNVHQ4EFgQU0aGYTYF_w7lr9gdnvVAS_pBF8VQwDwYDVR0TAQH_BAUwAwEB_zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSAAwRQIhALexPWUGMZ4X7EpOnNXUphTZyRqFN3iYsnLNg6Foe_iKAiAPYliR_IflDgGmjyuug7Qi3uhiMXaSDL95JndT0aVqrA"
},
"type": "public-key"
}`,
}
var packedTestResponseES512 = map[string]string{
`success`: `{
"rawId": "6YIJExgLDzTvfys9WgQlIGTL1L9Ys9bhaaA1Pr-OAPc",
"id": "6YIJExgLDzTvfys9WgQlIGTL1L9Ys9bhaaA1Pr-OAPc",
"response": {
"clientDataJSON": "eyJvcmlnaW4iOiJodHRwczovL2xvY2FsaG9zdDo0NDMyOSIsImNoYWxsZW5nZSI6IlFQQS1GckNTd2ctcUhoell2UklkbkEiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0",
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZzgjY3NpZ1iKMIGHAkE9Vr0j3zGzH6_YASuNse-D4bIDPU4ralNkJqgbCyv_tPNdt27VKaPDnK3WKWgv1qna04qMA7yukZeOPods8arRVQJCAZibACvAfmwBNT4cvR32MNvgGienLXmi2q8MwytcGrtOMnyhnxgco0pOFH7eWHXzn64mVqdSD-wPRTIfJ3McBxW0aGF1dGhEYXRhWOlJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0EAAABmI4irjYkVQUaTutQ-Zx0lOAAg6YIJExgLDzTvfys9WgQlIGTL1L9Ys9bhaaA1Pr-OAPelAQIDOCMgAyFYQgGzEwyupDz8u1IHtClxewg8CYWBRqD6_SufCj6-LevV57awHyeFGbyfS78ZB4e_I7RmndDI-jO24T3WZ1JMoE1mMCJYQgCpx32yAvYCfKWILgd5aLYuE5L8lEWuN5lhzGwNXoi6pj0JcQR60yCzI8HPlESzEvpqtCNBqF99eD2JETVIqkiwvQ"
},
"type": "public-key"
}`,
}
var packedTestResponseSolo2 = map[string]string{
`success`: `{
"id":"owBY6F5857tda9Pg5iFNCg6ksHpGOYhrNqIn46pkvhEMKIgNGcKS-vDGAUEroq0-VHnl1LhzQkPRQmYBTHjGcpLKZKSLa2m2ANI-91HjXzoJd_zFOiEnu7CDwQTff9KZ6uPlx7kUK-JJOHar-IyRKcNhc_kOJ2ezglmj1JYuIJLoDEyXlKkkviFdwk1vbWLnO3p_oWROUeIgH_S4CLVLPIJXkPe0YvMgp3ESs9CsrN6kvMTysVRIt_h5KUqpZo0TKCL96zwFk1X_2PwCLKWmOxVL35lJfUKOHG9rc3bmKlqZR6aOgZjerY6BpU8BTJkAqfOvdVlqFeEcywJQgveR7FOvnVtoqzd5oaEwjA",
"rawId":"owBY6F5857tda9Pg5iFNCg6ksHpGOYhrNqIn46pkvhEMKIgNGcKS-vDGAUEroq0-VHnl1LhzQkPRQmYBTHjGcpLKZKSLa2m2ANI-91HjXzoJd_zFOiEnu7CDwQTff9KZ6uPlx7kUK-JJOHar-IyRKcNhc_kOJ2ezglmj1JYuIJLoDEyXlKkkviFdwk1vbWLnO3p_oWROUeIgH_S4CLVLPIJXkPe0YvMgp3ESs9CsrN6kvMTysVRIt_h5KUqpZo0TKCL96zwFk1X_2PwCLKWmOxVL35lJfUKOHG9rc3bmKlqZR6aOgZjerY6BpU8BTJkAqfOvdVlqFeEcywJQgveR7FOvnVtoqzd5oaEwjA",
"response":{
"attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEgwRgIhAIXRMqmC2_bHTkKUwOvLvmAikuQPCk__9clILwjhOz3VAiEApJXTrN4WMiPwFXqTIh0oI8AZBm3vs-y_UotbQFSnX99jeDVjgVkCqzCCAqcwggJMoAMCAQICFGqj6W3EVhRWQJPun0qqCMyTlnqKMAoGCCqGSM49BAMCMC0xETAPBgNVBAoMCFNvbG9LZXlzMQswCQYDVQQGEwJDSDELMAkGA1UEAwwCRjEwIBcNMjEwNTIzMDA1MjA2WhgPMjA3MTA1MTEwMDUyMDZaMIGDMQswCQYDVQQGEwJVUzERMA8GA1UECgwIU29sb0tleXMxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xPTA7BgNVBAMMNFNvbG8gMiBORkMrVVNCLUMgMjM2OUQ0RDAxM0NFNDhDQjlGMjZGN0VEOEM5QTYwNjggQjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS6N5V2fT-agh34bRiW--Wl6CQPSsnLqqSEID0t5RRKjjl1NDI__mzuyYuOrWyb5yzGZRHgnHq65cm2ROpxo6AOo4HwMIHtMB0GA1UdDgQWBBQ6CEDC5W8_zAMOhVgV8wHJI8n3bzAfBgNVHSMEGDAWgBRBa7ZL76IZDeRiX_0pBJa5gim0-DAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly9pLnMycGtpLm5ldC9mMS8wJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL2MuczJwa2kubmV0L3IxLzAhBgsrBgEEAYLlHAEBBAQSBBAjadTQE85Iy58m9-2MmmBoMBMGCysGAQQBguUcAgEBBAQDAgQwMAoGCCqGSM49BAMCA0kAMEYCIQCP82Rolr0U2FvOJq53AZYcA6xfC4-cNDczvf0FtU1SQAIhAIvb21Z3D8RCvwk2-Ryn4wpsGnn2vma6Bw3E1f48hyVwaGF1dGhEYXRhWQFtarm78N-aFvkduzO7sTL6-dF8eCxIJsbscOzuWNl-9SpBAAAAJyNp1NATzkjLnyb37YyaYGgBDKMAWOhefOe7XWvT4OYhTQoOpLB6RjmIazaiJ-OqZL4RDCiIDRnCkvrwxgFBK6KtPlR55dS4c0JD0UJmAUx4xnKSymSki2tptgDSPvdR4186CXf8xTohJ7uwg8EE33_Smerj5ce5FCviSTh2q_iMkSnDYXP5Didns4JZo9SWLiCS6AxMl5SpJL4hXcJNb21i5zt6f6FkTlHiIB_0uAi1SzyCV5D3tGLzIKdxErPQrKzepLzE8rFUSLf4eSlKqWaNEygi_es8BZNV_9j8AiylpjsVS9-ZSX1Cjhxva3N25ipamUemjoGY3q2OgaVPAUyZAKnzr3VZahXhHMsCUIL3kexTr51baKs3eaGhMIykAQEDJyAGIVggjz9UkJ7cKooE3blSuzlqxkdLppMuFl3CIiST8odWS6k",
"clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQ1dieENUMEc0TDJ5T1JwQkw2U1dWaWd3ZTJrUUVYQmhvNUw2d0U0Ny1FcyIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uZmlyc3R5ZWFyLmlkLmF1IiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ"
},
"type":"public-key"
}`,
}