github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/cmd/swarm/access_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bytes" 21 "crypto/rand" 22 "encoding/hex" 23 "encoding/json" 24 "io" 25 "io/ioutil" 26 gorand "math/rand" 27 "net/http" 28 "os" 29 "runtime" 30 "strings" 31 "testing" 32 "time" 33 34 "github.com/ethereum/go-ethereum/crypto" 35 "github.com/ethereum/go-ethereum/crypto/ecies" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/ethereum/go-ethereum/swarm/api" 38 swarmapi "github.com/ethereum/go-ethereum/swarm/api/client" 39 "github.com/ethereum/go-ethereum/swarm/testutil" 40 "golang.org/x/crypto/sha3" 41 ) 42 43 const ( 44 hashRegexp = `[a-f\d]{128}` 45 data = "notsorandomdata" 46 ) 47 48 var DefaultCurve = crypto.S256() 49 50 func TestACT(t *testing.T) { 51 if runtime.GOOS == "windows" { 52 t.Skip() 53 } 54 55 cluster := newTestCluster(t, clusterSize) 56 defer cluster.Shutdown() 57 58 cases := []struct { 59 name string 60 f func(t *testing.T, cluster *testCluster) 61 }{ 62 {"Password", testPassword}, 63 {"PK", testPK}, 64 {"ACTWithoutBogus", testACTWithoutBogus}, 65 {"ACTWithBogus", testACTWithBogus}, 66 } 67 68 for _, tc := range cases { 69 t.Run(tc.name, func(t *testing.T) { 70 tc.f(t, cluster) 71 }) 72 } 73 } 74 75 // testPassword tests for the correct creation of an ACT manifest protected by a password. 76 // The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry 77 // The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded 78 // is then fetched through 2nd node. since the tested code is not key-aware - we can just 79 // fetch from the 2nd node using HTTP BasicAuth 80 func testPassword(t *testing.T, cluster *testCluster) { 81 dataFilename := testutil.TempFileWithContent(t, data) 82 defer os.RemoveAll(dataFilename) 83 84 // upload the file with 'swarm up' and expect a hash 85 up := runSwarm(t, 86 "--bzzapi", 87 cluster.Nodes[0].URL, 88 "up", 89 "--encrypt", 90 dataFilename) 91 _, matches := up.ExpectRegexp(hashRegexp) 92 up.ExpectExit() 93 94 if len(matches) < 1 { 95 t.Fatal("no matches found") 96 } 97 98 ref := matches[0] 99 tmp, err := ioutil.TempDir("", "swarm-test") 100 if err != nil { 101 t.Fatal(err) 102 } 103 defer os.RemoveAll(tmp) 104 password := "smth" 105 passwordFilename := testutil.TempFileWithContent(t, "smth") 106 defer os.RemoveAll(passwordFilename) 107 108 up = runSwarm(t, 109 "access", 110 "new", 111 "pass", 112 "--dry-run", 113 "--password", 114 passwordFilename, 115 ref, 116 ) 117 118 _, matches = up.ExpectRegexp(".+") 119 up.ExpectExit() 120 121 if len(matches) == 0 { 122 t.Fatalf("stdout not matched") 123 } 124 125 var m api.Manifest 126 127 err = json.Unmarshal([]byte(matches[0]), &m) 128 if err != nil { 129 t.Fatalf("unmarshal manifest: %v", err) 130 } 131 132 if len(m.Entries) != 1 { 133 t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) 134 } 135 136 e := m.Entries[0] 137 138 ct := "application/bzz-manifest+json" 139 if e.ContentType != ct { 140 t.Errorf("expected %q content type, got %q", ct, e.ContentType) 141 } 142 143 if e.Access == nil { 144 t.Fatal("manifest access is nil") 145 } 146 147 a := e.Access 148 149 if a.Type != "pass" { 150 t.Errorf(`got access type %q, expected "pass"`, a.Type) 151 } 152 if len(a.Salt) < 32 { 153 t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) 154 } 155 if a.KdfParams == nil { 156 t.Fatal("manifest access kdf params is nil") 157 } 158 if a.Publisher != "" { 159 t.Fatal("should be empty") 160 } 161 162 client := swarmapi.NewClient(cluster.Nodes[0].URL) 163 164 hash, err := client.UploadManifest(&m, false) 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash 170 171 httpClient := &http.Client{} 172 response, err := httpClient.Get(url) 173 if err != nil { 174 t.Fatal(err) 175 } 176 if response.StatusCode != http.StatusUnauthorized { 177 t.Fatal("should be a 401") 178 } 179 authHeader := response.Header.Get("WWW-Authenticate") 180 if authHeader == "" { 181 t.Fatal("should be something here") 182 } 183 184 req, err := http.NewRequest(http.MethodGet, url, nil) 185 if err != nil { 186 t.Fatal(err) 187 } 188 req.SetBasicAuth("", password) 189 190 response, err = http.DefaultClient.Do(req) 191 if err != nil { 192 t.Fatal(err) 193 } 194 defer response.Body.Close() 195 196 if response.StatusCode != http.StatusOK { 197 t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode) 198 } 199 d, err := ioutil.ReadAll(response.Body) 200 if err != nil { 201 t.Fatal(err) 202 } 203 if string(d) != data { 204 t.Errorf("expected decrypted data %q, got %q", data, string(d)) 205 } 206 207 wrongPasswordFilename := testutil.TempFileWithContent(t, "just wr0ng") 208 defer os.RemoveAll(wrongPasswordFilename) 209 210 //download file with 'swarm down' with wrong password 211 up = runSwarm(t, 212 "--bzzapi", 213 cluster.Nodes[0].URL, 214 "down", 215 "bzz:/"+hash, 216 tmp, 217 "--password", 218 wrongPasswordFilename) 219 220 _, matches = up.ExpectRegexp("unauthorized") 221 if len(matches) != 1 && matches[0] != "unauthorized" { 222 t.Fatal(`"unauthorized" not found in output"`) 223 } 224 up.ExpectExit() 225 } 226 227 // testPK tests for the correct creation of an ACT manifest between two parties (publisher and grantee). 228 // The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry 229 // The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears. 230 // Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware, 231 // the test will fail if the proxy's given private key is not granted on the ACT. 232 func testPK(t *testing.T, cluster *testCluster) { 233 dataFilename := testutil.TempFileWithContent(t, data) 234 defer os.RemoveAll(dataFilename) 235 236 // upload the file with 'swarm up' and expect a hash 237 up := runSwarm(t, 238 "--bzzapi", 239 cluster.Nodes[0].URL, 240 "up", 241 "--encrypt", 242 dataFilename) 243 _, matches := up.ExpectRegexp(hashRegexp) 244 up.ExpectExit() 245 246 if len(matches) < 1 { 247 t.Fatal("no matches found") 248 } 249 250 ref := matches[0] 251 pk := cluster.Nodes[0].PrivateKey 252 granteePubKey := crypto.CompressPubkey(&pk.PublicKey) 253 254 publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") 255 if err != nil { 256 t.Fatal(err) 257 } 258 259 passwordFilename := testutil.TempFileWithContent(t, testPassphrase) 260 defer os.RemoveAll(passwordFilename) 261 262 _, publisherAccount := getTestAccount(t, publisherDir) 263 up = runSwarm(t, 264 "--bzzaccount", 265 publisherAccount.Address.String(), 266 "--password", 267 passwordFilename, 268 "--datadir", 269 publisherDir, 270 "--bzzapi", 271 cluster.Nodes[0].URL, 272 "access", 273 "new", 274 "pk", 275 "--dry-run", 276 "--grant-key", 277 hex.EncodeToString(granteePubKey), 278 ref, 279 ) 280 281 _, matches = up.ExpectRegexp(".+") 282 up.ExpectExit() 283 284 if len(matches) == 0 { 285 t.Fatalf("stdout not matched") 286 } 287 288 //get the public key from the publisher directory 289 publicKeyFromDataDir := runSwarm(t, 290 "--bzzaccount", 291 publisherAccount.Address.String(), 292 "--password", 293 passwordFilename, 294 "--datadir", 295 publisherDir, 296 "print-keys", 297 "--compressed", 298 ) 299 _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+") 300 publicKeyFromDataDir.ExpectExit() 301 pkComp := strings.Split(publicKeyString[0], "=")[1] 302 var m api.Manifest 303 304 err = json.Unmarshal([]byte(matches[0]), &m) 305 if err != nil { 306 t.Fatalf("unmarshal manifest: %v", err) 307 } 308 309 if len(m.Entries) != 1 { 310 t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) 311 } 312 313 e := m.Entries[0] 314 315 ct := "application/bzz-manifest+json" 316 if e.ContentType != ct { 317 t.Errorf("expected %q content type, got %q", ct, e.ContentType) 318 } 319 320 if e.Access == nil { 321 t.Fatal("manifest access is nil") 322 } 323 324 a := e.Access 325 326 if a.Type != "pk" { 327 t.Errorf(`got access type %q, expected "pk"`, a.Type) 328 } 329 if len(a.Salt) < 32 { 330 t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) 331 } 332 if a.KdfParams != nil { 333 t.Fatal("manifest access kdf params should be nil") 334 } 335 if a.Publisher != pkComp { 336 t.Fatal("publisher key did not match") 337 } 338 client := swarmapi.NewClient(cluster.Nodes[0].URL) 339 340 hash, err := client.UploadManifest(&m, false) 341 if err != nil { 342 t.Fatal(err) 343 } 344 345 httpClient := &http.Client{} 346 347 url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash 348 response, err := httpClient.Get(url) 349 if err != nil { 350 t.Fatal(err) 351 } 352 if response.StatusCode != http.StatusOK { 353 t.Fatal("should be a 200") 354 } 355 d, err := ioutil.ReadAll(response.Body) 356 if err != nil { 357 t.Fatal(err) 358 } 359 if string(d) != data { 360 t.Errorf("expected decrypted data %q, got %q", data, string(d)) 361 } 362 } 363 364 // testACTWithoutBogus tests the creation of the ACT manifest end-to-end, without any bogus entries (i.e. default scenario = 3 nodes 1 unauthorized) 365 func testACTWithoutBogus(t *testing.T, cluster *testCluster) { 366 testACT(t, cluster, 0) 367 } 368 369 // testACTWithBogus tests the creation of the ACT manifest end-to-end, with 100 bogus entries (i.e. 100 EC keys + default scenario = 3 nodes 1 unauthorized = 103 keys in the ACT manifest) 370 func testACTWithBogus(t *testing.T, cluster *testCluster) { 371 testACT(t, cluster, 100) 372 } 373 374 // testACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection 375 // the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data 376 // set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access. 377 // the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails. 378 // the publisher uploads through one of the nodes then disappears. 379 func testACT(t *testing.T, cluster *testCluster, bogusEntries int) { 380 var uploadThroughNode = cluster.Nodes[0] 381 client := swarmapi.NewClient(uploadThroughNode.URL) 382 383 r1 := gorand.New(gorand.NewSource(time.Now().UnixNano())) 384 nodeToSkip := r1.Intn(clusterSize) // a number between 0 and 2 (node indices in `cluster`) 385 dataFilename := testutil.TempFileWithContent(t, data) 386 defer os.RemoveAll(dataFilename) 387 388 // upload the file with 'swarm up' and expect a hash 389 up := runSwarm(t, 390 "--bzzapi", 391 cluster.Nodes[0].URL, 392 "up", 393 "--encrypt", 394 dataFilename) 395 _, matches := up.ExpectRegexp(hashRegexp) 396 up.ExpectExit() 397 398 if len(matches) < 1 { 399 t.Fatal("no matches found") 400 } 401 402 ref := matches[0] 403 var grantees []string 404 for i, v := range cluster.Nodes { 405 if i == nodeToSkip { 406 continue 407 } 408 pk := v.PrivateKey 409 granteePubKey := crypto.CompressPubkey(&pk.PublicKey) 410 grantees = append(grantees, hex.EncodeToString(granteePubKey)) 411 } 412 413 if bogusEntries > 0 { 414 var bogusGrantees []string 415 416 for i := 0; i < bogusEntries; i++ { 417 prv, err := ecies.GenerateKey(rand.Reader, DefaultCurve, nil) 418 if err != nil { 419 t.Fatal(err) 420 } 421 bogusGrantees = append(bogusGrantees, hex.EncodeToString(crypto.CompressPubkey(&prv.ExportECDSA().PublicKey))) 422 } 423 r2 := gorand.New(gorand.NewSource(time.Now().UnixNano())) 424 for i := 0; i < len(grantees); i++ { 425 insertAtIdx := r2.Intn(len(bogusGrantees)) 426 bogusGrantees = append(bogusGrantees[:insertAtIdx], append([]string{grantees[i]}, bogusGrantees[insertAtIdx:]...)...) 427 } 428 grantees = bogusGrantees 429 } 430 granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n")) 431 defer os.RemoveAll(granteesPubkeyListFile) 432 433 publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") 434 if err != nil { 435 t.Fatal(err) 436 } 437 defer os.RemoveAll(publisherDir) 438 439 passwordFilename := testutil.TempFileWithContent(t, testPassphrase) 440 defer os.RemoveAll(passwordFilename) 441 actPasswordFilename := testutil.TempFileWithContent(t, "smth") 442 defer os.RemoveAll(actPasswordFilename) 443 _, publisherAccount := getTestAccount(t, publisherDir) 444 up = runSwarm(t, 445 "--bzzaccount", 446 publisherAccount.Address.String(), 447 "--password", 448 passwordFilename, 449 "--datadir", 450 publisherDir, 451 "--bzzapi", 452 cluster.Nodes[0].URL, 453 "access", 454 "new", 455 "act", 456 "--grant-keys", 457 granteesPubkeyListFile, 458 "--password", 459 actPasswordFilename, 460 ref, 461 ) 462 463 _, matches = up.ExpectRegexp(`[a-f\d]{64}`) 464 up.ExpectExit() 465 466 if len(matches) == 0 { 467 t.Fatalf("stdout not matched") 468 } 469 470 //get the public key from the publisher directory 471 publicKeyFromDataDir := runSwarm(t, 472 "--bzzaccount", 473 publisherAccount.Address.String(), 474 "--password", 475 passwordFilename, 476 "--datadir", 477 publisherDir, 478 "print-keys", 479 "--compressed", 480 ) 481 _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+") 482 publicKeyFromDataDir.ExpectExit() 483 pkComp := strings.Split(publicKeyString[0], "=")[1] 484 485 hash := matches[0] 486 m, _, err := client.DownloadManifest(hash) 487 if err != nil { 488 t.Fatalf("unmarshal manifest: %v", err) 489 } 490 491 if len(m.Entries) != 1 { 492 t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) 493 } 494 495 e := m.Entries[0] 496 497 ct := "application/bzz-manifest+json" 498 if e.ContentType != ct { 499 t.Errorf("expected %q content type, got %q", ct, e.ContentType) 500 } 501 502 if e.Access == nil { 503 t.Fatal("manifest access is nil") 504 } 505 506 a := e.Access 507 508 if a.Type != "act" { 509 t.Fatalf(`got access type %q, expected "act"`, a.Type) 510 } 511 if len(a.Salt) < 32 { 512 t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) 513 } 514 515 if a.Publisher != pkComp { 516 t.Fatal("publisher key did not match") 517 } 518 httpClient := &http.Client{} 519 520 // all nodes except the skipped node should be able to decrypt the content 521 for i, node := range cluster.Nodes { 522 log.Debug("trying to fetch from node", "node index", i) 523 524 url := node.URL + "/" + "bzz:/" + hash 525 response, err := httpClient.Get(url) 526 if err != nil { 527 t.Fatal(err) 528 } 529 log.Debug("got response from node", "response code", response.StatusCode) 530 531 if i == nodeToSkip { 532 log.Debug("reached node to skip", "status code", response.StatusCode) 533 534 if response.StatusCode != http.StatusUnauthorized { 535 t.Fatalf("should be a 401") 536 } 537 538 // try downloading using a password instead, using the unauthorized node 539 passwordUrl := strings.Replace(url, "http://", "http://:smth@", -1) 540 response, err = httpClient.Get(passwordUrl) 541 if err != nil { 542 t.Fatal(err) 543 } 544 if response.StatusCode != http.StatusOK { 545 t.Fatal("should be a 200") 546 } 547 548 // now try with the wrong password, expect 401 549 passwordUrl = strings.Replace(url, "http://", "http://:smthWrong@", -1) 550 response, err = httpClient.Get(passwordUrl) 551 if err != nil { 552 t.Fatal(err) 553 } 554 if response.StatusCode != http.StatusUnauthorized { 555 t.Fatal("should be a 401") 556 } 557 continue 558 } 559 560 if response.StatusCode != http.StatusOK { 561 t.Fatal("should be a 200") 562 } 563 d, err := ioutil.ReadAll(response.Body) 564 if err != nil { 565 t.Fatal(err) 566 } 567 if string(d) != data { 568 t.Errorf("expected decrypted data %q, got %q", data, string(d)) 569 } 570 } 571 } 572 573 // TestKeypairSanity is a sanity test for the crypto scheme for ACT. it asserts the correct shared secret according to 574 // the specs at https://github.com/ethersphere/swarm-docs/blob/eb857afda906c6e7bb90d37f3f334ccce5eef230/act.md 575 func TestKeypairSanity(t *testing.T) { 576 salt := make([]byte, 32) 577 if _, err := io.ReadFull(rand.Reader, salt); err != nil { 578 t.Fatalf("reading from crypto/rand failed: %v", err.Error()) 579 } 580 sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d" 581 582 for i, v := range []struct { 583 publisherPriv string 584 granteePub string 585 }{ 586 { 587 publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459", 588 granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a", 589 }, 590 { 591 publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d", 592 granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db", 593 }, 594 } { 595 b, _ := hex.DecodeString(v.granteePub) 596 granteePub, _ := crypto.DecompressPubkey(b) 597 publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv) 598 599 ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt) 600 if err != nil { 601 t.Fatal(err) 602 } 603 604 hasher := sha3.NewLegacyKeccak256() 605 hasher.Write(salt) 606 shared, err := hex.DecodeString(sharedSecret) 607 if err != nil { 608 t.Fatal(err) 609 } 610 hasher.Write(shared) 611 sum := hasher.Sum(nil) 612 613 if !bytes.Equal(ssKey, sum) { 614 t.Fatalf("%d: got a session key mismatch", i) 615 } 616 } 617 }