Skip to content

Commit 697bc4c

Browse files
authored
feat(metadata): mds3 support (#54)
This feature migrates from MDS2 to MDS3.
1 parent fa14fa7 commit 697bc4c

23 files changed

+1107
-557
lines changed

metadata/metadata.go

+298-238
Large diffs are not rendered by default.

metadata/metadata_test.go

+297-50
Large diffs are not rendered by default.

protocol/attestation.go

+38-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package protocol
22

33
import (
44
"crypto/sha256"
5+
"crypto/x509"
56
"encoding/json"
67
"fmt"
78

9+
"github.com/go-webauthn/webauthn/metadata"
810
"github.com/go-webauthn/webauthn/protocol/webauthncbor"
11+
"github.com/google/uuid"
912
)
1013

1114
// From §5.2.1 (https://www.w3.org/TR/webauthn/#authenticatorattestationresponse)
@@ -52,7 +55,6 @@ type ParsedAttestationResponse struct {
5255
// perform self attestation of the credential public key with the corresponding credential private key.
5356
// All this information is returned by authenticators any time a new public key credential is generated, in
5457
// the overall form of an attestation object. (https://www.w3.org/TR/webauthn/#attestation-object)
55-
//
5658
type AttestationObject struct {
5759
// The authenticator data, including the newly created public key. See AuthenticatorData for more info
5860
AuthData AuthenticatorData
@@ -147,10 +149,44 @@ func (attestationObject *AttestationObject) Verify(relyingPartyID string, client
147149
// Step 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature, by using
148150
// the attestation statement format fmt’s verification procedure given attStmt, authData and the hash of the serialized
149151
// client data computed in step 7.
150-
attestationType, _, err := formatHandler(*attestationObject, clientDataHash)
152+
attestationType, x5c, err := formatHandler(*attestationObject, clientDataHash)
151153
if err != nil {
152154
return err.(*Error).WithInfo(attestationType)
153155
}
154156

157+
uuid, err := uuid.FromBytes(attestationObject.AuthData.AttData.AAGUID)
158+
if err != nil {
159+
return err
160+
}
161+
if meta, ok := metadata.Metadata[uuid]; ok {
162+
for _, s := range meta.StatusReports {
163+
if metadata.IsUndesiredAuthenticatorStatus(s.Status) {
164+
return ErrInvalidAttestation.WithDetails("Authenticator with undesirable status encountered")
165+
}
166+
}
167+
168+
if x5c != nil {
169+
attestnCert, err := x509.ParseCertificate(x5c[0].([]byte))
170+
if err != nil {
171+
return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c")
172+
}
173+
if attestnCert.Subject.CommonName != attestnCert.Issuer.CommonName {
174+
var hasBasicFull = false
175+
for _, a := range meta.MetadataStatement.AttestationTypes {
176+
if metadata.AuthenticatorAttestationType(a) == metadata.BasicFull {
177+
hasBasicFull = true
178+
}
179+
}
180+
if !hasBasicFull {
181+
return ErrInvalidAttestation.WithDetails("Attestation with full attestation from authentictor that does not support full attestation")
182+
}
183+
}
184+
}
185+
} else {
186+
if metadata.Conformance {
187+
return ErrInvalidAttestation.WithDetails(fmt.Sprintf("AAGUID %s not found in metadata during conformance testing", uuid.String()))
188+
}
189+
}
190+
155191
return nil
156192
}

protocol/attestation_androidkey.go

+19-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/asn1"
77
"fmt"
88

9+
"github.com/go-webauthn/webauthn/metadata"
910
"github.com/go-webauthn/webauthn/protocol/webauthncose"
1011
)
1112

@@ -38,51 +39,51 @@ func verifyAndroidKeyFormat(att AttestationObject, clientDataHash []byte) (strin
3839
// used to generate the attestation signature.
3940
alg, present := att.AttStatement["alg"].(int64)
4041
if !present {
41-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving alg value")
42+
return "", nil, ErrAttestationFormat.WithDetails("Error retreiving alg value")
4243
}
4344

4445
// Get the sig value - A byte string containing the attestation signature.
4546
sig, present := att.AttStatement["sig"].([]byte)
4647
if !present {
47-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving sig value")
48+
return "", nil, ErrAttestationFormat.WithDetails("Error retreiving sig value")
4849
}
4950

5051
// If x5c is not present, return an error
5152
x5c, x509present := att.AttStatement["x5c"].([]interface{})
5253
if !x509present {
5354
// Handle Basic Attestation steps for the x509 Certificate
54-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving x5c value")
55+
return "", nil, ErrAttestationFormat.WithDetails("Error retreiving x5c value")
5556
}
5657

5758
// §8.4.2. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
5859
// using the public key in the first certificate in x5c with the algorithm specified in alg.
5960
attCertBytes, valid := x5c[0].([]byte)
6061
if !valid {
61-
return androidAttestationKey, nil, ErrAttestation.WithDetails("Error getting certificate from x5c cert chain")
62+
return "", nil, ErrAttestation.WithDetails("Error getting certificate from x5c cert chain")
6263
}
6364

6465
signatureData := append(att.RawAuthData, clientDataHash...)
6566

6667
attCert, err := x509.ParseCertificate(attCertBytes)
6768
if err != nil {
68-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing certificate from ASN.1 data: %+v", err))
69+
return "", nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing certificate from ASN.1 data: %+v", err))
6970
}
7071

7172
coseAlg := webauthncose.COSEAlgorithmIdentifier(alg)
7273
sigAlg := webauthncose.SigAlgFromCOSEAlg(coseAlg)
73-
74-
if err = attCert.CheckSignature(x509.SignatureAlgorithm(sigAlg), signatureData, sig); err != nil {
75-
return androidAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Signature validation error: %+v", err))
74+
err = attCert.CheckSignature(x509.SignatureAlgorithm(sigAlg), signatureData, sig)
75+
if err != nil {
76+
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Signature validation error: %+v\n", err))
7677
}
7778
// Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData.
7879
pubKey, err := webauthncose.ParsePublicKey(att.AuthData.AttData.CredentialPublicKey)
7980
if err != nil {
80-
return androidAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v", err))
81+
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v\n", err))
8182
}
8283
e := pubKey.(webauthncose.EC2PublicKeyData)
8384
valid, err = e.Verify(signatureData, sig)
84-
if err != nil || !valid {
85-
return androidAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v", err))
85+
if err != nil || valid != true {
86+
return "", nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v\n", err))
8687
}
8788
// §8.4.3. Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash.
8889
// attCert.Extensions
@@ -93,33 +94,33 @@ func verifyAndroidKeyFormat(att AttestationObject, clientDataHash []byte) (strin
9394
}
9495
}
9596
if len(attExtBytes) == 0 {
96-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions missing 1.3.6.1.4.1.11129.2.1.17")
97+
return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions missing 1.3.6.1.4.1.11129.2.1.17")
9798
}
9899
// As noted in §8.4.1 (https://w3c.github.io/webauthn/#key-attstn-cert-requirements) the Android Key Attestation attestation certificate's
99100
// android key attestation certificate extension data is identified by the OID "1.3.6.1.4.1.11129.2.1.17".
100101
decoded := keyDescription{}
101102
_, err = asn1.Unmarshal([]byte(attExtBytes), &decoded)
102103
if err != nil {
103-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Unable to parse Android key attestation certificate extensions")
104+
return "", nil, ErrAttestationFormat.WithDetails("Unable to parse Android key attestation certificate extensions")
104105
}
105106
// Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash.
106107
if 0 != bytes.Compare(decoded.AttestationChallenge, clientDataHash) {
107-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation challenge not equal to clientDataHash")
108+
return "", nil, ErrAttestationFormat.WithDetails("Attestation challenge not equal to clientDataHash")
108109
}
109110
// The AuthorizationList.allApplications field is not present on either authorization list (softwareEnforced nor teeEnforced), since PublicKeyCredential MUST be scoped to the RP ID.
110111
if nil != decoded.SoftwareEnforced.AllApplications || nil != decoded.TeeEnforced.AllApplications {
111-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains all applications field")
112+
return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains all applications field")
112113
}
113114
// For the following, use only the teeEnforced authorization list if the RP wants to accept only keys from a trusted execution environment, otherwise use the union of teeEnforced and softwareEnforced.
114115
// The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED. (which == 0)
115116
if KM_ORIGIN_GENERATED != decoded.SoftwareEnforced.Origin || KM_ORIGIN_GENERATED != decoded.TeeEnforced.Origin {
116-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains authorization list with origin not equal KM_ORIGIN_GENERATED")
117+
return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains authorization list with origin not equal KM_ORIGIN_GENERATED")
117118
}
118119
// The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN. (which == 2)
119120
if !contains(decoded.SoftwareEnforced.Purpose, KM_PURPOSE_SIGN) && !contains(decoded.TeeEnforced.Purpose, KM_PURPOSE_SIGN) {
120-
return androidAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains authorization list with purpose not equal KM_PURPOSE_SIGN")
121+
return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions contains authorization list with purpose not equal KM_PURPOSE_SIGN")
121122
}
122-
return androidAttestationKey, x5c, err
123+
return string(metadata.BasicFull), x5c, err
123124
}
124125

125126
func contains(s []int, e int) bool {
+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package protocol
2+
3+
import (
4+
"crypto/sha256"
5+
"testing"
6+
7+
"github.com/go-webauthn/webauthn/metadata"
8+
)
9+
10+
func TestVerifyAndroidKeyFormat(t *testing.T) {
11+
type args struct {
12+
att AttestationObject
13+
clientDataHash []byte
14+
}
15+
successAttResponse0 := attestationTestUnpackResponse(t, androidKeyTestResponse0["success"]).Response.AttestationObject
16+
successClientDataHash0 := sha256.Sum256(attestationTestUnpackResponse(t, androidKeyTestResponse0["success"]).Raw.AttestationResponse.ClientDataJSON)
17+
successAttResponse1 := attestationTestUnpackResponse(t, androidKeyTestResponse1["success"]).Response.AttestationObject
18+
successClientDataHash1 := sha256.Sum256(attestationTestUnpackResponse(t, androidKeyTestResponse1["success"]).Raw.AttestationResponse.ClientDataJSON)
19+
tests := []struct {
20+
name string
21+
args args
22+
want string
23+
want1 []interface{}
24+
wantErr bool
25+
}{
26+
{
27+
"success",
28+
args{
29+
successAttResponse0,
30+
successClientDataHash0[:],
31+
},
32+
string(metadata.BasicFull),
33+
nil,
34+
false,
35+
},
36+
{
37+
"success",
38+
args{
39+
successAttResponse1,
40+
successClientDataHash1[:],
41+
},
42+
string(metadata.BasicFull),
43+
nil,
44+
false,
45+
},
46+
}
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
got, _, err := verifyAndroidKeyFormat(tt.args.att, tt.args.clientDataHash)
50+
if (err != nil) != tt.wantErr {
51+
t.Errorf("verifyAndroidKeyFormat() error = %v, wantErr %v", err, tt.wantErr)
52+
return
53+
}
54+
if got != tt.want {
55+
t.Errorf("verifyAndroidKeyFormat() got = %v, want %v", got, tt.want)
56+
}
57+
//if !reflect.DeepEqual(got1, tt.want1) {
58+
// t.Errorf("verifySafetyNetFormat() got1 = %v, want %v", got1, tt.want1)
59+
//}
60+
})
61+
}
62+
}
63+
64+
var androidKeyTestResponse0 = map[string]string{
65+
`success`: `{
66+
"rawId": "U5cxFNxLbU9-SAi1K7k9atYwXhghkAMbxpL__VPtBlw",
67+
"id": "U5cxFNxLbU9-SAi1K7k9atYwXhghkAMbxpL__VPtBlw",
68+
"response": {
69+
"clientDataJSON": "eyJvcmlnaW4iOiJodHRwczovL2xvY2FsaG9zdDo0NDMyOSIsImNoYWxsZW5nZSI6IjlNNWY3bGp5MVl2UWNzOE9pV1FWQ3ciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0",
70+
"attestationObject": "o2NmbXRrYW5kcm9pZC1rZXlnYXR0U3RtdKNjYWxnJmNzaWdYSDBGAiEAlbQ-jtl8o9GtEstcEFH1Z_NlYsTYSn96lilEF17oEsMCIQDza5_axjn2jKZO63RlVf47DDFZbceW9b_tsh1nwOYQbmN4NWOCWQMFMIIDATCCAqegAwIBAgIBATAKBggqhkjOPQQDAjCBzjFFMEMGA1UEAww8RkFLRSBBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZSBGQUtFMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMCAXDTcwMDIwMTAwMDAwMFoYDzIwOTkwMTMxMjM1OTU5WjApMScwJQYDVQQDDB5GQUtFIEFuZHJvaWQgS2V5c3RvcmUgS2V5IEZBS0UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQbh-BQBJz7JeQ27dVvu3tyRieiEeXyDYoaWatRdy_D7q3TK96jumKlwIl5ZA2zHmKNLz4K2zsANq1X4tHp8MNZo4IBFjCCARIwCwYDVR0PBAQDAgeAMIHhBgorBgEEAdZ5AgERBIHSMIHPAgECCgEAAgEBCgEABCDc0UoXtU1CwwItW3ne2faKDcFCabFI31BufXEFVK_ENwQAMGm_hT0IAgYBXtPjz6C_hUVZBFcwVTEvMC0EKGNvbS5hbmRyb2lkLmtleXN0b3JlLmFuZHJvaWRrZXlzdG9yZWRlbW8CAQExIgQgdM_LUHSI9SkQhZHHpQWRnzJ3MvvB2ANSauqYAAbS2JgwMqEFMQMCAQKiAwIBA6MEAgIBAKUFMQMCAQSqAwIBAb-DeAMCAQK_hT4DAgEAv4U_AgUAMB8GA1UdIwQYMBaAFFKaGzLgVqrNUQ_vX4A3BovykSMdMAoGCCqGSM49BAMCA0gAMEUCIQDAPV7eQIWfL5BCmj82NszDlQ2IJsOZq_WxidwxD7On_QIgFipplgUF6OHvmHiDdaHJfFweeo60OtCDGDftjQEmF7FZAu4wggLqMIICkaADAgECAgECMAoGCCqGSM49BAMCMIHGMT0wOwYDVQQDDDRGQUtFIEFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdCBGQUtFMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDUwOTEyMzE0NFoXDTQ1MDkyNDEyMzE0NFowgc4xRTBDBgNVBAMMPEZBS0UgQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBJbnRlcm1lZGlhdGUgRkFLRTExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzEWMBQGA1UECgwNRklETyBBbGxpYW5jZTEMMAoGA1UECwwDQ1dHMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKtQYStiTRe7w7UbBEk7BUkLjB-LnbzzebLe3KB8UqHXtg3TIXXcK37dvCbbCNVfhvZxtpTcME2kooqMTgOm9cejZjBkMBIGA1UdEwEB_wQIMAYBAf8CAQAwDgYDVR0PAQH_BAQDAgKEMB0GA1UdDgQWBBSj0qos7w2M8iQC1Ry0YLy_alskFDAfBgNVHSMEGDAWgBRSmhsy4FaqzVEP71-ANwaL8pEjHTAKBggqhkjOPQQDAgNHADBEAiBp3Z6j8YH7Qko5rRoK37nS4zPXhv65RWBV-j3MmXi50gIgPtMPpvcGtVbpFCQqsGbyhxPdkji8ltcYXQVfMhdUpRZoYXV0aERhdGFYpEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAFpVDktUqkdAn5qVGrdsEwExACBTlzEU3EttT35ICLUruT1q1jBeGCGQAxvGkv_9U-0GXKUBAgMmIAEhWCAbh-BQBJz7JeQ27dVvu3tyRieiEeXyDYoaWatRdy_D7iJYIK3TK96jumKlwIl5ZA2zHmKNLz4K2zsANq1X4tHp8MNZ"
71+
},
72+
"type": "public-key"
73+
}`,
74+
}
75+
76+
var androidKeyTestResponse1 = map[string]string{
77+
`success`: `{
78+
"id": "V51GE29tGbhby7sbg1cZ_qL8V8njqEsXpAnwQBobvgw",
79+
"rawId": "V51GE29tGbhby7sbg1cZ_qL8V8njqEsXpAnwQBobvgw",
80+
"response": {
81+
"attestationObject": "o2NmbXRrYW5kcm9pZC1rZXlnYXR0U3RtdKNjYWxnJmNzaWdYRzBFAiAbZhfcF0KSXj5rdEevvnBcC8ZfRQlNl9XYWRTiIGKSHwIhAIerc7jWjOF_lJ71n_GAcaHwDUtPxkjAAdYugnZ4QxkmY3g1Y4JZAxowggMWMIICvaADAgECAgEBMAoGCCqGSM49BAMCMIHkMUUwQwYDVQQDDDxGQUtFIEFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gSW50ZXJtZWRpYXRlIEZBS0UxMTAvBgkqhkiG9w0BCQEWImNvbmZvcm1hbmNlLXRvb2xzQGZpZG9hbGxpYW5jZS5vcmcxFjAUBgNVBAoMDUZJRE8gQWxsaWFuY2UxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMCAXDTcwMDIwMTAwMDAwMFoYDzIwOTkwMTMxMjM1OTU5WjApMScwJQYDVQQDDB5GQUtFIEFuZHJvaWQgS2V5c3RvcmUgS2V5IEZBS0UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARuowgSu5AoRj8Vi_ZNSFBbGUZJXFG9MkDT6jADlr7tOK9NEgjVX53-ergXpyPaFZrAR9py-xnzfjILn_Kzb8Iqo4IBFjCCARIwCwYDVR0PBAQDAgeAMIHhBgorBgEEAdZ5AgERBIHSMIHPAgECCgEAAgEBCgEABCCfVEl83pSDSerk9I3pcICNTdzc5N3u4jt21cXdzBuJjgQAMGm_hT0IAgYBXtPjz6C_hUVZBFcwVTEvMC0EKGNvbS5hbmRyb2lkLmtleXN0b3JlLmFuZHJvaWRrZXlzdG9yZWRlbW8CAQExIgQgdM_LUHSI9SkQhZHHpQWRnzJ3MvvB2ANSauqYAAbS2JgwMqEFMQMCAQKiAwIBA6MEAgIBAKUFMQMCAQSqAwIBAb-DeAMCAQK_hT4DAgEAv4U_AgUAMB8GA1UdIwQYMBaAFKPSqizvDYzyJALVHLRgvL9qWyQUMAoGCCqGSM49BAMCA0cAMEQCIC7WHb2PyULnjp1M1TVI3Wti_eDhe6sFweuQAdecXtHhAiAS_eZkFsx_VNsrTu3XfZ2D7wIt-vT6nTljfHZ4zqU5xlkDGDCCAxQwggK6oAMCAQICAQIwCgYIKoZIzj0EAwIwgdwxPTA7BgNVBAMMNEZBS0UgQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290IEZBS0UxMTAvBgkqhkiG9w0BCQEWImNvbmZvcm1hbmNlLXRvb2xzQGZpZG9hbGxpYW5jZS5vcmcxFjAUBgNVBAoMDUZJRE8gQWxsaWFuY2UxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE5MDQyNTA1NDkzMloXDTQ2MDkxMDA1NDkzMlowgeQxRTBDBgNVBAMMPEZBS0UgQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBJbnRlcm1lZGlhdGUgRkFLRTExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzEWMBQGA1UECgwNRklETyBBbGxpYW5jZTEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1ZMRIwEAYDVQQHDAlXYWtlZmllbGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrUGErYk0Xu8O1GwRJOwVJC4wfi52883my3tygfFKh17YN0yF13Ct-3bwm2wjVX4b2cbaU3DBNpKKKjE4DpvXHo2MwYTAPBgNVHRMBAf8EBTADAQH_MA4GA1UdDwEB_wQEAwIChDAdBgNVHQ4EFgQUo9KqLO8NjPIkAtUctGC8v2pbJBQwHwYDVR0jBBgwFoAUUpobMuBWqs1RD-9fgDcGi_KRIx0wCgYIKoZIzj0EAwIDSAAwRQIhALFvLkAvtHrObTmN8P0-yLIT496P_weSEEbB6vCJWSh9AiBu-UOorCeLcF4WixOG9E5Li2nXe4uM2q6mbKGkll8u-WhhdXRoRGF0YVikPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBBAAAAYFUOS1SqR0CfmpUat2wTATEAIFedRhNvbRm4W8u7G4NXGf6i_FfJ46hLF6QJ8EAaG74MpQECAyYgASFYIG6jCBK7kChGPxWL9k1IUFsZRklcUb0yQNPqMAOWvu04Ilggr00SCNVfnf56uBenI9oVmsBH2nL7GfN-Mguf8rNvwio",
82+
"clientDataJSON": "eyJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJjaGFsbGVuZ2UiOiI0YWI3ZGZkMS1hNjk1LTQ3NzctOTg1Zi1hZDI5OTM4MjhlOTkiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
83+
},
84+
"type": "public-key"
85+
}`,
86+
}

0 commit comments

Comments
 (0)