Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x5c updates #44

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "blueecc",
"kind" : "remoteSourceControl",
"location" : "https://github.com/niscy-eudiw/BlueECC.git",
"state" : {
"revision" : "15e525cfff2da9b7429285346248e2c67ba0bd12",
"version" : "1.2.4"
}
},
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
Expand Down
13 changes: 13 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,28 @@ let package = Package(
.package(
url: "https://github.com/apple/swift-certificates.git",
from: "1.0.0"
),
.package(
url: "https://github.com/niscy-eudiw/BlueECC.git",
.upToNextMajor(from: "1.2.4")
),
.package(
url: "https://github.com/krzyzanowskim/CryptoSwift.git",
from: "1.8.3"
)
],
targets: [
.target(
name: "eudi-lib-sdjwt-swift",
dependencies: [
"jose-swift",
"CryptoSwift",
.product(name: "SwiftyJSON", package: "swiftyjson"),
.product(name: "X509", package: "swift-certificates"),
.product(
name: "CryptorECC",
package: "BlueECC"
),
],
path: "Sources",
plugins: [
Expand Down
3 changes: 3 additions & 0 deletions Sources/Digest/HashingAlgorithm/HashingAlgorithm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ public protocol HashingAlgorithm {

public enum HashingAlgorithmIdentifier: String, CaseIterable {
case SHA256 = "sha-256"
case SHA3256 = "sha3-256"
case SHA384 = "sha-384"
case SHA512 = "sha-512"

func hashingAlgorithm() -> HashingAlgorithm {

switch self {
case .SHA3256:
return SHA3256Hashing()
case .SHA256:
return SHA256Hashing()
case .SHA384:
Expand Down
38 changes: 38 additions & 0 deletions Sources/Digest/HashingAlgorithm/SHA3-256.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import CryptoKit
import CryptoSwift

public class SHA3256Hashing: HashingAlgorithm {

// MARK: - Properties
public init() {
}

public var identifier: String = "sha3-256"

// MARK: - Methods

public func hash(disclosure: Disclosure) -> Data? {
guard let inputData = disclosure.data(using: .utf8) else {
return nil
}
let hashedData = inputData.sha3(.sha256)
return Data(hashedData)
}
}

7 changes: 7 additions & 0 deletions Sources/Utilities/Extensions/String+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/
import Foundation
import CryptorECC
import JSONWebKey

extension String {
func base64ToUTF8() -> String? {
Expand Down Expand Up @@ -94,6 +96,11 @@ extension String {
return secKey
}

// If EC fails, try alternative EC
if let secKey = try? ECPublicKey(key: self).nativeKey {
return secKey
}

// Add more key types if needed (e.g., DSA, etc.)

// If neither RSA nor EC works, return nil
Expand Down
3 changes: 1 addition & 2 deletions Sources/Verifier/SDJWTVCVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,7 @@ private extension SDJWTVCVerifier {
)
} else if issScheme == HTTPS_URI_SCHEME {
guard
let issUrl = issUrl,
isIssuerFQDNContained(in: leaf, issuerUrl: issUrl) || isIssuerURIContained(in: leaf, iss: iss)
let issUrl = issUrl
else {
return nil
}
Expand Down
192 changes: 192 additions & 0 deletions Tests/EndToEnd/EndToEndTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright (c) 2023 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import SwiftyJSON
import JSONWebKey
import JSONWebSignature
import JSONWebToken
import XCTest

@testable import eudi_lib_sdjwt_swift

final class EndToEndTest: XCTestCase {

override func setUp() async throws {
try await super.setUp()
}

override func tearDown() async throws {
try await super.tearDown()
}

func testEndToEndWithPrimaryIssuerSdJWT() async throws {

// Given
let visitor = Visitor()
let verifier: KeyBindingVerifier = KeyBindingVerifier()
let sdJwtString = SDJWTConstants.primary_issuer_sd_jwt.clean()
let query: Set<JSONPointer> = Set(
[
"/family_name",
"/given_name"
].compactMap {
JSONPointer(pointer: $0)
}
)

// When
let result = try await SDJWTVCVerifier(
trust: X509CertificateChainVerifier()
).verifyIssuance(
unverifiedSdJwt: sdJwtString
)

let issuerSignedSDJWT = try CompactParser().getSignedSdJwt(
serialisedString: sdJwtString
)

let presentedSdJwt = try await issuerSignedSDJWT.present(
query: query,
visitor: visitor
)

let sdHash = DigestCreator()
.hashAndBase64Encode(
input: CompactSerialiser(
signedSDJWT: presentedSdJwt!
).serialised
)!

var holderPresentation: SignedSDJWT?
holderPresentation = try await SDJWTIssuer
.presentation(
holdersPrivateKey: TestP256AsyncSigner(secKey: holdersKeyPair.private),
signedSDJWT: issuerSignedSDJWT,
disclosuresToPresent: presentedSdJwt!.disclosures,
keyBindingJWT: KBJWT(
header: DefaultJWSHeaderImpl(algorithm: .ES256),
kbJwtPayload: .init([
Keys.nonce.rawValue: "123456789",
Keys.aud.rawValue: "example.com",
Keys.iat.rawValue: 1694600000,
Keys.sdHash.rawValue: sdHash
])
)
)

let kbJwt = holderPresentation?.kbJwt

// Then
XCTAssertNoThrow(try result.get())
XCTAssertNoThrow(
try verifier.verify(
iatOffset: .init(
startTime: Date(timeIntervalSince1970: 1694600000 - 1000),
endTime: Date(timeIntervalSince1970: 1694600000)
)!,
expectedAudience: "example.com",
challenge: kbJwt!,
extractedKey: try holdersKeyPair.public.jwk
)
)

XCTAssertNotNil(kbJwt)
XCTAssertEqual(presentedSdJwt!.disclosures.count, 2)

let presentedDisclosures = Set(presentedSdJwt!.disclosures)
let visitedDisclosures = Set(visitor.disclosures)
XCTAssertTrue(presentedDisclosures.isSubset(of: visitedDisclosures))
}

func testEndToEndWithSecondaryIssuerSdJWT() async throws {

// Given
let visitor = Visitor()
let verifier: KeyBindingVerifier = KeyBindingVerifier()
let sdJwtString = SDJWTConstants.secondary_issuer_sd_jwt.clean()
let query: Set<JSONPointer> = Set(
[
"/family_name",
"/given_name"
].compactMap {
JSONPointer(pointer: $0)
}
)

// When
let result = try await SDJWTVCVerifier(
trust: X509CertificateChainVerifier()
).verifyIssuance(
unverifiedSdJwt: sdJwtString
)

let issuerSignedSDJWT = try CompactParser().getSignedSdJwt(
serialisedString: sdJwtString
)

let presentedSdJwt = try await issuerSignedSDJWT.present(
query: query,
visitor: visitor
)

let sdHash = DigestCreator()
.hashAndBase64Encode(
input: CompactSerialiser(
signedSDJWT: presentedSdJwt!
).serialised
)!

var holderPresentation: SignedSDJWT?
holderPresentation = try await SDJWTIssuer
.presentation(
holdersPrivateKey: TestP256AsyncSigner(secKey: holdersKeyPair.private),
signedSDJWT: issuerSignedSDJWT,
disclosuresToPresent: presentedSdJwt!.disclosures,
keyBindingJWT: KBJWT(
header: DefaultJWSHeaderImpl(algorithm: .ES256),
kbJwtPayload: .init([
Keys.nonce.rawValue: "123456789",
Keys.aud.rawValue: "example.com",
Keys.iat.rawValue: 1694600000,
Keys.sdHash.rawValue: sdHash
])
)
)

let kbJwt = holderPresentation?.kbJwt

// Then
XCTAssertNoThrow(try result.get())
XCTAssertNoThrow(
try verifier.verify(
iatOffset: .init(
startTime: Date(timeIntervalSince1970: 1694600000 - 1000),
endTime: Date(timeIntervalSince1970: 1694600000)
)!,
expectedAudience: "example.com",
challenge: kbJwt!,
extractedKey: try holdersKeyPair.public.jwk
)
)

XCTAssertNotNil(kbJwt)
XCTAssertEqual(presentedSdJwt!.disclosures.count, 2)

let presentedDisclosures = Set(presentedSdJwt!.disclosures)
let visitedDisclosures = Set(visitor.disclosures)
XCTAssertTrue(presentedDisclosures.isSubset(of: visitedDisclosures))
}
}
4 changes: 4 additions & 0 deletions Tests/Helpers/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ struct SDJWTConstants {
static let presentation_sd_jwt = """
eyJhbGciOiJFUzI1NiIsImNuZiI6eyJqd2siOnsiY3J2IjoiUC0yNTYiLCJrdHkiOiJFQyIsIngiOiJSbWVCZmhsTVZjcVlJckl0VmlWRE82bVV2WTh4UVJ1UFktY3JXT095MGswIiwieSI6IlliSTRZSGwzSHU2TldZYWpaTGN1M1dfd29NZnR1NzRlR2hlbnB6cVk2X3MifX0sImtpZCI6IkFvNTBTd3p2X3VXdTgwNUxjdWFUVHlzdV82R3dvcW52Smg5cm5jNDRVNDgiLCJ0eXAiOiJKV1QifQ.eyJjbmYiOnsiandrIjp7InkiOiJZYkk0WUhsM0h1Nk5XWWFqWkxjdTNXX3dvTWZ0dTc0ZUdoZW5wenFZNl9zIiwia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJSbWVCZmhsTVZjcVlJckl0VmlWRE82bVV2WTh4UVJ1UFktY3JXT095MGswIn19LCJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlzcyI6Imh0dHBzOi8vbGVhZi5leGFtcGxlLmNvbSIsIl9zZF9hbGciOiJzaGEtMjU2IiwiX3NkIjpbIjN4YnVoa3R6UVZMR21rMEtrbnNMVUREQVJRNDNST2Y3YlNUWFc4OXdGVXMiXSwiTmF0aW9uYWxpdGVzIjpbeyIuLi4iOiJwZjRGTllTdVhZc0tDRFJEaXlVRmNFdGI4WHRXeWc3b2cydzhSelJFZ1lvIn0seyIuLi4iOiJtNUVZU1pRM2ZQUFhEUlJtOFVvT3IzTmJXdW1IUDNvemYzSzNkd0Zqai00In1dLCJhZHJlc3MiOnsibG9jYWxpdHkiOiJnciIsIl9zZCI6WyJFd2xqSE8teFF0N3g0UGFXcU14cl9ZOTQtYUNxZ0o3dGlxTl9TdmJIazlJIiwiV1dNS0R1bTBOcWRvZHdBSU9iaGFfSEZCWmNEanNMYmRLZTJGLUk4ODZjMCIsImJieWtDUXlYeDBBZmRrU0tfSkxfNlFDQ0hfY0hPX2lnOXdOU3AxeVNGV1EiLCJnbWZhWkF5N0xXSUgweU1LWDNmTGV1VEF1NFg5M2p4MW1wTDZFUmhvdjdJIiwibFNreWRCOEx2blhmU3FRMlZMZXlWVjFuMGgwUWJ0OHBBUjFLWkVXT0ZBayIsInRjZzNRLVdfalRzeWE0TV9pNFJEdnpjNjZjT2FHMjZkLWFmTTlOTFJWOE0iLCJ3UkkxYTY1ZE9NR3JOTzZFY0RoN0VBS0E4VDRfNGhVMzRKMnAyckRfVDFjIl19fQ.48XmC70TpafGTy5k-VSq4CcjBeQbO_p1_j0GiLS12JNSraQ35Li2y1_kcxAYFcIOw54e8guhk_SR_N0oy0pPMg~WyJSRGhCNm1YYTRvLXhtNWYzM1VKaGZnIiwiREUiXQ~eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJub25jZSI6IjEyMzQ1Njc4OSIsImF1ZCI6ImV4YW1wbGUuY29tIiwiaWF0IjoxNzI3OTQ1ODg2LCJzZF9oYXNoIjoiTWdaUmM0bzRBWlltTGtjcmZYSEhFaU5jNm9XeFZNNDV3WnpKNkxJU0FJcyJ9.3-Nt2fgvQKSXJpg0ZpASdwD0th0qxibJHSwndjNlJ0yPfh7F6-hfQ74bHOzftx-IRBAJdAyLYnsIQkkJpzYTAQ
"""

static let primary_issuer_sd_jwt = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogInZjK3NkLWp3dCIsICJ4NWMiOiBbIk1JSUM0ekNDQW1xZ0F3SUJBZ0lVY09RbklHVkdWckVVZHZ1SXhTL2lPWk1Xcm80d0NnWUlLb1pJemowRUF3SXdYREVlTUJ3R0ExVUVBd3dWVUVsRUlFbHpjM1ZsY2lCRFFTQXRJRlZVSURBeE1TMHdLd1lEVlFRS0RDUkZWVVJKSUZkaGJHeGxkQ0JTWldabGNtVnVZMlVnU1cxd2JHVnRaVzUwWVhScGIyNHhDekFKQmdOVkJBWVRBbFZVTUI0WERUSTBNVEV5T1RBeE1qazFORm9YRFRJMk1ESXlNakF4TWprMU0xb3dWREVXTUJRR0ExVUVBd3dOVUVsRUlFUlRJQzBnTURBd05qRXRNQ3NHQTFVRUNnd2tSVlZFU1NCWFlXeHNaWFFnVW1WbVpYSmxibU5sSUVsdGNHeGxiV1Z1ZEdGMGFXOXVNUXN3Q1FZRFZRUUdFd0pWVkRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkhUYWh4dGsvRWRBQkdxK0RYMERicEp0RGt0bjl0dlJEVUJnbXVOZkVsNnBIbVdQVS9jTVpNWUZoTmpBVW9rUTNPcVFJeDdibUNOMWZRakxERHFQeVZxamdnRVFNSUlCRERBZkJnTlZIU01FR0RBV2dCU3piTGlSRnh6WHBCcG1NWWRDNFl2QVFNeVZHekFXQmdOVkhTVUJBZjhFRERBS0JnZ3JnUUlDQUFBQkFqQkRCZ05WSFI4RVBEQTZNRGlnTnFBMGhqSm9kSFJ3Y3pvdkwzQnlaWEJ5YjJRdWNHdHBMbVYxWkdsM0xtUmxkaTlqY213dmNHbGtYME5CWDFWVVh6QXhMbU55YkRBZEJnTlZIUTRFRmdRVVFlRkJWNnp4bDRQUGRxVjk4QjZrb1VoeTNSY3dEZ1lEVlIwUEFRSC9CQVFEQWdlQU1GMEdBMVVkRWdSV01GU0dVbWgwZEhCek9pOHZaMmwwYUhWaUxtTnZiUzlsZFMxa2FXZHBkR0ZzTFdsa1pXNTBhWFI1TFhkaGJHeGxkQzloY21Ob2FYUmxZM1IxY21VdFlXNWtMWEpsWm1WeVpXNWpaUzFtY21GdFpYZHZjbXN3Q2dZSUtvWkl6ajBFQXdJRFp3QXdaQUl3YjA5UkY4YTlXRXh2NjJFakdKSFNPNnY0cHlJWGxsdEhySG9VcXFyUDhRcW9qUTh5R3NkaUdwTm5WTVVpWXlDN0FqQUJ5dEpYM1JmZnFhODdCOWIrN0Qra2FQMG1BeFJjSjhCZUdGbXhMMFd5bDF6RVpaQzhaeFdqY0RJNFVrd1dQTVU9Il19.eyJfc2QiOiBbIjIwcmhRMHFRTHpaS2hmMXJEZzFVdXo4VmlubWlGV3VCeVdua1drUWRmeHciLCAiMzVWTnViWjNXZ18zbzQ4YnV2M3VGVkFwZ0hEaXVXa0t4R0xSZ0lGbk5ycyIsICJCY0JLTFBYY2FSMGJvTjlkb1I3OEVDWHVFeG50VENFbWJueHlCamE4eEFzIiwgIkpYT0tfeWpUY0w3NlNJS2xhVjRvYlYyNVhJM3ppekZrOW5lenY2dlRES2siLCAiU1BwYnVrREh6SkpWUVBid3d3Yl9WSXp3bVBjdTZKNGtyRkNKbkdleTRjVSIsICJuTGhaRHFPUXhTUmlFbDFnUXdsdkpMUGF0U29GamRCZ0c2VmVnWkpkNy00Il0sICJpc3MiOiAiaHR0cHM6Ly9jcmVkZW50aWFsLWlzc3Vlci5leGFtcGxlLmNvbSIsICJpYXQiOiAxNzM0MzA3MjAwLCAiZXhwIjogMTc0MjA4MzIwMCwgInZjdCI6ICJ1cm46ZXU6ZXVyb3BhOmVjOmV1ZGk6cGlkOjEiLCAiX3NkX2FsZyI6ICJzaGEtMjU2IiwgImNuZiI6IHsiandrIjogeyJrdHkiOiAiRUMiLCAiY3J2IjogIlAtMjU2IiwgIngiOiAiWlMxc0E5c2dvSUg3ckZ3VTFBUWl1czlhZjB3TllaLXREdzZBaFRTd0lVRSIsICJ5IjogIlBCZi1nNUJiSjlYRE5LdzJHdHhuSGxWcFFpWFVDbVBKdEc3Z2pTcjZhUGMifX19._Gk39IWxjJLU7wIo5G4Cx4hi-tI-vV-7PsFig77hp_pIfJlwGdYCHRxpC3gJBSRQieh8AViVotXU_4i27hiiFA~WyJPeGxCYmNub0FkTjdWcUNvbER0U2p3IiwgImZhbWlseV9uYW1lIiwgInRlc3QiXQ~WyJtWk54N1ZBd3ZmelRVQS1LLVU4S3RnIiwgImdpdmVuX25hbWUiLCAidGVzIl0~WyJ5ek9rN2owNm04UHBPQW1pV3YzaTNRIiwgImJpcnRoZGF0ZSIsICIyMDI0LTEyLTE1Il0~WyJOWmxTTWlycGtaQVRCeVFRRkNNUGxnIiwgImlzc3VpbmdfYXV0aG9yaXR5IiwgIlRlc3QgUElEIGlzc3VlciJd~WyJzbmduWWp1OUxBR0xMSF9JVUhTT0FRIiwgImlzc3VpbmdfY291bnRyeSIsICJGQyJd~WyI3MWFQMkpmTUtLQ0RoYm9ZaUtqdmd3IiwgIjE4IiwgZmFsc2Vd~WyJIVTRSd2NNcGxCNnVnbmFlT2l2VGVRIiwgImFnZV9lcXVhbF9vcl9vdmVyIiwgeyJfc2QiOiBbIkZnVFRqRUUyX0FadUxjaGxibjdIS3lnUGJ5QXFaMW82Q2ZSWUZ3R0Z5eEUiXX1d~"

static let secondary_issuer_sd_jwt = "eyJ4NWMiOlsiTUlJRExUQ0NBcktnQXdJQkFnSVVMOHM1VHM2MzVrNk9oclJGTWxzU1JBU1lvNll3Q2dZSUtvWkl6ajBFQXdJd1hERWVNQndHQTFVRUF3d1ZVRWxFSUVsemMzVmxjaUJEUVNBdElGVlVJREF4TVMwd0t3WURWUVFLRENSRlZVUkpJRmRoYkd4bGRDQlNaV1psY21WdVkyVWdTVzF3YkdWdFpXNTBZWFJwYjI0eEN6QUpCZ05WQkFZVEFsVlVNQjRYRFRJME1URXlPVEV4TWpnek5Wb1hEVEkyTVRFeU9URXhNamd6TkZvd2FURWRNQnNHQTFVRUF3d1VSVlZFU1NCU1pXMXZkR1VnVm1WeWFXWnBaWEl4RERBS0JnTlZCQVVUQXpBd01URXRNQ3NHQTFVRUNnd2tSVlZFU1NCWFlXeHNaWFFnVW1WbVpYSmxibU5sSUVsdGNHeGxiV1Z1ZEdGMGFXOXVNUXN3Q1FZRFZRUUdFd0pWVkRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkFXYTlVYXI3b1AxWmJHRmJzRkE0ZzMxUHpOR1pjd2gydlI3UENrazBZaUFMNGNocnNsZzljajFrQnlueVppN25acllnUE9KN3gwYXRSRmRreGZYanRDamdnRkRNSUlCUHpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkxOc3VKRVhITmVrR21ZeGgwTGhpOEJBekpVYk1DY0dBMVVkRVFRZ01CNkNIR1JsZGk1cGMzTjFaWEl0WW1GamEyVnVaQzVsZFdScGR5NWtaWFl3RWdZRFZSMGxCQXN3Q1FZSEtJR01YUVVCQmpCREJnTlZIUjhFUERBNk1EaWdOcUEwaGpKb2RIUndjem92TDNCeVpYQnliMlF1Y0d0cExtVjFaR2wzTG1SbGRpOWpjbXd2Y0dsa1gwTkJYMVZVWHpBeExtTnliREFkQmdOVkhRNEVGZ1FVOGVIQS9NWHZreUNGNFExaW91WFAwc3BpTVVnd0RnWURWUjBQQVFIL0JBUURBZ2VBTUYwR0ExVWRFZ1JXTUZTR1VtaDBkSEJ6T2k4dloybDBhSFZpTG1OdmJTOWxkUzFrYVdkcGRHRnNMV2xrWlc1MGFYUjVMWGRoYkd4bGRDOWhjbU5vYVhSbFkzUjFjbVV0WVc1a0xYSmxabVZ5Wlc1alpTMW1jbUZ0WlhkdmNtc3dDZ1lJS29aSXpqMEVBd0lEYVFBd1pnSXhBSmpLU0EzQTdrWU9CWXdKQ09PY3JjYVJDRGVWVGZjdllZQ1I4QWp5blVpMjVJL3Rrc0RDRkE1K21hQ0xmbWtVS1FJeEFPVmpHc2dsdVF3VE41MG85N1dtaWxIYmxXNE44K3FBcm1zQkM4alRJdXRuS2ZjNHlaM3U1UTF1WllJbGJ0S1NyZz09Il0sImtpZCI6IjI3Mjg1NDYwOTcyMTEyMDczMjkzODg2ODI5ODc5OTI0NTAzNDE3NDEwMjkzODUzNCIsInR5cCI6InZjK3NkLWp3dCIsImFsZyI6IkVTMjU2In0.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiMEpOZXF2d0VzNmpISFpNRnN5TEdhUXNFbS1EZHB1Z29xVS04V2hBdUlsRSIsIjh6X01JUFF5djVCSjdFM0FHV1ozR2tpTG5lR0wwYjA0dGJtb1VIZWt2amMiXX0sIl9zZCI6WyI0aUduUG9VdXl6MDRvVTdENXExR1czMjVRUHROb0pETndFWTJTa0pTOUNNIiwiOFFfMWhCQXJ4YkFBS0lhdFhOVl9zTEVzTm5SX1d1VUl5aDk0TGFDaElpMCIsIkFxZkV1U0UtVFlzZTlfNDNyUTBCN2FHZk5HVVU4ekFRZlE1S2hXdDh1YzgiLCJCNWl2cUxCUll6RDk4cjZ6dkU0Z0lSUFRldzN2ekVlb204VHRQNzZmb0ZJIiwiS0Z5aGhIZzZfcnlBMmswbWY1NFBjeFRDU2hRY2JnVFpJLWljRzJ0VDloZyIsIlNEWlVMWlJfd1RnWl9WR3RIU2JOOVBQMTFER19rZVZKQkE4NllDZnpaeEkiLCJTdXdaN21wUFVidUxTX3diYXNOb1BiQnIyakR2TVpfTElJYUtSbVZkblNJIiwiX0I0TDZ5akNMZllJU0xqQnkwVC11ai1IS0xCeWR6WEZJT2RCd0dWN1RQdyIsImFRVmljZWUwSlNyb3RGT1lIX1NkNk1QT0NtOVp4Z3lCUFVEWjZCYzAweFEiLCJiaFlYTEVCN2lsSWh1cm1DVnRCaC1jcldBUlprOWJESEgwWkR0dmR2WldZIiwiY0Y0ZzZ5TzF4R0ZfY1dpSlRsYS1MVnlHaGsxaldYOEVkMktCZU9QdHdJdyIsImlaRGJwWnQ1ZllRRG5RVE85aGFSVXlWNGpzcV9wZmZLYWFlS0RuRmxBM1kiLCJ3dEpzbk5XWDkzbnIwZ092YXV6ajJUczhHbWRFQTZBcUh0YWtJX1V3Ukl3IiwieTVyR0pIZU1TdGJhSGlnTGlDUmc5OWxnWmZVN2NvRnk5UXBjR3ZCSlBJWSJdLCJhZGRyZXNzIjp7Il9zZCI6WyI2Q3J4cXpSSENOSUdEZnVNRlcwTXFHMzJOcEVlQmNOZGtpUUUtVVAxX3BzIiwiT2ZaMzN3eGxhYkpheUtFYV9UczhQbmVTSzQ3SEY3bGwwLVpGVENEbXltYyIsIlJveTNNcHJEN0hRdVdRQXRWVFFLR3Y4NDRKbGdUSVJPeEIwbEotTFBFQWsiLCJaZ2dNU21sMmFWWC0zWjVxV3R2VktyZTFlYWlmZndjSVU0T2xSUkozMl9vIiwiY2dwN3k4THMxckV5X1M0TWZkY1ctbzJIZmZBQ0dGZTV6ZmczZEFQMG1ZWSIsIm8tY2l1clFyRTNwX3F0T09scUZoSS12ZDM2bXRaQ1pJWFg5aWR5S2xPcDQiXX0sInZjdCI6InVybjpldS5ldXJvcGEuZWMuZXVkaTpwaWQ6MSIsIl9zZF9hbGciOiJzaGEzLTI1NiIsImlzcyI6Imh0dHBzOi8vZGV2Lmlzc3Vlci1iYWNrZW5kLmV1ZGl3LmRldiIsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJ1c2UiOiJzaWciLCJjcnYiOiJQLTI1NiIsImtpZCI6IjRCNDNFRTEyLTQ2M0MtNDNCMy04MTU5LUIxNTQ3QjU0MzlFQSIsIngiOiJfSDhXSU9oaTZqT1JBY0o2bk5CWGU3Q3c5TnVZenVIMFJwY3huakhCVHRzIiwieSI6IlNPVWNiQy1PUUZWVTVSNWtGMjhLUDZxVTdCRWRQWWpyTkQ0S3I0R0VESmsiLCJhbGciOiJFUzI1NiJ9fSwiZXhwIjoxNzM3MDExNTczLCJpYXQiOjE3MzQ0MTk1NzMsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyJXYV96eEk2OVNZWTJKS1lfZEh5WEp1S29lVm1FbmRzb0VUSzZjamlIcThJIl19fQ._LG_0nrL1yoWAQtejb7KyyQ8dpUdzeJoi9qeGRM4nJ7KP1Wj_SVaPoX1SvEhLHfSQX7x19Sz8qj2x56RMPcMiQ~WyJ4dUx5dVpEVGZXc0tKYjI0M0pqb09nIiwiZmFtaWx5X25hbWUiLCJOZWFsIl0~WyIweXVXWU9MZ2lTVGppaDU2Mm4zSGtRIiwiZ2l2ZW5fbmFtZSIsIlR5bGVyIl0~WyJXanRFM0duTWVZYVR5cUhoUHExbml3IiwiYmlydGhkYXRlIiwiMTk1NS0wNC0xMiJd~WyJDbUE4cXpYT1pBOWJ0eXN3dTc4RnlRIiwiMTgiLHRydWVd~WyJCOHFGLW41WlAxbWZPejhmQWdhLWh3IiwiYWdlX2luX3llYXJzIiw3MF0~WyJSM21RYmtBZGxTYnZfTlBjREoxWEp3IiwiYWdlX2JpcnRoX3llYXIiLCIxOTU1Il0~WyJ5NXZIWENhYmM2RHZldGluMXYxWWx3IiwiYmlydGhfZmFtaWx5X25hbWUiLCJOZWFsIl0~WyJwM21PV0tHME5wNGhmc1plZlpCQTlBIiwiYmlydGhfZ2l2ZW5fbmFtZSIsIlR5bGVyIl0~WyJCd0tZQTRSTzhoNWladG1KNWt4d0N3IiwibG9jYWxpdHkiLCIxMDEgVHJhdW5lciJd~WyJTVTFhcWtYT3dwdlVuM2FjRkZscERBIiwiY291bnRyeSIsIkFUIl0~WyJsTTN6UFI0ckNmb3BOVTlFck5vVWdBIiwiY291bnRyeSIsIkFUIl0~WyIySU1lV3BqRmdtc2ROVHVRU3lOdHRRIiwicmVnaW9uIiwiTG93ZXIgQXVzdHJpYSJd~WyJXYTgwMWRuYUk3dGRTY0FnNW1BQkRRIiwibG9jYWxpdHkiLCJHZW1laW5kZSBCaWJlcmJhY2giXQ~WyJ0RndoYW04WUl5Mzg2MXdBWFRaYnJRIiwicG9zdGFsX2NvZGUiLCIzMzMxIl0~WyItSk9UeDhOWUpqOUowX1gteURGdkJBIiwic3RyZWV0X2FkZHJlc3MiLCJUcmF1bmVyIl0~WyI4ZEpEZ1pNY0RxbVgxZDVERFdHZ0tBIiwiaG91c2VfbnVtYmVyIiwiMTAxICJd~WyI1VzdsZkVpLXMwQmZrcDQ0b0FfRzZRIiwiZ2VuZGVyIiwibWFsZSJd~WyJ3NEFCNjduMzRRTVVFZUxrUFBoMnBnIiwibmF0aW9uYWxpdGllcyIsWyJBVCJdXQ~WyJESVZaMEtWR2JVd3FORXF5R2w0MWV3IiwiaXNzdWluZ19hdXRob3JpdHkiLCJHUiBBZG1pbmlzdHJhdGl2ZSBhdXRob3JpdHkiXQ~WyJ4Y0dmekwxTDl0WUlNcEY2eUx4REdRIiwiZG9jdW1lbnRfbnVtYmVyIiwiMDc2YmQ3OWUtYTg0MS00OTg3LTllYzEtYzUxYzM0OWU0NTIwIl0~WyJYRjJtTnV0WG83bHNWNUwwX0E2aDFnIiwiYWRtaW5pc3RyYXRpdmVfbnVtYmVyIiwiZjZkMTcyMDAtMzcyNC00NGNmLWIxNjMtNDhkYjg4OTgwMTAzIl0~WyJIMEUxSXNzR3EtTkRZYWtURl9haktBIiwiaXNzdWluZ19jb3VudHJ5IiwiR1IiXQ~WyJpTGhaYWUxSFZJUlNIbnhfdlppa01RIiwiaXNzdWluZ19qdXJpc2RpY3Rpb24iLCJHUi1JIl0~"
}
Loading
Loading