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