├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── account.go ├── account_test.go ├── client.go ├── client_test.go ├── ecb └── ecb.go ├── encoding.go ├── errors.go ├── errors_test.go ├── example_test.go ├── examples ├── create_read_update_delete │ └── example.go ├── credentials.txt.example ├── logging │ └── example.go └── trust │ └── example.go ├── go.mod ├── go.sum ├── lastpass_go_suite_test.go ├── log.go ├── log_test.go ├── scripts └── create-unit-test-data.sh ├── session.go └── test ├── integration ├── integration_suite_test.go └── integration_test.go └── unit ├── data ├── blob-1iteration.txt ├── blob-3accts.txt ├── blob-ecb.txt ├── blob-groupaccount.txt ├── blob-sharedaccounts.txt ├── blob-sharingkeyrsaencrypted.txt ├── id-name0.txt ├── id-name1.txt ├── id-name2.txt ├── id-name3.txt ├── id-nameecb.txt ├── id-nameshared0.txt ├── id-nameshared1.txt ├── id-nameshared2.txt ├── passwd.txt ├── privatekeyencrypted-1iteration.txt ├── privatekeyencrypted.txt └── user.txt ├── dumpblob └── dumpblob.go ├── ecb └── ecb.go └── login └── login.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "17:00" 8 | pull-request-branch-name: 9 | separator: "-" 10 | open-pull-requests-limit: 10 11 | reviewers: 12 | - ansd 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.17.x] 8 | os: [ubuntu-latest, macos-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Unit Test 18 | run: go test . 19 | - name: Integration Test 20 | run: cd test/integration && go test . 21 | env: 22 | LASTPASS_USERNAME_1: ${{ secrets.LASTPASS_USERNAME_1 }} 23 | LASTPASS_MASTER_PASSWORD_1: ${{ secrets.LASTPASS_MASTER_PASSWORD_1 }} 24 | LASTPASS_USERNAME_2: ${{ secrets.LASTPASS_USERNAME_2 }} 25 | LASTPASS_MASTER_PASSWORD_2: ${{ secrets.LASTPASS_MASTER_PASSWORD_2 }} 26 | LASTPASS_SHARE: ${{ secrets.LASTPASS_SHARE }} 27 | LASTPASS_SHARE_READ_ONLY: ${{ secrets.LASTPASS_SHARE_READ_ONLY }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/credentials.txt 2 | **/.envrc 3 | *.pdf 4 | 5 | # executables 6 | examples/crud/crud 7 | examples/log/log 8 | test/unit/dumpblob/dumpblob 9 | test/unit/ecb/ecb 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 David Ansari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation](https://godoc.org/github.com/ansd/lastpass-go?status.svg)](https://pkg.go.dev/github.com/ansd/lastpass-go) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/ansd/lastpass-go)](https://goreportcard.com/report/github.com/ansd/lastpass-go) 3 | ![Test](https://github.com/ansd/lastpass-go/workflows/Test/badge.svg) 4 | # Go client for LastPass 5 | 6 | ## Features 7 | - login with 8 | - user name and master password 9 | - two-factor authentication with out-of-band mechanism such as push notification to LastPass Authenticator or Duo Security 10 | - two-factor authentication with one-time password from LastPass Authenticator, Google Authenticator, Microsoft Authenticator, YubiKey, Duo Security, Sesame, etc. 11 | - trust: after first successful login with two-factor authentication, the second factor can be skipped 12 | - create account 13 | - read accounts 14 | - update account 15 | - delete account 16 | - create / read / update / delete account in shared folder 17 | - logout 18 | 19 | ## Documentation 20 | https://pkg.go.dev/github.com/ansd/lastpass-go 21 | 22 | ## Installation 23 | 24 | Install: 25 | 26 | ```shell 27 | $ go get github.com/ansd/lastpass-go 28 | ``` 29 | 30 | Import: 31 | 32 | ```go 33 | import "github.com/ansd/lastpass-go" 34 | ``` 35 | 36 | ## Usage 37 | 38 | Below, error handling is excluded for brevity. 39 | 40 | See [examples](./examples) directory for more examples. 41 | 42 | ```go 43 | // authenticate with LastPass servers 44 | client, _ := lastpass.NewClient(context.Background(), "user name", "master password") 45 | 46 | // two-factor authentication with one-time password as second factor: 47 | // client, _ := lastpass.NewClient(context.Background(), "user name", "master password", lastpass.WithOneTimePassword("123456")) 48 | 49 | account := &lastpass.Account{ 50 | Name: "my site", 51 | Username: "my user", 52 | Password: "my pwd", 53 | URL: "https://myURL", 54 | Group: "my group", 55 | Notes: "my notes", 56 | } 57 | 58 | // Add() account 59 | client.Add(context.Background(), account) 60 | 61 | // read all Accounts() 62 | accounts, _ := client.Accounts(context.Background()) 63 | 64 | // Update() account 65 | account.Password = "updated password" 66 | client.Update(context.Background(), account) 67 | 68 | // Delete() account 69 | client.Delete(context.Background(), account) 70 | 71 | // Logout() 72 | client.Logout(context.Background()) 73 | ``` 74 | 75 | ## Notes 76 | 77 | This repository is a port of [detunized/lastpass-ruby](https://github.com/detunized/lastpass-ruby) 78 | and a clone of [mattn/lastpass-go](https://github.com/mattn/lastpass-go). 79 | 80 | This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. 81 | 82 | This repository's `ecb` (Electronic Codebook) package contains code which is "Copyright 2013 The Go Authors. All rights reserved." 83 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package lastpass 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "encoding/hex" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "net/http" 15 | "net/url" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | // Account represents a LastPass item. 21 | // An item can be a password, payment card, bank account, etc., or a custom item type. 22 | type Account struct { 23 | ID string 24 | Name string 25 | Username string 26 | Password string 27 | URL string 28 | Group string 29 | // Shared folder name. 30 | // If non-empty, it must have prefix "Shared-". 31 | // Empty means this Account is not in a shared folder. 32 | Share string 33 | Notes string 34 | // Timestamp in seconds (set by LastPass servers). 35 | LastModifiedGMT string 36 | LastTouch string 37 | } 38 | 39 | type encryptedAccount struct { 40 | id string 41 | name []byte 42 | username []byte 43 | password []byte 44 | url []byte 45 | group []byte 46 | notes []byte 47 | lastModifiedGMT string 48 | lastTouch string 49 | } 50 | 51 | // share represents a LastPass shared folder. 52 | type share struct { 53 | id string 54 | name string 55 | // Account fields within a shared folder are encrypted with this sharing key. 56 | key []byte 57 | // true if the shared folder admin marked shared folder as read-only for our user 58 | readOnly bool 59 | } 60 | 61 | // the blob returned by the /getaccts.php endpoint is made up of chunks 62 | type chunk struct { 63 | id uint32 64 | payload []byte 65 | } 66 | 67 | // Accounts lists all LastPass accounts. 68 | // 69 | // If Client is not logged in, an *AuthenticationError is returned. 70 | func (c *Client) Accounts(ctx context.Context) ([]*Account, error) { 71 | loggedIn, err := c.loggedIn(ctx) 72 | if err != nil { 73 | return nil, err 74 | } 75 | if !loggedIn { 76 | return nil, &AuthenticationError{"client not logged in"} 77 | } 78 | blob, err := c.FetchEncryptedAccounts(ctx) 79 | if err != nil { 80 | return nil, err 81 | } 82 | return c.ParseEncryptedAccounts(bytes.NewReader(blob)) 83 | } 84 | 85 | // FetchEncryptedAccounts fetches the user's encrypted accounts from LastPass. 86 | // The returned []byte can be parsed using the ParseEncryptedAccounts method. 87 | func (c *Client) FetchEncryptedAccounts(ctx context.Context) ([]byte, error) { 88 | endpoint := c.baseURL + EndpointGetAccts 89 | u, err := url.Parse(endpoint) 90 | if err != nil { 91 | return nil, err 92 | } 93 | u.RawQuery = url.Values{ 94 | "requestsrc": []string{"cli"}, 95 | "mobile": []string{"1"}, 96 | "b64": []string{"1"}, 97 | "hasplugin": []string{"1.3.3"}, 98 | }.Encode() 99 | req, err := http.NewRequest(http.MethodGet, u.String(), nil) 100 | if err != nil { 101 | return nil, err 102 | } 103 | req = req.WithContext(ctx) 104 | c.log(ctx, "%s %s\n", req.Method, req.URL) 105 | res, err := c.httpClient.Do(req) 106 | if err != nil { 107 | return nil, err 108 | } 109 | defer res.Body.Close() 110 | 111 | if res.StatusCode != http.StatusOK { 112 | return nil, fmt.Errorf("GET %s: %s", u.String(), res.Status) 113 | } 114 | 115 | blobBase64, err := ioutil.ReadAll(res.Body) 116 | if err != nil { 117 | return nil, err 118 | } 119 | return decodeBase64(blobBase64) 120 | } 121 | 122 | // ParseEncryptedAccounts parses encrypted accounts into a []*Account. 123 | // The original encrypted accounts data can be obtained from LastPass 124 | // using the FetchEncryptedAccounts method. 125 | func (c *Client) ParseEncryptedAccounts(r io.Reader) ([]*Account, error) { 126 | chunks, err := getCompleteChunks(r) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | accts := make([]*Account, 0) 132 | key := c.session.EncryptionKey 133 | var share share 134 | 135 | for _, chunk := range chunks { 136 | switch chunk.id { 137 | case chunkIDFromString("ACCT"): 138 | encryptedAccount, err := parseAccount(bytes.NewReader(chunk.payload)) 139 | if err != nil { 140 | return nil, err 141 | } 142 | acct, err := decryptAccount(encryptedAccount, key) 143 | if err != nil { 144 | return nil, err 145 | } 146 | if acct.URL == "http://group" { 147 | // ignore "group" accounts since they are made up by LastPass and have no credentials 148 | continue 149 | } 150 | acct.Share = share.name 151 | accts = append(accts, acct) 152 | 153 | case chunkIDFromString("SHAR"): 154 | share, err = parseShare( 155 | bytes.NewReader(chunk.payload), 156 | c.session.EncryptionKey, 157 | c.session.OptPrivateKey) 158 | if err != nil { 159 | return nil, err 160 | } 161 | // after SHAR chunk all the following ACCTs are enrypted with the SHAR's sharing key 162 | key = share.key 163 | default: 164 | // the blob contains many other chunks we're currently not interested in 165 | // see https://github.com/lastpass/lastpass-cli/blob/a84aa9629957033082c5930968dda7fbed751dfa/blob.c#L585-L676 166 | } 167 | } 168 | return accts, nil 169 | } 170 | 171 | func getCompleteChunks(r io.Reader) ([]*chunk, error) { 172 | chunks, err := extractChunks(r) 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | if !areComplete(chunks) { 178 | return nil, errors.New("blob is truncated") 179 | } 180 | return chunks, nil 181 | } 182 | 183 | // see https://github.com/lastpass/lastpass-cli/blob/8767b5e53192ad4e72d1352db4aa9218e928cbe1/blob.c#L356-L421 184 | func parseAccount(r io.Reader) (*encryptedAccount, error) { 185 | id, err := readItem(r) 186 | if err != nil { 187 | return nil, err 188 | } 189 | nameEncrypted, err := readItem(r) 190 | if err != nil { 191 | return nil, err 192 | } 193 | groupEncrypted, err := readItem(r) 194 | if err != nil { 195 | return nil, err 196 | } 197 | urlHexEncoded, err := readItem(r) 198 | if err != nil { 199 | return nil, err 200 | } 201 | notesEncrypted, err := readItem(r) 202 | if err != nil { 203 | return nil, err 204 | } 205 | for i := 0; i < 2; i++ { 206 | if err = skipItem(r); err != nil { 207 | return nil, err 208 | } 209 | } 210 | usernameEncrypted, err := readItem(r) 211 | if err != nil { 212 | return nil, err 213 | } 214 | passwordEncrypted, err := readItem(r) 215 | if err != nil { 216 | return nil, err 217 | } 218 | for i := 0; i < 3; i++ { 219 | if err = skipItem(r); err != nil { 220 | return nil, err 221 | } 222 | } 223 | lastTouch, err := readItem(r) 224 | if err != nil { 225 | return nil, err 226 | } 227 | for i := 0; i < 18; i++ { 228 | if err = skipItem(r); err != nil { 229 | return nil, err 230 | } 231 | } 232 | lastModifiedGMT, err := readItem(r) 233 | if err != nil { 234 | return nil, err 235 | } 236 | return &encryptedAccount{ 237 | string(id), 238 | nameEncrypted, 239 | usernameEncrypted, 240 | passwordEncrypted, 241 | urlHexEncoded, 242 | groupEncrypted, 243 | notesEncrypted, 244 | string(lastModifiedGMT), 245 | string(lastTouch), 246 | }, nil 247 | } 248 | 249 | func decryptAccount(encrypted *encryptedAccount, encryptionKey []byte) (*Account, error) { 250 | name, err := decryptItem(encrypted.name, encryptionKey) 251 | if err != nil { 252 | return nil, err 253 | } 254 | username, err := decryptItem(encrypted.username, encryptionKey) 255 | if err != nil { 256 | return nil, err 257 | } 258 | password, err := decryptItem(encrypted.password, encryptionKey) 259 | if err != nil { 260 | return nil, err 261 | } 262 | url, err := decodeHex(encrypted.url) 263 | if err != nil { 264 | return nil, err 265 | } 266 | group, err := decryptItem(encrypted.group, encryptionKey) 267 | if err != nil { 268 | return nil, err 269 | } 270 | notes, err := decryptItem(encrypted.notes, encryptionKey) 271 | if err != nil { 272 | return nil, err 273 | } 274 | return &Account{ 275 | encrypted.id, 276 | name, 277 | username, 278 | password, 279 | string(url), 280 | group, 281 | "", 282 | notes, 283 | encrypted.lastModifiedGMT, 284 | encrypted.lastTouch, 285 | }, nil 286 | } 287 | 288 | func parseShare(r io.Reader, encryptionKey []byte, privateKey *rsa.PrivateKey) (share, error) { 289 | shareID, err := readItem(r) 290 | if err != nil { 291 | return share{}, err 292 | } 293 | 294 | sharingKeyRSAEncryptedHex, err := readItem(r) 295 | if err != nil { 296 | return share{}, err 297 | } 298 | 299 | nameEncrypted, err := readItem(r) 300 | if err != nil { 301 | return share{}, err 302 | } 303 | 304 | readOnly, err := readItem(r) 305 | if err != nil { 306 | return share{}, err 307 | } 308 | 309 | if err = skipItem(r); err != nil { 310 | return share{}, err 311 | } 312 | 313 | sharingKeyAESEncrypted, err := readItem(r) 314 | if err != nil { 315 | return share{}, err 316 | } 317 | 318 | var sharingKey []byte 319 | if len(sharingKeyAESEncrypted) > 0 { 320 | // The sharing key is only AES encrypted with the regular encryption key. 321 | // The is the default case and happens after the user had already decrypted 322 | // the sharing key with their private key once before (possibly in some other LastPass client). 323 | key, err := decryptItem(sharingKeyAESEncrypted, encryptionKey) 324 | if err != nil { 325 | return share{}, err 326 | } 327 | sharingKey, err = hex.DecodeString(key) 328 | if err != nil { 329 | return share{}, err 330 | } 331 | 332 | } else { 333 | // The user who shares the folder with us, encrypted the sharing key with our public key. 334 | // Therefore, we decrypt the sharing key with our private key. 335 | if privateKey == nil { 336 | return share{}, errors.New("account private key is nil - " + 337 | "refer to the following url for more information: " + 338 | "https://support.lastpass.com/help/" + 339 | "why-am-i-seeing-an-error-no-private-key-cannot-decrypt-pending-shares-message-lp010147") 340 | } 341 | 342 | sharingKeyRSAEncrypted, err := decodeHex(sharingKeyRSAEncryptedHex) 343 | if err != nil { 344 | return share{}, err 345 | } 346 | 347 | key, err := privateKey.Decrypt(rand.Reader, sharingKeyRSAEncrypted, &rsa.OAEPOptions{ 348 | // The CLI uses RSA_PKCS1_OAEP_PADDING 349 | // (see https://github.com/lastpass/lastpass-cli/blob/a84aa9629957033082c5930968dda7fbed751dfa/cipher.c#L78). 350 | // As described on https://linux.die.net/man/3/rsa_private_decrypt, RSA_PKCS1_OAEP_PADDING uses SHA1. 351 | Hash: crypto.SHA1, 352 | }) 353 | if err != nil { 354 | return share{}, err 355 | } 356 | 357 | sharingKey, err = decodeHex(key) 358 | if err != nil { 359 | return share{}, err 360 | } 361 | } 362 | 363 | name, err := decryptItem(nameEncrypted, sharingKey) 364 | if err != nil { 365 | return share{}, err 366 | } 367 | 368 | // convert "0" to false and "1" to true 369 | readOnlyBool, err := strconv.ParseBool(string(readOnly)) 370 | if err != nil { 371 | return share{}, err 372 | } 373 | 374 | return share{ 375 | id: string(shareID), 376 | name: name, 377 | key: sharingKey, 378 | readOnly: readOnlyBool, 379 | }, nil 380 | } 381 | 382 | func areComplete(chunks []*chunk) bool { 383 | if len(chunks) == 0 { 384 | return false 385 | } 386 | lastChunk := chunks[len(chunks)-1] 387 | // ENDM = end marker 388 | return lastChunk.id == chunkIDFromString("ENDM") && 389 | string(lastChunk.payload) == "OK" 390 | } 391 | 392 | func (c *Client) getShare(ctx context.Context, shareName string) (share, error) { 393 | blob, err := c.FetchEncryptedAccounts(ctx) 394 | if err != nil { 395 | return share{}, err 396 | } 397 | chunks, err := getCompleteChunks(bytes.NewReader(blob)) 398 | if err != nil { 399 | return share{}, err 400 | } 401 | for _, chunk := range chunks { 402 | if chunk.id == chunkIDFromString("SHAR") { 403 | share, err := parseShare( 404 | bytes.NewReader(chunk.payload), 405 | c.session.EncryptionKey, 406 | c.session.OptPrivateKey) 407 | if err != nil { 408 | return share, err 409 | } 410 | if share.name == shareName { 411 | return share, nil 412 | } 413 | } 414 | } 415 | return share{}, fmt.Errorf("shared folder %s not found", shareName) 416 | } 417 | 418 | func (a *Account) isShared() bool { 419 | return strings.HasPrefix(a.Share, "Shared-") 420 | } 421 | -------------------------------------------------------------------------------- /account_test.go: -------------------------------------------------------------------------------- 1 | package lastpass_test 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "net/http" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | "github.com/onsi/gomega/ghttp" 12 | 13 | . "github.com/ansd/lastpass-go" 14 | ) 15 | 16 | var _ = Describe("Account", func() { 17 | var ( 18 | client *Client 19 | server *ghttp.Server 20 | user string 21 | passwd string 22 | ) 23 | 24 | BeforeEach(func() { 25 | user = readFile("user.txt") 26 | passwd = readFile("passwd.txt") 27 | server = ghttp.NewServer() 28 | server.AppendHandlers( 29 | ghttp.CombineHandlers( 30 | ghttp.VerifyRequest(http.MethodPost, EndpointLogin), 31 | ghttp.RespondWith(http.StatusOK, 32 | fmt.Sprintf("", readFile("privatekeyencrypted.txt"))), 33 | ), 34 | ghttp.CombineHandlers( 35 | ghttp.VerifyRequest(http.MethodPost, EndpointLoginCheck), 36 | ghttp.RespondWith(http.StatusOK, ` `), 37 | ), 38 | ) 39 | var err error 40 | client, err = NewClient(context.Background(), user, passwd, WithBaseURL(server.URL())) 41 | Expect(err).NotTo(HaveOccurred()) 42 | }) 43 | 44 | AfterEach(func() { 45 | server.Close() 46 | }) 47 | 48 | Describe("Accounts()", func() { 49 | When("server returns blob", func() { 50 | var rsp string 51 | JustBeforeEach(func() { 52 | server.AppendHandlers( 53 | ghttp.CombineHandlers( 54 | ghttp.VerifyRequest(http.MethodGet, EndpointGetAccts, 55 | "requestsrc=cli&mobile=1&b64=1&hasplugin=1.3.3"), 56 | ghttp.RespondWith(http.StatusOK, rsp), 57 | ), 58 | ) 59 | }) 60 | When("accounts including secure notes are returned", func() { 61 | BeforeEach(func() { 62 | rsp = readFile("blob-3accts.txt") 63 | }) 64 | It("parses the accounts", func() { 65 | accts, err := client.Accounts(context.Background()) 66 | Expect(err).NotTo(HaveOccurred()) 67 | Expect(accts).To(ConsistOf( 68 | &Account{ 69 | ID: readFile("id-name0.txt"), 70 | Name: "name0", 71 | Username: "user0", 72 | Password: "password0", 73 | URL: "http://url0", 74 | Group: "folder0", 75 | Notes: "notes0", 76 | LastModifiedGMT: "1566373887", 77 | LastTouch: "1566373925", 78 | }, 79 | &Account{ 80 | ID: readFile("id-name1.txt"), 81 | Name: "name1", 82 | URL: "http://sn", 83 | Group: "folder0", 84 | Notes: "some secure note", 85 | LastModifiedGMT: "1566373920", 86 | LastTouch: "1566373932", 87 | }, 88 | &Account{ 89 | ID: readFile("id-name2.txt"), 90 | Name: "name2", 91 | URL: "http://url2", 92 | LastModifiedGMT: "1566373921", 93 | LastTouch: "1566373938", 94 | }, 95 | )) 96 | // /login.php, /login_check.php, /getaccts.php 97 | Expect(server.ReceivedRequests()).To(HaveLen(3)) 98 | }) 99 | }) 100 | When("group accounts are returned", func() { 101 | BeforeEach(func() { 102 | rsp = readFile("blob-groupaccount.txt") 103 | }) 104 | It("filters out group accounts", func() { 105 | accts, err := client.Accounts(context.Background()) 106 | Expect(err).NotTo(HaveOccurred()) 107 | Expect(accts).To(BeEmpty()) 108 | // /login.php, /login_check.php, /getaccts.php 109 | Expect(server.ReceivedRequests()).To(HaveLen(3)) 110 | }) 111 | }) 112 | When("shared folders exist whose sharing key is AES encrypted with user's encryption key", func() { 113 | BeforeEach(func() { 114 | rsp = readFile("blob-sharedaccounts.txt") 115 | }) 116 | It("parses accounts in shared folders", func() { 117 | accts, err := client.Accounts(context.Background()) 118 | Expect(err).NotTo(HaveOccurred()) 119 | Expect(accts).To(ConsistOf( 120 | &Account{ 121 | ID: readFile("id-name0.txt"), 122 | Name: "name0", 123 | Username: "user0", 124 | Password: "password0", 125 | URL: "http://url0", 126 | Group: "folder0", 127 | Notes: "notes0", 128 | LastModifiedGMT: "1566373887", 129 | LastTouch: "0", 130 | }, 131 | &Account{ 132 | ID: readFile("id-nameshared0.txt"), 133 | Name: "nameShared0", 134 | Username: "userShared0", 135 | Password: "passwordShared0", 136 | URL: "http://urlShared0", 137 | Group: "", 138 | Share: "Shared-share1", 139 | Notes: "notesShared0", 140 | LastModifiedGMT: "1566373807", 141 | LastTouch: "0", 142 | }, 143 | &Account{ 144 | ID: readFile("id-nameshared1.txt"), 145 | Name: "nameShared1", 146 | Username: "userShared1", 147 | Password: "passwordShared1", 148 | URL: "http://urlShared1", 149 | Group: "", 150 | Share: "Shared-share2", 151 | Notes: "notesShared1", 152 | LastModifiedGMT: "1566373836", 153 | LastTouch: "0", 154 | }, 155 | &Account{ 156 | ID: readFile("id-nameshared2.txt"), 157 | Name: "nameShared2", 158 | Username: "userShared2", 159 | Password: "passwordShared2", 160 | URL: "http://urlShared2", 161 | Group: "", 162 | Share: "Shared-share2", 163 | Notes: "notesShared2", 164 | LastModifiedGMT: "1566373837", 165 | LastTouch: "0", 166 | }, 167 | )) 168 | // /login.php, /login_check.php, /getaccts.php 169 | Expect(server.ReceivedRequests()).To(HaveLen(3)) 170 | }) 171 | }) 172 | When("shared folder exists whose sharing key needs to be decrypted with user's RSA private key", func() { 173 | BeforeEach(func() { 174 | rsp = readFile("blob-sharingkeyrsaencrypted.txt") 175 | }) 176 | It("parses account in shared folder", func() { 177 | accts, err := client.Accounts(context.Background()) 178 | Expect(err).NotTo(HaveOccurred()) 179 | Expect(accts).To(ConsistOf( 180 | &Account{ 181 | ID: readFile("id-nameshared0.txt"), 182 | Name: "nameShared0", 183 | Username: "userShared0", 184 | Password: "passwordShared0", 185 | URL: "http://urlShared0", 186 | Group: "", 187 | Share: "Shared-share1", 188 | Notes: "notesShared0", 189 | LastModifiedGMT: "1566373807", 190 | LastTouch: "0", 191 | }, 192 | )) 193 | // /login.php, /login_check.php, /getaccts.php 194 | Expect(server.ReceivedRequests()).To(HaveLen(3)) 195 | }) 196 | }) 197 | When("an account is AES 256 ECB encrypted", func() { 198 | BeforeEach(func() { 199 | rsp = readFile("blob-ecb.txt") 200 | }) 201 | It("decrypts", func() { 202 | accts, err := client.Accounts(context.Background()) 203 | Expect(err).NotTo(HaveOccurred()) 204 | Expect(accts).To(ConsistOf( 205 | &Account{ 206 | ID: readFile("id-nameecb.txt"), 207 | Name: "nameECB", 208 | Username: "user ECB", 209 | Password: "password ECB", 210 | URL: "http://urlECB", 211 | Group: "groupECB", 212 | Notes: "notes ECB", 213 | LastModifiedGMT: "1566373979", 214 | LastTouch: "0", 215 | }, 216 | )) 217 | // /login.php, /login_check.php, /getaccts.php 218 | Expect(server.ReceivedRequests()).To(HaveLen(3)) 219 | }) 220 | }) 221 | When("blob is not base 64 encoded", func() { 222 | BeforeEach(func() { 223 | rsp = "!! blob not base64 encoded !!" 224 | }) 225 | It("returns base64.CorruptInputError", func() { 226 | _, err := client.Accounts(context.Background()) 227 | _, ok := err.(base64.CorruptInputError) 228 | Expect(ok).To(BeTrue()) 229 | }) 230 | }) 231 | When("blob is empty", func() { 232 | BeforeEach(func() { 233 | rsp = "" 234 | }) 235 | It("returns a descriptive error", func() { 236 | _, err := client.Accounts(context.Background()) 237 | Expect(err).To(MatchError("blob is truncated")) 238 | }) 239 | }) 240 | When("blob is truncated and therefore chunk cannot be extracted", func() { 241 | BeforeEach(func() { 242 | // 8 base64 digits (each 6 bit) = 48 bits = 6 bytes 243 | // chunk contains 4-byte ID, 4-byte size and payload of that size 244 | // therefore, the complete chunk cannot be read 245 | rsp = "TFBBVgAA" 246 | }) 247 | It("returns an EOF error", func() { 248 | _, err := client.Accounts(context.Background()) 249 | Expect(err).To(MatchError("EOF")) 250 | }) 251 | }) 252 | }) 253 | When("request gets canceled", func() { 254 | var ctx context.Context 255 | BeforeEach(func() { 256 | var cancel context.CancelFunc 257 | ctx, cancel = context.WithCancel(context.Background()) 258 | cancel() 259 | }) 260 | It("returns correct error", func() { 261 | _, err := client.Accounts(ctx) 262 | Expect(err).To(MatchError(MatchRegexp("context canceled"))) 263 | }) 264 | }) 265 | When("HTTP error response", func() { 266 | BeforeEach(func() { 267 | server.AppendHandlers( 268 | ghttp.CombineHandlers( 269 | ghttp.VerifyRequest(http.MethodGet, EndpointGetAccts, 270 | "requestsrc=cli&mobile=1&b64=1&hasplugin=1.3.3"), 271 | ghttp.RespondWith(http.StatusInternalServerError, ""), 272 | ), 273 | ) 274 | }) 275 | It("returns error including HTTP status code", func() { 276 | _, err := client.Accounts(context.Background()) 277 | Expect(err).To(MatchError(MatchRegexp( 278 | `GET http://127\.0\.0\.1:[0-9]{1,5}/getaccts.php` + 279 | `\?b64=1&hasplugin=1\.3\.3&mobile=1&requestsrc=cli: 500 Internal Server Error$`))) 280 | }) 281 | }) 282 | }) 283 | }) 284 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Package lastpass implements a LastPass client. 2 | package lastpass 3 | 4 | import ( 5 | "context" 6 | "crypto/rand" 7 | "encoding/hex" 8 | "encoding/xml" 9 | "errors" 10 | "fmt" 11 | "io/ioutil" 12 | "net/http" 13 | "net/http/cookiejar" 14 | "net/url" 15 | "os" 16 | "path/filepath" 17 | "runtime" 18 | "strings" 19 | ) 20 | 21 | // LastPass API endpoints used by this client. 22 | const ( 23 | EndpointLogin = "/login.php" 24 | EndpointTrust = "/trust.php" 25 | EndpointLoginCheck = "/login_check.php" 26 | EndpointGetAccts = "/getaccts.php" 27 | EndpointShowWebsite = "/show_website.php" 28 | EndpointLogout = "/logout.php" 29 | ) 30 | 31 | const ( 32 | fileTrustID = "trusted_id" 33 | allowedCharsInTrustID = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$" 34 | ) 35 | 36 | const ( 37 | defaultPasswdIterations = 100100 38 | ) 39 | 40 | // Client represents a LastPass client. 41 | // A Client can be logged in to a single account at a given time. 42 | type Client struct { 43 | httpClient HTTPClient 44 | session *Session 45 | baseURL string 46 | otp string 47 | logger Logger 48 | configDir string 49 | trust bool 50 | trustID string 51 | trustLabel string 52 | } 53 | 54 | // ClientOption is the type of constructor options for NewClient(...). 55 | type ClientOption func(c *Client) 56 | 57 | // NewClient authenticates with the LastPass servers. 58 | // 59 | // The following authentication schemes are supported: 60 | // single-factor authentication via master password, 61 | // two-factor authentication via out-of-band mechanism 62 | // (e.g. LastPass Authenticator Push Notification, Duo Security Push Notification), 63 | // and two-factor authentication via one-time password 64 | // (e.g. one-time verification code of LastPass Authenticator, Google Authenticator, 65 | // Microsoft Authenticator, YubiKey, Transakt, Duo Security, or Sesame) 66 | // 67 | // If authentication fails, an *AuthenticationError is returned. 68 | func NewClient(ctx context.Context, username, masterPassword string, opts ...ClientOption) (*Client, error) { 69 | if username == "" { 70 | return nil, &AuthenticationError{"username must not be empty"} 71 | } 72 | if masterPassword == "" { 73 | return nil, &AuthenticationError{"masterPassword must not be empty"} 74 | } 75 | client, err := setupClient(ctx, opts...) 76 | if err != nil { 77 | return nil, err 78 | } 79 | currentSession, err := client.login(ctx, username, masterPassword, defaultPasswdIterations) 80 | if err != nil { 81 | return nil, err 82 | } 83 | client.session = currentSession 84 | return client, nil 85 | } 86 | 87 | func NewClientFromSession(ctx context.Context, currentSession *Session, opts ...ClientOption) (*Client, error) { 88 | client, err := setupClient(ctx, opts...) 89 | if err != nil { 90 | return nil, err 91 | } 92 | client.session = currentSession 93 | return client, nil 94 | } 95 | 96 | func setupClient(ctx context.Context, opts ...ClientOption) (*Client, error) { 97 | c := &Client{ 98 | baseURL: "https://lastpass.com", 99 | } 100 | for _, opt := range opts { 101 | opt(c) 102 | } 103 | if c.httpClient == nil { 104 | cookieJar, err := cookiejar.New(nil) 105 | if err != nil { 106 | return nil, err 107 | } 108 | c.httpClient = &http.Client{ 109 | Jar: cookieJar, 110 | } 111 | } 112 | if err := c.setConfigDir(); err != nil { 113 | return nil, err 114 | } 115 | if err := c.calculateTrustID(ctx); err != nil { 116 | return nil, err 117 | } 118 | if err := c.calculateTrustLabel(); err != nil { 119 | return nil, err 120 | } 121 | return c, nil 122 | } 123 | 124 | // WithOneTimePassword enables two-factor authentication with a one-time password 125 | // as the second factor. For an example how to use this function see 126 | // https://godoc.org/github.com/ansd/lastpass-go#example-NewClient--OneTimePasswordAuthentication. 127 | func WithOneTimePassword(oneTimePassword string) ClientOption { 128 | return func(c *Client) { 129 | c.otp = oneTimePassword 130 | } 131 | } 132 | 133 | // WithBaseURL overwrites the Client's default base URL https://lastpass.com/. 134 | // This function is used for unit testing. 135 | func WithBaseURL(baseURL string) ClientOption { 136 | return func(c *Client) { 137 | c.baseURL = baseURL 138 | } 139 | } 140 | 141 | // WithLogger enables logging. 142 | func WithLogger(logger Logger) ClientOption { 143 | return func(c *Client) { 144 | c.logger = logger 145 | } 146 | } 147 | 148 | // WithConfigDir sets the path of this library's cofiguration directory to persist user specific configuration. 149 | // If this option is not specified, the configuration directory defaults to /lastpass-go 150 | // where is the path returned by method UserConfigDir, see https://golang.org/pkg/os/#UserConfigDir. 151 | // The only user specific configuration currently supported by this library is a file called `trusted_id`. 152 | func WithConfigDir(path string) ClientOption { 153 | return func(c *Client) { 154 | c.configDir = path 155 | } 156 | } 157 | 158 | // WithTrust will cause subsequent logins to not require multifactor authentication. 159 | // It behaves like the `lpass login --trust` option of the LastPass CLI. 160 | // If not already present, it will create a file `trusted_id` with a random trust ID in the configuration directory set by WithConfigDir. 161 | // It will create a trust label with the format ` lastpass-go` which will show up in the LastPass 162 | // Web Browser Extension under Account Settings => Trusted Devices. 163 | func WithTrust() ClientOption { 164 | return func(c *Client) { 165 | c.trust = true 166 | } 167 | } 168 | 169 | // HTTPClient abstracts a Go http.Client with the Do method. 170 | type HTTPClient interface { 171 | Do(req *http.Request) (*http.Response, error) 172 | } 173 | 174 | // WithHTTPClient optionally specifies a custom HTTPClient to use. 175 | // 176 | // A new instance of a http.Client is used if this option is 177 | // not specified. 178 | func WithHTTPClient(httpClient HTTPClient) ClientOption { 179 | return func(c *Client) { 180 | c.httpClient = httpClient 181 | } 182 | } 183 | 184 | func (c *Client) Session() (*Session, error) { 185 | if c.session == nil { 186 | return nil, errors.New("current session is nil") 187 | } 188 | 189 | return c.session, nil 190 | } 191 | 192 | // Logout invalidates the session cookie. 193 | func (c *Client) Logout(ctx context.Context) error { 194 | loggedIn, err := c.loggedIn(ctx) 195 | if err != nil { 196 | return err 197 | } 198 | if !loggedIn { 199 | return nil 200 | } 201 | 202 | res, err := c.postForm(ctx, EndpointLogout, url.Values{ 203 | "method": []string{"cli"}, 204 | "noredirect": []string{"1"}, 205 | "token": []string{c.session.Token}, 206 | }) 207 | if err != nil { 208 | return err 209 | } 210 | res.Body.Close() 211 | c.session = nil 212 | return nil 213 | } 214 | 215 | // Add adds the account to LastPass. 216 | // Since LastPass generates a new account ID, account.ID is ignored. 217 | // When this method returns (without an error), account.ID is set to the newly generated account ID. 218 | // If Client is not logged in, an *AuthenticationError is returned. 219 | // To add an account to a shared folder, account.Share must be prefixed with "Shared-". 220 | func (c *Client) Add(ctx context.Context, account *Account) error { 221 | if account.Name == "" { 222 | return errors.New("account.Name must not be empty") 223 | } 224 | account.ID = "0" 225 | result, err := c.upsert(ctx, account) 226 | if err != nil { 227 | return err 228 | } 229 | if result.Msg != "accountadded" { 230 | return errors.New("failed to add account") 231 | } 232 | account.ID = result.AccountID 233 | return nil 234 | } 235 | 236 | // Update updates the account with the given account.ID. 237 | // If account.ID does not exist in LastPass, an *AccountNotFoundError is returned. 238 | // If Client is not logged in, an *AuthenticationError is returned. 239 | // 240 | // Updating an account within a shared folder is supported unless field account.Share itself is modified: 241 | // To move an account to / from a shared folder, use Delete() and Add() functions instead. 242 | func (c *Client) Update(ctx context.Context, account *Account) error { 243 | result, err := c.upsert(ctx, account) 244 | if err != nil { 245 | return err 246 | } 247 | if result.Msg != "accountupdated" { 248 | return fmt.Errorf("failed to update account (ID=%s)", account.ID) 249 | } 250 | return nil 251 | } 252 | 253 | // Delete deletes the LastPass Account with the given account.ID. 254 | // If account.ID does not exist in LastPass, an *AccountNotFoundError is returned. 255 | // If Client is not logged in, an *AuthenticationError is returned. 256 | // If Client is not logged in, an *AuthenticationError is returned. 257 | // 258 | // All Account fields other than account.ID and account.Share are ignored. 259 | func (c *Client) Delete(ctx context.Context, account *Account) error { 260 | loggedIn, err := c.loggedIn(ctx) 261 | if err != nil { 262 | return err 263 | } 264 | if !loggedIn { 265 | return &AuthenticationError{"client not logged in"} 266 | } 267 | 268 | data := url.Values{ 269 | "extjs": []string{"1"}, 270 | "delete": []string{"1"}, 271 | "aid": []string{account.ID}, 272 | "token": []string{c.session.Token}, 273 | } 274 | 275 | if account.isShared() { 276 | share, err := c.getShare(ctx, account.Share) 277 | if err != nil { 278 | return err 279 | } 280 | if share.readOnly { 281 | return fmt.Errorf( 282 | "Account with ID %s cannot be deleted from read-only shared folder %s.", 283 | account.ID, account.Share) 284 | } 285 | data.Set("sharedfolderid", share.id) 286 | } 287 | 288 | res, err := c.postForm(ctx, EndpointShowWebsite, data) 289 | if err != nil { 290 | return err 291 | } 292 | defer res.Body.Close() 293 | 294 | var response struct { 295 | Result result `xml:"result"` 296 | } 297 | 298 | if res.Header.Get("Content-Length") == "0" { 299 | return &AccountNotFoundError{account.ID} 300 | } 301 | 302 | err = xml.NewDecoder(res.Body).Decode(&response) 303 | if err != nil { 304 | return err 305 | } 306 | if response.Result.Msg != "accountdeleted" { 307 | return fmt.Errorf("failed to delete account (ID=%s)", account.ID) 308 | } 309 | return nil 310 | } 311 | 312 | type result struct { 313 | Msg string `xml:"msg,attr"` 314 | AccountID string `xml:"aid,attr"` 315 | } 316 | 317 | func (c *Client) upsert(ctx context.Context, acct *Account) (result, error) { 318 | var response struct { 319 | Result result `xml:"result"` 320 | } 321 | 322 | loggedIn, err := c.loggedIn(ctx) 323 | if err != nil { 324 | return response.Result, err 325 | } 326 | if !loggedIn { 327 | return response.Result, &AuthenticationError{"client not logged in"} 328 | } 329 | 330 | key := c.session.EncryptionKey 331 | share := share{} 332 | if acct.isShared() { 333 | share, err = c.getShare(ctx, acct.Share) 334 | if err != nil { 335 | return response.Result, err 336 | } 337 | if share.readOnly { 338 | return response.Result, fmt.Errorf( 339 | "Account cannot be written to read-only shared folder %s.", 340 | acct.Share) 341 | } 342 | key = share.key 343 | } 344 | 345 | nameEncrypted, err := encryptAESCBC(acct.Name, key) 346 | if err != nil { 347 | return response.Result, err 348 | } 349 | userNameEncrypted, err := encryptAESCBC(acct.Username, key) 350 | if err != nil { 351 | return response.Result, err 352 | } 353 | passwordEncrypted, err := encryptAESCBC(acct.Password, key) 354 | if err != nil { 355 | return response.Result, err 356 | } 357 | groupEncrypted, err := encryptAESCBC(acct.Group, key) 358 | if err != nil { 359 | return response.Result, err 360 | } 361 | notesEncrypted, err := encryptAESCBC(acct.Notes, key) 362 | if err != nil { 363 | return response.Result, err 364 | } 365 | 366 | data := url.Values{ 367 | "extjs": []string{"1"}, 368 | "token": []string{c.session.Token}, 369 | "method": []string{"cli"}, 370 | "pwprotect": []string{"off"}, 371 | "aid": []string{acct.ID}, 372 | "url": []string{hex.EncodeToString([]byte(acct.URL))}, 373 | "name": []string{nameEncrypted}, 374 | "grouping": []string{groupEncrypted}, 375 | "username": []string{userNameEncrypted}, 376 | "password": []string{passwordEncrypted}, 377 | "extra": []string{notesEncrypted}, 378 | } 379 | if share.id != "" { 380 | data.Set("sharedfolderid", share.id) 381 | } 382 | 383 | res, err := c.postForm(ctx, EndpointShowWebsite, data) 384 | if err != nil { 385 | return response.Result, err 386 | } 387 | defer res.Body.Close() 388 | 389 | if res.Header.Get("Content-Length") == "0" { 390 | return response.Result, &AccountNotFoundError{acct.ID} 391 | } 392 | 393 | err = xml.NewDecoder(res.Body).Decode(&response) 394 | return response.Result, err 395 | } 396 | 397 | func (c *Client) loggedIn(ctx context.Context) (bool, error) { 398 | if c.session == nil || c.session.Token == "" { 399 | return false, nil 400 | } 401 | 402 | res, err := c.postForm(ctx, EndpointLoginCheck, url.Values{"method": []string{"cli"}}) 403 | if err != nil { 404 | return false, err 405 | } 406 | defer res.Body.Close() 407 | type ok struct { 408 | AcctsVersion string `xml:"accts_version,attr"` 409 | } 410 | var response struct { 411 | Ok ok `xml:"ok"` 412 | } 413 | if err = xml.NewDecoder(res.Body).Decode(&response); err != nil { 414 | return false, err 415 | } 416 | loggedIn := response.Ok.AcctsVersion != "" 417 | return loggedIn, nil 418 | } 419 | 420 | func (c *Client) postForm(ctx context.Context, path string, data url.Values) (*http.Response, error) { 421 | req, err := http.NewRequest(http.MethodPost, c.baseURL+path, strings.NewReader(data.Encode())) 422 | if err != nil { 423 | return nil, err 424 | } 425 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 426 | req = req.WithContext(ctx) 427 | c.log(ctx, "%s %s\n", req.Method, req.URL) 428 | res, err := c.httpClient.Do(req) 429 | if err != nil { 430 | return nil, err 431 | } 432 | if res.StatusCode != http.StatusOK { 433 | return nil, fmt.Errorf("POST %s%s: %s", c.baseURL, path, res.Status) 434 | } 435 | return res, nil 436 | } 437 | 438 | func (c *Client) setConfigDir() error { 439 | if c.configDir != "" { 440 | // user provided config dir 441 | return nil 442 | } 443 | // set default config dir 444 | dir, err := os.UserConfigDir() 445 | if err != nil { 446 | return err 447 | } 448 | c.configDir = filepath.Join(dir, "lastpass-go") 449 | return nil 450 | } 451 | 452 | func (c *Client) calculateTrustLabel() error { 453 | if c.trust { 454 | hostname, err := os.Hostname() 455 | if err != nil { 456 | return err 457 | } 458 | c.trustLabel = fmt.Sprintf("%s %s %s", hostname, runtime.GOOS, "lastpass-go") 459 | } 460 | return nil 461 | } 462 | 463 | // calculateTrustID implements 464 | // https://github.com/lastpass/lastpass-cli/blob/8767b5e53192ad4e72d1352db4aa9218e928cbe1/endpoints-login.c#L105-L118 465 | func (c *Client) calculateTrustID(ctx context.Context) error { 466 | pathTrustID := filepath.Join(c.configDir, fileTrustID) 467 | 468 | _, err := os.Stat(pathTrustID) 469 | if err == nil { 470 | // file exists 471 | data, err := ioutil.ReadFile(pathTrustID) 472 | if err != nil { 473 | return err 474 | } 475 | c.trustID = string(data) 476 | return nil 477 | } 478 | 479 | if os.IsNotExist(err) { 480 | // file does not exist 481 | if c.trust { 482 | // create file with a new random trust ID 483 | trustID, err := random(32) 484 | if err != nil { 485 | return err 486 | } 487 | if err := os.MkdirAll(c.configDir, 0700); err != nil { 488 | return err 489 | } 490 | if err := ioutil.WriteFile(pathTrustID, trustID, 0600); err != nil { 491 | return err 492 | } 493 | c.log(ctx, "wrote random trust ID to %s\n", pathTrustID) 494 | c.trustID = string(trustID) 495 | } 496 | return nil 497 | } 498 | 499 | // file may or may not exist 500 | return err 501 | } 502 | 503 | func random(length int) ([]byte, error) { 504 | bytes := make([]byte, length) 505 | if _, err := rand.Read(bytes); err != nil { 506 | return nil, err 507 | } 508 | for i, b := range bytes { 509 | bytes[i] = allowedCharsInTrustID[b%byte(len(allowedCharsInTrustID))] 510 | } 511 | return bytes, nil 512 | } 513 | -------------------------------------------------------------------------------- /ecb/ecb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Electronic Code Book (ECB) mode. 6 | 7 | // ECB provides confidentiality by assigning a fixed ciphertext block to each 8 | // plaintext block. 9 | 10 | // See NIST SP 800-38A, pp 08-09 11 | 12 | package ecb 13 | 14 | import ( 15 | "crypto/cipher" 16 | ) 17 | 18 | type ecb struct { 19 | b cipher.Block 20 | blockSize int 21 | } 22 | 23 | func newECB(b cipher.Block) *ecb { 24 | return &ecb{ 25 | b: b, 26 | blockSize: b.BlockSize(), 27 | } 28 | } 29 | 30 | type ecbEncrypter ecb 31 | 32 | // NewECBEncrypter returns a BlockMode which encrypts in electronic code book 33 | // mode, using the given Block. 34 | func NewECBEncrypter(b cipher.Block) cipher.BlockMode { 35 | return (*ecbEncrypter)(newECB(b)) 36 | } 37 | 38 | func (x *ecbEncrypter) BlockSize() int { return x.blockSize } 39 | 40 | func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { 41 | if len(src)%x.blockSize != 0 { 42 | panic("crypto/cipher: input not full blocks") 43 | } 44 | if len(dst) < len(src) { 45 | panic("crypto/cipher: output smaller than input") 46 | } 47 | for len(src) > 0 { 48 | x.b.Encrypt(dst, src[:x.blockSize]) 49 | src = src[x.blockSize:] 50 | dst = dst[x.blockSize:] 51 | } 52 | } 53 | 54 | type ecbDecrypter ecb 55 | 56 | // NewECBDecrypter returns a BlockMode which decrypts in electronic code book 57 | // mode, using the given Block. 58 | func NewECBDecrypter(b cipher.Block) cipher.BlockMode { 59 | return (*ecbDecrypter)(newECB(b)) 60 | } 61 | 62 | func (x *ecbDecrypter) BlockSize() int { return x.blockSize } 63 | 64 | func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { 65 | if len(src)%x.blockSize != 0 { 66 | panic("crypto/cipher: input not full blocks") 67 | } 68 | if len(dst) < len(src) { 69 | panic("crypto/cipher: output smaller than input") 70 | } 71 | for len(src) > 0 { 72 | x.b.Decrypt(dst, src[:x.blockSize]) 73 | src = src[x.blockSize:] 74 | dst = dst[x.blockSize:] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package lastpass 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "encoding/base64" 11 | "encoding/binary" 12 | "encoding/hex" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "strings" 17 | 18 | "github.com/ansd/lastpass-go/ecb" 19 | ) 20 | 21 | // LastPass blob chunk is made up of 4-byte ID, 22 | // big endian 4-byte size and payload of that size. 23 | // 24 | // Example: 25 | // 0000: "IDID" 26 | // 0004: 4 27 | // 0008: 0xDE 0xAD 0xBE 0xEF 28 | // 000C: --- Next chunk --- 29 | func extractChunks(r io.Reader) ([]*chunk, error) { 30 | chunks := make([]*chunk, 0) 31 | for { 32 | chunkID, err := readID(r) 33 | if err != nil { 34 | if err == io.EOF { 35 | break 36 | } 37 | return nil, err 38 | } 39 | payload, err := readItem(r) 40 | if err != nil { 41 | return nil, err 42 | } 43 | c := &chunk{chunkID, payload} 44 | chunks = append(chunks, c) 45 | } 46 | return chunks, nil 47 | } 48 | 49 | func readID(r io.Reader) (uint32, error) { 50 | var b [4]byte 51 | _, err := r.Read(b[:]) 52 | if err != nil { 53 | return 0, err 54 | } 55 | return chunkIDFromBytes(b), nil 56 | } 57 | 58 | func readItem(r io.Reader) ([]byte, error) { 59 | size, err := readSize(r) 60 | if err != nil { 61 | return nil, err 62 | } 63 | b := make([]byte, size) 64 | n, err := r.Read(b) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return b[:n], nil 69 | } 70 | 71 | func readSize(r io.Reader) (uint32, error) { 72 | var b [4]byte 73 | _, err := r.Read(b[:]) 74 | if err != nil { 75 | return 0, err 76 | } 77 | return binary.BigEndian.Uint32(b[:]), nil 78 | } 79 | 80 | func skipItem(r io.Reader) error { 81 | readSize, err := readSize(r) 82 | if err != nil { 83 | return err 84 | } 85 | b := make([]byte, readSize) 86 | _, err = r.Read(b) 87 | return err 88 | } 89 | 90 | func chunkIDFromBytes(b [4]byte) uint32 { 91 | return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) 92 | } 93 | 94 | func chunkIDFromString(s string) uint32 { 95 | b := []byte(s) 96 | return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) 97 | } 98 | 99 | func encryptAESCBC(plaintext string, encryptionKey []byte) (string, error) { 100 | if len(plaintext) == 0 { 101 | return "", nil 102 | } 103 | 104 | padded := pkcs7Pad([]byte(plaintext), aes.BlockSize) 105 | ciphertext := make([]byte, aes.BlockSize+len(padded)) 106 | iv := ciphertext[:aes.BlockSize] 107 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 108 | return "", err 109 | } 110 | 111 | block, err := aes.NewCipher(encryptionKey) 112 | if err != nil { 113 | return "", err 114 | } 115 | enc := cipher.NewCBCEncrypter(block, iv) 116 | enc.CryptBlocks(ciphertext[aes.BlockSize:], padded) 117 | 118 | ivBase64 := encodeBase64(iv) 119 | ciphertextBase64 := encodeBase64(ciphertext[aes.BlockSize:]) 120 | 121 | // use the same format as the CLI does it in (v1.3.3) 122 | // https://github.com/lastpass/lastpass-cli/blob/a84aa9629957033082c5930968dda7fbed751dfa/cipher.c#L296 123 | return fmt.Sprintf("!%s|%s", ivBase64, ciphertextBase64), nil 124 | } 125 | 126 | func decryptItem(data, encryptionKey []byte) (string, error) { 127 | size := len(data) 128 | if size == 0 { 129 | return "", nil 130 | } 131 | size16 := size % 16 132 | size64 := size % 64 133 | 134 | switch { 135 | case aes256CBCPlain(data, size16): 136 | data = data[1:] 137 | iv, in := data[:aes.BlockSize], data[aes.BlockSize:] 138 | return decryptAES256CBC(iv, in, encryptionKey) 139 | 140 | case aes256CBCBase64(data, size64): 141 | ivBase64 := data[1:25] 142 | iv, err := decodeBase64(ivBase64) 143 | if err != nil { 144 | return "", err 145 | } 146 | inBase64 := data[26:] 147 | in, err := decodeBase64(inBase64) 148 | if err != nil { 149 | return "", err 150 | } 151 | return decryptAES256CBC(iv, in, encryptionKey) 152 | 153 | case aes256ECBPlain(size16): 154 | return decryptAES256ECB(data, encryptionKey) 155 | 156 | case aes256ECBBase64(size64): 157 | data, err := decodeBase64(data) 158 | if err != nil { 159 | return "", err 160 | } 161 | return decryptAES256ECB(data, encryptionKey) 162 | } 163 | 164 | return "", errors.New("input doesn't seem to be AES-256 encrypted") 165 | } 166 | 167 | func aes256CBCPlain(data []byte, size16 int) bool { 168 | return data[0] == '!' && size16 == 1 169 | } 170 | func aes256CBCBase64(data []byte, size64 int) bool { 171 | return data[0] == '!' && data[25] == '|' && (size64 == 6 || size64 == 26 || size64 == 50) 172 | } 173 | func aes256ECBPlain(size16 int) bool { 174 | return size16 == 0 175 | } 176 | func aes256ECBBase64(size64 int) bool { 177 | return size64 == 0 || size64 == 24 || size64 == 44 178 | } 179 | 180 | // decrypt user's private key with user's encryption key 181 | // 182 | // Background: 183 | // The first time, the user logs into LastPass using any LastPass client 184 | // a key pair gets created. The public key is uploaded unencrypted to LastPass so that 185 | // other users can encrypt data for the user (e.g. sharing keys). 186 | // The private key gets encrypted locally with the user's encryption key and also 187 | // uploaded to LastPass. 188 | func decryptPrivateKey(privateKeyEncrypted string, encryptionKey []byte) (*rsa.PrivateKey, error) { 189 | if privateKeyEncrypted == "" { 190 | // Key pair is not yet created. This happens for example when the account got created via 191 | // https://lastpass.com/create-account.php but the user has never logged in. 192 | // https://support.lastpass.com/help/why-am-i-seeing-an-error-no-private-key-cannot-decrypt-pending-shares-message-lp010147 193 | return nil, nil 194 | } 195 | 196 | privateKeyAESEncrypted, err := hex.DecodeString(privateKeyEncrypted) 197 | if err != nil { 198 | return nil, err 199 | } 200 | 201 | iv := encryptionKey[:aes.BlockSize] 202 | keyAnnotated, err := decryptAES256CBC(iv, privateKeyAESEncrypted, encryptionKey) 203 | if err != nil { 204 | return nil, err 205 | } 206 | 207 | keyTrimmed := strings.TrimPrefix(keyAnnotated, "LastPassPrivateKey<") 208 | keyTrimmed = strings.TrimSuffix(keyTrimmed, ">LastPassPrivateKey") 209 | 210 | keyPlain, err := hex.DecodeString(keyTrimmed) 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | keyParsed, err := x509.ParsePKCS8PrivateKey(keyPlain) 216 | if err != nil { 217 | return nil, err 218 | } 219 | rsaPrivateKey, ok := keyParsed.(*rsa.PrivateKey) 220 | if !ok { 221 | return nil, errors.New("did not find RSA private key type in PKCS#8 wrapping") 222 | } 223 | return rsaPrivateKey, nil 224 | } 225 | 226 | func decryptAES256CBC(iv, in, encryptionKey []byte) (string, error) { 227 | lenIn := len(in) 228 | if lenIn < aes.BlockSize { 229 | return "", fmt.Errorf("input is only %d bytes; expected at least %d bytes", lenIn, aes.BlockSize) 230 | } 231 | if lenIn%aes.BlockSize != 0 { 232 | return "", fmt.Errorf("input size (%d bytes) is not a multilpe of %d bytes", lenIn, aes.BlockSize) 233 | } 234 | 235 | block, err := aes.NewCipher(encryptionKey) 236 | if err != nil { 237 | return "", err 238 | } 239 | dec := cipher.NewCBCDecrypter(block, iv) 240 | out := make([]byte, lenIn) 241 | dec.CryptBlocks(out, in) 242 | return string(pkcs7Unpad(out)), nil 243 | } 244 | 245 | func decryptAES256ECB(in, encryptionKey []byte) (string, error) { 246 | block, err := aes.NewCipher(encryptionKey) 247 | if err != nil { 248 | return "", err 249 | } 250 | dec := ecb.NewECBDecrypter(block) 251 | out := make([]byte, len(in)) 252 | dec.CryptBlocks(out, in) 253 | return string(pkcs7Unpad(out)), nil 254 | } 255 | 256 | func encodeBase64(b []byte) []byte { 257 | encoded := make([]byte, base64.StdEncoding.EncodedLen(len(b))) 258 | base64.StdEncoding.Encode(encoded, b) 259 | return encoded 260 | } 261 | 262 | func decodeBase64(b []byte) ([]byte, error) { 263 | d := make([]byte, len(b)) 264 | n, err := base64.StdEncoding.Decode(d, b) 265 | if err != nil { 266 | return nil, err 267 | } 268 | return d[:n], nil 269 | } 270 | 271 | func decodeHex(src []byte) ([]byte, error) { 272 | dst := make([]byte, hex.DecodedLen(len(src))) 273 | _, err := hex.Decode(dst, src) 274 | if err != nil { 275 | return nil, err 276 | } 277 | return dst, nil 278 | } 279 | 280 | func pkcs7Pad(data []byte, blockSize int) []byte { 281 | padding := blockSize - len(data)%blockSize 282 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 283 | return append(data, padtext...) 284 | } 285 | 286 | func pkcs7Unpad(data []byte) []byte { 287 | size := len(data) 288 | unpadding := int(data[size-1]) 289 | return data[:(size - unpadding)] 290 | } 291 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package lastpass 2 | 3 | // AccountNotFoundError indicates that no account with AccountNotFoundError.ID exists on LastPass. 4 | type AccountNotFoundError struct { 5 | // account ID that does not exist 6 | ID string 7 | } 8 | 9 | func (e *AccountNotFoundError) Error() string { 10 | return "could not find LastPass account with ID=" + e.ID 11 | } 12 | 13 | // AuthenticationError indicates that the Client is not logged in. 14 | type AuthenticationError struct { 15 | msg string 16 | } 17 | 18 | func (e *AuthenticationError) Error() string { 19 | return e.msg 20 | } 21 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package lastpass_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | . "github.com/ansd/lastpass-go" 8 | ) 9 | 10 | var _ = Describe("Errors", func() { 11 | Describe("AccountNotFoundError", func() { 12 | var acctID = "123" 13 | var err error = &AccountNotFoundError{acctID} 14 | 15 | Describe("Error()", func() { 16 | It("provides error message including account ID", func() { 17 | Expect(err).To(MatchError("could not find LastPass account with ID=" + acctID)) 18 | }) 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package lastpass_test 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | "github.com/ansd/lastpass-go" 9 | ) 10 | 11 | // Login with master password (without two-factor authentication). 12 | // 13 | // If an invalid user name or master password is supplied, 14 | // NewClient returns an error of type *AuthenticationError. 15 | func ExampleNewClient_passwordBasedAuthentication() { 16 | _, _ = lastpass.NewClient(context.Background(), "user name", "master password") 17 | } 18 | 19 | // Login with two-factor authentication: 20 | // 1st factor is master passord, 21 | // 2nd factor is out-of-band mechanism (e.g. LastPass Authenticator Push Notification or 22 | // Duo Security Push Notification). 23 | // 24 | // Below code is the same as the login without two-factor authentication. 25 | // Once the NewClient function got invoked, the user has around 90 seconds to accept 26 | // the out-of-band mechanism (e.g. by selecting "Approve" in the LastPass Authenticator or 27 | // Duo Security app.) 28 | // 29 | // If the user does not accept the out-of-band mechanism within the 90 seconds, 30 | // NewClient returns an error of type *AuthenticationError. 31 | func ExampleNewClient_outOfBandAuthentication() { 32 | _, _ = lastpass.NewClient(context.Background(), "user name", "master password") 33 | } 34 | 35 | // Login with two-factor authentication: 36 | // 1st factor is master passord, 37 | // 2nd factor is one-time password (e.g. one-time verification code of LastPass Authenticator, 38 | // Google Authenticator, Microsoft Authenticator, YubiKey, Transakt, Duo Security, or Sesame). 39 | // 40 | // If an invalid user name, master password, or one-time password is supplied, 41 | // NewClient returns an error of type *AuthenticationError. 42 | func ExampleNewClient_oneTimePasswordAuthentication() { 43 | _, _ = lastpass.NewClient(context.Background(), "user name", "master password", 44 | lastpass.WithOneTimePassword("123456"), 45 | ) 46 | } 47 | 48 | // Login with two-factor authentication and trust: 49 | // 50 | // The WithTrust option will cause subsequent logins to not require multifactor authentication. 51 | // It will create a trust label with the format ` lastpass-go` 52 | // which will show up in the LastPass Web Browser Extension under Account Settings => Trusted Devices. 53 | func ExampleNewClient_trust() { 54 | // On first login, the 2nd factor must be provided. 55 | _, _ = lastpass.NewClient(context.Background(), "user name", "master password", 56 | lastpass.WithOneTimePassword("123456"), 57 | lastpass.WithTrust(), 58 | ) 59 | // Thereafter, within the next 30 days, the 2nd factor can be omitted. 60 | // (If you want to disable the default limit of 30 days, in the LastPass Web Browser Extension select the checkbox 61 | // Account Settings => General => Show Advanced Settings => Don't end trust period after 30 days.) 62 | _, _ = lastpass.NewClient(context.Background(), "user name", "master password") 63 | } 64 | 65 | // WithLogger enables logging for all methods on lastpass.Client. 66 | func ExampleWithLogger() { 67 | logger := log.New(os.Stderr, "lastpass: ", log.LstdFlags) 68 | 69 | _, _ = lastpass.NewClient(context.Background(), "user name", "master password", 70 | lastpass.WithLogger(logger)) 71 | } 72 | 73 | // NewContextWithLogger logs only for a specific method (request scope). 74 | // In the following example, it emits logs for only the NewClient method. 75 | func ExampleNewContextWithLogger() { 76 | logger := log.New(os.Stderr, "lastpass: ", log.LstdFlags) 77 | 78 | _, _ = lastpass.NewClient( 79 | lastpass.NewContextWithLogger(context.Background(), logger), 80 | "user name", "master password") 81 | } 82 | -------------------------------------------------------------------------------- /examples/create_read_update_delete/example.go: -------------------------------------------------------------------------------- 1 | // Example showing how to create, read, update, delete accounts. 2 | package main 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "strings" 10 | 11 | "github.com/ansd/lastpass-go" 12 | ) 13 | 14 | func main() { 15 | // Read LastPass username and master password from file. 16 | b, err := ioutil.ReadFile("../credentials.txt") 17 | if err != nil { 18 | log.Fatalln(err) 19 | } 20 | lines := strings.Split(string(b), "\n") 21 | username := lines[0] 22 | masterPassword := lines[1] 23 | 24 | // NewClient() authenticates with LastPass servers. 25 | // Read examples at https://pkg.go.dev/github.com/ansd/lastpass-go#NewClient for two-factor authentication. 26 | client, err := lastpass.NewClient(context.Background(), username, masterPassword) 27 | if err != nil { 28 | log.Fatalln(err) 29 | } 30 | 31 | account := &lastpass.Account{ 32 | Name: "my site", 33 | Username: "my user", 34 | Password: "my pwd", 35 | URL: "https://myURL", 36 | Group: "my group", 37 | Notes: "my notes", 38 | } 39 | 40 | // Add() account 41 | if err = client.Add(context.Background(), account); err != nil { 42 | log.Fatalln(err) 43 | } 44 | 45 | // read all Accounts() 46 | accounts, err := client.Accounts(context.Background()) 47 | if err != nil { 48 | log.Fatalln(err) 49 | } 50 | 51 | // print all Accounts 52 | for _, a := range accounts { 53 | fmt.Printf("%+v\n", a) 54 | } 55 | 56 | // Update() account 57 | account.Username = "updated user" 58 | account.Password = "updated password" 59 | if err = client.Update(context.Background(), account); err != nil { 60 | log.Fatalln(err) 61 | } 62 | 63 | // Delete() account 64 | if err = client.Delete(context.Background(), account); err != nil { 65 | log.Fatalln(err) 66 | } 67 | 68 | // Logout() 69 | if err = client.Logout(context.Background()); err != nil { 70 | log.Fatalln(err) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/credentials.txt.example: -------------------------------------------------------------------------------- 1 | username@example.com 2 | password 3 | -------------------------------------------------------------------------------- /examples/logging/example.go: -------------------------------------------------------------------------------- 1 | // Example showing how to log HTTP requests 2 | package main 3 | 4 | import ( 5 | "context" 6 | "io/ioutil" 7 | "log" 8 | "net/http/httptrace" 9 | "os" 10 | "strings" 11 | 12 | "github.com/ansd/lastpass-go" 13 | ) 14 | 15 | func main() { 16 | // Read LastPass username and master password from file. 17 | b, err := ioutil.ReadFile("../credentials.txt") 18 | if err != nil { 19 | log.Fatalln(err) 20 | } 21 | lines := strings.Split(string(b), "\n") 22 | username := lines[0] 23 | masterPassword := lines[1] 24 | 25 | // There are three different options how to log HTTP requests. 26 | 27 | // Option 1: Enable logging for all methods on lastpass.Client 28 | // Use any logger which implements lastpass.Logger (i.e. func Printf(format string, v ...interface{})) 29 | logger := log.New(os.Stderr, "client logger ", log.LstdFlags) 30 | client, err := lastpass.NewClient(context.Background(), username, masterPassword, lastpass.WithLogger(logger)) 31 | if err != nil { 32 | log.Fatalln(err) 33 | } 34 | 35 | // Option 2: Enable logging for only a specific method (request scope). 36 | logger = log.New(os.Stderr, "context logger ", log.LstdFlags) 37 | _, err = client.Accounts(lastpass.NewContextWithLogger(context.Background(), logger)) 38 | if err != nil { 39 | log.Fatalln(err) 40 | } 41 | 42 | // Option 3: Enable HTTP tracing for a specific method (request scope). 43 | logger = log.New(os.Stderr, "HTTP tracer ", log.LstdFlags) 44 | trace := &httptrace.ClientTrace{ 45 | WroteHeaderField: func(key string, value []string) { 46 | if key == ":method" || key == ":path" { 47 | logger.Println(key, value) 48 | } 49 | }, 50 | } 51 | if err = client.Logout(httptrace.WithClientTrace(context.Background(), trace)); err != nil { 52 | log.Fatalln(err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/trust/example.go: -------------------------------------------------------------------------------- 1 | // Example showing the trust feature which allows to skip multifactor authentication in subsequent logins. 2 | // This example assumes multifactor authentication is set up. 3 | package main 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/ansd/lastpass-go" 15 | ) 16 | 17 | func main() { 18 | // Read LastPass username and master password from file. 19 | b, err := ioutil.ReadFile("../credentials.txt") 20 | if err != nil { 21 | log.Fatalln(err) 22 | } 23 | lines := strings.Split(string(b), "\n") 24 | username := lines[0] 25 | masterPassword := lines[1] 26 | 27 | // Store file trusted_id in directory $HOME/.lastpass-go/ 28 | homeDir, err := os.UserHomeDir() 29 | if err != nil { 30 | log.Fatalln(err) 31 | } 32 | configDir := filepath.Join(homeDir, ".lastpass-go") 33 | 34 | // On 1st login, provide the following: 35 | client, err := lastpass.NewClient( 36 | context.Background(), 37 | username, 38 | masterPassword, 39 | lastpass.WithOneTimePassword("123456"), // the 2nd factor (here in the form of a one-time password, e.g. from you Google Authenticator app) 40 | lastpass.WithTrust(), // this option will generate a random trust ID if file trusted_id doesn't exist already 41 | lastpass.WithConfigDir(configDir), // optionally, provide a configuration directory where file trusted_id will be stored 42 | ) 43 | if err != nil { 44 | log.Fatalln(err) 45 | } 46 | 47 | if err = client.Logout(context.Background()); err != nil { 48 | log.Fatalln(err) 49 | } 50 | 51 | // On 1st login using WithTrust(), a file `$HOME/.lastpass-go/trusted_id` is created. 52 | // This file contains a randomly generated trust ID which will replace the 2nd factor (one-time password) for the next 30 days. 53 | // (If you want to disable the default limit of 30 days, in the LastPass Web Browser Extension select the checkbox 54 | // Account Settings => General => Show Advanced Settings => Don't end trust period after 30 days.) 55 | // Additionally, a trust label with the format ` lastpass-go` will show up in the LastPass 56 | // Web Browser Extension under Account Settings => Trusted Devices. 57 | 58 | // From now on, you can omit the 2nd factor (one time password) when logging in from the same device. 59 | // If you set an optional configuration directory on 1st login (as done above), you'll need to set it every time 60 | // when creating a NewClient() (since the new client needs to know where the trusted_id file is located). 61 | client, err = lastpass.NewClient( 62 | context.Background(), 63 | username, 64 | masterPassword, 65 | lastpass.WithConfigDir(configDir), 66 | ) 67 | if err != nil { 68 | log.Fatalln(err) 69 | } 70 | 71 | // Print all account names. 72 | accounts, err := client.Accounts(context.Background()) 73 | if err != nil { 74 | log.Fatalln(err) 75 | } 76 | for _, a := range accounts { 77 | fmt.Println(a.Name) 78 | } 79 | 80 | if err = client.Logout(context.Background()); err != nil { 81 | log.Fatalln(err) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ansd/lastpass-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.1.6 7 | github.com/onsi/gomega v1.20.2 8 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 3 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 7 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 8 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 10 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 11 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 12 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 13 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 14 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 15 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 16 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 17 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 18 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 19 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 20 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 21 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 22 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 23 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 24 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 25 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 26 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 27 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 28 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 29 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 30 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 31 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 32 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 33 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 34 | github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 35 | github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= 36 | github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= 37 | github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= 38 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 39 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 40 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 41 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 42 | github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= 43 | github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= 44 | github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 48 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 49 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 50 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 51 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 52 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 53 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 54 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= 55 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 56 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 57 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 58 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 59 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 60 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 61 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 62 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 63 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 64 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 65 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 66 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 67 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 68 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 69 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= 70 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 71 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 72 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 73 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 74 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 75 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 76 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 78 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 85 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 90 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 95 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 97 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 98 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 99 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 100 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 101 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 102 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 103 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 104 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 105 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 106 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 107 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 108 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 109 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 110 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 111 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 113 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 114 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 115 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 116 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 117 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 118 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 119 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 120 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 121 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 122 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 123 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 125 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 126 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 127 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 128 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 130 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 131 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 132 | -------------------------------------------------------------------------------- /lastpass_go_suite_test.go: -------------------------------------------------------------------------------- 1 | package lastpass_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestLastpassGo(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "LastpassGo Suite") 13 | } 14 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package lastpass 2 | 3 | import "context" 4 | 5 | type key int 6 | 7 | const ( 8 | loggerKey key = iota 9 | ) 10 | 11 | // Logger is the interface which wraps the Printf method. 12 | type Logger interface { 13 | Printf(format string, v ...interface{}) 14 | } 15 | 16 | // NewContextWithLogger returns a new context with logging enabled. 17 | func NewContextWithLogger(ctx context.Context, logger Logger) context.Context { 18 | return context.WithValue(ctx, loggerKey, logger) 19 | } 20 | 21 | func (c *Client) log(ctx context.Context, format string, v ...interface{}) { 22 | if logger, ok := ctx.Value(loggerKey).(Logger); ok { 23 | logger.Printf(format, v...) 24 | } 25 | if c.logger != nil { 26 | c.logger.Printf(format, v...) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /log_test.go: -------------------------------------------------------------------------------- 1 | package lastpass_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strings" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | "github.com/onsi/gomega/ghttp" 13 | 14 | . "github.com/ansd/lastpass-go" 15 | ) 16 | 17 | var _ = Describe("Log", func() { 18 | var server *ghttp.Server 19 | var logger Logger 20 | var logs strings.Builder 21 | var err error 22 | var user string 23 | var passwd string 24 | 25 | BeforeEach(func() { 26 | user = readFile("user.txt") 27 | passwd = readFile("passwd.txt") 28 | 29 | server = ghttp.NewServer() 30 | server.AppendHandlers( 31 | ghttp.CombineHandlers( 32 | ghttp.VerifyRequest(http.MethodPost, EndpointLogin), 33 | ghttp.RespondWith(http.StatusOK, fmt.Sprintf("", 34 | "fakeToken", readFile("privatekeyencrypted.txt"))), 35 | ), 36 | ) 37 | logger = log.New(&logs, "", 0) 38 | }) 39 | 40 | AfterEach(func() { 41 | Expect(err).NotTo(HaveOccurred()) 42 | Expect(logs.String()).To(MatchRegexp(`^POST http://127\.0\.0\.1:[0-9]{1,5}/login\.php`)) 43 | server.Close() 44 | }) 45 | 46 | Describe("NewContextWithLogger()", func() { 47 | It("writes logs", func() { 48 | _, err = NewClient( 49 | NewContextWithLogger(context.Background(), logger), 50 | user, passwd, 51 | WithBaseURL(server.URL())) 52 | }) 53 | }) 54 | Describe("WithLogger()", func() { 55 | It("writes logs", func() { 56 | _, err = NewClient(context.Background(), user, passwd, 57 | WithBaseURL(server.URL()), 58 | WithLogger(logger)) 59 | }) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /scripts/create-unit-test-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script uses the LastPass CLI to create unit test data. 4 | # 5 | # This script expects 2 LastPass account credentials as arguments. 6 | # The 2nd account will share passwords with the 1st account. 7 | # 8 | # Prerequisites: 9 | # 10 | # The 2nd user needs to be a LastPass family user (since only they can create shared folders). 11 | # The 2nd user needs to have added the 1st user as a family member. 12 | # The 1st user needs to have accepted the family email invitation. 13 | # (However, the 1st user doesn't need to be upgraded to a family account.) 14 | # 15 | # Before starting this script, make sure to be logged out the 1st user's account. 16 | 17 | set -euo pipefail 18 | 19 | if [[ $# -ne 4 ]]; then 20 | echo "usage: $0 " 21 | exit 1 22 | fi 23 | 24 | read -p "I understand that all the secret data and passwords of the provided LastPass accounts 25 | incuding their master passwords will be made publicly available. 26 | I confirm that I have not stored any sensitive data in these LastPass accounts. 27 | I will permanently delete these accounts after running this script. (y/n)?" choice 28 | case "$choice" in 29 | y|Y ) 30 | ;; 31 | n|N ) 32 | echo "aborting" 33 | exit 1 34 | ;; 35 | * ) 36 | echo "invalid input: enter y or n" 37 | exit 1 38 | ;; 39 | esac 40 | 41 | user1=$1 42 | passwd1=$2 43 | user2=$3 44 | passwd2=$4 45 | 46 | 47 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 48 | cd "$SCRIPT_DIR"/../test/unit/ 49 | 50 | echo "$passwd2" | LPASS_DISABLE_PINENTRY=1 lpass login --force "$user2" 51 | 52 | echo "creating shared folder with 1 ACCT" 53 | lpass share create share1 54 | sleep 6 55 | lpass share useradd --read-only=false --hidden=false Shared-share1 "$user1" 56 | sleep 6 57 | 58 | cat< data/id-nameshared0.txt 69 | 70 | echo "writing blob" 71 | cd dumpblob 72 | go build 73 | cd .. 74 | dump=$(./dumpblob/dumpblob "$user1" "$passwd1") 75 | echo "$dump" | jq -j .PrivateKeyEncrypted > data/privatekeyencrypted.txt 76 | echo "$dump" | jq -j .Blob > data/blob-sharingkeyrsaencrypted.txt 77 | 78 | echo "creating shared folder with 2 ACCTs" 79 | lpass share create share2 80 | sleep 6 81 | lpass share useradd --read-only=false --hidden=false Shared-share2 "$user1" 82 | sleep 6 83 | 84 | cat< data/id-nameshared1.txt 105 | echo "$id_nameshared2" > data/id-nameshared2.txt 106 | sleep 6 107 | lpass logout --force 108 | 109 | read -p "Now, use the browser plugin to log into the 1st user's account. 110 | (This will AES encrypt the sharing key with the 1st user's encryption key.) 111 | When done, press enter to continue." 112 | 113 | echo "$passwd1" | LPASS_DISABLE_PINENTRY=1 lpass login --force "$user1" 114 | 115 | echo "$user1" > data/user.txt 116 | echo "$passwd1" > data/passwd.txt 117 | 118 | sleep 6 119 | echo "creating 1 ACCT" 120 | cat< data/blob-sharedaccounts.txt 131 | 132 | echo "removing the 3 shared ACCTs" 133 | lpass rm --sync=no "$id_nameshared0" 134 | sleep 6 135 | lpass rm --sync=no "$id_nameshared1" 136 | sleep 6 137 | lpass rm --sync=no "$id_nameshared2" 138 | sleep 6 139 | 140 | echo "creating 2 ACCTs" 141 | cat< data/id-name0.txt 159 | echo "$id_name1" > data/id-name1.txt 160 | echo "$id_name2" > data/id-name2.txt 161 | 162 | echo "writing blob" 163 | ./dumpblob/dumpblob "$user1" "$passwd1" | jq -j .Blob > data/blob-3accts.txt 164 | 165 | echo "removing all 3 ACCTs" 166 | lpass rm --sync=no "$id_name0" 167 | sleep 6 168 | lpass rm --sync=no "$id_name1" 169 | sleep 6 170 | lpass rm --sync=no "$id_name2" 171 | sleep 6 172 | 173 | echo "creating group account" 174 | cat< data/blob-groupaccount.txt 182 | 183 | echo "removing group account" 184 | lpass rm --sync=now groupAccount/ 185 | sleep 6 186 | 187 | echo "creating 1 ECB encrypted ACCT" 188 | cd ecb 189 | go build 190 | cd .. 191 | id_nameecb=$(./ecb/ecb "$user1" "$passwd1") 192 | echo "$id_nameecb" > data/id-nameecb.txt 193 | 194 | echo "writing blob" 195 | ./dumpblob/dumpblob "$user1" "$passwd1" | jq -j .Blob > data/blob-ecb.txt 196 | 197 | echo "removing ECB account" 198 | lpass rm --sync=now "$id_nameecb" 199 | sleep 6 200 | 201 | echo "creating 1 ACCT" 202 | cat< 'Show Advanced Settings' => Set 'Password Iterations' to '1' => 'Update'. 211 | Re-enter the 1st user's password => 'Confirm' and wait for the operation to complete. 212 | When done, press enter to continue." 213 | 214 | echo "$passwd1" | LPASS_DISABLE_PINENTRY=1 lpass login --force "$user1" 215 | 216 | id_name3=$(lpass show --id name3) 217 | echo "$id_name3" > data/id-name3.txt 218 | 219 | echo "writing blob" 220 | dump=$(./dumpblob/dumpblob --iterations=1 "$user1" "$passwd1") 221 | echo "$dump" | jq -j .PrivateKeyEncrypted > data/privatekeyencrypted-1iteration.txt 222 | echo "$dump" | jq -j .Blob > data/blob-1iteration.txt 223 | 224 | echo "removing 1 ACCT" 225 | lpass rm --sync=now "$id_name3" 226 | sleep 6 227 | 228 | lpass logout -f 229 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package lastpass 2 | 3 | import ( 4 | "context" 5 | "crypto/rsa" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "encoding/xml" 9 | "fmt" 10 | "net/url" 11 | "strconv" 12 | "time" 13 | 14 | "golang.org/x/crypto/pbkdf2" 15 | ) 16 | 17 | // MaxLoginRetries determines the maximum number of login retries 18 | // if the login fails with cause "outofbandrequired". 19 | // This increases the user's time to approve the out-of-band (2nd) factor 20 | // (e.g. approving a push notification sent to their mobile phone). 21 | const ( 22 | MaxLoginRetries = 7 23 | ) 24 | 25 | type Session struct { 26 | // PasswdIterations controls how many times the user's password 27 | // is hashed using PBKDF2 before being sent to LastPass. 28 | PasswdIterations int 29 | 30 | // Token is the session token returned by LastPass during the login process. 31 | Token string 32 | 33 | // EncryptionKey is derived by hashing the user's master password using PBKDF2. 34 | EncryptionKey []byte 35 | 36 | // OptPrivateKey is the user's private key for decrypting sharing 37 | // keys. Sharing keys are used for shared folders. 38 | // 39 | // The first time the user logs into LastPass using any official LastPass client 40 | // (e.g. browser extension) a key pair gets created. 41 | // The public key is uploaded unencrypted to LastPass so that 42 | // other users can encrypt data for the user (e.g. sharing keys). 43 | // The private key gets encrypted locally (within the client) with the user's encryption key 44 | // and also uploaded to LastPass. 45 | // 46 | // This is nil if the user has not generated a sharing key. See 47 | // https://support.lastpass.com/help/why-am-i-seeing-an-error-no-private-key-cannot-decrypt-pending-shares-message-lp010147 48 | OptPrivateKey *rsa.PrivateKey 49 | } 50 | 51 | func (c *Client) login(ctx context.Context, user string, passwd string, passwdIterations int) (*Session, error) { 52 | loginHash, encKey := loginHashAndEncKey(user, passwd, passwdIterations) 53 | 54 | form := url.Values{ 55 | "method": []string{"cli"}, 56 | "xml": []string{"1"}, 57 | "username": []string{user}, 58 | "hash": []string{loginHash}, 59 | "iterations": []string{fmt.Sprint(passwdIterations)}, 60 | "includeprivatekeyenc": []string{"1"}, 61 | } 62 | if c.trustID != "" { 63 | form.Set("uuid", c.trustID) 64 | } 65 | if c.trustLabel != "" { 66 | form.Set("trustlabel", c.trustLabel) 67 | } 68 | if c.otp != "" { 69 | form.Set("otp", c.otp) 70 | } 71 | 72 | loginStartTime := time.Now() 73 | httpRsp, err := c.postForm(ctx, EndpointLogin, form) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | type Error struct { 79 | Msg string `xml:"message,attr"` 80 | Cause string `xml:"cause,attr"` 81 | RetryID string `xml:"retryid,attr"` 82 | Iterations string `xml:"iterations,attr"` 83 | } 84 | type response struct { 85 | Error Error `xml:"error"` 86 | Token string `xml:"token,attr"` 87 | PrivateKeyEncrypted string `xml:"privatekeyenc,attr"` 88 | } 89 | rsp := &response{} 90 | err = xml.NewDecoder(httpRsp.Body).Decode(rsp) 91 | if closeErr := httpRsp.Body.Close(); closeErr != nil { 92 | return nil, closeErr 93 | } 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | if rsp.Error.Iterations != "" { 99 | var iterations int 100 | if iterations, err = strconv.Atoi(rsp.Error.Iterations); err != nil { 101 | return nil, fmt.Errorf( 102 | "failed to parse iterations count, expected '%s' to be integer: %w", 103 | rsp.Error.Iterations, err) 104 | } 105 | c.log(ctx, "failed to login with %d password iterations, re-trying with %d password iterations...", 106 | passwdIterations, iterations) 107 | return c.login(ctx, user, passwd, iterations) 108 | } 109 | 110 | const outOfBandRequired = "outofbandrequired" 111 | if rsp.Error.Cause == outOfBandRequired { 112 | form.Set("outofbandrequest", "1") 113 | form.Set("outofbandretry", "1") 114 | form.Set("outofbandretryid", rsp.Error.RetryID) 115 | for i := 0; i < MaxLoginRetries; i++ { 116 | rsp = &response{} 117 | oobResp, err := c.postForm(ctx, EndpointLogin, form) 118 | if err != nil { 119 | return nil, err 120 | } 121 | err = xml.NewDecoder(oobResp.Body).Decode(&rsp) 122 | if closeErr := oobResp.Body.Close(); closeErr != nil { 123 | return nil, closeErr 124 | } 125 | if err != nil { 126 | return nil, err 127 | } 128 | if rsp.Error.Cause != outOfBandRequired { 129 | break 130 | } 131 | } 132 | if rsp.Error.Cause == outOfBandRequired { 133 | return nil, &AuthenticationError{fmt.Sprintf( 134 | "didn't receive out-of-band approval within the last %.0f seconds", 135 | time.Since(loginStartTime).Seconds(), 136 | )} 137 | } 138 | } 139 | 140 | if rsp.Error.Cause != "" { 141 | return nil, &AuthenticationError{fmt.Sprintf("%s: %s", rsp.Error.Cause, rsp.Error.Msg)} 142 | } 143 | 144 | if c.trust { 145 | trustForm := url.Values{ 146 | "token": []string{rsp.Token}, 147 | "uuid": []string{c.trustID}, 148 | "trustlabel": []string{c.trustLabel}, 149 | } 150 | if _, err := c.postForm(ctx, EndpointTrust, trustForm); err != nil { 151 | return nil, err 152 | } 153 | } 154 | 155 | optPrivateKey, err := decryptPrivateKey(rsp.PrivateKeyEncrypted, encKey) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | return &Session{ 161 | PasswdIterations: passwdIterations, 162 | Token: rsp.Token, 163 | EncryptionKey: encKey, 164 | OptPrivateKey: optPrivateKey, 165 | }, nil 166 | } 167 | 168 | func loginHashAndEncKey(username string, password string, passwdIterations int) (string, []byte) { 169 | encKey := encryptionKey(username, password, passwdIterations) 170 | 171 | if passwdIterations == 1 { 172 | b := sha256.Sum256([]byte(hex.EncodeToString(encKey) + password)) 173 | return hex.EncodeToString(b[:]), encKey 174 | } 175 | return hex.EncodeToString(pbkdf2.Key(encKey, []byte(password), 1, 32, sha256.New)), encKey 176 | } 177 | 178 | func encryptionKey(username, password string, passwdIterations int) []byte { 179 | if passwdIterations == 1 { 180 | b := sha256.Sum256([]byte(username + password)) 181 | return b[:] 182 | } 183 | return pbkdf2.Key([]byte(password), []byte(username), passwdIterations, 32, sha256.New) 184 | } 185 | -------------------------------------------------------------------------------- /test/integration/integration_suite_test.go: -------------------------------------------------------------------------------- 1 | package integration_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/ansd/lastpass-go" 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var ( 14 | client *lastpass.Client 15 | username2 string 16 | password2 string 17 | ) 18 | 19 | func TestIntegration(t *testing.T) { 20 | RegisterFailHandler(Fail) 21 | RunSpecs(t, "Integration Suite") 22 | } 23 | 24 | var _ = BeforeSuite(func() { 25 | username1 := os.Getenv("LASTPASS_USERNAME_1") 26 | Expect(username1).NotTo(BeEmpty()) 27 | password1 := os.Getenv("LASTPASS_MASTER_PASSWORD_1") 28 | Expect(password1).NotTo(BeEmpty()) 29 | 30 | username2 = os.Getenv("LASTPASS_USERNAME_2") 31 | Expect(username2).NotTo(BeEmpty()) 32 | password2 = os.Getenv("LASTPASS_MASTER_PASSWORD_2") 33 | Expect(password2).NotTo(BeEmpty()) 34 | 35 | var err error 36 | client, err = lastpass.NewClient(context.Background(), username1, password1) 37 | Expect(err).NotTo(HaveOccurred()) 38 | }) 39 | 40 | var _ = AfterSuite(func() { 41 | Expect(client.Logout(context.Background())).To(Succeed()) 42 | Expect(client.Delete(context.Background(), nil)).To(MatchError("client not logged in")) 43 | }) 44 | -------------------------------------------------------------------------------- /test/integration/integration_test.go: -------------------------------------------------------------------------------- 1 | package integration_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/http/cookiejar" 10 | "os" 11 | "strconv" 12 | "time" 13 | 14 | . "github.com/ansd/lastpass-go" 15 | . "github.com/onsi/ginkgo/v2" 16 | . "github.com/onsi/gomega" 17 | . "github.com/onsi/gomega/gstruct" 18 | ) 19 | 20 | var _ = Describe("Integration", func() { 21 | It("creates, reads, updates, deletes account", func() { 22 | testStart := time.Now().Unix() 23 | acct := &Account{ 24 | ID: "", 25 | Name: "test site", 26 | Username: "test user", 27 | Password: "test pwd", 28 | URL: "https://testURL", 29 | Group: "test group", 30 | Notes: "test notes", 31 | } 32 | 33 | By("adding") 34 | Expect(client.Add(context.Background(), acct)).To(Succeed()) 35 | 36 | By("updating") 37 | acct.Username = "updated user" 38 | acct.Password = "updated pwd" 39 | Expect(client.Update(context.Background(), acct)).To(Succeed()) 40 | 41 | By("reading") 42 | updated := accountForID(client, acct.ID) 43 | Expect(updated).To( 44 | PointTo(MatchAllFields(Fields{ 45 | "ID": Equal(acct.ID), 46 | "Name": Equal(acct.Name), 47 | "Username": Equal(acct.Username), 48 | "Password": Equal(acct.Password), 49 | "URL": Equal(acct.URL), 50 | "Group": Equal(acct.Group), 51 | "Share": BeEmpty(), 52 | "Notes": Equal(acct.Notes), 53 | "LastModifiedGMT": Not(BeEmpty()), 54 | "LastTouch": Not(BeEmpty()), 55 | }))) 56 | lastModified, err := strconv.ParseUint(updated.LastModifiedGMT, 10, 32) 57 | Expect(err).ToNot(HaveOccurred()) 58 | Expect(lastModified).To(BeNumerically("~", testStart, 120)) 59 | 60 | lastTouch, err := strconv.ParseUint(updated.LastTouch, 10, 32) 61 | Expect(err).ToNot(HaveOccurred()) 62 | // lastTouch is not in GMT. 63 | // Expect it to be within 12 hours offset range from GMT. 64 | Expect(lastTouch).To(BeNumerically("~", testStart, 60*60*12)) 65 | 66 | By("deleting") 67 | Expect(client.Delete(context.Background(), acct)).To(Succeed()) 68 | Expect(accountForID(client, acct.ID)).To(BeNil()) 69 | }) 70 | 71 | When("accout does not exist", func() { 72 | var acct *Account 73 | const id string = "nonExisting" 74 | BeforeEach(func() { 75 | acct = &Account{ID: id} 76 | }) 77 | Describe("Update()", func() { 78 | It("returns AccountNotFoundError", func() { 79 | Expect(client.Update(context.Background(), acct)).To( 80 | MatchError(&AccountNotFoundError{ID: id})) 81 | }) 82 | }) 83 | 84 | Describe("Delete()", func() { 85 | It("returns AccountNotFoundError", func() { 86 | Expect(client.Delete(context.Background(), acct)).To( 87 | MatchError(&AccountNotFoundError{ID: id})) 88 | }) 89 | }) 90 | }) 91 | 92 | // Prerequisites: 93 | // Client 2 creates two shared folders and invites client 1 94 | // 1. LASTPASS_SHARE 95 | // 2. LASTPASS_SHARE_READ_ONLY with read only permissions 96 | Context("shared folder", func() { 97 | It("creates, reads, deletes accounts", func() { 98 | share := os.Getenv("LASTPASS_SHARE") 99 | Expect(share).NotTo(BeEmpty()) 100 | acct := &Account{ 101 | Name: "fake-name", 102 | Username: "fake-username", 103 | Password: "fake-password", 104 | URL: "http://fake-url", 105 | Group: "fake-group", 106 | Share: share, 107 | Notes: "fake-notes", 108 | } 109 | 110 | By("client 1 creating") 111 | Expect(client.Add(context.Background(), acct)).To(Succeed()) 112 | 113 | By("client 2 logging in") 114 | var err error 115 | client2, err := NewClient(context.Background(), username2, password2) 116 | Expect(err).NotTo(HaveOccurred()) 117 | 118 | By("client 2 reading") 119 | containSharedAccount := ContainElement(PointTo(MatchAllFields(Fields{ 120 | "ID": Equal(acct.ID), 121 | "Name": Equal(acct.Name), 122 | "Username": Equal(acct.Username), 123 | "Password": Equal(acct.Password), 124 | "URL": Equal(acct.URL), 125 | "Group": Equal(acct.Group), 126 | "Share": Equal(acct.Share), 127 | "Notes": Equal(acct.Notes), 128 | "LastModifiedGMT": Not(BeEmpty()), 129 | "LastTouch": Not(BeEmpty()), 130 | }))) 131 | Expect(client2.Accounts(context.Background())).To(containSharedAccount) 132 | 133 | By("client 1 deleting") 134 | Expect(client.Delete(context.Background(), acct)).To(Succeed()) 135 | 136 | By("client 2 not reading") 137 | Expect(client2.Accounts(context.Background())).NotTo(containSharedAccount) 138 | 139 | By("client 2 logging out") 140 | Expect(client2.Logout(context.Background())).To(Succeed()) 141 | }) 142 | 143 | It("fails to add to read-only share", func() { 144 | shareReadOnly := os.Getenv("LASTPASS_SHARE_READ_ONLY") 145 | Expect(shareReadOnly).NotTo(BeEmpty()) 146 | 147 | acct := &Account{ 148 | Name: "fake-name", 149 | Share: shareReadOnly, 150 | } 151 | Expect(client.Add(context.Background(), acct)).To( 152 | MatchError(fmt.Sprintf( 153 | "Account cannot be written to read-only shared folder %s.", shareReadOnly))) 154 | }) 155 | }) 156 | 157 | Context("offline client", func() { 158 | It("can buffer and defer HTTP requests", func() { 159 | cookieJar, err := cookiejar.New(nil) 160 | Expect(err).NotTo(HaveOccurred()) 161 | onlineHTTPClient := &http.Client{ 162 | Jar: cookieJar, 163 | } 164 | onlineClient, err := NewClient( 165 | context.Background(), 166 | username2, 167 | password2, 168 | WithHTTPClient(onlineHTTPClient), 169 | ) 170 | Expect(err).NotTo(HaveOccurred()) 171 | 172 | acct := &Account{ 173 | ID: "", 174 | Name: "offline test site", 175 | Username: "offline test user", 176 | Password: "offline test pwd", 177 | URL: "https://offlineTestURL", 178 | } 179 | Expect(onlineClient.Add(context.Background(), acct)).To(Succeed()) 180 | 181 | encryptedAccounts, err := onlineClient.FetchEncryptedAccounts(context.Background()) 182 | Expect(err).ToNot(HaveOccurred()) 183 | 184 | session, err := onlineClient.Session() 185 | Expect(err).ToNot(HaveOccurred()) 186 | 187 | offlineHTTPClient := &offlineHTTPClient{} 188 | offlineClient, err := NewClientFromSession( 189 | context.Background(), 190 | session, 191 | WithHTTPClient(offlineHTTPClient)) 192 | Expect(err).ToNot(HaveOccurred()) 193 | 194 | accounts, err := offlineClient.ParseEncryptedAccounts(bytes.NewReader(encryptedAccounts)) 195 | Expect(err).ToNot(HaveOccurred()) 196 | 197 | matchAccount := PointTo(MatchFields(IgnoreExtras, Fields{ 198 | "ID": Equal(acct.ID), 199 | "Name": Equal(acct.Name), 200 | "Username": Equal(acct.Username), 201 | "Password": Equal(acct.Password), 202 | "URL": Equal(acct.URL), 203 | })) 204 | Expect(accounts).To(ContainElement(matchAccount)) 205 | 206 | // should buffer the delete request in the offlineHTTPClient 207 | Expect(offlineClient.Delete(context.Background(), acct)).To(Succeed()) 208 | Expect(offlineHTTPClient.requests).To(HaveLen(1)) 209 | 210 | // check that account did not really get deleted 211 | Expect(accountForID(onlineClient, acct.ID)).To(matchAccount) 212 | 213 | // Let's really delete the account using the buffered request from the offlineHTTPClient. 214 | httpClient := &http.Client{ 215 | // valid cookie must be set 216 | Jar: cookieJar, 217 | } 218 | resp, err := httpClient.Do(offlineHTTPClient.requests[0]) 219 | Expect(err).ToNot(HaveOccurred()) 220 | Expect(resp).To(HaveHTTPStatus(http.StatusOK)) 221 | Expect(resp).To(HaveHTTPBody(ContainSubstring("msg=\"accountdeleted\""))) 222 | Expect(accountForID(onlineClient, acct.ID)).To(BeNil()) 223 | 224 | Expect(onlineClient.Logout(context.Background())).To(Succeed()) 225 | }) 226 | }) 227 | }) 228 | 229 | type offlineHTTPClient struct { 230 | requests []*http.Request 231 | } 232 | 233 | // handles only lastpass.Client.Delete() 234 | func (c *offlineHTTPClient) Do(req *http.Request) (*http.Response, error) { 235 | switch req.URL.Path { 236 | case "/login_check.php": 237 | return &http.Response{ 238 | StatusCode: http.StatusOK, 239 | Body: io.NopCloser(bytes.NewBufferString( 240 | " ")), 241 | }, nil 242 | case "/show_website.php": 243 | c.requests = append(c.requests, req) 244 | return &http.Response{ 245 | StatusCode: http.StatusOK, 246 | Body: io.NopCloser(bytes.NewBufferString( 247 | "")), 248 | }, nil 249 | } 250 | return nil, fmt.Errorf("unexpected request for path: %s", req.URL.Path) 251 | } 252 | 253 | func accountForID(c *Client, accountID string) *Account { 254 | accounts, err := c.Accounts(context.Background()) 255 | Expect(err).NotTo(HaveOccurred()) 256 | for _, a := range accounts { 257 | if a.ID == accountID { 258 | return a 259 | } 260 | } 261 | return nil 262 | } 263 | -------------------------------------------------------------------------------- /test/unit/data/blob-1iteration.txt: -------------------------------------------------------------------------------- 1 | TFBBVgAAAAIyM0VOVFUAAAABMUVOVE0AAAABMkFUVlIAAAABMEVOQ1UAAAAAQ0JDVQAAAAExQkJURQAAAAoxNTY4OTY0MTIxSVBURQAAAAoxNTY4OTY0MTIxV01URQAAAAoxNTY4OTY0MTIxQU5URQAAAAoxNTY4OTY0MTIxRE9URQAAAAoxNTY4OTY0MTIxRkVURQAAAAoxNTY4OTY0MTIxRlVURQAAAAoxNTY4OTY0MTIxU1lURQAAAAoxNTY4OTY0MTIxV09URQAAAAoxNTY4OTY0MTIxVEFURQAAAAoxNTY4OTY0MTIxV1BURQAAAAoxNTY4OTY0MTIxU1BNVAAAADcAAAABMAAAAAEwAAAAATAAAAABMAAAAAEwAAAAATEAAAABMQAAAAEwAAAAATAAAAABMAAAAAEwUFJFRgAAAJcAAAACLTEAAAACLTEAAAACLTEAAAABMAAAAAItMQAAAAItMQAAAAItMQAAAAAAAAABMAAAAAAAAAABMQAAAAExAAAAATEAAAABMAAAAAAAAAABMQAAAAExAAAAATEAAAABMQAAAAExAAAAATEAAAABMQAAAAExAAAAATAAAAABMAAAAAAAAAABMQAAAAEwAAAAATAAAAAATk1BQwAAAAExQUNDVAAAAS4AAAATNzA0Mjc4Mzc1NzUzNTMyNzM3MQAAACEh5wh2lzxib11ujs3iIIoiz3yLiOZu6iCLfT5x/qhasfgAAAAAAAAAFjY4NzQ3NDcwM2EyZjJmNzU3MjZjMzMAAAAAAAAAATAAAAAAAAAAAAAAAAAAAAABMAAAAAEwAAAAATAAAAAKMTU2NjM3NDAwOQAAAAEwAAAAATAAAAAAAAAAEzcwNDI3ODM3NTc1MzUzMjczNzEAAAAAAAAAAAAAAAAAAAABMAAAAAEwAAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAAKMTU2NjM3NDAwNAAAAAEwAAAACjE1NjYzNzM5ODgAAAAKMTU2NjM3Mzk4OAAAAAAAAAABMAAAAAEwAAAAAE5FVlIAAAApAAAAATMAAAAgNjQ3MjY5NzY2NTJlNjc2ZjZmNjc2YzY1MmU2MzZmNmRFUUROAAAAJQAAAAExAAAAHDYxNmQ2NTcyNjk3NDcyNjE2NDY1MmU2MzZmNmRFUUROAAAAKQAAAAExAAAAIDc0NjQ2MTZkNjU3MjY5NzQ3MjYxNjQ2NTJlNjM2ZjZkRVFETgAAACsAAAABMgAAACI2MjYxNmU2YjZmNjY2MTZkNjU3MjY5NjM2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2MjZmNjY2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2ZDYyNmU2MTJlNjM2ZjZkRVFETgAAAB8AAAABNAAAABY3OTZmNzU3NDc1NjI2NTJlNjM2ZjZkRVFETgAAAB0AAAABNAAAABQ2NzZmNmY2NzZjNjUyZTYzNmY2ZEVRRE4AAAAbAAAAATQAAAASNjc2ZDYxNjk2YzJlNjM2ZjZkRVFETgAAABsAAAABNQAAABI2MTcwNzA2YzY1MmU2MzZmNmRFUUROAAAAHQAAAAE1AAAAFDY5NjM2YzZmNzU2NDJlNjM2ZjZkRVFETgAAACUAAAABNgAAABw3NzY1NmM2YzczNjY2MTcyNjc2ZjJlNjM2ZjZkRVFETgAAABUAAAABNgAAAAw3NzY2MmU2MzZmNmRFUUROAAAAJgAAAAQyMjk2AAAAGjZkNzk2ZDY1NzI3MjY5NmM2YzJlNjM2ZjZkRVFETgAAABgAAAAEMjI5NgAAAAw2ZDZjMmU2MzZmNmRFUUROAAAALAAAAAIxMgAAACI2MTYzNjM2Zjc1NmU3NDZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMTIAAAAQNjM2OTc0NjkyZTYzNmY2ZEVRRE4AAAAiAAAAAjEyAAAAGDYzNjk3NDY5NjI2MTZlNmIyZTYzNmY2ZEVRRE4AAAAkAAAAAjEyAAAAGjYzNjk3NDY5NjM2MTcyNjQ3MzJlNjM2ZjZkRVFETgAAAC4AAAACMTIAAAAkNjM2OTc0Njk2MjYxNmU2YjZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNjM2ZTY1NzQyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDYzNmU2NTc0NzQ3NjJlNjM2ZjZkRVFETgAAACIAAAACMjIAAAAYNjQ2Zjc3NmU2YzZmNjE2NDJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNmU2NTc3NzMyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDc1NzA2YzZmNjE2NDJlNjM2ZjZkRVFETgAAAC4AAAACMzIAAAAkNjI2MTZlNjE2ZTYxNzI2NTcwNzU2MjZjNjk2MzJlNjM2ZjZkRVFETgAAABgAAAACMzIAAAAONjc2MTcwMmU2MzZmNmRFUUROAAAAIAAAAAIzMgAAABY2ZjZjNjQ2ZTYxNzY3OTJlNjM2ZjZkRVFETgAAABoAAAACNDIAAAAQNjI2OTZlNjcyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjY4NmY3NDZkNjE2OTZjMmU2MzZmNmRFUUROAAAAGgAAAAI0MgAAABA2YzY5NzY2NTJlNjM2ZjZkRVFETgAAACQAAAACNDIAAAAaNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmRFUUROAAAAGAAAAAI0MgAAAA42ZDczNmUyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjc3Njk2ZTY0NmY3NzczMmU2MzZmNmRFUUROAAAAKgAAAAI0MgAAACA3NzY5NmU2NDZmNzc3MzYxN2E3NTcyNjUyZTYzNmY2ZEVRRE4AAAAeAAAAAjQyAAAAFDZmNjY2NjY5NjM2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNzM2Yjc5NzA2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNjE3YTc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACNTIAAAASNzU2MTMyNjc2ZjJlNjM2ZjZkRVFETgAAAB4AAAACNTIAAAAUNzU2ZTY5NzQ2NTY0MmU2MzZmNmRFUUROAAAAJgAAAAI1MgAAABw3NTZlNjk3NDY1NjQ3NzY5NjY2OTJlNjM2ZjZkRVFETgAAACIAAAACODIAAAAYNmY3NjY1NzI3NDc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACODIAAAASNzk2MTY4NmY2ZjJlNjM2ZjZkRVFETgAAAB4AAAACODIAAAAUNjY2YzY5NjM2YjcyMmU2MzZmNmRFUUROAAAAJAAAAAI5MgAAABo3YTZmNmU2NTYxNmM2MTcyNmQyZTYzNmY2ZEVRRE4AAAAiAAAAAjkyAAAAGDdhNmY2ZTY1NmM2MTYyNzMyZTYzNmY2ZEVRRE4AAAAbAAAAAzg0MgAAABA2MTc2NmY2ZTJlNjM2ZjZkRVFETgAAACMAAAADODQyAAAAGDc5NmY3NTcyNjE3NjZmNmUyZTYzNmY2ZEVRRE4AAAAsAAAABDE0NzQAAAAgMzEzODMwMzA2MzZmNmU3NDYxNjM3NDczMmU2MzZmNmRFUUROAAAAKgAAAAQxNDc0AAAAHjM4MzAzMDYzNmY2ZTc0NjE2Mzc0NzMyZTYzNmY2ZEVRRE4AAAAgAAAABDIwMDAAAAAUNjE2ZDYxN2E2ZjZlMmU2MzZmNmRFUUROAAAAJAAAAAQyMDAwAAAAGDYxNmQ2MTdhNmY2ZTJlNjM2ZjJlNzU2YkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2MzYxRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY0NjVFUUROAAAAHgAAAAQyMDAwAAAAEjYxNmQ2MTdhNmY2ZTJlNjY3MkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2NTczRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY5NzRFUUROAAAAJgAAAAQyMDAwAAAAGjYxNmQ2MTdhNmY2ZTJlNjM2ZjZkMmU2MTc1RVFETgAAADIAAAAEMjAxMAAAACY2NTc4NzA3MjY1NzM3MzJkNzM2MzcyNjk3MDc0NzMyZTYzNmY2ZEVRRE4AAAAqAAAABDIwMTAAAAAeNmQ2NTY0NjM2ZjY4NjU2MTZjNzQ2ODJlNjM2ZjZkRVFETgAAABoAAAAEMjAxMQAAAA42MzZmNzgyZTYzNmY2ZEVRRE4AAAAaAAAABDIwMTEAAAAONjM2Zjc4MmU2ZTY1NzRFUUROAAAAKgAAAAQyMDExAAAAHjYzNmY3ODYyNzU3MzY5NmU2NTczNzMyZTYzNmY2ZEVRRE4AAAAyAAAABDIwMjEAAAAmNmQ3OTZlNmY3Mjc0NmY2ZTYxNjM2MzZmNzU2ZTc0MmU2MzZmNmRFUUROAAAAIAAAAAQyMDIxAAAAFDZlNmY3Mjc0NmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNmU2NTc0RVFETgAAACIAAAAEMjA0MQAAABY2YzZmNjc2ZDY1Njk2ZTJlNjM2ZjZkRVFETgAAABwAAAAEMjA0MQAAABA2YzZmNjc2ZDY1MmU2OTZlRVFETgAAACIAAAAEMjA1MQAAABY3MjYxNmI3NTc0NjU2ZTJlNjM2ZjZkRVFETgAAABoAAAAEMjA1MQAAAA42Mjc1NzkyZTYzNmY2ZEVRRE4AAAAkAAAABDIwNjEAAAAYNzM2OTcyNjk3NTczNzg2ZDJlNjM2ZjZkRVFETgAAACAAAAAEMjA2MQAAABQ3MzY5NzI2OTc1NzMyZTYzNmY2ZEVRRE4AAAAYAAAABDIwNzEAAAAMNjU2MTJlNjM2ZjZkRVFETgAAACAAAAAEMjA3MQAAABQ2ZjcyNjk2NzY5NmUyZTYzNmY2ZEVRRE4AAAAmAAAABDIwODEAAAAaMzMzNzczNjk2NzZlNjE2YzczMmU2MzZmNmRFUUROAAAAJAAAAAQyMDgxAAAAGDYyNjE3MzY1NjM2MTZkNzAyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjI2MTczNjU2MzYxNmQ3MDY4NzEyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjg2OTY3Njg3MjY5NzM2NTY4NzEyZTYzNmY2ZEVRRE4AAAAsAAAABDIwOTEAAAAgNzM3NDY1NjE2ZDcwNmY3NzY1NzI2NTY0MmU2MzZmNmRFUUROAAAAMAAAAAQyMDkxAAAAJDczNzQ2NTYxNmQ2MzZmNmQ2ZDc1NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIxMDEAAAAQNjM2ODYxNzI3NDJlNjk2ZkVRRE4AAAAiAAAABDIxMDEAAAAWNjM2ODYxNzI3NDY5NmYyZTYzNmY2ZEVRRE4AAAAqAAAABDIxMTEAAAAeNjc2Zjc0NmY2ZDY1NjU3NDY5NmU2NzJlNjM2ZjZkRVFETgAAACwAAAAEMjExMQAAACA2MzY5NzQ3MjY5Nzg2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIxMTEAAAAcNmM2ZjY3NmQ2NTY5NmU2OTZlNjMyZTYzNmY2ZEVRRE4AAAAiAAAABDIyMDEAAAAWNjc2ZjY3NmY2MTY5NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMDEAAAAgNjc2ZjY3NmY2OTZlNjY2YzY5Njc2ODc0MmU2MzZmNmRFUUROAAAAHgAAAAQyMjExAAAAEjZkNzk3MzcxNmMyZTYzNmY2ZEVRRE4AAAAgAAAABDIyMTEAAAAUNmY3MjYxNjM2YzY1MmU2MzZmNmRFUUROAAAAJAAAAAQyMjIxAAAAGDY0Njk3MzYzNmY3NjY1NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMjEAAAAgNjQ2OTczNjM2Zjc2NjU3MjYzNjE3MjY0MmU2MzZmNmRFUUROAAAAGgAAAAQyMjMxAAAADjY0NjM3NTJlNmY3MjY3RVFETgAAACgAAAAEMjIzMQAAABw2NDYzNzUyZDZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACoAAAAEMjI0MQAAAB42ZTY1NjY2Mzc1NmY2ZTZjNjk2ZTY1MmU2MzZmNmRFUUROAAAAHgAAAAQyMjQxAAAAEjZlNjU2NjYzNzUyZTYzNmY2ZEVRRE4AAAAmAAAABDIyNzEAAAAaNjM2NTZlNzQ3NTcyNzkzMjMxMmU2MzZmNmRFUUROAAAAJAAAAAQyMjcxAAAAGDMyMzE2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTZlNjU3NEVRRE4AAAAiAAAABDIyODEAAAAWNzg2NjY5NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTEAAAAmNjM3MjY5NjM2YjY1NzQ3NzY5NzI2NTZjNjU3MzczMmU2MzZmNmRFUUROAAAAKgAAAAQyMjkxAAAAHjYxNjk2Zjc3Njk3MjY1NmM2NTczNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIyOTIAAAAaNmQ2MTZlNjQ3NDYyNjE2ZTZiMmU2MzZmNmRFUUROAAAAGgAAAAQyMjkyAAAADjZkNzQ2MjJlNjM2ZjZkRVFETgAAACIAAAAEMjI5MwAAABY2MTZjNjk2MjYxNjI2MTJlNjM2ZjZkRVFETgAAACgAAAAEMjI5MwAAABw2MTZjNjk2NTc4NzA3MjY1NzM3MzJlNjM2ZjZkRVFETgAAACwAAAAEMjI5NAAAACA2ZDY1NzI2MzYxNjQ2ZjZjNjk3NjcyNjUyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTQAAAAmNmQ2NTcyNjM2MTY0NmY2YzY5NzY3MjY1MmU2MzZmNmQyZTYyNzJFUUROAAAALAAAAAQyMjk0AAAAIDZkNjU3MjYzNjE2NDZmNmM2OTYyNzI2NTJlNjM2ZjZkRVFETgAAACAAAAAEMjI5NQAAABQ3NDYxNmY2MjYxNmYyZTYzNmY2ZEVRRE4AAAAeAAAABDIyOTUAAAASNzQ2ZDYxNmM2YzJlNjM2ZjZkRVFETgAAACIAAAAEMjI5NQAAABY2MTZjNjk2ZDYxNmQ2MTJlNjM2ZjZkRVFETgAAABwAAAAEMjI5NQAAABAzMTM2MzgzODJlNjM2ZjZkRVFETgAAACoAAAAEMjI5NgAAAB42ZDY1NzI3MjY5NmM2YzY1NjQ2NzY1MmU2MzZmNmRFUUROAAAAIgAAAAQyMjk3AAAAFjdhNjU2ZTY0NjU3MzZiMmU2MzZmNmRFUUROAAAAHgAAAAQyMjk3AAAAEjdhNmY3MDY5NmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQ2NTZjNjU2YjZmNmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQyZDZmNmU2YzY5NmU2NTJlNjQ2NUVRRE4AAAAkAAAABDIyOTkAAAAYNjE3NTc0NmY2NDY1NzM2YjJlNjM2ZjZkRVFETgAAACYAAAAEMjI5OQAAABo3NDY5NmU2YjY1NzI2MzYxNjQyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDAAAAAaNzI2MTY5NmM2ZTYxNzQ2OTZmNmUyZTcyNzVFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NDY1RVFETgAAACoAAAAEMjMwMAAAAB43MjYxNjk2YzJkNmU2MTc0Njk2ZjZlMmU2MzZmNmRFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NzcyRVFETgAAACYAAAAEMjMwMAAAABo3MjYxNjk2YzZlNjE3NDY5NmY2ZTJlNzU3M0VRRE4AAAAoAAAABDIzMDAAAAAcNzQ3Mjc1NjM2YjZlNjE3NDY5NmY2ZTJlNjQ2NUVRRE4AAAAsAAAABDIzMDAAAAAgNzQ3MjYxNzY2OTYxNmU2NzYxNmQ2NTczMmU2MzZmNmRFUUROAAAAHgAAAAQyMzAxAAAAEjc3NzA2Mzc1MmU2MzZmNmY3MEVRRE4AAAAoAAAABDIzMDEAAAAcNzc3MDYzNzU2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIzMDIAAAAcNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZEVRRE4AAAAuAAAABDIzMDIAAAAiNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZDJlNjE3NUVRRE4AAAAsAAAABDIzMDIAAAAgNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmYyZTc1NmJFUUROAAAAGAAAAAQyMzAzAAAADDZkNjkyZTYzNmY2ZEVRRE4AAAAgAAAABDIzMDMAAAAUNzg2OTYxNmY2ZDY5MmU2MzZmNmRFUUROAAAAJAAAAAQyMzA0AAAAGDY2NjE2MzY1NjI2ZjZmNmIyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDQAAAAaNmQ2NTczNzM2NTZlNjc2NTcyMmU2MzZmNmRFUUROAAAAPAAAAAQyMzA1AAAAMDY0Njk3MzZlNjU3OTZkNmY3NjY5NjU3MzYxNmU3OTc3Njg2NTcyNjUyZTYzNmY2ZEVRRE4AAAAYAAAABDIzMDUAAAAMNjc2ZjJlNjM2ZjZkRVFETgAAACAAAAAEMjMwNQAAABQ2NDY5NzM2ZTY1NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDUAAAAQNjU3MzcwNmUyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNmQ3OTc1NzYyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNzU3Njc2NzUyZTYzNmY2ZEVRRE4AAAAsAAAABDIzMDcAAAAgNjI2MTZlNmIyZDc5NjE2ODYxNzYyZTYzNmYyZTY5NmNFUUROAAAAMAAAAAQyMzA3AAAAJDYyNjE2ZTZiNjg2MTcwNmY2MTZjNjk2ZDJlNjM2ZjJlNjk2Y0VRRE4AAAAeAAAABDIzMDgAAAASNmQ2NDczNmY2YzJlNjM2ZjZkRVFETgAAACYAAAAEMjMwOAAAABo2OTZkNjU2NDY5NjQ2MTc0NjEyZTYzNmY2ZEVRRE4AAAAwAAAABDIzMDkAAAAkNzY2ZjZjNzY2ZjZmNjM2NTYxNmU3MjYxNjM2NTJlNjM2ZjZkRVFETgAAADAAAAAEMjMwOQAAACQ3NjY5NzI3NDc1NjE2YzcyNjU2NzYxNzQ3NDYxMmU2MzZmNmRFUUROAAAAIAAAAAQyMzEwAAAAFDZkNzk2MzYxNmU2MTZjMmU2NjcyRVFETgAAACgAAAAEMjMxMAAAABw2MzYxNmU2MTZjMmQ3MDZjNzU3MzJlNjM2ZjZkRVFETgAAACYAAAAEMjMxMQAAABo3NDcyNzM3MjY1NzQ2OTcyNjUyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTEAAAAaNjQ2OTc2Njk2ZTc2NjU3Mzc0MmU2MzZmNmRFUUROAAAALAAAAAQyMzEyAAAAIDZlNmY3MjczNmIyZDc0Njk3MDcwNjk2ZTY3MmU2ZTZmRVFETgAAACAAAAAEMjMxMgAAABQ2Mjc1Nzk3MDYxNzM3MzJlNmU2ZkVRRE4AAAAqAAAABDIzMTMAAAAeNmQ3OTJkNjI2ZjZmNmI2OTZlNjc3MzJlNmY3MjY3RVFETgAAACgAAAAEMjMxMwAAABw2ZDc5MmQ2MjZmNmY2YjY5NmU2NzczMmU2MzYzRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk2NzZmMmU2MzZmMmU2ZTdhRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk3NDc2MmU2MzZmMmU2ZTdhRVFETgAAAB4AAAAEMjMxNQAAABI3ODY5NjE2ZDY5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE1AAAAFDYxNmM2OTcwNjE3OTJlNjM2ZjZkRVFETgAAACQAAAAEMjMxNgAAABg2MjYxNmU2MzZmNmQ2NTcyMmU2MzZmNmRFUUROAAAAKgAAAAQyMzE2AAAAHjYyNjE2ZTYzNmY2ZDY1NzIyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAoAAAABDIzMTYAAAAcNjI2Mjc2NjE2ZTY1NzQyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAkAAAABDIzMTcAAAAYNzQ3NTcyNjI2Zjc0NjE3ODJlNjM2ZjZkRVFETgAAACAAAAAEMjMxNwAAABQ2OTZlNzQ3NTY5NzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIzMTgAAAAWNzM2ODZmNzA2OTY2NzkyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTgAAAAaNmQ3OTczNjg2ZjcwNjk2Njc5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE5AAAAFDYzNmY2ZTYzNzU3MjJlNjM2ZjZkRVFETgAAADIAAAAEMjMxOQAAACY2MzZmNmU2Mzc1NzI3MzZmNmM3NTc0Njk2ZjZlNzMyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMjAAAAAQNjU2MjYxNzkyZTYzNmY2ZEVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYxNzRFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MjY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjM2MUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYzNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MzZlRVFETgAAACAAAAAEMjMyMAAAABQ2NTYyNjE3OTJlNjM2ZjJlNmE3MEVRRE4AAAAgAAAABDIzMjAAAAAUNjU2MjYxNzkyZTYzNmYyZTc0NjhFUUROAAAAIAAAAAQyMzIwAAAAFDY1NjI2MTc5MmU2MzZmMmU3NTZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2MTc1RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ODZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ZDc5RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3MzY3RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3NDc3RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjQ2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY1NzNFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjY3MkVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY5NmVFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTc0RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNmU2Y0VRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTcwNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU3MDZjRVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNzM2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTc2NmVFUUROAAAALAAAAAQyMzIwAAAAIDY3Njk3NDc0Njk2NzY5NjQ2OTc5NmY3MjJlNjM2ZjZkRVFETgAAACYAAAAEMjMyMAAAABo2NzZkNjE3MjZiNjU3NDJlNjM2ZjJlNmI3MkVRRE4AAAAgAAAABDIzMjEAAAAUNzM2MzY4Nzc2MTYyMmU2MzZmNmRFUUROAAAAKAAAAAQyMzIxAAAAHDczNjM2ODc3NjE2MjcwNmM2MTZlMmU2MzZmNmRFUUROAAAAHgAAAAQyMzIyAAAAEjY4NzY2NjYzNzUyZTZmNzI2N0VRRE4AAAAqAAAABDIzMjIAAAAeNjg3NjY2NjM3NTZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACIAAAAEMjMyMwAAABY2NjY5NzI2NTY2NmY3ODJlNjM2ZjZkRVFETgAAACIAAAAEMjMyMwAAABY2ZDZmN2E2OTZjNmM2MTJlNmY3MjY3RVFETgAAAC4AAAAEMjMyNAAAACI2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTJlNjM2ZjZkRVFETgAAAEIAAAAEMjMyNAAAADY2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTYzNmM2OTY1NmU3NDczNjU3Mjc2MmU2MzZmNmRFUUROAAAANAAAAAQyMzI0AAAAKDczNzQ2ZjYzNmI3MDZjNjE2ZTYzNmY2ZTZlNjU2Mzc0MmU2MzZmNmRFUUROAAAAGAAAAAQyMzI0AAAADDZkNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMjUAAAAaNjE3MzZiNzU2Mjc1NmU3NDc1MmU2MzZmNmRFUUROAAAALAAAAAQyMzI1AAAAIDZkNjE3NDY4NmY3NjY1NzI2NjZjNmY3NzJlNmU2NTc0RVFETgAAACoAAAAEMjMyNQAAAB43MzY1NzI3NjY1NzI2NjYxNzU2Yzc0MmU2MzZmNmRFUUROAAAAJgAAAAQyMzI1AAAAGjczNzQ2MTYzNmI2MTcwNzA3MzJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNjU3ODYzNjg2MTZlNjc2NTJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNmY3NjY1NzI2NjZjNmY3NzJlNjM2ZjZkRVFETgAAACYAAAAEMjMyNQAAABo3Mzc1NzA2NTcyNzU3MzY1NzIyZTYzNmY2ZFVSVUwAAAAtAAAAGjY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY2MTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACkAAAAWNmM2ZjY3NmQ2NTY5NmUyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAA/AAAALDczNjk3NDY1NzMyZTY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY3MzY5NzQ2NTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACcAAAAUNzc2NTY1NjI2Yzc5MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA43NzY5NzgyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAjAAAAEDc3NjU2MjczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODZmNmQ2NTczNzQ2NTYxNjQyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjc3NmY3MjY0NzA3MjY1NzM3MzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACUAAAASNmE2OTZkNjQ2ZjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAAC0AAAAaNzc2NTYyNzM3NDYxNzI3NDczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo3MzZlNjE3MDcwNjE2NzY1NzMyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAxAAAAHjYzNmM2Zjc1NjQ2MTYzNjM2NTczNzMyZTZlNjU3NAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjc3NjU2MjZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB42ZjZlNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODY1NzI2ZjZiNzU2MTcwNzAyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAfAAAADDZlNmY3NjJlNzI3NQAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjYxNzA3MDczNzA2Zjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3YTY1NmU2NDY1NzM2YjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmI2MTc5NjE2YjZmMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3MzY4NmY3MDY5NjY3OTJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmM2OTZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAJwAAABQ3NDc1NmQ2MjZjNzIyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjZlNjU3NDY2NmM2OTc4MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB43MzcwNzI2NTYxNjQ3MzY4Njk3Mjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA42NjcyNjU2NTJlNjY3MgAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjZmNzY2NTcyMmQ2MjZjNmY2NzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNjY2MTcyNmQ2MTJlNzM2OTc0NjUAAAABMQAAAAEwAAAAATBUT1RQAAAAEAAAAAEwAAAAATAAAAACe31QUklLAAATYEQ3NTc0NTJGMTdEQTZFRTc5QzVBOEQwN0QyMDM4RTAzMUJEMUVFNjY4MDE3QTQ3MzVCMzMxOUFDODRDNTYwRDZFQTFEOTE1MTI5RjdEQUY2MDFBQTZCQUY4MTE5QTdDRjI0NEM0NEUwRjNBMkVGNDNDQUFCRjE4RjVGNEJFNTNCMUQ5OUZBNDkzNDk3QTUzQTZGNzE0NkFBNjVEQUE2MkVGNUM4MkFDQkY1NTEzMUVGNDlEMjU0NkQyQTkwNEVCNTI2MDBEQjA1QTg0MEIwQTAzMTc2N0VDRDRBOUNGMjU2NjQ5MDhEMUFGQjAxQ0ZGODU0RDczNTgyNjgyNTUzNzRBOUNCRjk3MUI5NzIyN0Y5MjVDQUFCOERBNzk0OUY5QTlBRjQxQjhEM0NCNDQ5NjA1NDAxM0E0MTM2MzQ1NTMyMkI5NTYyRTM1NEQ0QkU2MzgyMUYxNzJFRDkxRjQyMDc2RDZGMzVBRTg3N0QxNzQwNjNGOUFDNkJBMThGQTkzRkNERjE2OEM2NzU3QjE3NDRBRTYxNEIzRjE3NDk0RkYxQ0M1MjI5QzhEMEMwMEYxNDBCNEY2NDM4ODEwN0EzNDAxRDdGN0ZCNzY4QjI2ODY0MUUxNDk1NzM2OTI3RjA1RDIyRDE1NDBCNzRDMDYxNTZCNjk0OTlCNjc1NTg5M0Y0NUU3QUIxRDZEODg2N0E0OTZEOTlGOTg4QjEzRjRDODUxQTYwRUExREUwM0NBMEY4RUNDQkVCQTk1NzQ5NDM4QzNGMDlEODA5RDI1QjlGRDNCNDc5QjQwRjYyMUJBRUY0OTJCQzM4NUVGMTg2REZCNzJENjAwMjVGQzhCMkM4NUYyMEU5MEI0RjlCMTJEMkE5RUYyMjNDMUM5MjhFQUQ0QjkwRTFBMDE3REZEREZDRTBDNUQ0MTdGM0Q4QkI3QzIwNTUyRjU0Mjk3QTZDQ0ZGODRGNUFEOUU5RDk5NzNGNTM4NDM0QzY1QjQ1NzE3NTA2Qzk1QjI3RjFCQURBNENGQjJDNjZDQjA1NUVGRkY3QUEwNUMyQzNFNjg2RDU5NEQ1MTRCMjBEQkUxREM3MURDRTNCQzEwNDhDQUQ3RTEwRDJFQ0JGNUIyNUNCODVBMkYzNEJEQjNFNENFM0E5NkYxRTkyMTI5MTQyRUQ5RjEyQjAyODA3MjhBRUE4Q0U1Njk5MUZEQjkxOTJGM0JCQ0JEQzg0NUIwMTg1NjU4MEYxQTUwMjhDQkVGQzYwRTk5QkNDQzc2MEE5MTMzMUZEQ0M1Qjg5OEVCN0I0NTgzQTMwNTJCMzQwRkIzMzNFRUZFNUI0RTkxQTBGODlGQUMwNTEyMzZFM0UxQzlBRTlDMkY3MjQxMTc2QjJDRDExMDJERDZBRkVENzFDNURGRjIxRkY0NkMxMDJBRjU5MTJDNjgxMjdCRTZCMDQ4M0Q2MzA0NEUzN0VBNzQyRDNDNjgwNUFENTREQUZERUE3N0Y5QTg5NjkxNjg1RUEyRjRBN0MxQ0I5MjcxNEIxRUVDN0IyMjA0RERFMzg3RkYzMDJDODgyQzIyRUQwREM5QkM4RThFMTMzRTk0MEExMkNBN0VDNjMwMTY2MEY2RkJCMjIwMDlBNzc0Q0QyODJEMEU1NkFFRDQ1Nzk0MEIyODIzRjkzNEYzMEIwOUY3MTUwQUUyREIyRTIzMUMyMDE0NEVFNDc2QTkyRkJEQzY1RERDNDczOTQzMTlGN0M1QTBBRTg0OUFGNzVCRDJDM0NDQUExRkRERUVBNjAxRDZFMUU2RkQ2NkUxMDIyRjk0Nzg0OUJBRDY1RkE0N0M2RTE2QjM2OUM0MUVDQTgzRDVCMzQ5MkI1NzU4ODVCNTdCQUYzRjhGRTQ2NDREMTgwNkI3RDMyNjE5RDdGNDI2MzJBQjRDNkRCMzdGOTIwNEI3QzM5NzJERDNBQjIzMkZCOTBDMkIxMTBGMkVCMDg3NDQ3QUI5QjQ3NTcyOEQyN0IwQjAzRUEyMjA2NDIyODJCNjE0MDA0QTFBNjIyNDEwRDg0Njk5OENCNTMxQThGNzE4MzQ4RjA4OUJBMjk4MDlBOUFGOTA3RTdBRDE5NzQ5QTM0RDI0NzI3NDM0REI2MUNENzZCMTRFRkQxOTA3RDdFOTEwMDlGMTJBNjUyRUNBODJDNDMxMEUwMzk5NEIwODVBQ0UzRjM2Mjc4QUM4QzkwNzg4MEJDODhGQkRFRjdEQjc5NjA0RTM2REZBMTc2MzM4NjgwRDcwRkE1OEIwODU0MzE1N0RGQ0E4MTQ5ODYxNDI2QkRBMDZCRDFBNEZCQUIzMjNBOUFBQTk2RDBGMkMzQ0NGQjgwQUM0NkJBQ0ZBODI4NEI2MUNGNTRFNzZEQ0IwNTJEMTIwN0UzQjhCQTA3MkNBOTU4MEZEREFENDcwRUNFOUZFQ0E2OEYyNUM4RjU5MkU3NTU2NDhEREFBNkM1QkM1NzkyMjlDMEY1OTY5MDMyQjZFQzBCMTMxMUIxODhDOERDN0Y3RTA0QkY0NUI4NjY4NTJDNEY3NjA4QjU2MjRDRTIzQUUxMzlDQTA3M0E2ODZBMDIzQzZEMDU5ODU0MEQ0RkJGNzAyMjE1NUQwRDNFQjY3OTYzNTU0QUIxNUExM0U4MUY0NTQ0Q0M3NzY4NUJFOERGNUFENzlEMjQ4ODMwMjJFQUZCQzMyQjg0OTBEMTQ4MTYzODAxMkJEQjUxMzZGNjAxODBCMTE4REMyRDI3M0E3QUNDRDI3NzE0RUUwQzg0QTAzOTVDQTYyRTk0NzA3MTA1NUEzNTUyNTY5QTM5RDY4MjEzNEJFMjVFQzU2REYzODI0MkQwOEJFREJGNUNBODYxNjRGRjQ2NEZGREZDQzRGNThFODg5Q0E3RTZEOEQ1RTgyOTJBQ0RBMEU5ODBENENDRENBOEUxODhEMkVBNTZGNjlBNjEzQkIxREJFOTI2Q0VCMDBEOTE5OURBNTI0OTRGNUJGQUFBRTRBMzlCNEU1RkUzRDM3NTcyNEE5N0NCRDExN0ExQkI4RjYyOTlENUY1M0U0NkEwOUYwMTkzMDQzMEU0RjMzNjVGODZDMkNCNjYxMzk0QjVBNDYwMzU2MTg5RjEzMjQ5NTMzNDBBNDdDQkJDMTkwNjg4RThBRjk4OEU4OTgxNjM3Q0Y2Qzk3OTdBOTMwQzIzODQ4Q0I3QjQ4OURBRThGQTE4NDYwNTk3NTdDRDA4MEUzNUI0RDY2QUVEM0YzOURGRTIzNURFNzFDNjZCNDg0NDdCMEFCNEE0RDEyNUJDRDQ1RTcyOEVEQkM4RjIxNDYyN0E3Q0YzMjU2QjI2MDZGODA1QTczREQxQUExRjdDNDVFN0U1MkU5NjQwMEIzREY1QUI0REUwRDRFQ0UxOTI2RjhGNjVFRUE0QzZBNDE1QTQyQ0I4RkE1M0QwNUU4QkI5NjQ0MjdDQjkyMjc3RDBFRDUzNTI4RTVDNDE1QjU4Q0FCNkFFRjM0NDJGMDdDQjQ2QUE3RUU5ODY1M0ZCRUNBQTg5NzZERUM2Q0ZEM0Q2NzIxOEMzODI1NUI2MUMwRTk2ODI4MDUzNEU3MTY2MENGNjRFMkU3MEVEOUZBRjZEQTk4QTA0Q0QwRDgwM0Y4RDI1NUU1Qjk1RTk1NUJCMDBGMUVENjk4MjhEMzE5MTQ0QkI3MDJENTUxMzIwMkU5Qjk1NzhERTFDN0NDQzUxQkVEQkNGRDdENDRGRjBGN0I0QzRBMjAxMDg4RUZFQzlBOTdDRUJFMkM3QjdCQkRENTU2RjcwM0Y4MUMyRTQyNjA3MzZCMEMzNkI2MzFENEVDOTIzQzY5MDI5ODFGNzA1QTNFQzJERTNFNDVBM0NGRTcyRENEMEU1Nzk3MTU1MDAwMDkzNTAxN0Y3REUxNUMxNDdERTUyODU5MkE0NkU1NzEwNzIzMTc2OEVGQ0E4OEI0REY2M0IxQkQyQTk0Mzk4RjQ1Q0IwQzJGMUE3Q0Y5NDFBQzA0RTg4RkE4RTZBNEE2QTQzMkE1MUM5M0I4NEUxQTFBRDQ0M0VGMzIxRDk0REU3RjJBREY0QzYyNUUyNTEyQTk0MDlEOUE1NEJFRjFDN0U5RUFFNkFDMDZENzA5NjdGMkJFQTY2NTcwMEZCRDEyMEZGOENGQjM3MzIwMjRCRDE4N0FEQ0JEQTY3QjJFQUNGQzQxMTExREMwRDk0ODYxN0QwNUY1NzI2REFFMTNDRjQ3MjQzQzEwQ0JGNjFFMDhGMUMzNzg5MUYxNUVBOEE5MzEwMEM3ODJFMEY2MUVEMEI3OEMyM0I3MDMzMkVEMEI3Q0FCRUMzQkY5ODMzRkU3QUFBQjdBRkQwN0M0RTQxODM4RDM5RDgxNzc1QzI1NzM0NTNEMUU1MTM0RTMxRjE3RTRBMEZGNjQ3Njg1QUUwRjFBRTNEQzc5NzAwNTYwOUE3NkQ5MEFCNkIyMUREODQ4QjRCQ0E5Qzk1NjIzMzQyN0E0QjdCQzZGRTZENUE3Q0I0QzlERUM1Q0U0MjEzRjAzMjg3QjExN0VCOTMzN0Y4Mjg0Njc3NzdEQjRBQTUwODkzMEU5Q0M5OERFMjU3NjE4NEQ2M0JCM0U2OTgxNTcyREZFNkI5QkExODdCM0I5RTA3MUJBOEQ0QTJCMzMzQ0I5Q0Y2NkE5NUFFREJCOEU5NzNDRUI5QjU5RTVCRjJCNEY2QkM1QUIyNDEyNEI2N0YxRTk4QTFCMzE0QUJEOTBDMTVBNkE1N0FGM0Y1Q0VCMjVCQjQ1QUIyNTY2MjY0RjVCOTNDMTgyRkUwNUQwMjAwMEU4NzNBMjNFRUU4QjQ0ODEzRDY3RjhDNjc2MTRDMzBCQkEyNEVGN0JERTA0QzcwOUEwNzdERUQ0QUIxOTRFRDhDNDhDNDYwRjZDRENEMDY3OTkxNEYxNkE5NTE4NzdGMjdFNDgyMkU4OUM1N0I0M0Y4MDk5N0IyQjdFOEJDNDlCMEUxN0Q0MzkwM0I5NjM0QTI5NDVBMTk1QjNBNzU0QTI5NTVCNUEwNjdGRjVBRjEyRDU5RjU2MTEwN0YyMUNBNEQ4QjY1MUVCRDkyNEM4RTA4QzE1M0MyMDMwMkFFRDc0MjY4QzU3QjIyRTBGRjA0RDM0Qjc3M0E4MzA2ODg3QkM3RjNDQ0RCQzg2ODcxQjlGQkE2RDBFNDJFNDcxMjBEMEM5QTBGQzNFQUJDOUFBNDNCOUNGMTYyM0JFNjBDNjlDM0QxODc5MUIwNTJCOEU1NkExNkU4NTYyOTFGQTk5MTA5MTAxRTlFNjI1ODg4NTA0NTY0RkYxMTg5RDM2QUVCMEJBNzNDMTEwNkNBQ0U5REJENDI2RUQ0N0VEOThFRUU3REE1MUQ5MTk0M0YwQTY2M0VDRTkxOTkxQkU5MDhGOENFOEM5MDQzMzE1QzUwOEIwODY1RTI2NDkxQ0VFMjhCM0NENEM5NkI4QzgzM0Y5MjlFMjBCQTVGMDUzNUMxMzlDN0FCNTJEQ0VBMjU2QUE0QUE2QUZENThBNUNEQjg3MURDMUIzMzlFOEIxN0I4MDQ4NUNBQkZDNkQ1RjIyRDdBNTQzRTBGNTZCNDcwQUY2ODg0RTM3NTdFNDMwRUJBQTM3MEVBOTRFM0ZBQTYwMDdGOTlGQzIyODhFMzRCN0FCNzNDRTM5MDIwMzczQkMzREFCMjQ5NDY2QUZCNkI1MEJGQzk3NkZGNEQyMjg1NUU1RjUyMDc2MzQ5Q0NBNTA0RUYxOTBGQUQzODQ3MTg0NkM3QzI0NTYwNkI1RkU2RkY1QzQ5NkU5MDNCMUVFQzQ3NkU4OTI0QUYxRkM4QjU0NTI0QzVEMUYxNjUxNEZBMEQwODQ3MzFBNTg5NjREMDc2RTFFODYyODkwMzgyRDc3QUU0Qzk0NzE4QjBGMzdBNkIxNjI1RTRDMzZGREVBOEM4MzYwNEIzRThFQUNDMjA2QzhGQjk2QUY5OEU0QjRDNDQzMTkzMTJBRTk4QzY1N0JGNzBDMTdBMzgxQTY5QTBBQ0MwMTkwQkM5MUMwQkI0N0VBQzc4QkFGODZFREM4RDQyNTYyRkM0RDhCNzE5MDI3NTBBNjM5QzYxNEUxRDk0NjQ4NTMwMDhEOTQ1NTI3NEFBMDUyNjIwMEYzQzg5RUI2RTU4OEI2NkU1OTkwNzZGQ0JDN0ZENDQzNjRFMkZEQTJFQjAxNEZCMjVDNkM2RDc4NDQwMDBDRjYzRTNCREE1RjAwQzAyNUJBM0Q1RjY3MjE1QTkwQkU3NDc3OTQyRTA2ODJGNTI2RTk0MDIyQTY0QjYzNkFDQzdBNDY3RTZCNzREREEzRkJBREZDNUM4RDI4NDg2MzNDNTc4M0Q3RjU4MTIzQ0VFQjFBNjUyREY1ODEyQjM0MUI5NzFFQjE2OUNCQzY4RkY5NjE0QjQ0MzA3MEZGQzY3QzZCQ0QyNDg2N0UyNTE1MkIxOTQ3OTAyRUJEM0JCQzJBRjUzNTA3NkQzQTMyRkQ0NUVCMTQ2RUVFNDY3MzBGN0ZCNTc2Nzg1NjkzNDJFRkY2NTMxODI4M0MyM0E4QzYyMzQ5NjdFMTc5OEE2NDY4OUZDNTc2MjhEMEYwQ0RENTAzNEMzMjg0MTQzMjc1NTU2N0ZTSEFSAAACwAAAAAkyNjAyMjA1MDEAAAIANzU5ZGJiNzk0MjNjN2FlZmZkZDQ5NTlhOWE5OTVlMmIzODg2YTgzMWQ5ZDBkODYxYjk5OGU3ZTk3ZTM2YjEzYmQwMmU3NGRhOGU4ODY4OTkyNWU0Yzg3ZmQ5ZWY1Y2Q1NjA1ZTYyZWRkOTExZTkxMDIyNjRiMzZmMmI1MGM4NWQ4NzkyMmY3YTNkYWUxNWU1NDRmYTBhZGQ4ZjQ0NjIwOTA0ZDg2NzAyMGM2ZDlkZjllZGVmZjQ3NzMyYTRlZDU5MTU5Y2I1YWU0Yjk2YWJjMTYwMGIzNGQzNDI3MDkyYmVhN2IxNDkzYmQ0OTZlMTA0YTc3YTI3MWEzYjBhZmViNDI4NGUzMjkzY2MyNWIzOWU1MDMyYWQ0NDNiNGY3YjAxOGFiZDRkYTg2MjcxNjJmMTBjMjA4NmRhMWVlMGM1NmU2YzM0Nzc5NzJlOWQzYWY2ZTQ1NTQ0ZTU1YjI4NTJkZjZmMTNkMjUxNGZjODU4YTBjMzc1YTA3M2M1YzlhMWYzNjU3N2MyYmNkZjYyMjdiMDAyMDYyYWUzMmQ5MjBhNmNkMGU5YjQ1OGRiNDNmNGU0MDc2YjA4NDdiMmQ5YmYyZjFmMTM2N2EyOTBkZDVjOTA0N2VlOTE3MDY4MTYxZmEyMTc4ZmFkMmVhNGY0Mzc0MWE3ZTcyZWE5YmU4MzVjZWMAAAAyIXpVTHZXNEI0eVJSQUVvdndsTUp6NXc9PXw5QzRtU0RzYUJJWERtdS9ETng1d1FRPT0AAAABMAAAAAExAAAAYSHjHHitbB6Y7Pwk+pg3lLN8BnJIXk3gBclfot514WHMppLFUQdv4DtaVr8CmR/pjaWp+gKerPyBDFxodv+Bh2yIur0w1gdabzsi9d5GStdrXUXyw4ID6GkV+rIjK7E/opkAAAABMAAAAAExU0hBUgAAAsAAAAAJMjYwMjIwNTUxAAACADA4OTUxZjgxMTU3NzRhNGJkZDA4M2U0NzM4ZmFhZDJmYmM0ZTM3MDk0MWU3MjQ0NGJmZmQ2NDBjYzQzOWQ5ZWEwY2QzMjkwYmI3MjEwNDZjOTRhMTgyZGM2YTM2MTczYzRhNWI4OWQ4NTEwZmMwMTczNzRmOGVjNzMwNmY0NTY1OWZiNzI4NWFlNzgyNDdmOTk3YmI2MGM4YTQ0OThlZTRhYjI2OGRiZDY1YTA2MTlmMWJmYjdjYjcwZmQ5ODkwMGQ1NTFjZWM5ZTFiZTRkM2U5MDUzOGFkOTA3NWJmMzM3MGNiZDFiNjBlMmFjODE1MmQxOGM0OTEyMzRiMDdkYTViNmM0OWFiYjU0N2M2MTcxMDc5ZTQ3ZmVlYWY0NTE0ZjU5Njg4YTU5YzU2ZjBkZGFlN2M0ZGI2ZmM0OGU2Nzk0M2U0MWQxYTZmMjFjY2JlNTVhZjk2MGYwZDhlNjA0NDhkNWFkYmM4NTg5YTlkOTMwOGYzMTdlYWIwYzM5OTJkOTFhZDMwY2U1OGUzNDg4ZWMzYmE4NDc0MjliNzZjYWM2NzFhMzZkZDAwNDcyYzk3NDMxNGYzODUwMmRjZDFjYjJlMzVkZDk4Mzg1MjJlY2MxYWE0NGNlYWE0ZWQ0ZmVhZmMwY2NmOTJlMGI4YTVlYTIxMjU1ZDVmMTEwNzlhMjI1AAAAMiFXR1R6UzAxQjZoZ0RGVnZINFc0WW5nPT18UEtidDA2SHFzR08vdHQxVkdKcWl0dz09AAAAATAAAAABMQAAAGEhvITKc5ExVX8A3F34MlglJWPXB+NAOKhJCC9JqiZHO+2NSUJ9BXui/CyIxz/yxDhfXN0V1XpWpog5/TjUMh1l5ZGxGVKj50glezBmQAbXuACz4A+Pa8dvXrNiHhflZSVEAAAAATAAAAABMUVORE0AAAACT0s= -------------------------------------------------------------------------------- /test/unit/data/blob-ecb.txt: -------------------------------------------------------------------------------- 1 | TFBBVgAAAAIyMUVOVFUAAAABMUVOVE0AAAABMkFUVlIAAAABMEVOQ1UAAAAsNVVjek82T3gwUGg0T2pXOWRCaHZPdU5ZSXhXbEdrRzhpKzZvazFic2Z3Yz1DQkNVAAAAATFCQlRFAAAACjE1Njg5NjQxMjFJUFRFAAAACjE1Njg5NjQxMjFXTVRFAAAACjE1Njg5NjQxMjFBTlRFAAAACjE1Njg5NjQxMjFET1RFAAAACjE1Njg5NjQxMjFGRVRFAAAACjE1Njg5NjQxMjFGVVRFAAAACjE1Njg5NjQxMjFTWVRFAAAACjE1Njg5NjQxMjFXT1RFAAAACjE1Njg5NjQxMjFUQVRFAAAACjE1Njg5NjQxMjFXUFRFAAAACjE1Njg5NjQxMjFTUE1UAAAANwAAAAEwAAAAATAAAAABMAAAAAEwAAAAATAAAAABMQAAAAExAAAAATAAAAABMAAAAAEwAAAAATBQUkVGAAAAlwAAAAItMQAAAAItMQAAAAItMQAAAAEwAAAAAi0xAAAAAi0xAAAAAi0xAAAAAAAAAAEwAAAAAAAAAAExAAAAATEAAAABMQAAAAEwAAAAAAAAAAExAAAAATEAAAABMQAAAAExAAAAATEAAAABMQAAAAExAAAAATEAAAABMAAAAAEwAAAAAAAAAAExAAAAATAAAAABMAAAAABOTUFDAAAAATFBQ0NUAAABWQAAABM0NDE1NjA2NzE3MzUwNjQxMjU3AAAAEBGreR/Ex8QADZVT2TKFXXEAAAAQYiy/iRGzYg/BpCewtqp23wAAABo2ODc0NzQ3MDNhMmYyZjc1NzI2YzQ1NDM0MgAAABCFdSUb1THEM0aLrEppTDssAAAAATAAAAAAAAAAEKww1/0uSWGEtOqeoHg0zCAAAAAQbXBbNgNfReDk/gBhv3X/KgAAAAEwAAAAATAAAAABMAAAAAEwAAAAATAAAAABMAAAAAAAAAATNDQxNTYwNjcxNzM1MDY0MTI1NwAAAAAAAAAAAAAAAAAAAAEwAAAAATAAAAAAAAAAAAAAAAEwAAAAATAAAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAAKMTU2NjM3Mzk3OQAAAAEwAAAACjE1NjYzNzM5NzkAAAAKMTU2NjM3Mzk3OQAAAAAAAAABMAAAAAEwAAAAAE5FVlIAAAApAAAAATMAAAAgNjQ3MjY5NzY2NTJlNjc2ZjZmNjc2YzY1MmU2MzZmNmRFUUROAAAAJQAAAAExAAAAHDYxNmQ2NTcyNjk3NDcyNjE2NDY1MmU2MzZmNmRFUUROAAAAKQAAAAExAAAAIDc0NjQ2MTZkNjU3MjY5NzQ3MjYxNjQ2NTJlNjM2ZjZkRVFETgAAACsAAAABMgAAACI2MjYxNmU2YjZmNjY2MTZkNjU3MjY5NjM2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2MjZmNjY2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2ZDYyNmU2MTJlNjM2ZjZkRVFETgAAAB8AAAABNAAAABY3OTZmNzU3NDc1NjI2NTJlNjM2ZjZkRVFETgAAAB0AAAABNAAAABQ2NzZmNmY2NzZjNjUyZTYzNmY2ZEVRRE4AAAAbAAAAATQAAAASNjc2ZDYxNjk2YzJlNjM2ZjZkRVFETgAAABsAAAABNQAAABI2MTcwNzA2YzY1MmU2MzZmNmRFUUROAAAAHQAAAAE1AAAAFDY5NjM2YzZmNzU2NDJlNjM2ZjZkRVFETgAAACUAAAABNgAAABw3NzY1NmM2YzczNjY2MTcyNjc2ZjJlNjM2ZjZkRVFETgAAABUAAAABNgAAAAw3NzY2MmU2MzZmNmRFUUROAAAAJgAAAAQyMjk2AAAAGjZkNzk2ZDY1NzI3MjY5NmM2YzJlNjM2ZjZkRVFETgAAABgAAAAEMjI5NgAAAAw2ZDZjMmU2MzZmNmRFUUROAAAALAAAAAIxMgAAACI2MTYzNjM2Zjc1NmU3NDZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMTIAAAAQNjM2OTc0NjkyZTYzNmY2ZEVRRE4AAAAiAAAAAjEyAAAAGDYzNjk3NDY5NjI2MTZlNmIyZTYzNmY2ZEVRRE4AAAAkAAAAAjEyAAAAGjYzNjk3NDY5NjM2MTcyNjQ3MzJlNjM2ZjZkRVFETgAAAC4AAAACMTIAAAAkNjM2OTc0Njk2MjYxNmU2YjZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNjM2ZTY1NzQyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDYzNmU2NTc0NzQ3NjJlNjM2ZjZkRVFETgAAACIAAAACMjIAAAAYNjQ2Zjc3NmU2YzZmNjE2NDJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNmU2NTc3NzMyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDc1NzA2YzZmNjE2NDJlNjM2ZjZkRVFETgAAAC4AAAACMzIAAAAkNjI2MTZlNjE2ZTYxNzI2NTcwNzU2MjZjNjk2MzJlNjM2ZjZkRVFETgAAABgAAAACMzIAAAAONjc2MTcwMmU2MzZmNmRFUUROAAAAIAAAAAIzMgAAABY2ZjZjNjQ2ZTYxNzY3OTJlNjM2ZjZkRVFETgAAABoAAAACNDIAAAAQNjI2OTZlNjcyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjY4NmY3NDZkNjE2OTZjMmU2MzZmNmRFUUROAAAAGgAAAAI0MgAAABA2YzY5NzY2NTJlNjM2ZjZkRVFETgAAACQAAAACNDIAAAAaNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmRFUUROAAAAGAAAAAI0MgAAAA42ZDczNmUyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjc3Njk2ZTY0NmY3NzczMmU2MzZmNmRFUUROAAAAKgAAAAI0MgAAACA3NzY5NmU2NDZmNzc3MzYxN2E3NTcyNjUyZTYzNmY2ZEVRRE4AAAAeAAAAAjQyAAAAFDZmNjY2NjY5NjM2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNzM2Yjc5NzA2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNjE3YTc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACNTIAAAASNzU2MTMyNjc2ZjJlNjM2ZjZkRVFETgAAAB4AAAACNTIAAAAUNzU2ZTY5NzQ2NTY0MmU2MzZmNmRFUUROAAAAJgAAAAI1MgAAABw3NTZlNjk3NDY1NjQ3NzY5NjY2OTJlNjM2ZjZkRVFETgAAACIAAAACODIAAAAYNmY3NjY1NzI3NDc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACODIAAAASNzk2MTY4NmY2ZjJlNjM2ZjZkRVFETgAAAB4AAAACODIAAAAUNjY2YzY5NjM2YjcyMmU2MzZmNmRFUUROAAAAJAAAAAI5MgAAABo3YTZmNmU2NTYxNmM2MTcyNmQyZTYzNmY2ZEVRRE4AAAAiAAAAAjkyAAAAGDdhNmY2ZTY1NmM2MTYyNzMyZTYzNmY2ZEVRRE4AAAAbAAAAAzg0MgAAABA2MTc2NmY2ZTJlNjM2ZjZkRVFETgAAACMAAAADODQyAAAAGDc5NmY3NTcyNjE3NjZmNmUyZTYzNmY2ZEVRRE4AAAAsAAAABDE0NzQAAAAgMzEzODMwMzA2MzZmNmU3NDYxNjM3NDczMmU2MzZmNmRFUUROAAAAKgAAAAQxNDc0AAAAHjM4MzAzMDYzNmY2ZTc0NjE2Mzc0NzMyZTYzNmY2ZEVRRE4AAAAgAAAABDIwMDAAAAAUNjE2ZDYxN2E2ZjZlMmU2MzZmNmRFUUROAAAAJAAAAAQyMDAwAAAAGDYxNmQ2MTdhNmY2ZTJlNjM2ZjJlNzU2YkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2MzYxRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY0NjVFUUROAAAAHgAAAAQyMDAwAAAAEjYxNmQ2MTdhNmY2ZTJlNjY3MkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2NTczRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY5NzRFUUROAAAAJgAAAAQyMDAwAAAAGjYxNmQ2MTdhNmY2ZTJlNjM2ZjZkMmU2MTc1RVFETgAAADIAAAAEMjAxMAAAACY2NTc4NzA3MjY1NzM3MzJkNzM2MzcyNjk3MDc0NzMyZTYzNmY2ZEVRRE4AAAAqAAAABDIwMTAAAAAeNmQ2NTY0NjM2ZjY4NjU2MTZjNzQ2ODJlNjM2ZjZkRVFETgAAABoAAAAEMjAxMQAAAA42MzZmNzgyZTYzNmY2ZEVRRE4AAAAaAAAABDIwMTEAAAAONjM2Zjc4MmU2ZTY1NzRFUUROAAAAKgAAAAQyMDExAAAAHjYzNmY3ODYyNzU3MzY5NmU2NTczNzMyZTYzNmY2ZEVRRE4AAAAyAAAABDIwMjEAAAAmNmQ3OTZlNmY3Mjc0NmY2ZTYxNjM2MzZmNzU2ZTc0MmU2MzZmNmRFUUROAAAAIAAAAAQyMDIxAAAAFDZlNmY3Mjc0NmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNmU2NTc0RVFETgAAACIAAAAEMjA0MQAAABY2YzZmNjc2ZDY1Njk2ZTJlNjM2ZjZkRVFETgAAABwAAAAEMjA0MQAAABA2YzZmNjc2ZDY1MmU2OTZlRVFETgAAACIAAAAEMjA1MQAAABY3MjYxNmI3NTc0NjU2ZTJlNjM2ZjZkRVFETgAAABoAAAAEMjA1MQAAAA42Mjc1NzkyZTYzNmY2ZEVRRE4AAAAkAAAABDIwNjEAAAAYNzM2OTcyNjk3NTczNzg2ZDJlNjM2ZjZkRVFETgAAACAAAAAEMjA2MQAAABQ3MzY5NzI2OTc1NzMyZTYzNmY2ZEVRRE4AAAAYAAAABDIwNzEAAAAMNjU2MTJlNjM2ZjZkRVFETgAAACAAAAAEMjA3MQAAABQ2ZjcyNjk2NzY5NmUyZTYzNmY2ZEVRRE4AAAAmAAAABDIwODEAAAAaMzMzNzczNjk2NzZlNjE2YzczMmU2MzZmNmRFUUROAAAAJAAAAAQyMDgxAAAAGDYyNjE3MzY1NjM2MTZkNzAyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjI2MTczNjU2MzYxNmQ3MDY4NzEyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjg2OTY3Njg3MjY5NzM2NTY4NzEyZTYzNmY2ZEVRRE4AAAAsAAAABDIwOTEAAAAgNzM3NDY1NjE2ZDcwNmY3NzY1NzI2NTY0MmU2MzZmNmRFUUROAAAAMAAAAAQyMDkxAAAAJDczNzQ2NTYxNmQ2MzZmNmQ2ZDc1NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIxMDEAAAAQNjM2ODYxNzI3NDJlNjk2ZkVRRE4AAAAiAAAABDIxMDEAAAAWNjM2ODYxNzI3NDY5NmYyZTYzNmY2ZEVRRE4AAAAqAAAABDIxMTEAAAAeNjc2Zjc0NmY2ZDY1NjU3NDY5NmU2NzJlNjM2ZjZkRVFETgAAACwAAAAEMjExMQAAACA2MzY5NzQ3MjY5Nzg2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIxMTEAAAAcNmM2ZjY3NmQ2NTY5NmU2OTZlNjMyZTYzNmY2ZEVRRE4AAAAiAAAABDIyMDEAAAAWNjc2ZjY3NmY2MTY5NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMDEAAAAgNjc2ZjY3NmY2OTZlNjY2YzY5Njc2ODc0MmU2MzZmNmRFUUROAAAAHgAAAAQyMjExAAAAEjZkNzk3MzcxNmMyZTYzNmY2ZEVRRE4AAAAgAAAABDIyMTEAAAAUNmY3MjYxNjM2YzY1MmU2MzZmNmRFUUROAAAAJAAAAAQyMjIxAAAAGDY0Njk3MzYzNmY3NjY1NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMjEAAAAgNjQ2OTczNjM2Zjc2NjU3MjYzNjE3MjY0MmU2MzZmNmRFUUROAAAAGgAAAAQyMjMxAAAADjY0NjM3NTJlNmY3MjY3RVFETgAAACgAAAAEMjIzMQAAABw2NDYzNzUyZDZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACoAAAAEMjI0MQAAAB42ZTY1NjY2Mzc1NmY2ZTZjNjk2ZTY1MmU2MzZmNmRFUUROAAAAHgAAAAQyMjQxAAAAEjZlNjU2NjYzNzUyZTYzNmY2ZEVRRE4AAAAmAAAABDIyNzEAAAAaNjM2NTZlNzQ3NTcyNzkzMjMxMmU2MzZmNmRFUUROAAAAJAAAAAQyMjcxAAAAGDMyMzE2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTZlNjU3NEVRRE4AAAAiAAAABDIyODEAAAAWNzg2NjY5NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTEAAAAmNjM3MjY5NjM2YjY1NzQ3NzY5NzI2NTZjNjU3MzczMmU2MzZmNmRFUUROAAAAKgAAAAQyMjkxAAAAHjYxNjk2Zjc3Njk3MjY1NmM2NTczNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIyOTIAAAAaNmQ2MTZlNjQ3NDYyNjE2ZTZiMmU2MzZmNmRFUUROAAAAGgAAAAQyMjkyAAAADjZkNzQ2MjJlNjM2ZjZkRVFETgAAACIAAAAEMjI5MwAAABY2MTZjNjk2MjYxNjI2MTJlNjM2ZjZkRVFETgAAACgAAAAEMjI5MwAAABw2MTZjNjk2NTc4NzA3MjY1NzM3MzJlNjM2ZjZkRVFETgAAACwAAAAEMjI5NAAAACA2ZDY1NzI2MzYxNjQ2ZjZjNjk3NjcyNjUyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTQAAAAmNmQ2NTcyNjM2MTY0NmY2YzY5NzY3MjY1MmU2MzZmNmQyZTYyNzJFUUROAAAALAAAAAQyMjk0AAAAIDZkNjU3MjYzNjE2NDZmNmM2OTYyNzI2NTJlNjM2ZjZkRVFETgAAACAAAAAEMjI5NQAAABQ3NDYxNmY2MjYxNmYyZTYzNmY2ZEVRRE4AAAAeAAAABDIyOTUAAAASNzQ2ZDYxNmM2YzJlNjM2ZjZkRVFETgAAACIAAAAEMjI5NQAAABY2MTZjNjk2ZDYxNmQ2MTJlNjM2ZjZkRVFETgAAABwAAAAEMjI5NQAAABAzMTM2MzgzODJlNjM2ZjZkRVFETgAAACoAAAAEMjI5NgAAAB42ZDY1NzI3MjY5NmM2YzY1NjQ2NzY1MmU2MzZmNmRFUUROAAAAIgAAAAQyMjk3AAAAFjdhNjU2ZTY0NjU3MzZiMmU2MzZmNmRFUUROAAAAHgAAAAQyMjk3AAAAEjdhNmY3MDY5NmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQ2NTZjNjU2YjZmNmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQyZDZmNmU2YzY5NmU2NTJlNjQ2NUVRRE4AAAAkAAAABDIyOTkAAAAYNjE3NTc0NmY2NDY1NzM2YjJlNjM2ZjZkRVFETgAAACYAAAAEMjI5OQAAABo3NDY5NmU2YjY1NzI2MzYxNjQyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDAAAAAaNzI2MTY5NmM2ZTYxNzQ2OTZmNmUyZTcyNzVFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NDY1RVFETgAAACoAAAAEMjMwMAAAAB43MjYxNjk2YzJkNmU2MTc0Njk2ZjZlMmU2MzZmNmRFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NzcyRVFETgAAACYAAAAEMjMwMAAAABo3MjYxNjk2YzZlNjE3NDY5NmY2ZTJlNzU3M0VRRE4AAAAoAAAABDIzMDAAAAAcNzQ3Mjc1NjM2YjZlNjE3NDY5NmY2ZTJlNjQ2NUVRRE4AAAAsAAAABDIzMDAAAAAgNzQ3MjYxNzY2OTYxNmU2NzYxNmQ2NTczMmU2MzZmNmRFUUROAAAAHgAAAAQyMzAxAAAAEjc3NzA2Mzc1MmU2MzZmNmY3MEVRRE4AAAAoAAAABDIzMDEAAAAcNzc3MDYzNzU2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIzMDIAAAAcNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZEVRRE4AAAAuAAAABDIzMDIAAAAiNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZDJlNjE3NUVRRE4AAAAsAAAABDIzMDIAAAAgNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmYyZTc1NmJFUUROAAAAGAAAAAQyMzAzAAAADDZkNjkyZTYzNmY2ZEVRRE4AAAAgAAAABDIzMDMAAAAUNzg2OTYxNmY2ZDY5MmU2MzZmNmRFUUROAAAAJAAAAAQyMzA0AAAAGDY2NjE2MzY1NjI2ZjZmNmIyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDQAAAAaNmQ2NTczNzM2NTZlNjc2NTcyMmU2MzZmNmRFUUROAAAAPAAAAAQyMzA1AAAAMDY0Njk3MzZlNjU3OTZkNmY3NjY5NjU3MzYxNmU3OTc3Njg2NTcyNjUyZTYzNmY2ZEVRRE4AAAAYAAAABDIzMDUAAAAMNjc2ZjJlNjM2ZjZkRVFETgAAACAAAAAEMjMwNQAAABQ2NDY5NzM2ZTY1NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDUAAAAQNjU3MzcwNmUyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNmQ3OTc1NzYyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNzU3Njc2NzUyZTYzNmY2ZEVRRE4AAAAsAAAABDIzMDcAAAAgNjI2MTZlNmIyZDc5NjE2ODYxNzYyZTYzNmYyZTY5NmNFUUROAAAAMAAAAAQyMzA3AAAAJDYyNjE2ZTZiNjg2MTcwNmY2MTZjNjk2ZDJlNjM2ZjJlNjk2Y0VRRE4AAAAeAAAABDIzMDgAAAASNmQ2NDczNmY2YzJlNjM2ZjZkRVFETgAAACYAAAAEMjMwOAAAABo2OTZkNjU2NDY5NjQ2MTc0NjEyZTYzNmY2ZEVRRE4AAAAwAAAABDIzMDkAAAAkNzY2ZjZjNzY2ZjZmNjM2NTYxNmU3MjYxNjM2NTJlNjM2ZjZkRVFETgAAADAAAAAEMjMwOQAAACQ3NjY5NzI3NDc1NjE2YzcyNjU2NzYxNzQ3NDYxMmU2MzZmNmRFUUROAAAAIAAAAAQyMzEwAAAAFDZkNzk2MzYxNmU2MTZjMmU2NjcyRVFETgAAACgAAAAEMjMxMAAAABw2MzYxNmU2MTZjMmQ3MDZjNzU3MzJlNjM2ZjZkRVFETgAAACYAAAAEMjMxMQAAABo3NDcyNzM3MjY1NzQ2OTcyNjUyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTEAAAAaNjQ2OTc2Njk2ZTc2NjU3Mzc0MmU2MzZmNmRFUUROAAAALAAAAAQyMzEyAAAAIDZlNmY3MjczNmIyZDc0Njk3MDcwNjk2ZTY3MmU2ZTZmRVFETgAAACAAAAAEMjMxMgAAABQ2Mjc1Nzk3MDYxNzM3MzJlNmU2ZkVRRE4AAAAqAAAABDIzMTMAAAAeNmQ3OTJkNjI2ZjZmNmI2OTZlNjc3MzJlNmY3MjY3RVFETgAAACgAAAAEMjMxMwAAABw2ZDc5MmQ2MjZmNmY2YjY5NmU2NzczMmU2MzYzRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk2NzZmMmU2MzZmMmU2ZTdhRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk3NDc2MmU2MzZmMmU2ZTdhRVFETgAAAB4AAAAEMjMxNQAAABI3ODY5NjE2ZDY5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE1AAAAFDYxNmM2OTcwNjE3OTJlNjM2ZjZkRVFETgAAACQAAAAEMjMxNgAAABg2MjYxNmU2MzZmNmQ2NTcyMmU2MzZmNmRFUUROAAAAKgAAAAQyMzE2AAAAHjYyNjE2ZTYzNmY2ZDY1NzIyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAoAAAABDIzMTYAAAAcNjI2Mjc2NjE2ZTY1NzQyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAkAAAABDIzMTcAAAAYNzQ3NTcyNjI2Zjc0NjE3ODJlNjM2ZjZkRVFETgAAACAAAAAEMjMxNwAAABQ2OTZlNzQ3NTY5NzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIzMTgAAAAWNzM2ODZmNzA2OTY2NzkyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTgAAAAaNmQ3OTczNjg2ZjcwNjk2Njc5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE5AAAAFDYzNmY2ZTYzNzU3MjJlNjM2ZjZkRVFETgAAADIAAAAEMjMxOQAAACY2MzZmNmU2Mzc1NzI3MzZmNmM3NTc0Njk2ZjZlNzMyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMjAAAAAQNjU2MjYxNzkyZTYzNmY2ZEVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYxNzRFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MjY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjM2MUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYzNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MzZlRVFETgAAACAAAAAEMjMyMAAAABQ2NTYyNjE3OTJlNjM2ZjJlNmE3MEVRRE4AAAAgAAAABDIzMjAAAAAUNjU2MjYxNzkyZTYzNmYyZTc0NjhFUUROAAAAIAAAAAQyMzIwAAAAFDY1NjI2MTc5MmU2MzZmMmU3NTZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2MTc1RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ODZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ZDc5RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3MzY3RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3NDc3RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjQ2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY1NzNFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjY3MkVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY5NmVFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTc0RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNmU2Y0VRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTcwNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU3MDZjRVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNzM2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTc2NmVFUUROAAAALAAAAAQyMzIwAAAAIDY3Njk3NDc0Njk2NzY5NjQ2OTc5NmY3MjJlNjM2ZjZkRVFETgAAACYAAAAEMjMyMAAAABo2NzZkNjE3MjZiNjU3NDJlNjM2ZjJlNmI3MkVRRE4AAAAgAAAABDIzMjEAAAAUNzM2MzY4Nzc2MTYyMmU2MzZmNmRFUUROAAAAKAAAAAQyMzIxAAAAHDczNjM2ODc3NjE2MjcwNmM2MTZlMmU2MzZmNmRFUUROAAAAHgAAAAQyMzIyAAAAEjY4NzY2NjYzNzUyZTZmNzI2N0VRRE4AAAAqAAAABDIzMjIAAAAeNjg3NjY2NjM3NTZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACIAAAAEMjMyMwAAABY2NjY5NzI2NTY2NmY3ODJlNjM2ZjZkRVFETgAAACIAAAAEMjMyMwAAABY2ZDZmN2E2OTZjNmM2MTJlNmY3MjY3RVFETgAAAC4AAAAEMjMyNAAAACI2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTJlNjM2ZjZkRVFETgAAAEIAAAAEMjMyNAAAADY2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTYzNmM2OTY1NmU3NDczNjU3Mjc2MmU2MzZmNmRFUUROAAAANAAAAAQyMzI0AAAAKDczNzQ2ZjYzNmI3MDZjNjE2ZTYzNmY2ZTZlNjU2Mzc0MmU2MzZmNmRFUUROAAAAGAAAAAQyMzI0AAAADDZkNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMjUAAAAaNjE3MzZiNzU2Mjc1NmU3NDc1MmU2MzZmNmRFUUROAAAALAAAAAQyMzI1AAAAIDZkNjE3NDY4NmY3NjY1NzI2NjZjNmY3NzJlNmU2NTc0RVFETgAAACoAAAAEMjMyNQAAAB43MzY1NzI3NjY1NzI2NjYxNzU2Yzc0MmU2MzZmNmRFUUROAAAAJgAAAAQyMzI1AAAAGjczNzQ2MTYzNmI2MTcwNzA3MzJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNjU3ODYzNjg2MTZlNjc2NTJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNmY3NjY1NzI2NjZjNmY3NzJlNjM2ZjZkRVFETgAAACYAAAAEMjMyNQAAABo3Mzc1NzA2NTcyNzU3MzY1NzIyZTYzNmY2ZFVSVUwAAAAtAAAAGjY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY2MTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACkAAAAWNmM2ZjY3NmQ2NTY5NmUyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAA/AAAALDczNjk3NDY1NzMyZTY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY3MzY5NzQ2NTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACcAAAAUNzc2NTY1NjI2Yzc5MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA43NzY5NzgyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAjAAAAEDc3NjU2MjczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODZmNmQ2NTczNzQ2NTYxNjQyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjc3NmY3MjY0NzA3MjY1NzM3MzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACUAAAASNmE2OTZkNjQ2ZjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAAC0AAAAaNzc2NTYyNzM3NDYxNzI3NDczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo3MzZlNjE3MDcwNjE2NzY1NzMyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAxAAAAHjYzNmM2Zjc1NjQ2MTYzNjM2NTczNzMyZTZlNjU3NAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjc3NjU2MjZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB42ZjZlNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODY1NzI2ZjZiNzU2MTcwNzAyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAfAAAADDZlNmY3NjJlNzI3NQAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjYxNzA3MDczNzA2Zjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3YTY1NmU2NDY1NzM2YjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmI2MTc5NjE2YjZmMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3MzY4NmY3MDY5NjY3OTJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmM2OTZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAJwAAABQ3NDc1NmQ2MjZjNzIyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjZlNjU3NDY2NmM2OTc4MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB43MzcwNzI2NTYxNjQ3MzY4Njk3Mjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA42NjcyNjU2NTJlNjY3MgAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjZmNzY2NTcyMmQ2MjZjNmY2NzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNjY2MTcyNmQ2MTJlNzM2OTc0NjUAAAABMQAAAAEwAAAAATBUT1RQAAAAEAAAAAEwAAAAATAAAAACe31QUklLAAATYDE2NUQ2MTAyMTM4MEQyQTkyNzE1MkY5MjAzRkZGN0VGQTlGN0NFNkVFNUY1RDFCNDYxQTI0NjdCMkZFRTg2QjE3NjExMjcxM0ZGNkM0MDkzNUJGMEQ4MDM4RUY2QTdCRkYwMTFBRjQwRUI5Q0FCNzQ5MUM5QjIyMUY0M0I5REQyMjY0Njk1NzI5MDBFMjQ1Q0VBRUY3NTlFRkU4MDQzMDk4OURGMEE4RDc3MzFDNjZCRDI0MDNEQTAzOTMxREQ1REM3QzM5MjA2NUQyN0M5NTMyMzE0MzM3QTk2N0Y2RkE4RTg0QjEzREZGOTY2Q0IyNDIwNjRBQUM4QTYxRTgzODJCMDk2MjVFNzc3MUVBQzAwODAxQjgxOTlGMEM4MTg2QTgzOTgzQjI4NTAyQjhFMjY1OUE5NDk3N0ZEMkUxNDhCNkI5NkMzN0FCQzYxMjUxRjAxMUQ2MURCNUQzMzRBNDc4RENGMzRGQjE1NUI3OEFGMUIyQjk3N0NDODc3QTY1QTJENkFEOENEREQ2Q0ZCODY1ODZDMTY3NENBMUMyMzQ2MzYxQjExNjlDRkVDNzcwQzYzMkM4M0U4QkZEQ0M1NzRFODZCRjcxMkJCQTRCMzg0MTFCMTU5Q0FBMjk4RkUxRjVDQkEyRjJERTY5OThBMjI3NTQ4RTFBQkYxNEUyNkFCNzg3NUFCREU3N0FDMEY3Q0ExRUI1MjQxN0QxOTNDMjM4RjM2QzU4OUYxRkQ0RTQzMDAyQ0VFNTI1RUE2RkI5MUQ2NTREMTFEOTU2RTA4MEZBRTU1OTJDQTFGMUE1OTFEQkU3NTczQ0Y0NTQyRUVCNDIzRkYxRkY2Nzk1MDYxQUEzQzA1Mjk3Q0I3Rjc3MTAyRjAwOTkxNjYxMDI0QTIxNjRGODE3RUU4Q0I4RDdERTdFNjQ3NTQ0NDkwQzMxQThFMTQ4MjAwNkNFODA3RDA1NDE3N0NDMUE3MzdBRkFERUM2NTA1RkZCMURFQUQwREY5OEM5MDY0NjkzQzM1Q0I2NTA5QzNDN0Q4NjhEOTZCRDgxNjAzRUFDRjczNTJBMTU3OTQwMjA3NkY4RjZBM0ZBNEE3MDBGNERFOThENTJGQzg0NDc0OThCNzY3RkQ4OEM3QjY3RDEwQzhFQjVCQ0Q1OUEyMzY5OEJGOUI5ODdBMEM5OUYyQTYzNjk3MjlFNUMzQ0E1MTI4MUVDRTA0RjVFODhBNTRCMDUyMjM0Qjg0MjhERUU5MTYyQkIxRkU1RTU2RERFRTY0MUNBREMzQUZDRDk3MkQxQzhDRTY2ODA5RjgxQ0FBNTJDQjg2NEU0RTEzNUE0QTIwMEE3OTVGMzFENTRDNUU2Nzk1QzZFMEE5QjhDNTlCQTcwMEI5NzFBMERDREUxQzE3QjE0NERCRUIxNjQ3NEJGRkJEQUIzODU5NzU4M0IzQ0Q1ODRBNjVEMkIyNUU5QzJGRjRERDE5NkVGNTFGRTE4RkVEQTMwRTQyRDMwMTRCOEUwMTZBQkYzODkzRkIyOEU0RTUxMDdFMEY5MkM4OUMxRkE5MTFFNkNCMTc0QjMzODkxQzZBOTMxMkNDNEUwN0REQzk1NDE0MUMzQkE1RjZCMkQ1RjYyMURBRjVBM0QyMEVBQTNFQUMwQTg1MzY4MTY4OUFGRTRBQzE3MjFDMDkwQUIwOTJEN0VENUJBRTE5NUJDNkU2NDUyNUJCQjUxNTgxNjVFNzQ1RkQ1OENBQzNGQkRDMDVGMzlBNjZFN0I1REJBQUQxQjcxMjBDQjQwOTREREMwMUY4QUNBRTI2MDVFMDZBNzk5RkMzQjE0RUNCRDlDNUJCOEQ4MUJCOThBMzE3RTc1RDQyQjBCNEEzQzhEODZBOTAyOTgzRkYyNTBGN0UwRDVDMDhDODIxQUUyREJDMTJENjVDNjVBNTRGRUI2RjhBNjVBNkQ0RjVEQjczMzA0MzhENUY3QzdBNDZCNzVCQjI4OTIzNzQ1MDg5OEQxQ0IyODUwMDdGMjQyRjQzQUI1REFBQkIyMEZBREMwREI3NkIwRkMxNzhFMDI4QzY0RTFDQTQ4RjMzQUU4MTM0MDI3N0MzRDUxMTMyMUQ0OTE1QkM0RkJBNjU2NkQzMERENEVEQzFEODc4NTdDOUY1REY2N0Y4QzUxOTRBN0I5NDgwNDhGNTREMzUxMjQ5MEUwNDlFNzg0Qjk5NUFDNzJFMTVCQjZCQTZCRDczRTkxRjA0NjkyQTdCRTRCQTI2MzJGODU0NEI1RUUyNUJCOTEwRTY0QUI0MjUzNUQwNUZDNDRGQTg3RTU3NEUyRDFCQkI5NUM4MkY2OTU4NEU0NTc1NDhENkFBMzU4MDkzQkM1REYwQTcxMjlFMjA5MDYxQjY1OTNGOEEyRTI2RjkwQzc3RUM4QTZEOEY1RERERUNDNTU0QkVBRkI2QkJGN0JCNzdCRDRGMTYxMzBDNEUyNjUwRTNGOUJCOUIyRTJGRjQ5MDBDMjkwRDY1NjMxOEMzODJDNDdEMUYwMzk0M0IyRUJDNDQyRTA2RDZFMjAxMTcxOUI0NUE3RkJBNTJBRDg1OUMyMjFDQ0FGRDA4MjYwMzlDREU3NUEyMUZEMTg2MDEwMUYxNkZGRTFEMkQ1QjA1N0UxMkMxMkU3N0UyREFCN0QwQkJCRkIyRTM0NzhCMzI0MzUwMjU1NDE2QjdDOUYzMjA1MDE2MUU0RjU0MjY0Q0VDNEQ1RDI1QkEwNTZCQkM3NkNCQTk5QjhBQTczQjgyNjA4MDMxNTBDOURDNzQ3RURGQzc0MDZFQjNBOTMwMkJENkEyNjgwRkM5ODgwRDFCODI0QkEyQUNFNzc3RDNFQ0NDQ0E5MkVBQjRDMTZGOTdGM0QxQzZENjI0RjFFRTdCNjcxQzg3MURCRjI1OEY4NzE1NTI5RTRCRjdEODdDQTg1Nzc2MjA3OTBBRkRFRjdBRjQxODNCNzEzMkEwMDk2QzRFMzgzOTFGQzc3MjVCMkQzMUYwODA2MUNBMDlFRUU4NThCQjdERjQ2NDg3MzhGMjYyQjE3NTU5QUFEMTk1NjYxRDNENkQxOTlENjE0QTI1Q0YyMzI1RDMxMDM5QTQ0MTlBRTE0MTEyNTc0OTQ0NUZFNUYxNzYxNjg1NUJEMDBBRjI4QzQ0OEZGRTgwNUQxODJEMEZEMTg4QTU2QTdDNzlEMjQwMUJFNjZFNjg1Rjc1QzVEMjQ4QTRDOTgzQjE3M0EyN0RDNThERDc5MjNCNUIyODFENzQ4MDVFRDJFRkEwQUU4Qzg5QzUxRDNGQTY4Rjk3NDcyOUQ4Q0U5NjhGMUU5MTM0OTI5QTZDM0U3RDM4RDQ2MDk1MDI3RDBDMzY2Nzg0M0I4NUYxNzA4OTE2QTU0NTUxMjM2RTBDQ0U2QUJGQjUxNjQzM0Q4MjU1QzZDRkIzQUVENTZGMjkzNjhEODZCRkI1NDU4QTM1MzQ5NzVEQTExMUM0OEY2NUQ4RUYwNzBDQUM1RERFNEMwMzM0ODRENEQ2N0RBNjkyMjRFRTAxQkExOUI5QjI4QThDQTI0MjIyNzhENEYzNTczRUQzQkY2RENFRTIyRURGRUM2NTJEQkFDQzA5MEQ0NzVERDUyOTg0NkE1NjhFQTY2ODVCRkJBNUJCODhGN0I2RTZEQzQ3QkI4ODExNDA2N0IzOENDRkM5MzMwOTU1MDA2MkMyRTI1MTUxQTUwQUFEQkRCRTMxOTA0OTM0MTgyNEFFNkJBNjhBRUM1QzFBMDYxOURBOTE2RTM0RUM5RkZERUFFQjcyNUNDQjlGNjk5MzdDMEQyMThBRkUyODQxNTZBN0I1MzNDQzI0MTBFRTEwQUJBQjQyNEIyMEFDQUYwQjVBMjA4MTBEMUIzQjFGMDE0MkZEQzNFMzcwMzZCQkJBODc3QzEyREZEMUQ5RDc3MTQwQ0I2QUM0OUNCMzgwQTJEMjY3NTkwMjVDN0ZCNTNDM0EyM0UwMjJGNTcxQTk0RDBGM0QwRkU4OUU1NTgwRTlFQUEyNEZFQ0M4QkQxRTY1NjM0QjI1NjAwOTg4NEU5MTdGN0M0NTgyOTRDMDQwNDI3MUYwMjdBQkM5RTQyMUFBNTk2N0NDMkJEQzQzNTZGQjc1NzI5RjE4REQ1QjNGQ0FEQUVGNEUyQzQxNTM4MUQ1NURCQ0Q3OTVENDgwNkUzQzRGNTI5RUY3MTVBQTU4QThGNzlBREQxMjg4Q0I3MjVBOUMyNDlFMUYyNjMxMTE3NzEyQTAzODVEOEVDQTFFMEFGMkRERjRENDM5RUU2OTAwMENEODkxMjU5NEJGRjc5NEZCMEUxRkFBNEE2MDIzQUQxMjI2QzUzOEU1Rjg0MkUxQTNENUFFRTg4MjhDMEZENjRGMzg1NzBFRDY3QTc4MDI0MkQ5MjI3MTJENDEzREJFRUY0MzBGMUNDQUY2QTJBMDAyMUREMzJBN0RBN0U2QzA4ODE2QUJFQzA1RTYwRTE5NTE2QjNBNjM2MERFRDgxNDZBMDEwREY4RkZFMDM2RjBBQzlFRkZBRTBGQ0M4MjYzRTgxQzRBNTY3NDhGQjg3QTNDNUMyNjc5OEUxRUZENzE2QTJGQUM4RTg1QTRGRjkyNTkxMDIwRTEyRTNEMkNGNjU5MEQ3NzNGQUIzOUNGMEY1MUVEN0I2MTcxQTJGRDMwODIwODBBQjEwMDhDRDZBRjBDODIyMEQ4QkY2OTUyN0E4MjIxMjRGMDRGMDlDOTgyNzg0NjQyNDg1MzYxM0JFMDI3Njc5NjI2NEIxOERGNEQxRTQ2RTM5MzM0RkEzRDVGMjYzRDJENjgyODkzQTUzQTFDM0Q3MkUzMzRBNDU2QjcyOUYxMTU4NDFBQjVDQ0ZCRTRBRjlEODIwMzg1QTU2MUEwODI4REYyMDdEQTk1OThBNjU4RjA2MDg4OTkxOTNDMjZGQzIwQjUwQjZGNUYxMjYyNkI0QkI5RUFBNzlCNEIyODBCODMwM0JBRjlEQjJEMkVERDc1MzBGMDE5MTJGRUQzNkUxRTk2MUFCRTU3QjJBOUE0MDVDREMwRUI2Q0NDQzY0RjMxQzlGQUYwQTJGOUUzM0QyQzRGNjFERjhBMkFBRTQ4Q0EyNDRFNzVBRTUyQTFBNkU1OTE0RDQxMkE0NEQ5NERDNDE2RTg5ODMwRjU0MTBDMkY1OERDMjc4MERERDk4QUE3MEU1QUIzQkEzMjAwQzNGQTQ4NzIxNTY2QzMzMUMxNTBFOUY3NDMxNTExNUYwMTE5QkE5NjU5NUIzQjM1OEVDMzA0RkNCOEFFNTM2RTg3RTg0M0JEMUM3RkQyMzkxNUU2NzZGMzFFMENCRERGN0UxMUJCQzE2RDRDMUQ5NEUwMjEzNTUwNjU0N0JFNTUzNTQ4MUZGNDgxQ0Y5NjExOUQxNDA5QjQxQjYzRDcwQUY1QjFDNTc3RUUxRTJDQjc4MDY0RDI5RjUwRjcyMzFGNTUwRkQ0MDc2ODIzMTBFODQ2OEFCNTBCMUY4M0I5NDJGNDg3QjU3MkM2NDlFM0NDNzQxM0JBNTU5MDU0QTZDMzg3MDZENTc5NzAwMzFGMkMyMkVFREI2REY0NDRBNTA5MTVBRjkzOUJEMjRGMUE4RTc5MDE4OTdFNEU3MTA4MEQyRTBFOTkzQzM0NzVFMzI1QzY2NkY0QTc0MEQ0NzJBRkJERjQwQzEwOENCNDczOEZBNjlFRDlCRjgxMTAyRjUwRTc2OUI5Q0Y0RThBQkY0QkVDNTAxNjI1ODBFREQ5NjM4RDczMEIxNDA1NjI4MEUzQkQyRENCREI3ODlDMUJEQkIwNEI1QkNBMDdFOTNBMjJEQkJERjhFRTc2MTIxMDU2QzMyQkY1MUVCMkJFQ0Q0RDk5QzJBNDQwNTJFQTYwNzg4OTE3NzA4RThFN0UzQUM2NTg4OTk4MTE1NzczRDNGNjQxQTFFQUM4QjE0ODExNzI1RjNBNTg0QjQ5NDJDOEQ4NzQ2NjI3M0M5Nzg5MzM1QTQ0RUQ3NTc1NzdDQkE4RkYxNzA3ODA5MTE3N0QxNDNFOEMzODU4OTRGRkZDQjU1RTc5NjEyMjFFMzg2MDEwMzU0QzgxODVEODQxNkE5NjU1NTRDMUIzM0JFNzA1NUU3MDdGRjYwMTEzNzIwMTUyQjBFNTQ1RTE3MzdBMzBCRTBGMzAxOUYwMkVDNzUzQTUyNTgxM0UwRUY3M0ZFQjA2NzFGRTlGREE3QjBCQ0I4RDU5MzRFNjk0NUMwM0ZEQzRCOTU5NzAwRUY3NUM4OTM5MjZDRjZGQUVGMTU5MjZFMEY5NDJFNDNGMzIwQTc1OUE0OTNGNDdDM0Y0MTdFODBFNTc4ODZBNTQ1NzE5ODNCRDEyOTk2MzY1MTc1Q0M1NEEyODE2MEY3QkYzRDQ1OTEyQjJCQzMwNjQxRDI5MEI1Q0VFRUM0NDY4RDMzQzk3RENDRTQwMEYwRTg4MUZDMkEzM0IyNEI3RTA5OTBCNUI3NDkxMDk1NkYyMDY0MzRCRTY3OEFCMzc2RTU0NENGN0U5Nzc2MzMyOEEzMzkwMEE2RUIxOEJCRkYxNjZERUIxODNCOUM2RTdBNUM1NTlBQkY4N0JGREIxNTIzMjY5NzNCNTU5MEZTSEFSAAACwAAAAAkyNjAyMjA1MDEAAAIANzU5ZGJiNzk0MjNjN2FlZmZkZDQ5NTlhOWE5OTVlMmIzODg2YTgzMWQ5ZDBkODYxYjk5OGU3ZTk3ZTM2YjEzYmQwMmU3NGRhOGU4ODY4OTkyNWU0Yzg3ZmQ5ZWY1Y2Q1NjA1ZTYyZWRkOTExZTkxMDIyNjRiMzZmMmI1MGM4NWQ4NzkyMmY3YTNkYWUxNWU1NDRmYTBhZGQ4ZjQ0NjIwOTA0ZDg2NzAyMGM2ZDlkZjllZGVmZjQ3NzMyYTRlZDU5MTU5Y2I1YWU0Yjk2YWJjMTYwMGIzNGQzNDI3MDkyYmVhN2IxNDkzYmQ0OTZlMTA0YTc3YTI3MWEzYjBhZmViNDI4NGUzMjkzY2MyNWIzOWU1MDMyYWQ0NDNiNGY3YjAxOGFiZDRkYTg2MjcxNjJmMTBjMjA4NmRhMWVlMGM1NmU2YzM0Nzc5NzJlOWQzYWY2ZTQ1NTQ0ZTU1YjI4NTJkZjZmMTNkMjUxNGZjODU4YTBjMzc1YTA3M2M1YzlhMWYzNjU3N2MyYmNkZjYyMjdiMDAyMDYyYWUzMmQ5MjBhNmNkMGU5YjQ1OGRiNDNmNGU0MDc2YjA4NDdiMmQ5YmYyZjFmMTM2N2EyOTBkZDVjOTA0N2VlOTE3MDY4MTYxZmEyMTc4ZmFkMmVhNGY0Mzc0MWE3ZTcyZWE5YmU4MzVjZWMAAAAyIXpVTHZXNEI0eVJSQUVvdndsTUp6NXc9PXw5QzRtU0RzYUJJWERtdS9ETng1d1FRPT0AAAABMAAAAAExAAAAYSGuba6dAIJKJV6fh883Da7ha7X0lklOC0VkcOhILi+Ss2zSzlHXG6F02f9uaInLhWRgP2rShLGxY/DFl+H0x3nxOCHl33kxqH1WsyFtBRK3PTdTebEKlpFFtXc1pKsCejoAAAABMAAAAAExU0hBUgAAAsAAAAAJMjYwMjIwNTUxAAACADA4OTUxZjgxMTU3NzRhNGJkZDA4M2U0NzM4ZmFhZDJmYmM0ZTM3MDk0MWU3MjQ0NGJmZmQ2NDBjYzQzOWQ5ZWEwY2QzMjkwYmI3MjEwNDZjOTRhMTgyZGM2YTM2MTczYzRhNWI4OWQ4NTEwZmMwMTczNzRmOGVjNzMwNmY0NTY1OWZiNzI4NWFlNzgyNDdmOTk3YmI2MGM4YTQ0OThlZTRhYjI2OGRiZDY1YTA2MTlmMWJmYjdjYjcwZmQ5ODkwMGQ1NTFjZWM5ZTFiZTRkM2U5MDUzOGFkOTA3NWJmMzM3MGNiZDFiNjBlMmFjODE1MmQxOGM0OTEyMzRiMDdkYTViNmM0OWFiYjU0N2M2MTcxMDc5ZTQ3ZmVlYWY0NTE0ZjU5Njg4YTU5YzU2ZjBkZGFlN2M0ZGI2ZmM0OGU2Nzk0M2U0MWQxYTZmMjFjY2JlNTVhZjk2MGYwZDhlNjA0NDhkNWFkYmM4NTg5YTlkOTMwOGYzMTdlYWIwYzM5OTJkOTFhZDMwY2U1OGUzNDg4ZWMzYmE4NDc0MjliNzZjYWM2NzFhMzZkZDAwNDcyYzk3NDMxNGYzODUwMmRjZDFjYjJlMzVkZDk4Mzg1MjJlY2MxYWE0NGNlYWE0ZWQ0ZmVhZmMwY2NmOTJlMGI4YTVlYTIxMjU1ZDVmMTEwNzlhMjI1AAAAMiFXR1R6UzAxQjZoZ0RGVnZINFc0WW5nPT18UEtidDA2SHFzR08vdHQxVkdKcWl0dz09AAAAATAAAAABMQAAAGEhJR/EGgc/1Ah5wbPQPqt7NvYRV1ULHgLSo9f4ZzU1bRLLcaIJfm1OTQ94Zws4jaVH80+7e4ukwzSNMfAWVhxiNcp3LFJLIV92rdREEt4/3gYoKL4uOX6/VzhEMj2IMi7MAAAAATAAAAABMUVORE0AAAACT0s= -------------------------------------------------------------------------------- /test/unit/data/blob-groupaccount.txt: -------------------------------------------------------------------------------- 1 | TFBBVgAAAAIxOUVOVFUAAAABMUVOVE0AAAABMkFUVlIAAAABMEVOQ1UAAAAsNVVjek82T3gwUGg0T2pXOWRCaHZPdU5ZSXhXbEdrRzhpKzZvazFic2Z3Yz1DQkNVAAAAATFCQlRFAAAACjE1Njg5NjQxMjFJUFRFAAAACjE1Njg5NjQxMjFXTVRFAAAACjE1Njg5NjQxMjFBTlRFAAAACjE1Njg5NjQxMjFET1RFAAAACjE1Njg5NjQxMjFGRVRFAAAACjE1Njg5NjQxMjFGVVRFAAAACjE1Njg5NjQxMjFTWVRFAAAACjE1Njg5NjQxMjFXT1RFAAAACjE1Njg5NjQxMjFUQVRFAAAACjE1Njg5NjQxMjFXUFRFAAAACjE1Njg5NjQxMjFTUE1UAAAANwAAAAEwAAAAATAAAAABMAAAAAEwAAAAATAAAAABMQAAAAExAAAAATAAAAABMAAAAAEwAAAAATBQUkVGAAAAlwAAAAItMQAAAAItMQAAAAItMQAAAAEwAAAAAi0xAAAAAi0xAAAAAi0xAAAAAAAAAAEwAAAAAAAAAAExAAAAATEAAAABMQAAAAEwAAAAAAAAAAExAAAAATEAAAABMQAAAAExAAAAATEAAAABMQAAAAExAAAAATEAAAABMAAAAAEwAAAAAAAAAAExAAAAATAAAAABMAAAAABOTUFDAAAAATFBQ0NUAAABKAAAABM4MTcxMTIwODU2NjEwNDQwNjU5AAAAAAAAACEhDQ7g5lfdzUg2t4LJO1dSzxI04m1PoLQUaNuL8bhTW+gAAAAYNjg3NDc0NzAzYTJmMmY2NzcyNmY3NTcwAAAAAAAAAAEwAAAAAAAAAAAAAAAAAAAAATAAAAABMAAAAAEwAAAAATAAAAABMAAAAAEwAAAAAAAAABM4MTcxMTIwODU2NjEwNDQwNjU5AAAAAAAAAAAAAAAAAAAAATAAAAABMAAAAAAAAAAAAAAAATAAAAABMAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAoxNTY2MzczOTY1AAAAATAAAAAKMTU2NjM3Mzk2NQAAAAoxNTY2MzczOTY1AAAAAAAAAAEwAAAAATAAAAAAQUNPRgAAAFAAAAAFR3JvdXAAAAAIcGFzc3dvcmQAAAAhIeawhz5YOSlQYfjAQ5O7PyBsWAbycQpBWwk3LlS9rPVFAAAAAAAAAAAAAAABMAAAAAEwAAAAAE5FVlIAAAApAAAAATMAAAAgNjQ3MjY5NzY2NTJlNjc2ZjZmNjc2YzY1MmU2MzZmNmRFUUROAAAAJQAAAAExAAAAHDYxNmQ2NTcyNjk3NDcyNjE2NDY1MmU2MzZmNmRFUUROAAAAKQAAAAExAAAAIDc0NjQ2MTZkNjU3MjY5NzQ3MjYxNjQ2NTJlNjM2ZjZkRVFETgAAACsAAAABMgAAACI2MjYxNmU2YjZmNjY2MTZkNjU3MjY5NjM2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2MjZmNjY2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2ZDYyNmU2MTJlNjM2ZjZkRVFETgAAAB8AAAABNAAAABY3OTZmNzU3NDc1NjI2NTJlNjM2ZjZkRVFETgAAAB0AAAABNAAAABQ2NzZmNmY2NzZjNjUyZTYzNmY2ZEVRRE4AAAAbAAAAATQAAAASNjc2ZDYxNjk2YzJlNjM2ZjZkRVFETgAAABsAAAABNQAAABI2MTcwNzA2YzY1MmU2MzZmNmRFUUROAAAAHQAAAAE1AAAAFDY5NjM2YzZmNzU2NDJlNjM2ZjZkRVFETgAAACUAAAABNgAAABw3NzY1NmM2YzczNjY2MTcyNjc2ZjJlNjM2ZjZkRVFETgAAABUAAAABNgAAAAw3NzY2MmU2MzZmNmRFUUROAAAAJgAAAAQyMjk2AAAAGjZkNzk2ZDY1NzI3MjY5NmM2YzJlNjM2ZjZkRVFETgAAABgAAAAEMjI5NgAAAAw2ZDZjMmU2MzZmNmRFUUROAAAALAAAAAIxMgAAACI2MTYzNjM2Zjc1NmU3NDZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMTIAAAAQNjM2OTc0NjkyZTYzNmY2ZEVRRE4AAAAiAAAAAjEyAAAAGDYzNjk3NDY5NjI2MTZlNmIyZTYzNmY2ZEVRRE4AAAAkAAAAAjEyAAAAGjYzNjk3NDY5NjM2MTcyNjQ3MzJlNjM2ZjZkRVFETgAAAC4AAAACMTIAAAAkNjM2OTc0Njk2MjYxNmU2YjZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNjM2ZTY1NzQyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDYzNmU2NTc0NzQ3NjJlNjM2ZjZkRVFETgAAACIAAAACMjIAAAAYNjQ2Zjc3NmU2YzZmNjE2NDJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNmU2NTc3NzMyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDc1NzA2YzZmNjE2NDJlNjM2ZjZkRVFETgAAAC4AAAACMzIAAAAkNjI2MTZlNjE2ZTYxNzI2NTcwNzU2MjZjNjk2MzJlNjM2ZjZkRVFETgAAABgAAAACMzIAAAAONjc2MTcwMmU2MzZmNmRFUUROAAAAIAAAAAIzMgAAABY2ZjZjNjQ2ZTYxNzY3OTJlNjM2ZjZkRVFETgAAABoAAAACNDIAAAAQNjI2OTZlNjcyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjY4NmY3NDZkNjE2OTZjMmU2MzZmNmRFUUROAAAAGgAAAAI0MgAAABA2YzY5NzY2NTJlNjM2ZjZkRVFETgAAACQAAAACNDIAAAAaNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmRFUUROAAAAGAAAAAI0MgAAAA42ZDczNmUyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjc3Njk2ZTY0NmY3NzczMmU2MzZmNmRFUUROAAAAKgAAAAI0MgAAACA3NzY5NmU2NDZmNzc3MzYxN2E3NTcyNjUyZTYzNmY2ZEVRRE4AAAAeAAAAAjQyAAAAFDZmNjY2NjY5NjM2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNzM2Yjc5NzA2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNjE3YTc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACNTIAAAASNzU2MTMyNjc2ZjJlNjM2ZjZkRVFETgAAAB4AAAACNTIAAAAUNzU2ZTY5NzQ2NTY0MmU2MzZmNmRFUUROAAAAJgAAAAI1MgAAABw3NTZlNjk3NDY1NjQ3NzY5NjY2OTJlNjM2ZjZkRVFETgAAACIAAAACODIAAAAYNmY3NjY1NzI3NDc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACODIAAAASNzk2MTY4NmY2ZjJlNjM2ZjZkRVFETgAAAB4AAAACODIAAAAUNjY2YzY5NjM2YjcyMmU2MzZmNmRFUUROAAAAJAAAAAI5MgAAABo3YTZmNmU2NTYxNmM2MTcyNmQyZTYzNmY2ZEVRRE4AAAAiAAAAAjkyAAAAGDdhNmY2ZTY1NmM2MTYyNzMyZTYzNmY2ZEVRRE4AAAAbAAAAAzg0MgAAABA2MTc2NmY2ZTJlNjM2ZjZkRVFETgAAACMAAAADODQyAAAAGDc5NmY3NTcyNjE3NjZmNmUyZTYzNmY2ZEVRRE4AAAAsAAAABDE0NzQAAAAgMzEzODMwMzA2MzZmNmU3NDYxNjM3NDczMmU2MzZmNmRFUUROAAAAKgAAAAQxNDc0AAAAHjM4MzAzMDYzNmY2ZTc0NjE2Mzc0NzMyZTYzNmY2ZEVRRE4AAAAgAAAABDIwMDAAAAAUNjE2ZDYxN2E2ZjZlMmU2MzZmNmRFUUROAAAAJAAAAAQyMDAwAAAAGDYxNmQ2MTdhNmY2ZTJlNjM2ZjJlNzU2YkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2MzYxRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY0NjVFUUROAAAAHgAAAAQyMDAwAAAAEjYxNmQ2MTdhNmY2ZTJlNjY3MkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2NTczRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY5NzRFUUROAAAAJgAAAAQyMDAwAAAAGjYxNmQ2MTdhNmY2ZTJlNjM2ZjZkMmU2MTc1RVFETgAAADIAAAAEMjAxMAAAACY2NTc4NzA3MjY1NzM3MzJkNzM2MzcyNjk3MDc0NzMyZTYzNmY2ZEVRRE4AAAAqAAAABDIwMTAAAAAeNmQ2NTY0NjM2ZjY4NjU2MTZjNzQ2ODJlNjM2ZjZkRVFETgAAABoAAAAEMjAxMQAAAA42MzZmNzgyZTYzNmY2ZEVRRE4AAAAaAAAABDIwMTEAAAAONjM2Zjc4MmU2ZTY1NzRFUUROAAAAKgAAAAQyMDExAAAAHjYzNmY3ODYyNzU3MzY5NmU2NTczNzMyZTYzNmY2ZEVRRE4AAAAyAAAABDIwMjEAAAAmNmQ3OTZlNmY3Mjc0NmY2ZTYxNjM2MzZmNzU2ZTc0MmU2MzZmNmRFUUROAAAAIAAAAAQyMDIxAAAAFDZlNmY3Mjc0NmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNmU2NTc0RVFETgAAACIAAAAEMjA0MQAAABY2YzZmNjc2ZDY1Njk2ZTJlNjM2ZjZkRVFETgAAABwAAAAEMjA0MQAAABA2YzZmNjc2ZDY1MmU2OTZlRVFETgAAACIAAAAEMjA1MQAAABY3MjYxNmI3NTc0NjU2ZTJlNjM2ZjZkRVFETgAAABoAAAAEMjA1MQAAAA42Mjc1NzkyZTYzNmY2ZEVRRE4AAAAkAAAABDIwNjEAAAAYNzM2OTcyNjk3NTczNzg2ZDJlNjM2ZjZkRVFETgAAACAAAAAEMjA2MQAAABQ3MzY5NzI2OTc1NzMyZTYzNmY2ZEVRRE4AAAAYAAAABDIwNzEAAAAMNjU2MTJlNjM2ZjZkRVFETgAAACAAAAAEMjA3MQAAABQ2ZjcyNjk2NzY5NmUyZTYzNmY2ZEVRRE4AAAAmAAAABDIwODEAAAAaMzMzNzczNjk2NzZlNjE2YzczMmU2MzZmNmRFUUROAAAAJAAAAAQyMDgxAAAAGDYyNjE3MzY1NjM2MTZkNzAyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjI2MTczNjU2MzYxNmQ3MDY4NzEyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjg2OTY3Njg3MjY5NzM2NTY4NzEyZTYzNmY2ZEVRRE4AAAAsAAAABDIwOTEAAAAgNzM3NDY1NjE2ZDcwNmY3NzY1NzI2NTY0MmU2MzZmNmRFUUROAAAAMAAAAAQyMDkxAAAAJDczNzQ2NTYxNmQ2MzZmNmQ2ZDc1NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIxMDEAAAAQNjM2ODYxNzI3NDJlNjk2ZkVRRE4AAAAiAAAABDIxMDEAAAAWNjM2ODYxNzI3NDY5NmYyZTYzNmY2ZEVRRE4AAAAqAAAABDIxMTEAAAAeNjc2Zjc0NmY2ZDY1NjU3NDY5NmU2NzJlNjM2ZjZkRVFETgAAACwAAAAEMjExMQAAACA2MzY5NzQ3MjY5Nzg2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIxMTEAAAAcNmM2ZjY3NmQ2NTY5NmU2OTZlNjMyZTYzNmY2ZEVRRE4AAAAiAAAABDIyMDEAAAAWNjc2ZjY3NmY2MTY5NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMDEAAAAgNjc2ZjY3NmY2OTZlNjY2YzY5Njc2ODc0MmU2MzZmNmRFUUROAAAAHgAAAAQyMjExAAAAEjZkNzk3MzcxNmMyZTYzNmY2ZEVRRE4AAAAgAAAABDIyMTEAAAAUNmY3MjYxNjM2YzY1MmU2MzZmNmRFUUROAAAAJAAAAAQyMjIxAAAAGDY0Njk3MzYzNmY3NjY1NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMjEAAAAgNjQ2OTczNjM2Zjc2NjU3MjYzNjE3MjY0MmU2MzZmNmRFUUROAAAAGgAAAAQyMjMxAAAADjY0NjM3NTJlNmY3MjY3RVFETgAAACgAAAAEMjIzMQAAABw2NDYzNzUyZDZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACoAAAAEMjI0MQAAAB42ZTY1NjY2Mzc1NmY2ZTZjNjk2ZTY1MmU2MzZmNmRFUUROAAAAHgAAAAQyMjQxAAAAEjZlNjU2NjYzNzUyZTYzNmY2ZEVRRE4AAAAmAAAABDIyNzEAAAAaNjM2NTZlNzQ3NTcyNzkzMjMxMmU2MzZmNmRFUUROAAAAJAAAAAQyMjcxAAAAGDMyMzE2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTZlNjU3NEVRRE4AAAAiAAAABDIyODEAAAAWNzg2NjY5NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTEAAAAmNjM3MjY5NjM2YjY1NzQ3NzY5NzI2NTZjNjU3MzczMmU2MzZmNmRFUUROAAAAKgAAAAQyMjkxAAAAHjYxNjk2Zjc3Njk3MjY1NmM2NTczNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIyOTIAAAAaNmQ2MTZlNjQ3NDYyNjE2ZTZiMmU2MzZmNmRFUUROAAAAGgAAAAQyMjkyAAAADjZkNzQ2MjJlNjM2ZjZkRVFETgAAACIAAAAEMjI5MwAAABY2MTZjNjk2MjYxNjI2MTJlNjM2ZjZkRVFETgAAACgAAAAEMjI5MwAAABw2MTZjNjk2NTc4NzA3MjY1NzM3MzJlNjM2ZjZkRVFETgAAACwAAAAEMjI5NAAAACA2ZDY1NzI2MzYxNjQ2ZjZjNjk3NjcyNjUyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTQAAAAmNmQ2NTcyNjM2MTY0NmY2YzY5NzY3MjY1MmU2MzZmNmQyZTYyNzJFUUROAAAALAAAAAQyMjk0AAAAIDZkNjU3MjYzNjE2NDZmNmM2OTYyNzI2NTJlNjM2ZjZkRVFETgAAACAAAAAEMjI5NQAAABQ3NDYxNmY2MjYxNmYyZTYzNmY2ZEVRRE4AAAAeAAAABDIyOTUAAAASNzQ2ZDYxNmM2YzJlNjM2ZjZkRVFETgAAACIAAAAEMjI5NQAAABY2MTZjNjk2ZDYxNmQ2MTJlNjM2ZjZkRVFETgAAABwAAAAEMjI5NQAAABAzMTM2MzgzODJlNjM2ZjZkRVFETgAAACoAAAAEMjI5NgAAAB42ZDY1NzI3MjY5NmM2YzY1NjQ2NzY1MmU2MzZmNmRFUUROAAAAIgAAAAQyMjk3AAAAFjdhNjU2ZTY0NjU3MzZiMmU2MzZmNmRFUUROAAAAHgAAAAQyMjk3AAAAEjdhNmY3MDY5NmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQ2NTZjNjU2YjZmNmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQyZDZmNmU2YzY5NmU2NTJlNjQ2NUVRRE4AAAAkAAAABDIyOTkAAAAYNjE3NTc0NmY2NDY1NzM2YjJlNjM2ZjZkRVFETgAAACYAAAAEMjI5OQAAABo3NDY5NmU2YjY1NzI2MzYxNjQyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDAAAAAaNzI2MTY5NmM2ZTYxNzQ2OTZmNmUyZTcyNzVFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NDY1RVFETgAAACoAAAAEMjMwMAAAAB43MjYxNjk2YzJkNmU2MTc0Njk2ZjZlMmU2MzZmNmRFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NzcyRVFETgAAACYAAAAEMjMwMAAAABo3MjYxNjk2YzZlNjE3NDY5NmY2ZTJlNzU3M0VRRE4AAAAoAAAABDIzMDAAAAAcNzQ3Mjc1NjM2YjZlNjE3NDY5NmY2ZTJlNjQ2NUVRRE4AAAAsAAAABDIzMDAAAAAgNzQ3MjYxNzY2OTYxNmU2NzYxNmQ2NTczMmU2MzZmNmRFUUROAAAAHgAAAAQyMzAxAAAAEjc3NzA2Mzc1MmU2MzZmNmY3MEVRRE4AAAAoAAAABDIzMDEAAAAcNzc3MDYzNzU2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIzMDIAAAAcNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZEVRRE4AAAAuAAAABDIzMDIAAAAiNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZDJlNjE3NUVRRE4AAAAsAAAABDIzMDIAAAAgNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmYyZTc1NmJFUUROAAAAGAAAAAQyMzAzAAAADDZkNjkyZTYzNmY2ZEVRRE4AAAAgAAAABDIzMDMAAAAUNzg2OTYxNmY2ZDY5MmU2MzZmNmRFUUROAAAAJAAAAAQyMzA0AAAAGDY2NjE2MzY1NjI2ZjZmNmIyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDQAAAAaNmQ2NTczNzM2NTZlNjc2NTcyMmU2MzZmNmRFUUROAAAAPAAAAAQyMzA1AAAAMDY0Njk3MzZlNjU3OTZkNmY3NjY5NjU3MzYxNmU3OTc3Njg2NTcyNjUyZTYzNmY2ZEVRRE4AAAAYAAAABDIzMDUAAAAMNjc2ZjJlNjM2ZjZkRVFETgAAACAAAAAEMjMwNQAAABQ2NDY5NzM2ZTY1NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDUAAAAQNjU3MzcwNmUyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNmQ3OTc1NzYyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNzU3Njc2NzUyZTYzNmY2ZEVRRE4AAAAsAAAABDIzMDcAAAAgNjI2MTZlNmIyZDc5NjE2ODYxNzYyZTYzNmYyZTY5NmNFUUROAAAAMAAAAAQyMzA3AAAAJDYyNjE2ZTZiNjg2MTcwNmY2MTZjNjk2ZDJlNjM2ZjJlNjk2Y0VRRE4AAAAeAAAABDIzMDgAAAASNmQ2NDczNmY2YzJlNjM2ZjZkRVFETgAAACYAAAAEMjMwOAAAABo2OTZkNjU2NDY5NjQ2MTc0NjEyZTYzNmY2ZEVRRE4AAAAwAAAABDIzMDkAAAAkNzY2ZjZjNzY2ZjZmNjM2NTYxNmU3MjYxNjM2NTJlNjM2ZjZkRVFETgAAADAAAAAEMjMwOQAAACQ3NjY5NzI3NDc1NjE2YzcyNjU2NzYxNzQ3NDYxMmU2MzZmNmRFUUROAAAAIAAAAAQyMzEwAAAAFDZkNzk2MzYxNmU2MTZjMmU2NjcyRVFETgAAACgAAAAEMjMxMAAAABw2MzYxNmU2MTZjMmQ3MDZjNzU3MzJlNjM2ZjZkRVFETgAAACYAAAAEMjMxMQAAABo3NDcyNzM3MjY1NzQ2OTcyNjUyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTEAAAAaNjQ2OTc2Njk2ZTc2NjU3Mzc0MmU2MzZmNmRFUUROAAAALAAAAAQyMzEyAAAAIDZlNmY3MjczNmIyZDc0Njk3MDcwNjk2ZTY3MmU2ZTZmRVFETgAAACAAAAAEMjMxMgAAABQ2Mjc1Nzk3MDYxNzM3MzJlNmU2ZkVRRE4AAAAqAAAABDIzMTMAAAAeNmQ3OTJkNjI2ZjZmNmI2OTZlNjc3MzJlNmY3MjY3RVFETgAAACgAAAAEMjMxMwAAABw2ZDc5MmQ2MjZmNmY2YjY5NmU2NzczMmU2MzYzRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk2NzZmMmU2MzZmMmU2ZTdhRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk3NDc2MmU2MzZmMmU2ZTdhRVFETgAAAB4AAAAEMjMxNQAAABI3ODY5NjE2ZDY5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE1AAAAFDYxNmM2OTcwNjE3OTJlNjM2ZjZkRVFETgAAACQAAAAEMjMxNgAAABg2MjYxNmU2MzZmNmQ2NTcyMmU2MzZmNmRFUUROAAAAKgAAAAQyMzE2AAAAHjYyNjE2ZTYzNmY2ZDY1NzIyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAoAAAABDIzMTYAAAAcNjI2Mjc2NjE2ZTY1NzQyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAkAAAABDIzMTcAAAAYNzQ3NTcyNjI2Zjc0NjE3ODJlNjM2ZjZkRVFETgAAACAAAAAEMjMxNwAAABQ2OTZlNzQ3NTY5NzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIzMTgAAAAWNzM2ODZmNzA2OTY2NzkyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTgAAAAaNmQ3OTczNjg2ZjcwNjk2Njc5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE5AAAAFDYzNmY2ZTYzNzU3MjJlNjM2ZjZkRVFETgAAADIAAAAEMjMxOQAAACY2MzZmNmU2Mzc1NzI3MzZmNmM3NTc0Njk2ZjZlNzMyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMjAAAAAQNjU2MjYxNzkyZTYzNmY2ZEVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYxNzRFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MjY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjM2MUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYzNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MzZlRVFETgAAACAAAAAEMjMyMAAAABQ2NTYyNjE3OTJlNjM2ZjJlNmE3MEVRRE4AAAAgAAAABDIzMjAAAAAUNjU2MjYxNzkyZTYzNmYyZTc0NjhFUUROAAAAIAAAAAQyMzIwAAAAFDY1NjI2MTc5MmU2MzZmMmU3NTZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2MTc1RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ODZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ZDc5RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3MzY3RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3NDc3RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjQ2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY1NzNFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjY3MkVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY5NmVFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTc0RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNmU2Y0VRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTcwNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU3MDZjRVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNzM2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTc2NmVFUUROAAAALAAAAAQyMzIwAAAAIDY3Njk3NDc0Njk2NzY5NjQ2OTc5NmY3MjJlNjM2ZjZkRVFETgAAACYAAAAEMjMyMAAAABo2NzZkNjE3MjZiNjU3NDJlNjM2ZjJlNmI3MkVRRE4AAAAgAAAABDIzMjEAAAAUNzM2MzY4Nzc2MTYyMmU2MzZmNmRFUUROAAAAKAAAAAQyMzIxAAAAHDczNjM2ODc3NjE2MjcwNmM2MTZlMmU2MzZmNmRFUUROAAAAHgAAAAQyMzIyAAAAEjY4NzY2NjYzNzUyZTZmNzI2N0VRRE4AAAAqAAAABDIzMjIAAAAeNjg3NjY2NjM3NTZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACIAAAAEMjMyMwAAABY2NjY5NzI2NTY2NmY3ODJlNjM2ZjZkRVFETgAAACIAAAAEMjMyMwAAABY2ZDZmN2E2OTZjNmM2MTJlNmY3MjY3RVFETgAAAC4AAAAEMjMyNAAAACI2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTJlNjM2ZjZkRVFETgAAAEIAAAAEMjMyNAAAADY2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTYzNmM2OTY1NmU3NDczNjU3Mjc2MmU2MzZmNmRFUUROAAAANAAAAAQyMzI0AAAAKDczNzQ2ZjYzNmI3MDZjNjE2ZTYzNmY2ZTZlNjU2Mzc0MmU2MzZmNmRFUUROAAAAGAAAAAQyMzI0AAAADDZkNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMjUAAAAaNjE3MzZiNzU2Mjc1NmU3NDc1MmU2MzZmNmRFUUROAAAALAAAAAQyMzI1AAAAIDZkNjE3NDY4NmY3NjY1NzI2NjZjNmY3NzJlNmU2NTc0RVFETgAAACoAAAAEMjMyNQAAAB43MzY1NzI3NjY1NzI2NjYxNzU2Yzc0MmU2MzZmNmRFUUROAAAAJgAAAAQyMzI1AAAAGjczNzQ2MTYzNmI2MTcwNzA3MzJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNjU3ODYzNjg2MTZlNjc2NTJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNmY3NjY1NzI2NjZjNmY3NzJlNjM2ZjZkRVFETgAAACYAAAAEMjMyNQAAABo3Mzc1NzA2NTcyNzU3MzY1NzIyZTYzNmY2ZFVSVUwAAAAtAAAAGjY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY2MTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACkAAAAWNmM2ZjY3NmQ2NTY5NmUyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAA/AAAALDczNjk3NDY1NzMyZTY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY3MzY5NzQ2NTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACcAAAAUNzc2NTY1NjI2Yzc5MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA43NzY5NzgyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAjAAAAEDc3NjU2MjczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODZmNmQ2NTczNzQ2NTYxNjQyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjc3NmY3MjY0NzA3MjY1NzM3MzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACUAAAASNmE2OTZkNjQ2ZjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAAC0AAAAaNzc2NTYyNzM3NDYxNzI3NDczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo3MzZlNjE3MDcwNjE2NzY1NzMyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAxAAAAHjYzNmM2Zjc1NjQ2MTYzNjM2NTczNzMyZTZlNjU3NAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjc3NjU2MjZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB42ZjZlNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODY1NzI2ZjZiNzU2MTcwNzAyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAfAAAADDZlNmY3NjJlNzI3NQAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjYxNzA3MDczNzA2Zjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3YTY1NmU2NDY1NzM2YjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmI2MTc5NjE2YjZmMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3MzY4NmY3MDY5NjY3OTJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmM2OTZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAJwAAABQ3NDc1NmQ2MjZjNzIyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjZlNjU3NDY2NmM2OTc4MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB43MzcwNzI2NTYxNjQ3MzY4Njk3Mjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA42NjcyNjU2NTJlNjY3MgAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjZmNzY2NTcyMmQ2MjZjNmY2NzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNjY2MTcyNmQ2MTJlNzM2OTc0NjUAAAABMQAAAAEwAAAAATBUT1RQAAAAEAAAAAEwAAAAATAAAAACe31QUklLAAATYDE2NUQ2MTAyMTM4MEQyQTkyNzE1MkY5MjAzRkZGN0VGQTlGN0NFNkVFNUY1RDFCNDYxQTI0NjdCMkZFRTg2QjE3NjExMjcxM0ZGNkM0MDkzNUJGMEQ4MDM4RUY2QTdCRkYwMTFBRjQwRUI5Q0FCNzQ5MUM5QjIyMUY0M0I5REQyMjY0Njk1NzI5MDBFMjQ1Q0VBRUY3NTlFRkU4MDQzMDk4OURGMEE4RDc3MzFDNjZCRDI0MDNEQTAzOTMxREQ1REM3QzM5MjA2NUQyN0M5NTMyMzE0MzM3QTk2N0Y2RkE4RTg0QjEzREZGOTY2Q0IyNDIwNjRBQUM4QTYxRTgzODJCMDk2MjVFNzc3MUVBQzAwODAxQjgxOTlGMEM4MTg2QTgzOTgzQjI4NTAyQjhFMjY1OUE5NDk3N0ZEMkUxNDhCNkI5NkMzN0FCQzYxMjUxRjAxMUQ2MURCNUQzMzRBNDc4RENGMzRGQjE1NUI3OEFGMUIyQjk3N0NDODc3QTY1QTJENkFEOENEREQ2Q0ZCODY1ODZDMTY3NENBMUMyMzQ2MzYxQjExNjlDRkVDNzcwQzYzMkM4M0U4QkZEQ0M1NzRFODZCRjcxMkJCQTRCMzg0MTFCMTU5Q0FBMjk4RkUxRjVDQkEyRjJERTY5OThBMjI3NTQ4RTFBQkYxNEUyNkFCNzg3NUFCREU3N0FDMEY3Q0ExRUI1MjQxN0QxOTNDMjM4RjM2QzU4OUYxRkQ0RTQzMDAyQ0VFNTI1RUE2RkI5MUQ2NTREMTFEOTU2RTA4MEZBRTU1OTJDQTFGMUE1OTFEQkU3NTczQ0Y0NTQyRUVCNDIzRkYxRkY2Nzk1MDYxQUEzQzA1Mjk3Q0I3Rjc3MTAyRjAwOTkxNjYxMDI0QTIxNjRGODE3RUU4Q0I4RDdERTdFNjQ3NTQ0NDkwQzMxQThFMTQ4MjAwNkNFODA3RDA1NDE3N0NDMUE3MzdBRkFERUM2NTA1RkZCMURFQUQwREY5OEM5MDY0NjkzQzM1Q0I2NTA5QzNDN0Q4NjhEOTZCRDgxNjAzRUFDRjczNTJBMTU3OTQwMjA3NkY4RjZBM0ZBNEE3MDBGNERFOThENTJGQzg0NDc0OThCNzY3RkQ4OEM3QjY3RDEwQzhFQjVCQ0Q1OUEyMzY5OEJGOUI5ODdBMEM5OUYyQTYzNjk3MjlFNUMzQ0E1MTI4MUVDRTA0RjVFODhBNTRCMDUyMjM0Qjg0MjhERUU5MTYyQkIxRkU1RTU2RERFRTY0MUNBREMzQUZDRDk3MkQxQzhDRTY2ODA5RjgxQ0FBNTJDQjg2NEU0RTEzNUE0QTIwMEE3OTVGMzFENTRDNUU2Nzk1QzZFMEE5QjhDNTlCQTcwMEI5NzFBMERDREUxQzE3QjE0NERCRUIxNjQ3NEJGRkJEQUIzODU5NzU4M0IzQ0Q1ODRBNjVEMkIyNUU5QzJGRjRERDE5NkVGNTFGRTE4RkVEQTMwRTQyRDMwMTRCOEUwMTZBQkYzODkzRkIyOEU0RTUxMDdFMEY5MkM4OUMxRkE5MTFFNkNCMTc0QjMzODkxQzZBOTMxMkNDNEUwN0REQzk1NDE0MUMzQkE1RjZCMkQ1RjYyMURBRjVBM0QyMEVBQTNFQUMwQTg1MzY4MTY4OUFGRTRBQzE3MjFDMDkwQUIwOTJEN0VENUJBRTE5NUJDNkU2NDUyNUJCQjUxNTgxNjVFNzQ1RkQ1OENBQzNGQkRDMDVGMzlBNjZFN0I1REJBQUQxQjcxMjBDQjQwOTREREMwMUY4QUNBRTI2MDVFMDZBNzk5RkMzQjE0RUNCRDlDNUJCOEQ4MUJCOThBMzE3RTc1RDQyQjBCNEEzQzhEODZBOTAyOTgzRkYyNTBGN0UwRDVDMDhDODIxQUUyREJDMTJENjVDNjVBNTRGRUI2RjhBNjVBNkQ0RjVEQjczMzA0MzhENUY3QzdBNDZCNzVCQjI4OTIzNzQ1MDg5OEQxQ0IyODUwMDdGMjQyRjQzQUI1REFBQkIyMEZBREMwREI3NkIwRkMxNzhFMDI4QzY0RTFDQTQ4RjMzQUU4MTM0MDI3N0MzRDUxMTMyMUQ0OTE1QkM0RkJBNjU2NkQzMERENEVEQzFEODc4NTdDOUY1REY2N0Y4QzUxOTRBN0I5NDgwNDhGNTREMzUxMjQ5MEUwNDlFNzg0Qjk5NUFDNzJFMTVCQjZCQTZCRDczRTkxRjA0NjkyQTdCRTRCQTI2MzJGODU0NEI1RUUyNUJCOTEwRTY0QUI0MjUzNUQwNUZDNDRGQTg3RTU3NEUyRDFCQkI5NUM4MkY2OTU4NEU0NTc1NDhENkFBMzU4MDkzQkM1REYwQTcxMjlFMjA5MDYxQjY1OTNGOEEyRTI2RjkwQzc3RUM4QTZEOEY1RERERUNDNTU0QkVBRkI2QkJGN0JCNzdCRDRGMTYxMzBDNEUyNjUwRTNGOUJCOUIyRTJGRjQ5MDBDMjkwRDY1NjMxOEMzODJDNDdEMUYwMzk0M0IyRUJDNDQyRTA2RDZFMjAxMTcxOUI0NUE3RkJBNTJBRDg1OUMyMjFDQ0FGRDA4MjYwMzlDREU3NUEyMUZEMTg2MDEwMUYxNkZGRTFEMkQ1QjA1N0UxMkMxMkU3N0UyREFCN0QwQkJCRkIyRTM0NzhCMzI0MzUwMjU1NDE2QjdDOUYzMjA1MDE2MUU0RjU0MjY0Q0VDNEQ1RDI1QkEwNTZCQkM3NkNCQTk5QjhBQTczQjgyNjA4MDMxNTBDOURDNzQ3RURGQzc0MDZFQjNBOTMwMkJENkEyNjgwRkM5ODgwRDFCODI0QkEyQUNFNzc3RDNFQ0NDQ0E5MkVBQjRDMTZGOTdGM0QxQzZENjI0RjFFRTdCNjcxQzg3MURCRjI1OEY4NzE1NTI5RTRCRjdEODdDQTg1Nzc2MjA3OTBBRkRFRjdBRjQxODNCNzEzMkEwMDk2QzRFMzgzOTFGQzc3MjVCMkQzMUYwODA2MUNBMDlFRUU4NThCQjdERjQ2NDg3MzhGMjYyQjE3NTU5QUFEMTk1NjYxRDNENkQxOTlENjE0QTI1Q0YyMzI1RDMxMDM5QTQ0MTlBRTE0MTEyNTc0OTQ0NUZFNUYxNzYxNjg1NUJEMDBBRjI4QzQ0OEZGRTgwNUQxODJEMEZEMTg4QTU2QTdDNzlEMjQwMUJFNjZFNjg1Rjc1QzVEMjQ4QTRDOTgzQjE3M0EyN0RDNThERDc5MjNCNUIyODFENzQ4MDVFRDJFRkEwQUU4Qzg5QzUxRDNGQTY4Rjk3NDcyOUQ4Q0U5NjhGMUU5MTM0OTI5QTZDM0U3RDM4RDQ2MDk1MDI3RDBDMzY2Nzg0M0I4NUYxNzA4OTE2QTU0NTUxMjM2RTBDQ0U2QUJGQjUxNjQzM0Q4MjU1QzZDRkIzQUVENTZGMjkzNjhEODZCRkI1NDU4QTM1MzQ5NzVEQTExMUM0OEY2NUQ4RUYwNzBDQUM1RERFNEMwMzM0ODRENEQ2N0RBNjkyMjRFRTAxQkExOUI5QjI4QThDQTI0MjIyNzhENEYzNTczRUQzQkY2RENFRTIyRURGRUM2NTJEQkFDQzA5MEQ0NzVERDUyOTg0NkE1NjhFQTY2ODVCRkJBNUJCODhGN0I2RTZEQzQ3QkI4ODExNDA2N0IzOENDRkM5MzMwOTU1MDA2MkMyRTI1MTUxQTUwQUFEQkRCRTMxOTA0OTM0MTgyNEFFNkJBNjhBRUM1QzFBMDYxOURBOTE2RTM0RUM5RkZERUFFQjcyNUNDQjlGNjk5MzdDMEQyMThBRkUyODQxNTZBN0I1MzNDQzI0MTBFRTEwQUJBQjQyNEIyMEFDQUYwQjVBMjA4MTBEMUIzQjFGMDE0MkZEQzNFMzcwMzZCQkJBODc3QzEyREZEMUQ5RDc3MTQwQ0I2QUM0OUNCMzgwQTJEMjY3NTkwMjVDN0ZCNTNDM0EyM0UwMjJGNTcxQTk0RDBGM0QwRkU4OUU1NTgwRTlFQUEyNEZFQ0M4QkQxRTY1NjM0QjI1NjAwOTg4NEU5MTdGN0M0NTgyOTRDMDQwNDI3MUYwMjdBQkM5RTQyMUFBNTk2N0NDMkJEQzQzNTZGQjc1NzI5RjE4REQ1QjNGQ0FEQUVGNEUyQzQxNTM4MUQ1NURCQ0Q3OTVENDgwNkUzQzRGNTI5RUY3MTVBQTU4QThGNzlBREQxMjg4Q0I3MjVBOUMyNDlFMUYyNjMxMTE3NzEyQTAzODVEOEVDQTFFMEFGMkRERjRENDM5RUU2OTAwMENEODkxMjU5NEJGRjc5NEZCMEUxRkFBNEE2MDIzQUQxMjI2QzUzOEU1Rjg0MkUxQTNENUFFRTg4MjhDMEZENjRGMzg1NzBFRDY3QTc4MDI0MkQ5MjI3MTJENDEzREJFRUY0MzBGMUNDQUY2QTJBMDAyMUREMzJBN0RBN0U2QzA4ODE2QUJFQzA1RTYwRTE5NTE2QjNBNjM2MERFRDgxNDZBMDEwREY4RkZFMDM2RjBBQzlFRkZBRTBGQ0M4MjYzRTgxQzRBNTY3NDhGQjg3QTNDNUMyNjc5OEUxRUZENzE2QTJGQUM4RTg1QTRGRjkyNTkxMDIwRTEyRTNEMkNGNjU5MEQ3NzNGQUIzOUNGMEY1MUVEN0I2MTcxQTJGRDMwODIwODBBQjEwMDhDRDZBRjBDODIyMEQ4QkY2OTUyN0E4MjIxMjRGMDRGMDlDOTgyNzg0NjQyNDg1MzYxM0JFMDI3Njc5NjI2NEIxOERGNEQxRTQ2RTM5MzM0RkEzRDVGMjYzRDJENjgyODkzQTUzQTFDM0Q3MkUzMzRBNDU2QjcyOUYxMTU4NDFBQjVDQ0ZCRTRBRjlEODIwMzg1QTU2MUEwODI4REYyMDdEQTk1OThBNjU4RjA2MDg4OTkxOTNDMjZGQzIwQjUwQjZGNUYxMjYyNkI0QkI5RUFBNzlCNEIyODBCODMwM0JBRjlEQjJEMkVERDc1MzBGMDE5MTJGRUQzNkUxRTk2MUFCRTU3QjJBOUE0MDVDREMwRUI2Q0NDQzY0RjMxQzlGQUYwQTJGOUUzM0QyQzRGNjFERjhBMkFBRTQ4Q0EyNDRFNzVBRTUyQTFBNkU1OTE0RDQxMkE0NEQ5NERDNDE2RTg5ODMwRjU0MTBDMkY1OERDMjc4MERERDk4QUE3MEU1QUIzQkEzMjAwQzNGQTQ4NzIxNTY2QzMzMUMxNTBFOUY3NDMxNTExNUYwMTE5QkE5NjU5NUIzQjM1OEVDMzA0RkNCOEFFNTM2RTg3RTg0M0JEMUM3RkQyMzkxNUU2NzZGMzFFMENCRERGN0UxMUJCQzE2RDRDMUQ5NEUwMjEzNTUwNjU0N0JFNTUzNTQ4MUZGNDgxQ0Y5NjExOUQxNDA5QjQxQjYzRDcwQUY1QjFDNTc3RUUxRTJDQjc4MDY0RDI5RjUwRjcyMzFGNTUwRkQ0MDc2ODIzMTBFODQ2OEFCNTBCMUY4M0I5NDJGNDg3QjU3MkM2NDlFM0NDNzQxM0JBNTU5MDU0QTZDMzg3MDZENTc5NzAwMzFGMkMyMkVFREI2REY0NDRBNTA5MTVBRjkzOUJEMjRGMUE4RTc5MDE4OTdFNEU3MTA4MEQyRTBFOTkzQzM0NzVFMzI1QzY2NkY0QTc0MEQ0NzJBRkJERjQwQzEwOENCNDczOEZBNjlFRDlCRjgxMTAyRjUwRTc2OUI5Q0Y0RThBQkY0QkVDNTAxNjI1ODBFREQ5NjM4RDczMEIxNDA1NjI4MEUzQkQyRENCREI3ODlDMUJEQkIwNEI1QkNBMDdFOTNBMjJEQkJERjhFRTc2MTIxMDU2QzMyQkY1MUVCMkJFQ0Q0RDk5QzJBNDQwNTJFQTYwNzg4OTE3NzA4RThFN0UzQUM2NTg4OTk4MTE1NzczRDNGNjQxQTFFQUM4QjE0ODExNzI1RjNBNTg0QjQ5NDJDOEQ4NzQ2NjI3M0M5Nzg5MzM1QTQ0RUQ3NTc1NzdDQkE4RkYxNzA3ODA5MTE3N0QxNDNFOEMzODU4OTRGRkZDQjU1RTc5NjEyMjFFMzg2MDEwMzU0QzgxODVEODQxNkE5NjU1NTRDMUIzM0JFNzA1NUU3MDdGRjYwMTEzNzIwMTUyQjBFNTQ1RTE3MzdBMzBCRTBGMzAxOUYwMkVDNzUzQTUyNTgxM0UwRUY3M0ZFQjA2NzFGRTlGREE3QjBCQ0I4RDU5MzRFNjk0NUMwM0ZEQzRCOTU5NzAwRUY3NUM4OTM5MjZDRjZGQUVGMTU5MjZFMEY5NDJFNDNGMzIwQTc1OUE0OTNGNDdDM0Y0MTdFODBFNTc4ODZBNTQ1NzE5ODNCRDEyOTk2MzY1MTc1Q0M1NEEyODE2MEY3QkYzRDQ1OTEyQjJCQzMwNjQxRDI5MEI1Q0VFRUM0NDY4RDMzQzk3RENDRTQwMEYwRTg4MUZDMkEzM0IyNEI3RTA5OTBCNUI3NDkxMDk1NkYyMDY0MzRCRTY3OEFCMzc2RTU0NENGN0U5Nzc2MzMyOEEzMzkwMEE2RUIxOEJCRkYxNjZERUIxODNCOUM2RTdBNUM1NTlBQkY4N0JGREIxNTIzMjY5NzNCNTU5MEZTSEFSAAACwAAAAAkyNjAyMjA1MDEAAAIANzU5ZGJiNzk0MjNjN2FlZmZkZDQ5NTlhOWE5OTVlMmIzODg2YTgzMWQ5ZDBkODYxYjk5OGU3ZTk3ZTM2YjEzYmQwMmU3NGRhOGU4ODY4OTkyNWU0Yzg3ZmQ5ZWY1Y2Q1NjA1ZTYyZWRkOTExZTkxMDIyNjRiMzZmMmI1MGM4NWQ4NzkyMmY3YTNkYWUxNWU1NDRmYTBhZGQ4ZjQ0NjIwOTA0ZDg2NzAyMGM2ZDlkZjllZGVmZjQ3NzMyYTRlZDU5MTU5Y2I1YWU0Yjk2YWJjMTYwMGIzNGQzNDI3MDkyYmVhN2IxNDkzYmQ0OTZlMTA0YTc3YTI3MWEzYjBhZmViNDI4NGUzMjkzY2MyNWIzOWU1MDMyYWQ0NDNiNGY3YjAxOGFiZDRkYTg2MjcxNjJmMTBjMjA4NmRhMWVlMGM1NmU2YzM0Nzc5NzJlOWQzYWY2ZTQ1NTQ0ZTU1YjI4NTJkZjZmMTNkMjUxNGZjODU4YTBjMzc1YTA3M2M1YzlhMWYzNjU3N2MyYmNkZjYyMjdiMDAyMDYyYWUzMmQ5MjBhNmNkMGU5YjQ1OGRiNDNmNGU0MDc2YjA4NDdiMmQ5YmYyZjFmMTM2N2EyOTBkZDVjOTA0N2VlOTE3MDY4MTYxZmEyMTc4ZmFkMmVhNGY0Mzc0MWE3ZTcyZWE5YmU4MzVjZWMAAAAyIXpVTHZXNEI0eVJSQUVvdndsTUp6NXc9PXw5QzRtU0RzYUJJWERtdS9ETng1d1FRPT0AAAABMAAAAAExAAAAYSGuba6dAIJKJV6fh883Da7ha7X0lklOC0VkcOhILi+Ss2zSzlHXG6F02f9uaInLhWRgP2rShLGxY/DFl+H0x3nxOCHl33kxqH1WsyFtBRK3PTdTebEKlpFFtXc1pKsCejoAAAABMAAAAAExU0hBUgAAAsAAAAAJMjYwMjIwNTUxAAACADA4OTUxZjgxMTU3NzRhNGJkZDA4M2U0NzM4ZmFhZDJmYmM0ZTM3MDk0MWU3MjQ0NGJmZmQ2NDBjYzQzOWQ5ZWEwY2QzMjkwYmI3MjEwNDZjOTRhMTgyZGM2YTM2MTczYzRhNWI4OWQ4NTEwZmMwMTczNzRmOGVjNzMwNmY0NTY1OWZiNzI4NWFlNzgyNDdmOTk3YmI2MGM4YTQ0OThlZTRhYjI2OGRiZDY1YTA2MTlmMWJmYjdjYjcwZmQ5ODkwMGQ1NTFjZWM5ZTFiZTRkM2U5MDUzOGFkOTA3NWJmMzM3MGNiZDFiNjBlMmFjODE1MmQxOGM0OTEyMzRiMDdkYTViNmM0OWFiYjU0N2M2MTcxMDc5ZTQ3ZmVlYWY0NTE0ZjU5Njg4YTU5YzU2ZjBkZGFlN2M0ZGI2ZmM0OGU2Nzk0M2U0MWQxYTZmMjFjY2JlNTVhZjk2MGYwZDhlNjA0NDhkNWFkYmM4NTg5YTlkOTMwOGYzMTdlYWIwYzM5OTJkOTFhZDMwY2U1OGUzNDg4ZWMzYmE4NDc0MjliNzZjYWM2NzFhMzZkZDAwNDcyYzk3NDMxNGYzODUwMmRjZDFjYjJlMzVkZDk4Mzg1MjJlY2MxYWE0NGNlYWE0ZWQ0ZmVhZmMwY2NmOTJlMGI4YTVlYTIxMjU1ZDVmMTEwNzlhMjI1AAAAMiFXR1R6UzAxQjZoZ0RGVnZINFc0WW5nPT18UEtidDA2SHFzR08vdHQxVkdKcWl0dz09AAAAATAAAAABMQAAAGEhJR/EGgc/1Ah5wbPQPqt7NvYRV1ULHgLSo9f4ZzU1bRLLcaIJfm1OTQ94Zws4jaVH80+7e4ukwzSNMfAWVhxiNcp3LFJLIV92rdREEt4/3gYoKL4uOX6/VzhEMj2IMi7MAAAAATAAAAABMUVORE0AAAACT0s= -------------------------------------------------------------------------------- /test/unit/data/blob-sharingkeyrsaencrypted.txt: -------------------------------------------------------------------------------- 1 | TFBBVgAAAAE1RU5UVQAAAAExRU5UTQAAAAEyQVRWUgAAAAEwRU5DVQAAACw1VWN6TzZPeDBQaDRPalc5ZEJodk91TllJeFdsR2tHOGkrNm9rMWJzZndjPUNCQ1UAAAABMUJCVEUAAAAKMTU2ODk2NDEyMUlQVEUAAAAKMTU2ODk2NDEyMVdNVEUAAAAKMTU2ODk2NDEyMUFOVEUAAAAKMTU2ODk2NDEyMURPVEUAAAAKMTU2ODk2NDEyMUZFVEUAAAAKMTU2ODk2NDEyMUZVVEUAAAAKMTU2ODk2NDEyMVNZVEUAAAAKMTU2ODk2NDEyMVdPVEUAAAAKMTU2ODk2NDEyMVRBVEUAAAAKMTU2ODk2NDEyMVdQVEUAAAAKMTU2ODk2NDEyMVNQTVQAAAA3AAAAATAAAAABMAAAAAEwAAAAATAAAAABMAAAAAExAAAAATEAAAABMAAAAAEwAAAAATAAAAABMFBSRUYAAACXAAAAAi0xAAAAAi0xAAAAAi0xAAAAATAAAAACLTEAAAACLTEAAAACLTEAAAAAAAAAATAAAAAAAAAAATEAAAABMQAAAAExAAAAATAAAAAAAAAAATEAAAABMQAAAAExAAAAATEAAAABMQAAAAExAAAAATEAAAABMQAAAAEwAAAAATAAAAAAAAAAATEAAAABMAAAAAEwAAAAAE5NQUMAAAABMU5FVlIAAAApAAAAATMAAAAgNjQ3MjY5NzY2NTJlNjc2ZjZmNjc2YzY1MmU2MzZmNmRFUUROAAAAJQAAAAExAAAAHDYxNmQ2NTcyNjk3NDcyNjE2NDY1MmU2MzZmNmRFUUROAAAAKQAAAAExAAAAIDc0NjQ2MTZkNjU3MjY5NzQ3MjYxNjQ2NTJlNjM2ZjZkRVFETgAAACsAAAABMgAAACI2MjYxNmU2YjZmNjY2MTZkNjU3MjY5NjM2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2MjZmNjY2MTJlNjM2ZjZkRVFETgAAABkAAAABMgAAABA2ZDYyNmU2MTJlNjM2ZjZkRVFETgAAAB8AAAABNAAAABY3OTZmNzU3NDc1NjI2NTJlNjM2ZjZkRVFETgAAAB0AAAABNAAAABQ2NzZmNmY2NzZjNjUyZTYzNmY2ZEVRRE4AAAAbAAAAATQAAAASNjc2ZDYxNjk2YzJlNjM2ZjZkRVFETgAAABsAAAABNQAAABI2MTcwNzA2YzY1MmU2MzZmNmRFUUROAAAAHQAAAAE1AAAAFDY5NjM2YzZmNzU2NDJlNjM2ZjZkRVFETgAAACUAAAABNgAAABw3NzY1NmM2YzczNjY2MTcyNjc2ZjJlNjM2ZjZkRVFETgAAABUAAAABNgAAAAw3NzY2MmU2MzZmNmRFUUROAAAAJgAAAAQyMjk2AAAAGjZkNzk2ZDY1NzI3MjY5NmM2YzJlNjM2ZjZkRVFETgAAABgAAAAEMjI5NgAAAAw2ZDZjMmU2MzZmNmRFUUROAAAALAAAAAIxMgAAACI2MTYzNjM2Zjc1NmU3NDZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMTIAAAAQNjM2OTc0NjkyZTYzNmY2ZEVRRE4AAAAiAAAAAjEyAAAAGDYzNjk3NDY5NjI2MTZlNmIyZTYzNmY2ZEVRRE4AAAAkAAAAAjEyAAAAGjYzNjk3NDY5NjM2MTcyNjQ3MzJlNjM2ZjZkRVFETgAAAC4AAAACMTIAAAAkNjM2OTc0Njk2MjYxNmU2YjZmNmU2YzY5NmU2NTJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNjM2ZTY1NzQyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDYzNmU2NTc0NzQ3NjJlNjM2ZjZkRVFETgAAACIAAAACMjIAAAAYNjQ2Zjc3NmU2YzZmNjE2NDJlNjM2ZjZkRVFETgAAABoAAAACMjIAAAAQNmU2NTc3NzMyZTYzNmY2ZEVRRE4AAAAeAAAAAjIyAAAAFDc1NzA2YzZmNjE2NDJlNjM2ZjZkRVFETgAAAC4AAAACMzIAAAAkNjI2MTZlNjE2ZTYxNzI2NTcwNzU2MjZjNjk2MzJlNjM2ZjZkRVFETgAAABgAAAACMzIAAAAONjc2MTcwMmU2MzZmNmRFUUROAAAAIAAAAAIzMgAAABY2ZjZjNjQ2ZTYxNzY3OTJlNjM2ZjZkRVFETgAAABoAAAACNDIAAAAQNjI2OTZlNjcyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjY4NmY3NDZkNjE2OTZjMmU2MzZmNmRFUUROAAAAGgAAAAI0MgAAABA2YzY5NzY2NTJlNjM2ZjZkRVFETgAAACQAAAACNDIAAAAaNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmRFUUROAAAAGAAAAAI0MgAAAA42ZDczNmUyZTYzNmY2ZEVRRE4AAAAgAAAAAjQyAAAAFjc3Njk2ZTY0NmY3NzczMmU2MzZmNmRFUUROAAAAKgAAAAI0MgAAACA3NzY5NmU2NDZmNzc3MzYxN2E3NTcyNjUyZTYzNmY2ZEVRRE4AAAAeAAAAAjQyAAAAFDZmNjY2NjY5NjM2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNzM2Yjc5NzA2NTJlNjM2ZjZkRVFETgAAABwAAAACNDIAAAASNjE3YTc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACNTIAAAASNzU2MTMyNjc2ZjJlNjM2ZjZkRVFETgAAAB4AAAACNTIAAAAUNzU2ZTY5NzQ2NTY0MmU2MzZmNmRFUUROAAAAJgAAAAI1MgAAABw3NTZlNjk3NDY1NjQ3NzY5NjY2OTJlNjM2ZjZkRVFETgAAACIAAAACODIAAAAYNmY3NjY1NzI3NDc1NzI2NTJlNjM2ZjZkRVFETgAAABwAAAACODIAAAASNzk2MTY4NmY2ZjJlNjM2ZjZkRVFETgAAAB4AAAACODIAAAAUNjY2YzY5NjM2YjcyMmU2MzZmNmRFUUROAAAAJAAAAAI5MgAAABo3YTZmNmU2NTYxNmM2MTcyNmQyZTYzNmY2ZEVRRE4AAAAiAAAAAjkyAAAAGDdhNmY2ZTY1NmM2MTYyNzMyZTYzNmY2ZEVRRE4AAAAbAAAAAzg0MgAAABA2MTc2NmY2ZTJlNjM2ZjZkRVFETgAAACMAAAADODQyAAAAGDc5NmY3NTcyNjE3NjZmNmUyZTYzNmY2ZEVRRE4AAAAsAAAABDE0NzQAAAAgMzEzODMwMzA2MzZmNmU3NDYxNjM3NDczMmU2MzZmNmRFUUROAAAAKgAAAAQxNDc0AAAAHjM4MzAzMDYzNmY2ZTc0NjE2Mzc0NzMyZTYzNmY2ZEVRRE4AAAAgAAAABDIwMDAAAAAUNjE2ZDYxN2E2ZjZlMmU2MzZmNmRFUUROAAAAJAAAAAQyMDAwAAAAGDYxNmQ2MTdhNmY2ZTJlNjM2ZjJlNzU2YkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2MzYxRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY0NjVFUUROAAAAHgAAAAQyMDAwAAAAEjYxNmQ2MTdhNmY2ZTJlNjY3MkVRRE4AAAAeAAAABDIwMDAAAAASNjE2ZDYxN2E2ZjZlMmU2NTczRVFETgAAAB4AAAAEMjAwMAAAABI2MTZkNjE3YTZmNmUyZTY5NzRFUUROAAAAJgAAAAQyMDAwAAAAGjYxNmQ2MTdhNmY2ZTJlNjM2ZjZkMmU2MTc1RVFETgAAADIAAAAEMjAxMAAAACY2NTc4NzA3MjY1NzM3MzJkNzM2MzcyNjk3MDc0NzMyZTYzNmY2ZEVRRE4AAAAqAAAABDIwMTAAAAAeNmQ2NTY0NjM2ZjY4NjU2MTZjNzQ2ODJlNjM2ZjZkRVFETgAAABoAAAAEMjAxMQAAAA42MzZmNzgyZTYzNmY2ZEVRRE4AAAAaAAAABDIwMTEAAAAONjM2Zjc4MmU2ZTY1NzRFUUROAAAAKgAAAAQyMDExAAAAHjYzNmY3ODYyNzU3MzY5NmU2NTczNzMyZTYzNmY2ZEVRRE4AAAAyAAAABDIwMjEAAAAmNmQ3OTZlNmY3Mjc0NmY2ZTYxNjM2MzZmNzU2ZTc0MmU2MzZmNmRFUUROAAAAIAAAAAQyMDIxAAAAFDZlNmY3Mjc0NmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNjM2ZjZkRVFETgAAACIAAAAEMjAzMQAAABY3NjY1NzI2OTdhNmY2ZTJlNmU2NTc0RVFETgAAACIAAAAEMjA0MQAAABY2YzZmNjc2ZDY1Njk2ZTJlNjM2ZjZkRVFETgAAABwAAAAEMjA0MQAAABA2YzZmNjc2ZDY1MmU2OTZlRVFETgAAACIAAAAEMjA1MQAAABY3MjYxNmI3NTc0NjU2ZTJlNjM2ZjZkRVFETgAAABoAAAAEMjA1MQAAAA42Mjc1NzkyZTYzNmY2ZEVRRE4AAAAkAAAABDIwNjEAAAAYNzM2OTcyNjk3NTczNzg2ZDJlNjM2ZjZkRVFETgAAACAAAAAEMjA2MQAAABQ3MzY5NzI2OTc1NzMyZTYzNmY2ZEVRRE4AAAAYAAAABDIwNzEAAAAMNjU2MTJlNjM2ZjZkRVFETgAAACAAAAAEMjA3MQAAABQ2ZjcyNjk2NzY5NmUyZTYzNmY2ZEVRRE4AAAAmAAAABDIwODEAAAAaMzMzNzczNjk2NzZlNjE2YzczMmU2MzZmNmRFUUROAAAAJAAAAAQyMDgxAAAAGDYyNjE3MzY1NjM2MTZkNzAyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjI2MTczNjU2MzYxNmQ3MDY4NzEyZTYzNmY2ZEVRRE4AAAAoAAAABDIwODEAAAAcNjg2OTY3Njg3MjY5NzM2NTY4NzEyZTYzNmY2ZEVRRE4AAAAsAAAABDIwOTEAAAAgNzM3NDY1NjE2ZDcwNmY3NzY1NzI2NTY0MmU2MzZmNmRFUUROAAAAMAAAAAQyMDkxAAAAJDczNzQ2NTYxNmQ2MzZmNmQ2ZDc1NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIxMDEAAAAQNjM2ODYxNzI3NDJlNjk2ZkVRRE4AAAAiAAAABDIxMDEAAAAWNjM2ODYxNzI3NDY5NmYyZTYzNmY2ZEVRRE4AAAAqAAAABDIxMTEAAAAeNjc2Zjc0NmY2ZDY1NjU3NDY5NmU2NzJlNjM2ZjZkRVFETgAAACwAAAAEMjExMQAAACA2MzY5NzQ3MjY5Nzg2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIxMTEAAAAcNmM2ZjY3NmQ2NTY5NmU2OTZlNjMyZTYzNmY2ZEVRRE4AAAAiAAAABDIyMDEAAAAWNjc2ZjY3NmY2MTY5NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMDEAAAAgNjc2ZjY3NmY2OTZlNjY2YzY5Njc2ODc0MmU2MzZmNmRFUUROAAAAHgAAAAQyMjExAAAAEjZkNzk3MzcxNmMyZTYzNmY2ZEVRRE4AAAAgAAAABDIyMTEAAAAUNmY3MjYxNjM2YzY1MmU2MzZmNmRFUUROAAAAJAAAAAQyMjIxAAAAGDY0Njk3MzYzNmY3NjY1NzIyZTYzNmY2ZEVRRE4AAAAsAAAABDIyMjEAAAAgNjQ2OTczNjM2Zjc2NjU3MjYzNjE3MjY0MmU2MzZmNmRFUUROAAAAGgAAAAQyMjMxAAAADjY0NjM3NTJlNmY3MjY3RVFETgAAACgAAAAEMjIzMQAAABw2NDYzNzUyZDZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACoAAAAEMjI0MQAAAB42ZTY1NjY2Mzc1NmY2ZTZjNjk2ZTY1MmU2MzZmNmRFUUROAAAAHgAAAAQyMjQxAAAAEjZlNjU2NjYzNzUyZTYzNmY2ZEVRRE4AAAAmAAAABDIyNzEAAAAaNjM2NTZlNzQ3NTcyNzkzMjMxMmU2MzZmNmRFUUROAAAAJAAAAAQyMjcxAAAAGDMyMzE2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyODEAAAAWNjM2ZjZkNjM2MTczNzQyZTZlNjU3NEVRRE4AAAAiAAAABDIyODEAAAAWNzg2NjY5NmU2OTc0NzkyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTEAAAAmNjM3MjY5NjM2YjY1NzQ3NzY5NzI2NTZjNjU3MzczMmU2MzZmNmRFUUROAAAAKgAAAAQyMjkxAAAAHjYxNjk2Zjc3Njk3MjY1NmM2NTczNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIyOTIAAAAaNmQ2MTZlNjQ3NDYyNjE2ZTZiMmU2MzZmNmRFUUROAAAAGgAAAAQyMjkyAAAADjZkNzQ2MjJlNjM2ZjZkRVFETgAAACIAAAAEMjI5MwAAABY2MTZjNjk2MjYxNjI2MTJlNjM2ZjZkRVFETgAAACgAAAAEMjI5MwAAABw2MTZjNjk2NTc4NzA3MjY1NzM3MzJlNjM2ZjZkRVFETgAAACwAAAAEMjI5NAAAACA2ZDY1NzI2MzYxNjQ2ZjZjNjk3NjcyNjUyZTYzNmY2ZEVRRE4AAAAyAAAABDIyOTQAAAAmNmQ2NTcyNjM2MTY0NmY2YzY5NzY3MjY1MmU2MzZmNmQyZTYyNzJFUUROAAAALAAAAAQyMjk0AAAAIDZkNjU3MjYzNjE2NDZmNmM2OTYyNzI2NTJlNjM2ZjZkRVFETgAAACAAAAAEMjI5NQAAABQ3NDYxNmY2MjYxNmYyZTYzNmY2ZEVRRE4AAAAeAAAABDIyOTUAAAASNzQ2ZDYxNmM2YzJlNjM2ZjZkRVFETgAAACIAAAAEMjI5NQAAABY2MTZjNjk2ZDYxNmQ2MTJlNjM2ZjZkRVFETgAAABwAAAAEMjI5NQAAABAzMTM2MzgzODJlNjM2ZjZkRVFETgAAACoAAAAEMjI5NgAAAB42ZDY1NzI3MjY5NmM2YzY1NjQ2NzY1MmU2MzZmNmRFUUROAAAAIgAAAAQyMjk3AAAAFjdhNjU2ZTY0NjU3MzZiMmU2MzZmNmRFUUROAAAAHgAAAAQyMjk3AAAAEjdhNmY3MDY5NmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQ2NTZjNjU2YjZmNmQyZTYzNmY2ZEVRRE4AAAAiAAAABDIyOTgAAAAWNzQyZDZmNmU2YzY5NmU2NTJlNjQ2NUVRRE4AAAAkAAAABDIyOTkAAAAYNjE3NTc0NmY2NDY1NzM2YjJlNjM2ZjZkRVFETgAAACYAAAAEMjI5OQAAABo3NDY5NmU2YjY1NzI2MzYxNjQyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDAAAAAaNzI2MTY5NmM2ZTYxNzQ2OTZmNmUyZTcyNzVFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NDY1RVFETgAAACoAAAAEMjMwMAAAAB43MjYxNjk2YzJkNmU2MTc0Njk2ZjZlMmU2MzZmNmRFUUROAAAAJgAAAAQyMzAwAAAAGjcyNjE2OTZjNmU2MTc0Njk2ZjZlMmU2NzcyRVFETgAAACYAAAAEMjMwMAAAABo3MjYxNjk2YzZlNjE3NDY5NmY2ZTJlNzU3M0VRRE4AAAAoAAAABDIzMDAAAAAcNzQ3Mjc1NjM2YjZlNjE3NDY5NmY2ZTJlNjQ2NUVRRE4AAAAsAAAABDIzMDAAAAAgNzQ3MjYxNzY2OTYxNmU2NzYxNmQ2NTczMmU2MzZmNmRFUUROAAAAHgAAAAQyMzAxAAAAEjc3NzA2Mzc1MmU2MzZmNmY3MEVRRE4AAAAoAAAABDIzMDEAAAAcNzc3MDYzNzU2ZjZlNmM2OTZlNjUyZTYzNmY2ZEVRRE4AAAAoAAAABDIzMDIAAAAcNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZEVRRE4AAAAuAAAABDIzMDIAAAAiNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmY2ZDJlNjE3NUVRRE4AAAAsAAAABDIzMDIAAAAgNmQ2MTc0Njg2YzY1NzQ2OTYzNzMyZTYzNmYyZTc1NmJFUUROAAAAGAAAAAQyMzAzAAAADDZkNjkyZTYzNmY2ZEVRRE4AAAAgAAAABDIzMDMAAAAUNzg2OTYxNmY2ZDY5MmU2MzZmNmRFUUROAAAAJAAAAAQyMzA0AAAAGDY2NjE2MzY1NjI2ZjZmNmIyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMDQAAAAaNmQ2NTczNzM2NTZlNjc2NTcyMmU2MzZmNmRFUUROAAAAPAAAAAQyMzA1AAAAMDY0Njk3MzZlNjU3OTZkNmY3NjY5NjU3MzYxNmU3OTc3Njg2NTcyNjUyZTYzNmY2ZEVRRE4AAAAYAAAABDIzMDUAAAAMNjc2ZjJlNjM2ZjZkRVFETgAAACAAAAAEMjMwNQAAABQ2NDY5NzM2ZTY1NzkyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDUAAAAQNjU3MzcwNmUyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNmQ3OTc1NzYyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMDYAAAAQNzU3Njc2NzUyZTYzNmY2ZEVRRE4AAAAsAAAABDIzMDcAAAAgNjI2MTZlNmIyZDc5NjE2ODYxNzYyZTYzNmYyZTY5NmNFUUROAAAAMAAAAAQyMzA3AAAAJDYyNjE2ZTZiNjg2MTcwNmY2MTZjNjk2ZDJlNjM2ZjJlNjk2Y0VRRE4AAAAeAAAABDIzMDgAAAASNmQ2NDczNmY2YzJlNjM2ZjZkRVFETgAAACYAAAAEMjMwOAAAABo2OTZkNjU2NDY5NjQ2MTc0NjEyZTYzNmY2ZEVRRE4AAAAwAAAABDIzMDkAAAAkNzY2ZjZjNzY2ZjZmNjM2NTYxNmU3MjYxNjM2NTJlNjM2ZjZkRVFETgAAADAAAAAEMjMwOQAAACQ3NjY5NzI3NDc1NjE2YzcyNjU2NzYxNzQ3NDYxMmU2MzZmNmRFUUROAAAAIAAAAAQyMzEwAAAAFDZkNzk2MzYxNmU2MTZjMmU2NjcyRVFETgAAACgAAAAEMjMxMAAAABw2MzYxNmU2MTZjMmQ3MDZjNzU3MzJlNjM2ZjZkRVFETgAAACYAAAAEMjMxMQAAABo3NDcyNzM3MjY1NzQ2OTcyNjUyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTEAAAAaNjQ2OTc2Njk2ZTc2NjU3Mzc0MmU2MzZmNmRFUUROAAAALAAAAAQyMzEyAAAAIDZlNmY3MjczNmIyZDc0Njk3MDcwNjk2ZTY3MmU2ZTZmRVFETgAAACAAAAAEMjMxMgAAABQ2Mjc1Nzk3MDYxNzM3MzJlNmU2ZkVRRE4AAAAqAAAABDIzMTMAAAAeNmQ3OTJkNjI2ZjZmNmI2OTZlNjc3MzJlNmY3MjY3RVFETgAAACgAAAAEMjMxMwAAABw2ZDc5MmQ2MjZmNmY2YjY5NmU2NzczMmU2MzYzRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk2NzZmMmU2MzZmMmU2ZTdhRVFETgAAACIAAAAEMjMxNAAAABY3MzZiNzk3NDc2MmU2MzZmMmU2ZTdhRVFETgAAAB4AAAAEMjMxNQAAABI3ODY5NjE2ZDY5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE1AAAAFDYxNmM2OTcwNjE3OTJlNjM2ZjZkRVFETgAAACQAAAAEMjMxNgAAABg2MjYxNmU2MzZmNmQ2NTcyMmU2MzZmNmRFUUROAAAAKgAAAAQyMzE2AAAAHjYyNjE2ZTYzNmY2ZDY1NzIyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAoAAAABDIzMTYAAAAcNjI2Mjc2NjE2ZTY1NzQyZTYzNmY2ZDJlNmQ3OEVRRE4AAAAkAAAABDIzMTcAAAAYNzQ3NTcyNjI2Zjc0NjE3ODJlNjM2ZjZkRVFETgAAACAAAAAEMjMxNwAAABQ2OTZlNzQ3NTY5NzQyZTYzNmY2ZEVRRE4AAAAiAAAABDIzMTgAAAAWNzM2ODZmNzA2OTY2NzkyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMTgAAAAaNmQ3OTczNjg2ZjcwNjk2Njc5MmU2MzZmNmRFUUROAAAAIAAAAAQyMzE5AAAAFDYzNmY2ZTYzNzU3MjJlNjM2ZjZkRVFETgAAADIAAAAEMjMxOQAAACY2MzZmNmU2Mzc1NzI3MzZmNmM3NTc0Njk2ZjZlNzMyZTYzNmY2ZEVRRE4AAAAcAAAABDIzMjAAAAAQNjU2MjYxNzkyZTYzNmY2ZEVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYxNzRFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MjY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjM2MUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTYzNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2MzZlRVFETgAAACAAAAAEMjMyMAAAABQ2NTYyNjE3OTJlNjM2ZjJlNmE3MEVRRE4AAAAgAAAABDIzMjAAAAAUNjU2MjYxNzkyZTYzNmYyZTc0NjhFUUROAAAAIAAAAAQyMzIwAAAAFDY1NjI2MTc5MmU2MzZmMmU3NTZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2MTc1RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ODZiRVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU2ZDc5RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3MzY3RVFETgAAACIAAAAEMjMyMAAAABY2NTYyNjE3OTJlNjM2ZjZkMmU3NDc3RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjQ2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY1NzNFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTY1RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNjY3MkVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTY5NmVFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU2OTc0RVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNmU2Y0VRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTcwNjhFUUROAAAAGgAAAAQyMzIwAAAADjY1NjI2MTc5MmU3MDZjRVFETgAAABoAAAAEMjMyMAAAAA42NTYyNjE3OTJlNzM2NUVRRE4AAAAaAAAABDIzMjAAAAAONjU2MjYxNzkyZTc2NmVFUUROAAAALAAAAAQyMzIwAAAAIDY3Njk3NDc0Njk2NzY5NjQ2OTc5NmY3MjJlNjM2ZjZkRVFETgAAACYAAAAEMjMyMAAAABo2NzZkNjE3MjZiNjU3NDJlNjM2ZjJlNmI3MkVRRE4AAAAgAAAABDIzMjEAAAAUNzM2MzY4Nzc2MTYyMmU2MzZmNmRFUUROAAAAKAAAAAQyMzIxAAAAHDczNjM2ODc3NjE2MjcwNmM2MTZlMmU2MzZmNmRFUUROAAAAHgAAAAQyMzIyAAAAEjY4NzY2NjYzNzUyZTZmNzI2N0VRRE4AAAAqAAAABDIzMjIAAAAeNjg3NjY2NjM3NTZmNmU2YzY5NmU2NTJlNmY3MjY3RVFETgAAACIAAAAEMjMyMwAAABY2NjY5NzI2NTY2NmY3ODJlNjM2ZjZkRVFETgAAACIAAAAEMjMyMwAAABY2ZDZmN2E2OTZjNmM2MTJlNmY3MjY3RVFETgAAAC4AAAAEMjMyNAAAACI2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTJlNjM2ZjZkRVFETgAAAEIAAAAEMjMyNAAAADY2ZDZmNzI2NzYxNmU3Mzc0NjE2ZTZjNjU3OTYzNmM2OTY1NmU3NDczNjU3Mjc2MmU2MzZmNmRFUUROAAAANAAAAAQyMzI0AAAAKDczNzQ2ZjYzNmI3MDZjNjE2ZTYzNmY2ZTZlNjU2Mzc0MmU2MzZmNmRFUUROAAAAGAAAAAQyMzI0AAAADDZkNzMyZTYzNmY2ZEVRRE4AAAAmAAAABDIzMjUAAAAaNjE3MzZiNzU2Mjc1NmU3NDc1MmU2MzZmNmRFUUROAAAALAAAAAQyMzI1AAAAIDZkNjE3NDY4NmY3NjY1NzI2NjZjNmY3NzJlNmU2NTc0RVFETgAAACoAAAAEMjMyNQAAAB43MzY1NzI3NjY1NzI2NjYxNzU2Yzc0MmU2MzZmNmRFUUROAAAAJgAAAAQyMzI1AAAAGjczNzQ2MTYzNmI2MTcwNzA3MzJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNjU3ODYzNjg2MTZlNjc2NTJlNjM2ZjZkRVFETgAAAC4AAAAEMjMyNQAAACI3Mzc0NjE2MzZiNmY3NjY1NzI2NjZjNmY3NzJlNjM2ZjZkRVFETgAAACYAAAAEMjMyNQAAABo3Mzc1NzA2NTcyNzU3MzY1NzIyZTYzNmY2ZFVSVUwAAAAtAAAAGjY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY2MTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACkAAAAWNmM2ZjY3NmQ2NTY5NmUyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAA/AAAALDczNjk3NDY1NzMyZTY3NmY2ZjY3NmM2NTJlNjM2ZjZkMmY3MzY5NzQ2NTJmAAAAATAAAAABMAAAAAEwVVJVTAAAACcAAAAUNzc2NTY1NjI2Yzc5MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA43NzY5NzgyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAjAAAAEDc3NjU2MjczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODZmNmQ2NTczNzQ2NTYxNjQyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjc3NmY3MjY0NzA3MjY1NzM3MzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACUAAAASNmE2OTZkNjQ2ZjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAAC0AAAAaNzc2NTYyNzM3NDYxNzI3NDczMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo3MzZlNjE3MDcwNjE2NzY1NzMyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAxAAAAHjYzNmM2Zjc1NjQ2MTYzNjM2NTczNzMyZTZlNjU3NAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjc3NjU2MjZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB42ZjZlNmQ2OTYzNzI2ZjczNmY2Njc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAALQAAABo2ODY1NzI2ZjZiNzU2MTcwNzAyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAAfAAAADDZlNmY3NjJlNzI3NQAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjYxNzA3MDczNzA2Zjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3YTY1NmU2NDY1NzM2YjJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmI2MTc5NjE2YjZmMmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAKQAAABY3MzY4NmY3MDY5NjY3OTJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNmM2OTZlNmY2NDY1MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAJwAAABQ3NDc1NmQ2MjZjNzIyZTYzNmY2ZAAAAAExAAAAATAAAAABMFVSVUwAAAApAAAAFjZlNjU3NDY2NmM2OTc4MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAMQAAAB43MzcwNzI2NTYxNjQ3MzY4Njk3Mjc0MmU2MzZmNmQAAAABMQAAAAEwAAAAATBVUlVMAAAAIQAAAA42NjcyNjU2NTJlNjY3MgAAAAExAAAAATAAAAABMFVSVUwAAAAtAAAAGjZmNzY2NTcyMmQ2MjZjNmY2NzJlNjM2ZjZkAAAAATEAAAABMAAAAAEwVVJVTAAAACcAAAAUNjY2MTcyNmQ2MTJlNzM2OTc0NjUAAAABMQAAAAEwAAAAATBUT1RQAAAAEAAAAAEwAAAAATAAAAACe31QUklLAAATYDE2NUQ2MTAyMTM4MEQyQTkyNzE1MkY5MjAzRkZGN0VGQTlGN0NFNkVFNUY1RDFCNDYxQTI0NjdCMkZFRTg2QjE3NjExMjcxM0ZGNkM0MDkzNUJGMEQ4MDM4RUY2QTdCRkYwMTFBRjQwRUI5Q0FCNzQ5MUM5QjIyMUY0M0I5REQyMjY0Njk1NzI5MDBFMjQ1Q0VBRUY3NTlFRkU4MDQzMDk4OURGMEE4RDc3MzFDNjZCRDI0MDNEQTAzOTMxREQ1REM3QzM5MjA2NUQyN0M5NTMyMzE0MzM3QTk2N0Y2RkE4RTg0QjEzREZGOTY2Q0IyNDIwNjRBQUM4QTYxRTgzODJCMDk2MjVFNzc3MUVBQzAwODAxQjgxOTlGMEM4MTg2QTgzOTgzQjI4NTAyQjhFMjY1OUE5NDk3N0ZEMkUxNDhCNkI5NkMzN0FCQzYxMjUxRjAxMUQ2MURCNUQzMzRBNDc4RENGMzRGQjE1NUI3OEFGMUIyQjk3N0NDODc3QTY1QTJENkFEOENEREQ2Q0ZCODY1ODZDMTY3NENBMUMyMzQ2MzYxQjExNjlDRkVDNzcwQzYzMkM4M0U4QkZEQ0M1NzRFODZCRjcxMkJCQTRCMzg0MTFCMTU5Q0FBMjk4RkUxRjVDQkEyRjJERTY5OThBMjI3NTQ4RTFBQkYxNEUyNkFCNzg3NUFCREU3N0FDMEY3Q0ExRUI1MjQxN0QxOTNDMjM4RjM2QzU4OUYxRkQ0RTQzMDAyQ0VFNTI1RUE2RkI5MUQ2NTREMTFEOTU2RTA4MEZBRTU1OTJDQTFGMUE1OTFEQkU3NTczQ0Y0NTQyRUVCNDIzRkYxRkY2Nzk1MDYxQUEzQzA1Mjk3Q0I3Rjc3MTAyRjAwOTkxNjYxMDI0QTIxNjRGODE3RUU4Q0I4RDdERTdFNjQ3NTQ0NDkwQzMxQThFMTQ4MjAwNkNFODA3RDA1NDE3N0NDMUE3MzdBRkFERUM2NTA1RkZCMURFQUQwREY5OEM5MDY0NjkzQzM1Q0I2NTA5QzNDN0Q4NjhEOTZCRDgxNjAzRUFDRjczNTJBMTU3OTQwMjA3NkY4RjZBM0ZBNEE3MDBGNERFOThENTJGQzg0NDc0OThCNzY3RkQ4OEM3QjY3RDEwQzhFQjVCQ0Q1OUEyMzY5OEJGOUI5ODdBMEM5OUYyQTYzNjk3MjlFNUMzQ0E1MTI4MUVDRTA0RjVFODhBNTRCMDUyMjM0Qjg0MjhERUU5MTYyQkIxRkU1RTU2RERFRTY0MUNBREMzQUZDRDk3MkQxQzhDRTY2ODA5RjgxQ0FBNTJDQjg2NEU0RTEzNUE0QTIwMEE3OTVGMzFENTRDNUU2Nzk1QzZFMEE5QjhDNTlCQTcwMEI5NzFBMERDREUxQzE3QjE0NERCRUIxNjQ3NEJGRkJEQUIzODU5NzU4M0IzQ0Q1ODRBNjVEMkIyNUU5QzJGRjRERDE5NkVGNTFGRTE4RkVEQTMwRTQyRDMwMTRCOEUwMTZBQkYzODkzRkIyOEU0RTUxMDdFMEY5MkM4OUMxRkE5MTFFNkNCMTc0QjMzODkxQzZBOTMxMkNDNEUwN0REQzk1NDE0MUMzQkE1RjZCMkQ1RjYyMURBRjVBM0QyMEVBQTNFQUMwQTg1MzY4MTY4OUFGRTRBQzE3MjFDMDkwQUIwOTJEN0VENUJBRTE5NUJDNkU2NDUyNUJCQjUxNTgxNjVFNzQ1RkQ1OENBQzNGQkRDMDVGMzlBNjZFN0I1REJBQUQxQjcxMjBDQjQwOTREREMwMUY4QUNBRTI2MDVFMDZBNzk5RkMzQjE0RUNCRDlDNUJCOEQ4MUJCOThBMzE3RTc1RDQyQjBCNEEzQzhEODZBOTAyOTgzRkYyNTBGN0UwRDVDMDhDODIxQUUyREJDMTJENjVDNjVBNTRGRUI2RjhBNjVBNkQ0RjVEQjczMzA0MzhENUY3QzdBNDZCNzVCQjI4OTIzNzQ1MDg5OEQxQ0IyODUwMDdGMjQyRjQzQUI1REFBQkIyMEZBREMwREI3NkIwRkMxNzhFMDI4QzY0RTFDQTQ4RjMzQUU4MTM0MDI3N0MzRDUxMTMyMUQ0OTE1QkM0RkJBNjU2NkQzMERENEVEQzFEODc4NTdDOUY1REY2N0Y4QzUxOTRBN0I5NDgwNDhGNTREMzUxMjQ5MEUwNDlFNzg0Qjk5NUFDNzJFMTVCQjZCQTZCRDczRTkxRjA0NjkyQTdCRTRCQTI2MzJGODU0NEI1RUUyNUJCOTEwRTY0QUI0MjUzNUQwNUZDNDRGQTg3RTU3NEUyRDFCQkI5NUM4MkY2OTU4NEU0NTc1NDhENkFBMzU4MDkzQkM1REYwQTcxMjlFMjA5MDYxQjY1OTNGOEEyRTI2RjkwQzc3RUM4QTZEOEY1RERERUNDNTU0QkVBRkI2QkJGN0JCNzdCRDRGMTYxMzBDNEUyNjUwRTNGOUJCOUIyRTJGRjQ5MDBDMjkwRDY1NjMxOEMzODJDNDdEMUYwMzk0M0IyRUJDNDQyRTA2RDZFMjAxMTcxOUI0NUE3RkJBNTJBRDg1OUMyMjFDQ0FGRDA4MjYwMzlDREU3NUEyMUZEMTg2MDEwMUYxNkZGRTFEMkQ1QjA1N0UxMkMxMkU3N0UyREFCN0QwQkJCRkIyRTM0NzhCMzI0MzUwMjU1NDE2QjdDOUYzMjA1MDE2MUU0RjU0MjY0Q0VDNEQ1RDI1QkEwNTZCQkM3NkNCQTk5QjhBQTczQjgyNjA4MDMxNTBDOURDNzQ3RURGQzc0MDZFQjNBOTMwMkJENkEyNjgwRkM5ODgwRDFCODI0QkEyQUNFNzc3RDNFQ0NDQ0E5MkVBQjRDMTZGOTdGM0QxQzZENjI0RjFFRTdCNjcxQzg3MURCRjI1OEY4NzE1NTI5RTRCRjdEODdDQTg1Nzc2MjA3OTBBRkRFRjdBRjQxODNCNzEzMkEwMDk2QzRFMzgzOTFGQzc3MjVCMkQzMUYwODA2MUNBMDlFRUU4NThCQjdERjQ2NDg3MzhGMjYyQjE3NTU5QUFEMTk1NjYxRDNENkQxOTlENjE0QTI1Q0YyMzI1RDMxMDM5QTQ0MTlBRTE0MTEyNTc0OTQ0NUZFNUYxNzYxNjg1NUJEMDBBRjI4QzQ0OEZGRTgwNUQxODJEMEZEMTg4QTU2QTdDNzlEMjQwMUJFNjZFNjg1Rjc1QzVEMjQ4QTRDOTgzQjE3M0EyN0RDNThERDc5MjNCNUIyODFENzQ4MDVFRDJFRkEwQUU4Qzg5QzUxRDNGQTY4Rjk3NDcyOUQ4Q0U5NjhGMUU5MTM0OTI5QTZDM0U3RDM4RDQ2MDk1MDI3RDBDMzY2Nzg0M0I4NUYxNzA4OTE2QTU0NTUxMjM2RTBDQ0U2QUJGQjUxNjQzM0Q4MjU1QzZDRkIzQUVENTZGMjkzNjhEODZCRkI1NDU4QTM1MzQ5NzVEQTExMUM0OEY2NUQ4RUYwNzBDQUM1RERFNEMwMzM0ODRENEQ2N0RBNjkyMjRFRTAxQkExOUI5QjI4QThDQTI0MjIyNzhENEYzNTczRUQzQkY2RENFRTIyRURGRUM2NTJEQkFDQzA5MEQ0NzVERDUyOTg0NkE1NjhFQTY2ODVCRkJBNUJCODhGN0I2RTZEQzQ3QkI4ODExNDA2N0IzOENDRkM5MzMwOTU1MDA2MkMyRTI1MTUxQTUwQUFEQkRCRTMxOTA0OTM0MTgyNEFFNkJBNjhBRUM1QzFBMDYxOURBOTE2RTM0RUM5RkZERUFFQjcyNUNDQjlGNjk5MzdDMEQyMThBRkUyODQxNTZBN0I1MzNDQzI0MTBFRTEwQUJBQjQyNEIyMEFDQUYwQjVBMjA4MTBEMUIzQjFGMDE0MkZEQzNFMzcwMzZCQkJBODc3QzEyREZEMUQ5RDc3MTQwQ0I2QUM0OUNCMzgwQTJEMjY3NTkwMjVDN0ZCNTNDM0EyM0UwMjJGNTcxQTk0RDBGM0QwRkU4OUU1NTgwRTlFQUEyNEZFQ0M4QkQxRTY1NjM0QjI1NjAwOTg4NEU5MTdGN0M0NTgyOTRDMDQwNDI3MUYwMjdBQkM5RTQyMUFBNTk2N0NDMkJEQzQzNTZGQjc1NzI5RjE4REQ1QjNGQ0FEQUVGNEUyQzQxNTM4MUQ1NURCQ0Q3OTVENDgwNkUzQzRGNTI5RUY3MTVBQTU4QThGNzlBREQxMjg4Q0I3MjVBOUMyNDlFMUYyNjMxMTE3NzEyQTAzODVEOEVDQTFFMEFGMkRERjRENDM5RUU2OTAwMENEODkxMjU5NEJGRjc5NEZCMEUxRkFBNEE2MDIzQUQxMjI2QzUzOEU1Rjg0MkUxQTNENUFFRTg4MjhDMEZENjRGMzg1NzBFRDY3QTc4MDI0MkQ5MjI3MTJENDEzREJFRUY0MzBGMUNDQUY2QTJBMDAyMUREMzJBN0RBN0U2QzA4ODE2QUJFQzA1RTYwRTE5NTE2QjNBNjM2MERFRDgxNDZBMDEwREY4RkZFMDM2RjBBQzlFRkZBRTBGQ0M4MjYzRTgxQzRBNTY3NDhGQjg3QTNDNUMyNjc5OEUxRUZENzE2QTJGQUM4RTg1QTRGRjkyNTkxMDIwRTEyRTNEMkNGNjU5MEQ3NzNGQUIzOUNGMEY1MUVEN0I2MTcxQTJGRDMwODIwODBBQjEwMDhDRDZBRjBDODIyMEQ4QkY2OTUyN0E4MjIxMjRGMDRGMDlDOTgyNzg0NjQyNDg1MzYxM0JFMDI3Njc5NjI2NEIxOERGNEQxRTQ2RTM5MzM0RkEzRDVGMjYzRDJENjgyODkzQTUzQTFDM0Q3MkUzMzRBNDU2QjcyOUYxMTU4NDFBQjVDQ0ZCRTRBRjlEODIwMzg1QTU2MUEwODI4REYyMDdEQTk1OThBNjU4RjA2MDg4OTkxOTNDMjZGQzIwQjUwQjZGNUYxMjYyNkI0QkI5RUFBNzlCNEIyODBCODMwM0JBRjlEQjJEMkVERDc1MzBGMDE5MTJGRUQzNkUxRTk2MUFCRTU3QjJBOUE0MDVDREMwRUI2Q0NDQzY0RjMxQzlGQUYwQTJGOUUzM0QyQzRGNjFERjhBMkFBRTQ4Q0EyNDRFNzVBRTUyQTFBNkU1OTE0RDQxMkE0NEQ5NERDNDE2RTg5ODMwRjU0MTBDMkY1OERDMjc4MERERDk4QUE3MEU1QUIzQkEzMjAwQzNGQTQ4NzIxNTY2QzMzMUMxNTBFOUY3NDMxNTExNUYwMTE5QkE5NjU5NUIzQjM1OEVDMzA0RkNCOEFFNTM2RTg3RTg0M0JEMUM3RkQyMzkxNUU2NzZGMzFFMENCRERGN0UxMUJCQzE2RDRDMUQ5NEUwMjEzNTUwNjU0N0JFNTUzNTQ4MUZGNDgxQ0Y5NjExOUQxNDA5QjQxQjYzRDcwQUY1QjFDNTc3RUUxRTJDQjc4MDY0RDI5RjUwRjcyMzFGNTUwRkQ0MDc2ODIzMTBFODQ2OEFCNTBCMUY4M0I5NDJGNDg3QjU3MkM2NDlFM0NDNzQxM0JBNTU5MDU0QTZDMzg3MDZENTc5NzAwMzFGMkMyMkVFREI2REY0NDRBNTA5MTVBRjkzOUJEMjRGMUE4RTc5MDE4OTdFNEU3MTA4MEQyRTBFOTkzQzM0NzVFMzI1QzY2NkY0QTc0MEQ0NzJBRkJERjQwQzEwOENCNDczOEZBNjlFRDlCRjgxMTAyRjUwRTc2OUI5Q0Y0RThBQkY0QkVDNTAxNjI1ODBFREQ5NjM4RDczMEIxNDA1NjI4MEUzQkQyRENCREI3ODlDMUJEQkIwNEI1QkNBMDdFOTNBMjJEQkJERjhFRTc2MTIxMDU2QzMyQkY1MUVCMkJFQ0Q0RDk5QzJBNDQwNTJFQTYwNzg4OTE3NzA4RThFN0UzQUM2NTg4OTk4MTE1NzczRDNGNjQxQTFFQUM4QjE0ODExNzI1RjNBNTg0QjQ5NDJDOEQ4NzQ2NjI3M0M5Nzg5MzM1QTQ0RUQ3NTc1NzdDQkE4RkYxNzA3ODA5MTE3N0QxNDNFOEMzODU4OTRGRkZDQjU1RTc5NjEyMjFFMzg2MDEwMzU0QzgxODVEODQxNkE5NjU1NTRDMUIzM0JFNzA1NUU3MDdGRjYwMTEzNzIwMTUyQjBFNTQ1RTE3MzdBMzBCRTBGMzAxOUYwMkVDNzUzQTUyNTgxM0UwRUY3M0ZFQjA2NzFGRTlGREE3QjBCQ0I4RDU5MzRFNjk0NUMwM0ZEQzRCOTU5NzAwRUY3NUM4OTM5MjZDRjZGQUVGMTU5MjZFMEY5NDJFNDNGMzIwQTc1OUE0OTNGNDdDM0Y0MTdFODBFNTc4ODZBNTQ1NzE5ODNCRDEyOTk2MzY1MTc1Q0M1NEEyODE2MEY3QkYzRDQ1OTEyQjJCQzMwNjQxRDI5MEI1Q0VFRUM0NDY4RDMzQzk3RENDRTQwMEYwRTg4MUZDMkEzM0IyNEI3RTA5OTBCNUI3NDkxMDk1NkYyMDY0MzRCRTY3OEFCMzc2RTU0NENGN0U5Nzc2MzMyOEEzMzkwMEE2RUIxOEJCRkYxNjZERUIxODNCOUM2RTdBNUM1NTlBQkY4N0JGREIxNTIzMjY5NzNCNTU5MEZTSEFSAAACXwAAAAkyNjAyMjA1MDEAAAIANzU5ZGJiNzk0MjNjN2FlZmZkZDQ5NTlhOWE5OTVlMmIzODg2YTgzMWQ5ZDBkODYxYjk5OGU3ZTk3ZTM2YjEzYmQwMmU3NGRhOGU4ODY4OTkyNWU0Yzg3ZmQ5ZWY1Y2Q1NjA1ZTYyZWRkOTExZTkxMDIyNjRiMzZmMmI1MGM4NWQ4NzkyMmY3YTNkYWUxNWU1NDRmYTBhZGQ4ZjQ0NjIwOTA0ZDg2NzAyMGM2ZDlkZjllZGVmZjQ3NzMyYTRlZDU5MTU5Y2I1YWU0Yjk2YWJjMTYwMGIzNGQzNDI3MDkyYmVhN2IxNDkzYmQ0OTZlMTA0YTc3YTI3MWEzYjBhZmViNDI4NGUzMjkzY2MyNWIzOWU1MDMyYWQ0NDNiNGY3YjAxOGFiZDRkYTg2MjcxNjJmMTBjMjA4NmRhMWVlMGM1NmU2YzM0Nzc5NzJlOWQzYWY2ZTQ1NTQ0ZTU1YjI4NTJkZjZmMTNkMjUxNGZjODU4YTBjMzc1YTA3M2M1YzlhMWYzNjU3N2MyYmNkZjYyMjdiMDAyMDYyYWUzMmQ5MjBhNmNkMGU5YjQ1OGRiNDNmNGU0MDc2YjA4NDdiMmQ5YmYyZjFmMTM2N2EyOTBkZDVjOTA0N2VlOTE3MDY4MTYxZmEyMTc4ZmFkMmVhNGY0Mzc0MWE3ZTcyZWE5YmU4MzVjZWMAAAAyIXpVTHZXNEI0eVJSQUVvdndsTUp6NXc9PXw5QzRtU0RzYUJJWERtdS9ETng1d1FRPT0AAAABMAAAAAExAAAAAAAAAAEwAAAAATFBQ0NUAAABlAAAABM3NDQzMjQ3ODg1MTQ5NDk3ODI5AAAAISG0qH2eg1zbGf5UX/Q0KrYhNVSLYkcRN6BCUnrfT+IuWgAAAAAAAAAiNjg3NDc0NzAzYTJmMmY3NTcyNmM1MzY4NjE3MjY1NjQzMAAAACEhd0lX8cG+G2Z/ahGtpO6E7u0WR4SL6YufpjzuQNo8UJMAAAABMAAAAAAAAAAhIYlGGn3mJPvx3y/Jb3eRHiCJr/OAH/qNAs5ZWOVypuSqAAAAISFQU2tkEKlb2PuK/sLA8y2CthTys78fG7IZYPOe5A3aiAAAAAEwAAAAATAAAAABMAAAAAEwAAAAATAAAAABMAAAAAAAAAATNzQ0MzI0Nzg4NTE0OTQ5NzgyOQAAAAAAAAAAAAAAAAAAAAEwAAAAATAAAAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAoxNTY2MzczODA3AAAAATAAAAAKMTU2NjM3MzgwNwAAAAoxNTY2MzczODA3AAAAAAAAAAEwAAAAATAAAAAARU5ETQAAAAJPSw== -------------------------------------------------------------------------------- /test/unit/data/id-name0.txt: -------------------------------------------------------------------------------- 1 | 1037006047237486614 2 | -------------------------------------------------------------------------------- /test/unit/data/id-name1.txt: -------------------------------------------------------------------------------- 1 | 8282477981358108221 2 | -------------------------------------------------------------------------------- /test/unit/data/id-name2.txt: -------------------------------------------------------------------------------- 1 | 1946675219085482871 2 | -------------------------------------------------------------------------------- /test/unit/data/id-name3.txt: -------------------------------------------------------------------------------- 1 | 7042783757535327371 2 | -------------------------------------------------------------------------------- /test/unit/data/id-nameecb.txt: -------------------------------------------------------------------------------- 1 | 4415606717350641257 2 | -------------------------------------------------------------------------------- /test/unit/data/id-nameshared0.txt: -------------------------------------------------------------------------------- 1 | 7443247885149497829 2 | -------------------------------------------------------------------------------- /test/unit/data/id-nameshared1.txt: -------------------------------------------------------------------------------- 1 | 4824798977331544422 2 | -------------------------------------------------------------------------------- /test/unit/data/id-nameshared2.txt: -------------------------------------------------------------------------------- 1 | 1340638684566522639 2 | -------------------------------------------------------------------------------- /test/unit/data/passwd.txt: -------------------------------------------------------------------------------- 1 | .3W[yuwL3UJ@4 2 | -------------------------------------------------------------------------------- /test/unit/data/privatekeyencrypted-1iteration.txt: -------------------------------------------------------------------------------- 1 | D757452F17DA6EE79C5A8D07D2038E031BD1EE668017A4735B3319AC84C560D6EA1D915129F7DAF601AA6BAF8119A7CF244C44E0F3A2EF43CAABF18F5F4BE53B1D99FA493497A53A6F7146AA65DAA62EF5C82ACBF55131EF49D2546D2A904EB52600DB05A840B0A031767ECD4A9CF25664908D1AFB01CFF854D7358268255374A9CBF971B97227F925CAAB8DA7949F9A9AF41B8D3CB4496054013A41363455322B9562E354D4BE63821F172ED91F42076D6F35AE877D174063F9AC6BA18FA93FCDF168C6757B1744AE614B3F17494FF1CC5229C8D0C00F140B4F64388107A3401D7F7FB768B268641E1495736927F05D22D1540B74C06156B69499B6755893F45E7AB1D6D8867A496D99F988B13F4C851A60EA1DE03CA0F8ECCBEBA95749438C3F09D809D25B9FD3B479B40F621BAEF492BC385EF186DFB72D60025FC8B2C85F20E90B4F9B12D2A9EF223C1C928EAD4B90E1A017DFDDFCE0C5D417F3D8BB7C20552F54297A6CCFF84F5AD9E9D9973F538434C65B45717506C95B27F1BADA4CFB2C66CB055EFFF7AA05C2C3E686D594D514B20DBE1DC71DCE3BC1048CAD7E10D2ECBF5B25CB85A2F34BDB3E4CE3A96F1E92129142ED9F12B0280728AEA8CE56991FDB9192F3BBCBDC845B01856580F1A5028CBEFC60E99BCCC760A91331FDCC5B898EB7B4583A3052B340FB333EEFE5B4E91A0F89FAC051236E3E1C9AE9C2F7241176B2CD1102DD6AFED71C5DFF21FF46C102AF5912C68127BE6B0483D63044E37EA742D3C6805AD54DAFDEA77F9A89691685EA2F4A7C1CB92714B1EEC7B2204DDE387FF302C882C22ED0DC9BC8E8E133E940A12CA7EC6301660F6FBB22009A774CD282D0E56AED457940B2823F934F30B09F7150AE2DB2E231C20144EE476A92FBDC65DDC47394319F7C5A0AE849AF75BD2C3CCAA1FDDEEA601D6E1E6FD66E1022F947849BAD65FA47C6E16B369C41ECA83D5B3492B575885B57BAF3F8FE4644D1806B7D32619D7F42632AB4C6DB37F9204B7C3972DD3AB232FB90C2B110F2EB087447AB9B475728D27B0B03EA220642282B614004A1A622410D846998CB531A8F718348F089BA29809A9AF907E7AD19749A34D24727434DB61CD76B14EFD1907D7E91009F12A652ECA82C4310E03994B085ACE3F36278AC8C907880BC88FBDEF7DB79604E36DFA176338680D70FA58B08543157DFCA8149861426BDA06BD1A4FBAB323A9AAA96D0F2C3CCFB80AC46BACFA8284B61CF54E76DCB052D1207E3B8BA072CA9580FDDAD470ECE9FECA68F25C8F592E755648DDAA6C5BC579229C0F5969032B6EC0B1311B188C8DC7F7E04BF45B866852C4F7608B5624CE23AE139CA073A686A023C6D0598540D4FBF7022155D0D3EB67963554AB15A13E81F4544CC77685BE8DF5AD79D24883022EAFBC32B8490D1481638012BDB5136F60180B118DC2D273A7ACCD27714EE0C84A0395CA62E947071055A3552569A39D682134BE25EC56DF38242D08BEDBF5CA86164FF464FFDFCC4F58E889CA7E6D8D5E8292ACDA0E980D4CCDCA8E188D2EA56F69A613BB1DBE926CEB00D9199DA52494F5BFAAAE4A39B4E5FE3D375724A97CBD117A1BB8F6299D5F53E46A09F01930430E4F3365F86C2CB661394B5A460356189F1324953340A47CBBC190688E8AF988E8981637CF6C9797A930C23848CB7B489DAE8FA1846059757CD080E35B4D66AED3F39DFE235DE71C66B48447B0AB4A4D125BCD45E728EDBC8F214627A7CF3256B2606F805A73DD1AA1F7C45E7E52E96400B3DF5AB4DE0D4ECE1926F8F65EEA4C6A415A42CB8FA53D05E8BB964427CB92277D0ED53528E5C415B58CAB6AEF3442F07CB46AA7EE98653FBECAA8976DEC6CFD3D67218C38255B61C0E968280534E71660CF64E2E70ED9FAF6DA98A04CD0D803F8D255E5B95E955BB00F1ED69828D319144BB702D5513202E9B9578DE1C7CCC51BEDBCFD7D44FF0F7B4C4A201088EFEC9A97CEBE2C7B7BBDD556F703F81C2E4260736B0C36B631D4EC923C6902981F705A3EC2DE3E45A3CFE72DCD0E57971550000935017F7DE15C147DE528592A46E57107231768EFCA88B4DF63B1BD2A94398F45CB0C2F1A7CF941AC04E88FA8E6A4A6A432A51C93B84E1A1AD443EF321D94DE7F2ADF4C625E2512A9409D9A54BEF1C7E9EAE6AC06D70967F2BEA665700FBD120FF8CFB3732024BD187ADCBDA67B2EACFC41111DC0D948617D05F5726DAE13CF47243C10CBF61E08F1C37891F15EA8A93100C782E0F61ED0B78C23B70332ED0B7CABEC3BF9833FE7AAAB7AFD07C4E41838D39D81775C2573453D1E5134E31F17E4A0FF647685AE0F1AE3DC797005609A76D90AB6B21DD848B4BCA9C956233427A4B7BC6FE6D5A7CB4C9DEC5CE4213F03287B117EB9337F828467777DB4AA508930E9CC98DE2576184D63BB3E6981572DFE6B9BA187B3B9E071BA8D4A2B333CB9CF66A95AEDBB8E973CEB9B59E5BF2B4F6BC5AB24124B67F1E98A1B314ABD90C15A6A57AF3F5CEB25BB45AB2566264F5B93C182FE05D02000E873A23EEE8B44813D67F8C67614C30BBA24EF7BDE04C709A077DED4AB194ED8C48C460F6CDCD0679914F16A951877F27E4822E89C57B43F80997B2B7E8BC49B0E17D43903B9634A2945A195B3A754A2955B5A067FF5AF12D59F561107F21CA4D8B651EBD924C8E08C153C20302AED74268C57B22E0FF04D34B773A8306887BC7F3CCDBC86871B9FBA6D0E42E47120D0C9A0FC3EABC9AA43B9CF1623BE60C69C3D18791B052B8E56A16E856291FA99109101E9E625888504564FF1189D36AEB0BA73C1106CACE9DBD426ED47ED98EEE7DA51D91943F0A663ECE91991BE908F8CE8C9043315C508B0865E26491CEE28B3CD4C96B8C833F929E20BA5F0535C139C7AB52DCEA256AA4AA6AFD58A5CDB871DC1B339E8B17B80485CABFC6D5F22D7A543E0F56B470AF6884E3757E430EBAA370EA94E3FAA6007F99FC2288E34B7AB73CE39020373BC3DAB249466AFB6B50BFC976FF4D22855E5F52076349CCA504EF190FAD38471846C7C245606B5FE6FF5C496E903B1EEC476E8924AF1FC8B54524C5D1F16514FA0D084731A58964D076E1E862890382D77AE4C94718B0F37A6B1625E4C36FDEA8C83604B3E8EACC206C8FB96AF98E4B4C44319312AE98C657BF70C17A381A69A0ACC0190BC91C0BB47EAC78BAF86EDC8D42562FC4D8B71902750A639C614E1D9464853008D9455274AA0526200F3C89EB6E588B66E599076FCBC7FD44364E2FDA2EB014FB25C6C6D7844000CF63E3BDA5F00C025BA3D5F67215A90BE7477942E0682F526E94022A64B636ACC7A467E6B74DDA3FBADFC5C8D2848633C5783D7F58123CEEB1A652DF5812B341B971EB169CBC68FF9614B443070FFC67C6BCD24867E25152B1947902EBD3BBC2AF535076D3A32FD45EB146EEE46730F7FB57678569342EFF65318283C23A8C6234967E1798A64689FC57628D0F0CDD5034C32841432755567F -------------------------------------------------------------------------------- /test/unit/data/privatekeyencrypted.txt: -------------------------------------------------------------------------------- 1 | 165D61021380D2A927152F9203FFF7EFA9F7CE6EE5F5D1B461A2467B2FEE86B176112713FF6C40935BF0D8038EF6A7BFF011AF40EB9CAB7491C9B221F43B9DD226469572900E245CEAEF759EFE80430989DF0A8D7731C66BD2403DA03931DD5DC7C392065D27C9532314337A967F6FA8E84B13DFF966CB242064AAC8A61E8382B09625E7771EAC00801B8199F0C8186A83983B28502B8E2659A94977FD2E148B6B96C37ABC61251F011D61DB5D334A478DCF34FB155B78AF1B2B977CC877A65A2D6AD8CDDD6CFB86586C1674CA1C2346361B1169CFEC770C632C83E8BFDCC574E86BF712BBA4B38411B159CAA298FE1F5CBA2F2DE6998A227548E1ABF14E26AB7875ABDE77AC0F7CA1EB52417D193C238F36C589F1FD4E43002CEE525EA6FB91D654D11D956E080FAE5592CA1F1A591DBE7573CF4542EEB423FF1FF6795061AA3C05297CB7F77102F00991661024A2164F817EE8CB8D7DE7E647544490C31A8E1482006CE807D054177CC1A737AFADEC6505FFB1DEAD0DF98C9064693C35CB6509C3C7D868D96BD81603EACF7352A1579402076F8F6A3FA4A700F4DE98D52FC8447498B767FD88C7B67D10C8EB5BCD59A23698BF9B987A0C99F2A6369729E5C3CA51281ECE04F5E88A54B052234B8428DEE9162BB1FE5E56DDEE641CADC3AFCD972D1C8CE66809F81CAA52CB864E4E135A4A200A795F31D54C5E6795C6E0A9B8C59BA700B971A0DCDE1C17B144DBEB16474BFFBDAB38597583B3CD584A65D2B25E9C2FF4DD196EF51FE18FEDA30E42D3014B8E016ABF3893FB28E4E5107E0F92C89C1FA911E6CB174B33891C6A9312CC4E07DDC954141C3BA5F6B2D5F621DAF5A3D20EAA3EAC0A853681689AFE4AC1721C090AB092D7ED5BAE195BC6E64525BBB5158165E745FD58CAC3FBDC05F39A66E7B5DBAAD1B7120CB4094DDC01F8ACAE2605E06A799FC3B14ECBD9C5BB8D81BB98A317E75D42B0B4A3C8D86A902983FF250F7E0D5C08C821AE2DBC12D65C65A54FEB6F8A65A6D4F5DB7330438D5F7C7A46B75BB289237450898D1CB285007F242F43AB5DAABB20FADC0DB76B0FC178E028C64E1CA48F33AE81340277C3D511321D4915BC4FBA6566D30DD4EDC1D87857C9F5DF67F8C5194A7B948048F54D3512490E049E784B995AC72E15BB6BA6BD73E91F04692A7BE4BA2632F8544B5EE25BB910E64AB42535D05FC44FA87E574E2D1BBB95C82F69584E457548D6AA358093BC5DF0A7129E209061B6593F8A2E26F90C77EC8A6D8F5DDDECC554BEAFB6BBF7BB77BD4F16130C4E2650E3F9BB9B2E2FF4900C290D656318C382C47D1F03943B2EBC442E06D6E2011719B45A7FBA52AD859C221CCAFD0826039CDE75A21FD1860101F16FFE1D2D5B057E12C12E77E2DAB7D0BBBFB2E3478B324350255416B7C9F32050161E4F54264CEC4D5D25BA056BBC76CBA99B8AA73B8260803150C9DC747EDFC7406EB3A9302BD6A2680FC9880D1B824BA2ACE777D3ECCCCA92EAB4C16F97F3D1C6D624F1EE7B671C871DBF258F8715529E4BF7D87CA8577620790AFDEF7AF4183B7132A0096C4E38391FC7725B2D31F08061CA09EEE858BB7DF4648738F262B17559AAD195661D3D6D199D614A25CF2325D31039A4419AE141125749445FE5F17616855BD00AF28C448FFE805D182D0FD188A56A7C79D2401BE66E685F75C5D248A4C983B173A27DC58DD7923B5B281D74805ED2EFA0AE8C89C51D3FA68F974729D8CE968F1E9134929A6C3E7D38D46095027D0C3667843B85F1708916A54551236E0CCE6ABFB516433D8255C6CFB3AED56F29368D86BFB5458A3534975DA111C48F65D8EF070CAC5DDE4C033484D4D67DA69224EE01BA19B9B28A8CA2422278D4F3573ED3BF6DCEE22EDFEC652DBACC090D475DD529846A568EA6685BFBA5BB88F7B6E6DC47BB88114067B38CCFC93309550062C2E25151A50AADBDBE319049341824AE6BA68AEC5C1A0619DA916E34EC9FFDEAEB725CCB9F69937C0D218AFE284156A7B533CC2410EE10ABAB424B20ACAF0B5A20810D1B3B1F0142FDC3E37036BBBA877C12DFD1D9D77140CB6AC49CB380A2D26759025C7FB53C3A23E022F571A94D0F3D0FE89E5580E9EAA24FECC8BD1E65634B256009884E917F7C458294C0404271F027ABC9E421AA5967CC2BDC4356FB75729F18DD5B3FCADAEF4E2C415381D55DBCD795D4806E3C4F529EF715AA58A8F79ADD1288CB725A9C249E1F2631117712A0385D8ECA1E0AF2DDF4D439EE69000CD8912594BFF794FB0E1FAA4A6023AD1226C538E5F842E1A3D5AEE8828C0FD64F38570ED67A780242D922712D413DBEEF430F1CCAF6A2A0021DD32A7DA7E6C08816ABEC05E60E19516B3A6360DED8146A010DF8FFE036F0AC9EFFAE0FCC8263E81C4A56748FB87A3C5C26798E1EFD716A2FAC8E85A4FF92591020E12E3D2CF6590D773FAB39CF0F51ED7B6171A2FD3082080AB1008CD6AF0C8220D8BF69527A822124F04F09C9827846424853613BE0276796264B18DF4D1E46E39334FA3D5F263D2D682893A53A1C3D72E334A456B729F115841AB5CCFBE4AF9D820385A561A0828DF207DA9598A658F0608899193C26FC20B50B6F5F12626B4BB9EAA79B4B280B8303BAF9DB2D2EDD7530F01912FED36E1E961ABE57B2A9A405CDC0EB6CCCC64F31C9FAF0A2F9E33D2C4F61DF8A2AAE48CA244E75AE52A1A6E5914D412A44D94DC416E89830F5410C2F58DC2780DDD98AA70E5AB3BA3200C3FA48721566C331C150E9F74315115F0119BA96595B3B358EC304FCB8AE536E87E843BD1C7FD23915E676F31E0CBDDF7E11BBC16D4C1D94E02135506547BE5535481FF481CF96119D1409B41B63D70AF5B1C577EE1E2CB78064D29F50F7231F550FD407682310E8468AB50B1F83B942F487B572C649E3CC7413BA559054A6C38706D57970031F2C22EEDB6DF444A50915AF939BD24F1A8E7901897E4E71080D2E0E993C3475E325C666F4A740D472AFBDF40C108CB4738FA69ED9BF81102F50E769B9CF4E8ABF4BEC50162580EDD9638D730B14056280E3BD2DCBDB789C1BDBB04B5BCA07E93A22DBBDF8EE76121056C32BF51EB2BECD4D99C2A44052EA60788917708E8E7E3AC6588998115773D3F641A1EAC8B14811725F3A584B4942C8D87466273C9789335A44ED757577CBA8FF17078091177D143E8C385894FFFCB55E7961221E386010354C8185D8416A965554C1B33BE7055E707FF60113720152B0E545E1737A30BE0F3019F02EC753A525813E0EF73FEB0671FE9FDA7B0BCB8D5934E6945C03FDC4B959700EF75C893926CF6FAEF15926E0F942E43F320A759A493F47C3F417E80E57886A54571983BD12996365175CC54A28160F7BF3D45912B2BC30641D290B5CEEEC4468D33C97DCCE400F0E881FC2A33B24B7E0990B5B74910956F206434BE678AB376E544CF7E97763328A33900A6EB18BBFF166DEB183B9C6E7A5C559ABF87BFDB152326973B5590F -------------------------------------------------------------------------------- /test/unit/data/user.txt: -------------------------------------------------------------------------------- 1 | lpass-991@mailinator.com 2 | -------------------------------------------------------------------------------- /test/unit/dumpblob/dumpblob.go: -------------------------------------------------------------------------------- 1 | // This is a helper program used by scripts/create-unit-test-data.sh. 2 | // 3 | // It expects 2 arguments: username and password. 4 | // 5 | // It outputs in JSON format the user's encrypted private key as returned by /login.php 6 | // and the base64 encoded blob as returned by /getaccts.php. 7 | 8 | package main 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "io/ioutil" 14 | "net/http" 15 | 16 | "github.com/ansd/lastpass-go/test/unit/login" 17 | ) 18 | 19 | func main() { 20 | type result struct { 21 | PrivateKeyEncrypted string `xml:"privatekeyenc,attr"` 22 | Blob string 23 | } 24 | output := &result{} 25 | 26 | client, respLogin := login.NewClient() 27 | output.PrivateKeyEncrypted = respLogin.PrivateKeyEncrypted 28 | 29 | respGetAccts, err := client.Get("https://lastpass.com/getaccts.php?b64=1&requestsrc=cli&mobile=1&hasplugin=1.3.3") 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer respGetAccts.Body.Close() 34 | if respGetAccts.StatusCode != http.StatusOK { 35 | panic("/getaccts.php " + respGetAccts.Status) 36 | } 37 | blob, err := ioutil.ReadAll(respGetAccts.Body) 38 | if err != nil { 39 | panic(err) 40 | } 41 | output.Blob = string(blob) 42 | 43 | json, err := json.Marshal(output) 44 | if err != nil { 45 | panic(err) 46 | } 47 | fmt.Printf("%s", json) 48 | } 49 | -------------------------------------------------------------------------------- /test/unit/ecb/ecb.go: -------------------------------------------------------------------------------- 1 | // This is a helper program used by scripts/create-unit-test-data.sh. 2 | // 3 | // It expects 2 arguments: username and password. 4 | // 5 | // It adds an AES 256 ECB base 64 encrypted account to LastPass. 6 | // It outputs the ID of the added account. 7 | 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "crypto/aes" 13 | "encoding/base64" 14 | "encoding/hex" 15 | "encoding/xml" 16 | "fmt" 17 | "net/http" 18 | "net/url" 19 | 20 | "github.com/ansd/lastpass-go" 21 | "github.com/ansd/lastpass-go/ecb" 22 | "github.com/ansd/lastpass-go/test/unit/login" 23 | ) 24 | 25 | func main() { 26 | client, resp := login.NewClient() 27 | key := resp.EncryptionKey 28 | 29 | acctECBBase64 := &lastpass.Account{ 30 | Name: encryptECBBase64("nameECB", key), 31 | Username: encryptECBBase64("user ECB", key), 32 | Password: encryptECBBase64("password ECB", key), 33 | Group: encryptECBBase64("groupECB", key), 34 | Notes: encryptECBBase64("notes ECB", key), 35 | URL: hex.EncodeToString([]byte("http://urlECB")), 36 | } 37 | add(client, resp.Token, acctECBBase64) 38 | fmt.Print(acctECBBase64.ID) 39 | } 40 | 41 | func add(client *http.Client, token string, acct *lastpass.Account) { 42 | type result struct { 43 | Msg string `xml:"msg,attr"` 44 | AccountID string `xml:"aid,attr"` 45 | } 46 | var response struct { 47 | Result result `xml:"result"` 48 | } 49 | res, err := client.PostForm("https://lastpass.com/show_website.php", url.Values{ 50 | "extjs": []string{"1"}, 51 | "token": []string{token}, 52 | "method": []string{"cli"}, 53 | "pwprotect": []string{"off"}, 54 | "aid": []string{"0"}, 55 | "url": []string{acct.URL}, 56 | "name": []string{acct.Name}, 57 | "grouping": []string{acct.Group}, 58 | "username": []string{acct.Username}, 59 | "password": []string{acct.Password}, 60 | "extra": []string{acct.Notes}, 61 | }) 62 | if err != nil { 63 | panic(err) 64 | } 65 | defer res.Body.Close() 66 | if res.StatusCode != http.StatusOK { 67 | panic(res.Status) 68 | } 69 | if res.Header.Get("Content-Length") == "0" { 70 | panic("Empty response") 71 | } 72 | if err = xml.NewDecoder(res.Body).Decode(&response); err != nil { 73 | panic(err) 74 | } 75 | if response.Result.Msg != "accountadded" { 76 | panic("failed to add account") 77 | } 78 | acct.ID = response.Result.AccountID 79 | } 80 | 81 | func encryptECBBase64(plaintext string, encryptionKey []byte) string { 82 | if len(plaintext) == 0 { 83 | return "" 84 | } 85 | encrypted := encryptAES256ECB(plaintext, encryptionKey) 86 | encoded := make([]byte, base64.StdEncoding.EncodedLen(len(encrypted))) 87 | base64.StdEncoding.Encode(encoded, encrypted) 88 | return string(encoded) 89 | } 90 | 91 | func encryptAES256ECB(plaintext string, encryptionKey []byte) []byte { 92 | padded := pkcs7Pad([]byte(plaintext), aes.BlockSize) 93 | 94 | block, err := aes.NewCipher(encryptionKey) 95 | if err != nil { 96 | panic(err) 97 | } 98 | enc := ecb.NewECBEncrypter(block) 99 | encrypted := make([]byte, len(padded)) 100 | enc.CryptBlocks(encrypted, padded) 101 | return encrypted 102 | } 103 | 104 | func pkcs7Pad(data []byte, blockSize int) []byte { 105 | padding := blockSize - len(data)%blockSize 106 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 107 | return append(data, padtext...) 108 | } 109 | -------------------------------------------------------------------------------- /test/unit/login/login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/xml" 7 | "flag" 8 | "fmt" 9 | "net/http" 10 | "net/http/cookiejar" 11 | "net/url" 12 | "os" 13 | "strconv" 14 | 15 | "golang.org/x/crypto/pbkdf2" 16 | ) 17 | 18 | // Response contains fields which got parsed from the response of the /login.php endpoint. 19 | type Response struct { 20 | PrivateKeyEncrypted string `xml:"privatekeyenc,attr"` 21 | Token string `xml:"token,attr"` 22 | EncryptionKey []byte 23 | } 24 | 25 | // NewClient reads the executable's username and password arguments and authenticates with the LastPass servers. 26 | // It returns an http.Client with the cookie set returned by LastPass and a Response containing parsed fields from the /login.php response. 27 | func NewClient() (*http.Client, *Response) { 28 | var passwordIterations = flag.Int("iterations", 100100, "LastPass password iterations count") 29 | flag.Parse() 30 | args := flag.Args() 31 | if len(args) != 2 { 32 | path, err := os.Executable() 33 | if err != nil { 34 | panic(err) 35 | } 36 | panic(fmt.Sprintf("Usage: %s ", path)) 37 | } 38 | user, passwd := args[0], args[1] 39 | 40 | cookieJar, err := cookiejar.New(nil) 41 | if err != nil { 42 | panic(err) 43 | } 44 | client := &http.Client{ 45 | Jar: cookieJar, 46 | } 47 | 48 | var encryptionKey []byte 49 | var loginHash string 50 | if *passwordIterations == 1 { 51 | key := sha256.Sum256([]byte(user + passwd)) 52 | encryptionKey = key[:] 53 | b := sha256.Sum256([]byte(hex.EncodeToString(encryptionKey) + passwd)) 54 | loginHash = hex.EncodeToString(b[:]) 55 | } else { 56 | encryptionKey = pbkdf2.Key([]byte(passwd), []byte(user), *passwordIterations, 32, sha256.New) 57 | loginHash = hex.EncodeToString(pbkdf2.Key(encryptionKey, []byte(passwd), 1, 32, sha256.New)) 58 | } 59 | 60 | form := url.Values{ 61 | "method": []string{"cli"}, 62 | "xml": []string{"1"}, 63 | "username": []string{user}, 64 | "hash": []string{loginHash}, 65 | "iterations": []string{strconv.Itoa(*passwordIterations)}, 66 | "includeprivatekeyenc": []string{"1"}, 67 | } 68 | rsp, err := client.PostForm("https://lastpass.com/login.php", form) 69 | if err != nil { 70 | panic(err) 71 | } 72 | defer rsp.Body.Close() 73 | if rsp.StatusCode != http.StatusOK { 74 | panic("/login.php " + rsp.Status) 75 | } 76 | 77 | parsed := &Response{EncryptionKey: encryptionKey} 78 | if err := xml.NewDecoder(rsp.Body).Decode(parsed); err != nil { 79 | panic(err) 80 | } 81 | return client, parsed 82 | } 83 | --------------------------------------------------------------------------------