github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/cmd/swarm/access_test.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:33</date> 10 //</624450070401519616> 11 12 13 package main 14 15 import ( 16 "bytes" 17 "crypto/rand" 18 "encoding/hex" 19 "encoding/json" 20 "io" 21 "io/ioutil" 22 gorand "math/rand" 23 "net/http" 24 "os" 25 "runtime" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/ethereum/go-ethereum/crypto" 31 "github.com/ethereum/go-ethereum/crypto/ecies" 32 "github.com/ethereum/go-ethereum/log" 33 "github.com/ethereum/go-ethereum/swarm/api" 34 swarmapi "github.com/ethereum/go-ethereum/swarm/api/client" 35 "github.com/ethereum/go-ethereum/swarm/testutil" 36 "golang.org/x/crypto/sha3" 37 ) 38 39 const ( 40 hashRegexp = `[a-f\d]{128}` 41 data = "notsorandomdata" 42 ) 43 44 var DefaultCurve = crypto.S256() 45 46 func TestACT(t *testing.T) { 47 if runtime.GOOS == "windows" { 48 t.Skip() 49 } 50 51 initCluster(t) 52 53 cases := []struct { 54 name string 55 f func(t *testing.T) 56 }{ 57 {"Password", testPassword}, 58 {"PK", testPK}, 59 {"ACTWithoutBogus", testACTWithoutBogus}, 60 {"ACTWithBogus", testACTWithBogus}, 61 } 62 63 for _, tc := range cases { 64 t.Run(tc.name, tc.f) 65 } 66 } 67 68 // 69 // 70 // 71 // 72 // 73 func testPassword(t *testing.T) { 74 dataFilename := testutil.TempFileWithContent(t, data) 75 defer os.RemoveAll(dataFilename) 76 77 //用“swarm up”上传文件,并期望得到一个哈希值 78 up := runSwarm(t, 79 "--bzzapi", 80 cluster.Nodes[0].URL, 81 "up", 82 "--encrypt", 83 dataFilename) 84 _, matches := up.ExpectRegexp(hashRegexp) 85 up.ExpectExit() 86 87 if len(matches) < 1 { 88 t.Fatal("no matches found") 89 } 90 91 ref := matches[0] 92 tmp, err := ioutil.TempDir("", "swarm-test") 93 if err != nil { 94 t.Fatal(err) 95 } 96 defer os.RemoveAll(tmp) 97 password := "smth" 98 passwordFilename := testutil.TempFileWithContent(t, "smth") 99 defer os.RemoveAll(passwordFilename) 100 101 up = runSwarm(t, 102 "access", 103 "new", 104 "pass", 105 "--dry-run", 106 "--password", 107 passwordFilename, 108 ref, 109 ) 110 111 _, matches = up.ExpectRegexp(".+") 112 up.ExpectExit() 113 114 if len(matches) == 0 { 115 t.Fatalf("stdout not matched") 116 } 117 118 var m api.Manifest 119 120 err = json.Unmarshal([]byte(matches[0]), &m) 121 if err != nil { 122 t.Fatalf("unmarshal manifest: %v", err) 123 } 124 125 if len(m.Entries) != 1 { 126 t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) 127 } 128 129 e := m.Entries[0] 130 131 ct := "application/bzz-manifest+json" 132 if e.ContentType != ct { 133 t.Errorf("expected %q content type, got %q", ct, e.ContentType) 134 } 135 136 if e.Access == nil { 137 t.Fatal("manifest access is nil") 138 } 139 140 a := e.Access 141 142 if a.Type != "pass" { 143 t.Errorf(`got access type %q, expected "pass"`, a.Type) 144 } 145 if len(a.Salt) < 32 { 146 t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) 147 } 148 if a.KdfParams == nil { 149 t.Fatal("manifest access kdf params is nil") 150 } 151 if a.Publisher != "" { 152 t.Fatal("should be empty") 153 } 154 155 client := swarmapi.NewClient(cluster.Nodes[0].URL) 156 157 hash, err := client.UploadManifest(&m, false) 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash 163 164 httpClient := &http.Client{} 165 response, err := httpClient.Get(url) 166 if err != nil { 167 t.Fatal(err) 168 } 169 if response.StatusCode != http.StatusUnauthorized { 170 t.Fatal("should be a 401") 171 } 172 authHeader := response.Header.Get("WWW-Authenticate") 173 if authHeader == "" { 174 t.Fatal("should be something here") 175 } 176 177 req, err := http.NewRequest(http.MethodGet, url, nil) 178 if err != nil { 179 t.Fatal(err) 180 } 181 req.SetBasicAuth("", password) 182 183 response, err = http.DefaultClient.Do(req) 184 if err != nil { 185 t.Fatal(err) 186 } 187 defer response.Body.Close() 188 189 if response.StatusCode != http.StatusOK { 190 t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode) 191 } 192 d, err := ioutil.ReadAll(response.Body) 193 if err != nil { 194 t.Fatal(err) 195 } 196 if string(d) != data { 197 t.Errorf("expected decrypted data %q, got %q", data, string(d)) 198 } 199 200 wrongPasswordFilename := testutil.TempFileWithContent(t, "just wr0ng") 201 defer os.RemoveAll(wrongPasswordFilename) 202 203 //下载带有错误密码的“swarm down”文件 204 up = runSwarm(t, 205 "--bzzapi", 206 cluster.Nodes[0].URL, 207 "down", 208 "bzz:/"+hash, 209 tmp, 210 "--password", 211 wrongPasswordFilename) 212 213 _, matches = up.ExpectRegexp("unauthorized") 214 if len(matches) != 1 && matches[0] != "unauthorized" { 215 t.Fatal(`"unauthorized" not found in output"`) 216 } 217 up.ExpectExit() 218 } 219 220 //testpk测试在双方(发布者和被授予者)之间正确创建行为清单。 221 //测试创建伪内容,将其加密上载,然后使用访问项创建包装清单。 222 //参与方-节点(发布者),上载到第二个节点(也是被授予者),然后消失。 223 // 224 // 225 func testPK(t *testing.T) { 226 dataFilename := testutil.TempFileWithContent(t, data) 227 defer os.RemoveAll(dataFilename) 228 229 //用“swarm up”上传文件,并期望得到一个哈希值 230 up := runSwarm(t, 231 "--bzzapi", 232 cluster.Nodes[0].URL, 233 "up", 234 "--encrypt", 235 dataFilename) 236 _, matches := up.ExpectRegexp(hashRegexp) 237 up.ExpectExit() 238 239 if len(matches) < 1 { 240 t.Fatal("no matches found") 241 } 242 243 ref := matches[0] 244 pk := cluster.Nodes[0].PrivateKey 245 granteePubKey := crypto.CompressPubkey(&pk.PublicKey) 246 247 publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") 248 if err != nil { 249 t.Fatal(err) 250 } 251 252 passwordFilename := testutil.TempFileWithContent(t, testPassphrase) 253 defer os.RemoveAll(passwordFilename) 254 255 _, publisherAccount := getTestAccount(t, publisherDir) 256 up = runSwarm(t, 257 "--bzzaccount", 258 publisherAccount.Address.String(), 259 "--password", 260 passwordFilename, 261 "--datadir", 262 publisherDir, 263 "--bzzapi", 264 cluster.Nodes[0].URL, 265 "access", 266 "new", 267 "pk", 268 "--dry-run", 269 "--grant-key", 270 hex.EncodeToString(granteePubKey), 271 ref, 272 ) 273 274 _, matches = up.ExpectRegexp(".+") 275 up.ExpectExit() 276 277 if len(matches) == 0 { 278 t.Fatalf("stdout not matched") 279 } 280 281 // 282 publicKeyFromDataDir := runSwarm(t, 283 "--bzzaccount", 284 publisherAccount.Address.String(), 285 "--password", 286 passwordFilename, 287 "--datadir", 288 publisherDir, 289 "print-keys", 290 "--compressed", 291 ) 292 _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+") 293 publicKeyFromDataDir.ExpectExit() 294 pkComp := strings.Split(publicKeyString[0], "=")[1] 295 var m api.Manifest 296 297 err = json.Unmarshal([]byte(matches[0]), &m) 298 if err != nil { 299 t.Fatalf("unmarshal manifest: %v", err) 300 } 301 302 if len(m.Entries) != 1 { 303 t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) 304 } 305 306 e := m.Entries[0] 307 308 ct := "application/bzz-manifest+json" 309 if e.ContentType != ct { 310 t.Errorf("expected %q content type, got %q", ct, e.ContentType) 311 } 312 313 if e.Access == nil { 314 t.Fatal("manifest access is nil") 315 } 316 317 a := e.Access 318 319 if a.Type != "pk" { 320 t.Errorf(`got access type %q, expected "pk"`, a.Type) 321 } 322 if len(a.Salt) < 32 { 323 t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) 324 } 325 if a.KdfParams != nil { 326 t.Fatal("manifest access kdf params should be nil") 327 } 328 if a.Publisher != pkComp { 329 t.Fatal("publisher key did not match") 330 } 331 client := swarmapi.NewClient(cluster.Nodes[0].URL) 332 333 hash, err := client.UploadManifest(&m, false) 334 if err != nil { 335 t.Fatal(err) 336 } 337 338 httpClient := &http.Client{} 339 340 url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash 341 response, err := httpClient.Get(url) 342 if err != nil { 343 t.Fatal(err) 344 } 345 if response.StatusCode != http.StatusOK { 346 t.Fatal("should be a 200") 347 } 348 d, err := ioutil.ReadAll(response.Body) 349 if err != nil { 350 t.Fatal(err) 351 } 352 if string(d) != data { 353 t.Errorf("expected decrypted data %q, got %q", data, string(d)) 354 } 355 } 356 357 // 358 func testACTWithoutBogus(t *testing.T) { 359 testACT(t, 0) 360 } 361 362 // 363 func testACTWithBogus(t *testing.T) { 364 testACT(t, 100) 365 } 366 367 //测试测试E2E的创建、上传和下载,带有EC密钥和密码保护的ACT访问控制 368 //测试触发一个3节点的集群,然后随机选择2个节点,这些节点将充当数据的被授予者。 369 //设置并使用密码保护行为。第三个节点应该无法对引用进行解码,因为它不会被授予访问权限。 370 // 371 // 372 func testACT(t *testing.T, bogusEntries int) { 373 var uploadThroughNode = cluster.Nodes[0] 374 client := swarmapi.NewClient(uploadThroughNode.URL) 375 376 r1 := gorand.New(gorand.NewSource(time.Now().UnixNano())) 377 nodeToSkip := r1.Intn(clusterSize) // 378 dataFilename := testutil.TempFileWithContent(t, data) 379 defer os.RemoveAll(dataFilename) 380 381 //用“swarm up”上传文件,并期望得到一个哈希值 382 up := runSwarm(t, 383 "--bzzapi", 384 cluster.Nodes[0].URL, 385 "up", 386 "--encrypt", 387 dataFilename) 388 _, matches := up.ExpectRegexp(hashRegexp) 389 up.ExpectExit() 390 391 if len(matches) < 1 { 392 t.Fatal("no matches found") 393 } 394 395 ref := matches[0] 396 grantees := []string{} 397 for i, v := range cluster.Nodes { 398 if i == nodeToSkip { 399 continue 400 } 401 pk := v.PrivateKey 402 granteePubKey := crypto.CompressPubkey(&pk.PublicKey) 403 grantees = append(grantees, hex.EncodeToString(granteePubKey)) 404 } 405 406 if bogusEntries > 0 { 407 bogusGrantees := []string{} 408 409 for i := 0; i < bogusEntries; i++ { 410 prv, err := ecies.GenerateKey(rand.Reader, DefaultCurve, nil) 411 if err != nil { 412 t.Fatal(err) 413 } 414 bogusGrantees = append(bogusGrantees, hex.EncodeToString(crypto.CompressPubkey(&prv.ExportECDSA().PublicKey))) 415 } 416 r2 := gorand.New(gorand.NewSource(time.Now().UnixNano())) 417 for i := 0; i < len(grantees); i++ { 418 insertAtIdx := r2.Intn(len(bogusGrantees)) 419 bogusGrantees = append(bogusGrantees[:insertAtIdx], append([]string{grantees[i]}, bogusGrantees[insertAtIdx:]...)...) 420 } 421 grantees = bogusGrantees 422 } 423 granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n")) 424 defer os.RemoveAll(granteesPubkeyListFile) 425 426 publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") 427 if err != nil { 428 t.Fatal(err) 429 } 430 defer os.RemoveAll(publisherDir) 431 432 passwordFilename := testutil.TempFileWithContent(t, testPassphrase) 433 defer os.RemoveAll(passwordFilename) 434 actPasswordFilename := testutil.TempFileWithContent(t, "smth") 435 defer os.RemoveAll(actPasswordFilename) 436 _, publisherAccount := getTestAccount(t, publisherDir) 437 up = runSwarm(t, 438 "--bzzaccount", 439 publisherAccount.Address.String(), 440 "--password", 441 passwordFilename, 442 "--datadir", 443 publisherDir, 444 "--bzzapi", 445 cluster.Nodes[0].URL, 446 "access", 447 "new", 448 "act", 449 "--grant-keys", 450 granteesPubkeyListFile, 451 "--password", 452 actPasswordFilename, 453 ref, 454 ) 455 456 _, matches = up.ExpectRegexp(`[a-f\d]{64}`) 457 up.ExpectExit() 458 459 if len(matches) == 0 { 460 t.Fatalf("stdout not matched") 461 } 462 463 // 464 publicKeyFromDataDir := runSwarm(t, 465 "--bzzaccount", 466 publisherAccount.Address.String(), 467 "--password", 468 passwordFilename, 469 "--datadir", 470 publisherDir, 471 "print-keys", 472 "--compressed", 473 ) 474 _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+") 475 publicKeyFromDataDir.ExpectExit() 476 pkComp := strings.Split(publicKeyString[0], "=")[1] 477 478 hash := matches[0] 479 m, _, err := client.DownloadManifest(hash) 480 if err != nil { 481 t.Fatalf("unmarshal manifest: %v", err) 482 } 483 484 if len(m.Entries) != 1 { 485 t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) 486 } 487 488 e := m.Entries[0] 489 490 ct := "application/bzz-manifest+json" 491 if e.ContentType != ct { 492 t.Errorf("expected %q content type, got %q", ct, e.ContentType) 493 } 494 495 if e.Access == nil { 496 t.Fatal("manifest access is nil") 497 } 498 499 a := e.Access 500 501 if a.Type != "act" { 502 t.Fatalf(`got access type %q, expected "act"`, a.Type) 503 } 504 if len(a.Salt) < 32 { 505 t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) 506 } 507 508 if a.Publisher != pkComp { 509 t.Fatal("publisher key did not match") 510 } 511 httpClient := &http.Client{} 512 513 //除了跳过的节点之外,所有节点都应该能够解密内容 514 for i, node := range cluster.Nodes { 515 log.Debug("trying to fetch from node", "node index", i) 516 517 url := node.URL + "/" + "bzz:/" + hash 518 response, err := httpClient.Get(url) 519 if err != nil { 520 t.Fatal(err) 521 } 522 log.Debug("got response from node", "response code", response.StatusCode) 523 524 if i == nodeToSkip { 525 log.Debug("reached node to skip", "status code", response.StatusCode) 526 527 if response.StatusCode != http.StatusUnauthorized { 528 t.Fatalf("should be a 401") 529 } 530 531 // 532 passwordUrl := strings.Replace(url, "http:// 533 response, err = httpClient.Get(passwordUrl) 534 if err != nil { 535 t.Fatal(err) 536 } 537 if response.StatusCode != http.StatusOK { 538 t.Fatal("should be a 200") 539 } 540 541 //现在尝试使用错误的密码,预计401 542 passwordUrl = strings.Replace(url, "http:// 543 response, err = httpClient.Get(passwordUrl) 544 if err != nil { 545 t.Fatal(err) 546 } 547 if response.StatusCode != http.StatusUnauthorized { 548 t.Fatal("should be a 401") 549 } 550 continue 551 } 552 553 if response.StatusCode != http.StatusOK { 554 t.Fatal("should be a 200") 555 } 556 d, err := ioutil.ReadAll(response.Body) 557 if err != nil { 558 t.Fatal(err) 559 } 560 if string(d) != data { 561 t.Errorf("expected decrypted data %q, got %q", data, string(d)) 562 } 563 } 564 } 565 566 // 567 // 568 func TestKeypairSanity(t *testing.T) { 569 salt := make([]byte, 32) 570 if _, err := io.ReadFull(rand.Reader, salt); err != nil { 571 t.Fatalf("reading from crypto/rand failed: %v", err.Error()) 572 } 573 sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d" 574 575 for i, v := range []struct { 576 publisherPriv string 577 granteePub string 578 }{ 579 { 580 publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459", 581 granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a", 582 }, 583 { 584 publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d", 585 granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db", 586 }, 587 } { 588 b, _ := hex.DecodeString(v.granteePub) 589 granteePub, _ := crypto.DecompressPubkey(b) 590 publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv) 591 592 ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt) 593 if err != nil { 594 t.Fatal(err) 595 } 596 597 hasher := sha3.NewLegacyKeccak256() 598 hasher.Write(salt) 599 shared, err := hex.DecodeString(sharedSecret) 600 if err != nil { 601 t.Fatal(err) 602 } 603 hasher.Write(shared) 604 sum := hasher.Sum(nil) 605 606 if !bytes.Equal(ssKey, sum) { 607 t.Fatalf("%d: got a session key mismatch", i) 608 } 609 } 610 } 611