This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-webauthn/webauthn/metadata"
|
||||
)
|
||||
|
||||
func TestAttestationVerify(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
options string
|
||||
response string
|
||||
}{
|
||||
{
|
||||
name: "ShouldVerifySelfAttestationEC256MacOS",
|
||||
options: testAttestationOptions[0],
|
||||
response: testAttestationResponses[0],
|
||||
},
|
||||
{
|
||||
name: "ShouldVerifyDirectAttestationEC256Titan",
|
||||
options: testAttestationOptions[1],
|
||||
response: testAttestationResponses[1],
|
||||
},
|
||||
{
|
||||
name: "ShouldVerifyNoneAttestationEC256Titan",
|
||||
options: testAttestationOptions[2],
|
||||
response: testAttestationResponses[2],
|
||||
},
|
||||
{
|
||||
name: "ShouldVerifyPackedAttestationGramThanos",
|
||||
options: testAttestationOptions[3],
|
||||
response: testAttestationResponses[3],
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
options := CredentialCreation{}
|
||||
|
||||
require.NoError(t, json.Unmarshal([]byte(tc.options), &options))
|
||||
|
||||
ccr := CredentialCreationResponse{}
|
||||
|
||||
require.NoError(t, json.Unmarshal([]byte(tc.response), &ccr))
|
||||
|
||||
var pcc ParsedCredentialCreationData
|
||||
|
||||
pcc.ID, pcc.RawID, pcc.Type, pcc.ClientExtensionResults = ccr.ID, ccr.RawID, ccr.Type, ccr.ClientExtensionResults
|
||||
pcc.Raw = ccr
|
||||
|
||||
parsedAttestationResponse, err := ccr.AttestationResponse.Parse()
|
||||
require.NoError(t, err)
|
||||
|
||||
pcc.Response = *parsedAttestationResponse
|
||||
|
||||
_, err = pcc.Verify(options.Response.Challenge.String(), options.Response.RelyingParty.ID, []string{options.Response.RelyingParty.Name}, nil, TopOriginExplicitVerificationMode, false, false, false, nil, options.Response.Parameters)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackedAttestationVerification(t *testing.T) {
|
||||
pcc := attestationTestUnpackResponse(t, testAttestationResponses[0])
|
||||
|
||||
clientDataHash := sha256.Sum256(pcc.Raw.AttestationResponse.ClientDataJSON)
|
||||
|
||||
_, _, err := attestationFormatValidationHandlerPacked(pcc.Response.AttestationObject, clientDataHash[:], nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAttestationResponseParse_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
response AuthenticatorAttestationResponse
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "ShouldFailInvalidClientDataJSON",
|
||||
response: AuthenticatorAttestationResponse{
|
||||
AuthenticatorResponse: AuthenticatorResponse{
|
||||
ClientDataJSON: []byte("not-json"),
|
||||
},
|
||||
AttestationObject: []byte{0xa0},
|
||||
},
|
||||
err: "Error parsing the authenticator response",
|
||||
},
|
||||
{
|
||||
name: "ShouldFailInvalidAttestationObjectCBOR",
|
||||
response: AuthenticatorAttestationResponse{
|
||||
AuthenticatorResponse: AuthenticatorResponse{
|
||||
ClientDataJSON: []byte(`{"type":"webauthn.create","challenge":"dGVzdA","origin":"https://example.com"}`),
|
||||
},
|
||||
AttestationObject: []byte("not-cbor"),
|
||||
},
|
||||
err: "Error parsing the authenticator response",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := tc.response.Parse()
|
||||
require.EqualError(t, err, tc.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationObject_VerifyAttestation_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
att AttestationObject
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "ShouldFailNoneFormatWithStatement",
|
||||
att: AttestationObject{
|
||||
Format: "none",
|
||||
AttStatement: map[string]any{"key": "value"},
|
||||
},
|
||||
err: "Invalid attestation format",
|
||||
},
|
||||
{
|
||||
name: "ShouldFailUnsupportedFormat",
|
||||
att: AttestationObject{
|
||||
Format: "unsupported-format",
|
||||
AttStatement: map[string]any{},
|
||||
},
|
||||
err: "Invalid attestation format",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.att.VerifyAttestation([]byte("hash"), nil)
|
||||
require.EqualError(t, err, tc.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationObject_Verify_AlgorithmMismatch(t *testing.T) {
|
||||
pcc := attestationTestUnpackResponse(t, testAttestationResponses[0])
|
||||
att := pcc.Response.AttestationObject
|
||||
clientDataHash := sha256.Sum256(pcc.Raw.AttestationResponse.ClientDataJSON)
|
||||
|
||||
wrongParams := []CredentialParameter{{Type: PublicKeyCredentialType, Algorithm: -257}}
|
||||
|
||||
err := att.Verify("localhost", clientDataHash[:], false, false, nil, wrongParams)
|
||||
require.EqualError(t, err, "Invalid attestation format")
|
||||
}
|
||||
|
||||
func TestAttestationObject_VerifyAttestation_HandlerErrors(t *testing.T) {
|
||||
withFreshAttestationRegistry(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
format string
|
||||
handler attestationFormatValidationHandler
|
||||
authData AuthenticatorData
|
||||
err string
|
||||
errType string
|
||||
errDetails string
|
||||
}{
|
||||
{
|
||||
name: "ShouldWrapProtocolError",
|
||||
format: "test-format",
|
||||
handler: func(att AttestationObject, clientDataHash []byte, mds metadata.Provider) (string, []any, error) {
|
||||
return string(metadata.BasicFull), nil, ErrInvalidAttestation.WithDetails("handler failed")
|
||||
},
|
||||
err: "handler failed",
|
||||
errType: ErrInvalidAttestation.Type,
|
||||
},
|
||||
{
|
||||
name: "ShouldWrapNonProtocolError",
|
||||
format: "test-format",
|
||||
handler: func(att AttestationObject, clientDataHash []byte, mds metadata.Provider) (string, []any, error) {
|
||||
return string(metadata.BasicFull), nil, fmt.Errorf("stdlib error")
|
||||
},
|
||||
err: "stdlib error",
|
||||
errType: ErrInvalidAttestation.Type,
|
||||
},
|
||||
{
|
||||
name: "ShouldReturnNilForCompoundAttestationType",
|
||||
format: "test-format",
|
||||
handler: func(att AttestationObject, clientDataHash []byte, mds metadata.Provider) (string, []any, error) {
|
||||
return string(AttestationFormatCompound), nil, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ShouldFailWithInvalidAAGUIDLength",
|
||||
format: "test-format",
|
||||
handler: func(att AttestationObject, clientDataHash []byte, mds metadata.Provider) (string, []any, error) {
|
||||
return string(metadata.BasicFull), nil, nil
|
||||
},
|
||||
authData: AuthenticatorData{
|
||||
AttData: AttestedCredentialData{
|
||||
AAGUID: []byte{0x01, 0x02, 0x03},
|
||||
},
|
||||
},
|
||||
err: "invalid UUID (got 3 bytes)",
|
||||
errType: ErrInvalidAttestation.Type,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
attestationRegistry[AttestationFormat(tc.format)] = tc.handler
|
||||
|
||||
att := AttestationObject{
|
||||
Format: tc.format,
|
||||
AttStatement: map[string]any{},
|
||||
AuthData: tc.authData,
|
||||
}
|
||||
|
||||
err := att.VerifyAttestation([]byte("hash"), nil)
|
||||
|
||||
if tc.err != "" {
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, tc.err)
|
||||
|
||||
if tc.errType != "" {
|
||||
var protoErr *Error
|
||||
|
||||
require.ErrorAs(t, err, &protoErr)
|
||||
assert.Equal(t, tc.errType, protoErr.Type)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Supporting functions.
|
||||
|
||||
func attestationTestUnpackResponse(t *testing.T, response string) (pcc ParsedCredentialCreationData) {
|
||||
t.Helper()
|
||||
|
||||
ccr := CredentialCreationResponse{}
|
||||
|
||||
require.NoError(t, json.Unmarshal([]byte(response), &ccr))
|
||||
|
||||
pcc.ID, pcc.RawID, pcc.Type, pcc.ClientExtensionResults = ccr.ID, ccr.RawID, ccr.Type, ccr.ClientExtensionResults
|
||||
pcc.Raw = ccr
|
||||
|
||||
parsedAttestationResponse, err := ccr.AttestationResponse.Parse()
|
||||
require.NoError(t, err)
|
||||
|
||||
pcc.Response = *parsedAttestationResponse
|
||||
|
||||
return pcc
|
||||
}
|
||||
|
||||
// Test data.
|
||||
|
||||
var testAttestationOptions = []string{
|
||||
// Direct Self Attestation with EC256 - MacOS.
|
||||
`{"publicKey": {
|
||||
"challenge": "rWiex8xDOPfiCgyFu4BLW6vVOmXKgPwHrlMCgEs9SBA",
|
||||
"rp": {
|
||||
"name": "http://localhost:9005",
|
||||
"id": "localhost"
|
||||
},
|
||||
"user": {
|
||||
"name": "self",
|
||||
"displayName": "self",
|
||||
"id": "2iEAAAAAAAAAAA=="
|
||||
},
|
||||
"pubKeyCredParams": [
|
||||
{
|
||||
"type": "public-key",
|
||||
"alg": -7
|
||||
}
|
||||
],
|
||||
"authenticatorSelection": {
|
||||
"authenticatorAttachment": "cross-platform",
|
||||
"userVerification": "preferred"
|
||||
},
|
||||
"timeout": 60000,
|
||||
"attestation": "direct"
|
||||
}}`,
|
||||
// Direct Attestation with EC256.
|
||||
`{"publicKey": {
|
||||
"challenge": "-Ri5NZTzJ8b6mvW3TVScLotEoALfgBa2Bn4YSaIObHc",
|
||||
"rp": {
|
||||
"name": "https://webauthn.io",
|
||||
"id": "webauthn.io"
|
||||
},
|
||||
"user": {
|
||||
"name": "flort",
|
||||
"displayName": "flort",
|
||||
"id": "1DMAAAAAAAAAAA=="
|
||||
},
|
||||
"pubKeyCredParams": [
|
||||
{
|
||||
"type": "public-key",
|
||||
"alg": -7
|
||||
}
|
||||
],
|
||||
"authenticatorSelection": {
|
||||
"authenticatorAttachment": "cross-platform",
|
||||
"userVerification": "preferred"
|
||||
},
|
||||
"timeout": 60000,
|
||||
"attestation": "direct"
|
||||
}}`,
|
||||
// None Attestation with EC256.
|
||||
`{
|
||||
"publicKey": {
|
||||
"challenge": "sVt4ScceMzqFSnfAq8hgLzblvo3fa4_aFVEcIESHIJ0",
|
||||
"rp": {
|
||||
"name": "https://webauthn.io",
|
||||
"id": "webauthn.io"
|
||||
},
|
||||
"user": {
|
||||
"name": "testuser1",
|
||||
"displayName": "testuser1",
|
||||
"id": "1zMAAAAAAAAAAA=="
|
||||
},
|
||||
"pubKeyCredParams": [
|
||||
{
|
||||
"type": "public-key",
|
||||
"alg": -7
|
||||
}
|
||||
],
|
||||
"authenticatorSelection": {
|
||||
"authenticatorAttachment": "cross-platform",
|
||||
"userVerification": "preferred"
|
||||
},
|
||||
"timeout": 60000,
|
||||
"attestation": "none"
|
||||
}
|
||||
}`,
|
||||
`{
|
||||
"publicKey": {
|
||||
"rp": {
|
||||
"name": "https://gramthanos.github.io",
|
||||
"id": "gramthanos.github.io"
|
||||
},
|
||||
"user": {
|
||||
"name": "john.smith@email.com",
|
||||
"displayName": "J. Smith",
|
||||
"id": "am9obi5zbWl0aEBlbWFpbC5jb20="
|
||||
},
|
||||
"challenge": "Dw4NDAsKCQgHBgUEAwIBAA==",
|
||||
"pubKeyCredParams": [
|
||||
{"type": "public-key", "alg": -7},
|
||||
{"type": "public-key", "alg": -37},
|
||||
{"type": "public-key", "alg": -257},
|
||||
{"type": "public-key", "alg": -8}
|
||||
],
|
||||
"timeout": 120000,
|
||||
"attestation": "direct"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
var testAttestationResponses = []string{
|
||||
// Self Attestation with EC256 - MacOS.
|
||||
`{
|
||||
"id": "AOx6vFGGITtlwjhqFFvAkJmBzSzfwE1dBa1fVR_Ltq5L35FJRNdgkXe84v3-0TEVNCSp",
|
||||
"rawId": "AOx6vFGGITtlwjhqFFvAkJmBzSzfwE1dBa1fVR_Ltq5L35FJRNdgkXe84v3-0TEVNCSp",
|
||||
"response": {
|
||||
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhAJgdgw5x8JzE4JfR6x1RBO8eCHNE8eW_L1VTV03zpyL5AiBv8eUzua3XSS3bPYC7m8eXzJhcaRyeGe7UcuqIrDSvC2hhdXRoRGF0YVi3SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFXJE5zK3OAAI1vMYKZIsLJfHwVQMAMwDserxRhiE7ZcI4ahRbwJCZgc0s38BNXQWtX1Ufy7auS9-RSUTXYJF3vOL9_tExFTQkqaUBAgMmIAEhWCCm9OYidwiIoH9SwVQqUAnH8Gj5ZJ2_qr8gjbg41q4M1SJYIA07XKpHSgS1mE7R1MjotVIQqyHi9WAxGwHQsCteVK2V",
|
||||
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiJyV2lleDh4RE9QZmlDZ3lGdTRCTFc2dlZPbVhLZ1B3SHJsTUNnRXM5U0JBIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo5MDA1IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9"
|
||||
},
|
||||
"type": "public-key"
|
||||
}`,
|
||||
// Direct Attestation with EC256 - Titan.
|
||||
`{
|
||||
"id": "FOxcmsqPLNCHtyILvbNkrtHMdKAeqSJXYZDbeFd0kc5Enm8Kl6a0Jp0szgLilDw1S4CjZhe9Z2611EUGbjyEmg",
|
||||
"rawId": "FOxcmsqPLNCHtyILvbNkrtHMdKAeqSJXYZDbeFd0kc5Enm8Kl6a0Jp0szgLilDw1S4CjZhe9Z2611EUGbjyEmg",
|
||||
"response": {
|
||||
"attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEYwRAIgfyIhwZj-fkEVyT1GOK8chDHJR2chXBLSRg6bTCjODmwCIHH6GXI_BQrcR-GHg5JfazKVQdezp6_QWIFfT4ltTCO2Y3g1Y4FZAlMwggJPMIIBN6ADAgECAgQSNtF_MA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAxMS8wLQYDVQQDDCZZdWJpY28gVTJGIEVFIFNlcmlhbCAyMzkyNTczNDEwMzI0MTA4NzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNNlqR5emeDVtDnA2a-7h_QFjkfdErFE7bFNKzP401wVE-QNefD5maviNnGVk4HJ3CsHhYuCrGNHYgTM9zTWriGjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMA0GCSqGSIb3DQEBCwUAA4IBAQAiG5uzsnIk8T6-oyLwNR6vRklmo29yaYV8jiP55QW1UnXdTkEiPn8mEQkUac-Sn6UmPmzHdoGySG2q9B-xz6voVQjxP2dQ9sgbKd5gG15yCLv6ZHblZKkdfWSrUkrQTrtaziGLFSbxcfh83vUjmOhDLFC5vxV4GXq2674yq9F2kzg4nCS4yXrO4_G8YWR2yvQvE2ffKSjQJlXGO5080Ktptplv5XN4i5lS-AKrT5QRVbEJ3B4g7G0lQhdYV-6r4ZtHil8mF4YNMZ0-RaYPxAaYNWkFYdzOZCaIdQbXRZefgGfbMUiAC2gwWN7fiPHV9eu82NYypGU32OijG9BjhGt_aGF1dGhEYXRhWMR0puqSE8mcL3SyJJKzIM9AJiqUwalQoDl_KSULYIQe8EEAAAAAAAAAAAAAAAAAAAAAAAAAAABAFOxcmsqPLNCHtyILvbNkrtHMdKAeqSJXYZDbeFd0kc5Enm8Kl6a0Jp0szgLilDw1S4CjZhe9Z2611EUGbjyEmqUBAgMmIAEhWCD_ap3Q9zU8OsGe967t48vyRxqn8NfFTk307mC1WsH2ISJYIIcqAuW3MxhU0uDtaSX8-Ftf_zeNJLdCOEjZJGHsrLxH",
|
||||
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiItUmk1TlpUeko4YjZtdlczVFZTY0xvdEVvQUxmZ0JhMkJuNFlTYUlPYkhjIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ"
|
||||
},
|
||||
"type": "public-key"
|
||||
}`,
|
||||
// None Attestation with EC256 - Titan.
|
||||
`{
|
||||
"id": "6Jry73M_WVWDoXLsGxRsBVVHpPWDpNy1ETGXUEvJLdTAn5Ew6nDGU6W8iO3ZkcLEqr-CBwvx0p2WAxzt8RiwQQ",
|
||||
"rawId": "6Jry73M_WVWDoXLsGxRsBVVHpPWDpNy1ETGXUEvJLdTAn5Ew6nDGU6W8iO3ZkcLEqr-CBwvx0p2WAxzt8RiwQQ",
|
||||
"response": {
|
||||
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQOia8u9zP1lVg6Fy7BsUbAVVR6T1g6TctRExl1BLyS3UwJ-RMOpwxlOlvIjt2ZHCxKq_ggcL8dKdlgMc7fEYsEGlAQIDJiABIVgg--n_QvZithDycYmnifk6vMHiwBP6kugn2PlsnvkrcSgiWCBAlBYm2B-rMtQlp5MxGTLoGDHoktxb0p364Hy2BH9U2Q",
|
||||
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiJzVnQ0U2NjZU16cUZTbmZBcThoZ0x6Ymx2bzNmYTRfYUZWRWNJRVNISUowIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ"
|
||||
},
|
||||
"type": "public-key"
|
||||
}`, `{
|
||||
"type": "public-key",
|
||||
"id": "GramThanos8pyTMpdk0qJLv3eLhUP3EXIXjD-uyqD0gab1pdvGy1ig77ZLl_ZU_vnd2296FoIZ67pZqTChpSJPq_oqUhjmr5Osv_LLiY7YGsAafMUdIb_LKOdwc6sfXyy_Ygl3_w-vl3tU9EPGyzgtI7hTBeMXnSIaOV6CUUf6d9op4JyxEDJr-roWxRMJPfnVAMLvv4lF_Cpd6Of0o75nDcCtEsTiynINihIwee1gmg0BAVKh3seWoNqXMpiXgPWc9Jt8ibjN9O-bsag3tELVs9uOoe-NZEmwbph0jJh_Y6e2H5Nwkp7WghST0P6krTL_sUlbpmDolhfFut0YljLrOrz_llW-WHySwvaAG2vzgvxA",
|
||||
"rawId": "GramThanos8pyTMpdk0qJLv3eLhUP3EXIXjD-uyqD0gab1pdvGy1ig77ZLl_ZU_vnd2296FoIZ67pZqTChpSJPq_oqUhjmr5Osv_LLiY7YGsAafMUdIb_LKOdwc6sfXyy_Ygl3_w-vl3tU9EPGyzgtI7hTBeMXnSIaOV6CUUf6d9op4JyxEDJr-roWxRMJPfnVAMLvv4lF_Cpd6Of0o75nDcCtEsTiynINihIwee1gmg0BAVKh3seWoNqXMpiXgPWc9Jt8ibjN9O-bsag3tELVs9uOoe-NZEmwbph0jJh_Y6e2H5Nwkp7WghST0P6krTL_sUlbpmDolhfFut0YljLrOrz_llW-WHySwvaAG2vzgvxA",
|
||||
"response": {
|
||||
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiRHc0TkRBc0tDUWdIQmdVRUF3SUJBQSIsIm9yaWdpbiI6Imh0dHBzOi8vZ3JhbXRoYW5vcy5naXRodWIuaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2UsInZpcnR1YWxfYXV0aGVudGljYXRvciI6IkdyYW1UaGFub3MgJiBVbml2ZXJzaXR5IG9mIFBpcmFldXMifQ",
|
||||
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEYwRAIgaTjQj-hC9GH1fCbOT_8m4wdVJBZMG0252iBEwIGKWkUCIApZyPGh_ihn57GRKN-qTVCwgBqe4V40LL-r9_Y2pRXiY3g1Y4FZAgUwggIBMIIBpqADAgECAgVixtGpsjAKBggqhkjOPQQDAjBQMQswCQYDVQQGEwJHUjESMBAGA1UECgwJVU5JUEkgU1NMMS0wKwYDVQQDEyRVTklQSSBGSURPMiBWaXJ0dWFsIEF1dGhlbnRpY2F0b3IgQ0EwIhgPMjAyMDEyMzEyMjAwMDBaGA8yMTIwMTIzMTIyMDAwMFowcTELMAkGA1UEBhMCR1IxEjAQBgNVBAoMCVVOSVBJIFNTTDEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEqMCgGA1UEAwwhVU5JUEkgRklETzIgVmlydHVhbCBBdXRoZW50aWNhdG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE_l8G-E0tTiXogmgXZZ0nRUMc7NO5-sowWP0lZhX8GZbU_n2TPO1J-39UbRABHUK_2J-ZbzcDAu2oy_nazsz4CqNIMEYwIQYLKwYBBAGC5RwBAQQEEgQQCJhwWMrcS4G24TDeUNy-ljATBgsrBgEEAYLlHAIBAQQEAwIFIDAMBgNVHRMBAf8EAjAAMAoGCCqGSM49BAMCA0kAMEYCIQDsyXh97GlMAcRq8khd4U-26d1E92a0lupZUGNBlki_MQIhAJFqO_qmBakyeD1esP4v3gIWsYKmHpiwJ64UKlid5NobaGF1dGhEYXRhWQGWou-FTChrR7AO-C0KXtsaxN1QIX4DOq_aCmYeKeUXnlZFAAAAAQiYcFjK3EuBtuEw3lDcvpYBEhq2pk4Wp6LPKckzKXZNKiS793i4VD9xFyF4w_rsqg9IGm9aXbxstYoO-2S5f2VP753dtvehaCGeu6WakwoaUiT6v6KlIY5q-TrL_yy4mO2BrAGnzFHSG_yyjncHOrH18sv2IJd_8Pr5d7VPRDxss4LSO4UwXjF50iGjleglFH-nfaKeCcsRAya_q6FsUTCT351QDC77-JRfwqXejn9KO-Zw3ArRLE4spyDYoSMHntYJoNAQFSod7HlqDalzKYl4D1nPSbfIm4zfTvm7GoN7RC1bPbjqHvjWRJsG6YdIyYf2Onth-TcJKe1oIUk9D-pK0y_7FJW6Zg6JYXxbrdGJYy6zq8_5ZVvlh8ksL2gBtr84L8SlAQIDJiABIVgg_l8G-E0tTiXogmgXZZ0nRUMc7NO5-sowWP0lZhX8GZYiWCDU_n2TPO1J-39UbRABHUK_2J-ZbzcDAu2oy_nazsz4Cg"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
Reference in New Issue
Block a user