From 56c59d57b12cd4ca25f3cfaa6229f47014e6ed12 Mon Sep 17 00:00:00 2001 From: lash Date: Wed, 17 Jan 2018 06:04:49 +0100 Subject: [PATCH] swarm/storage: External signing, chunk data verification --- swarm/storage/resource.go | 121 +++++++++++++++++++-------------- swarm/storage/resource_ens.go | 12 ++-- swarm/storage/resource_test.go | 82 +++++++++++++++++----- 3 files changed, 138 insertions(+), 77 deletions(-) diff --git a/swarm/storage/resource.go b/swarm/storage/resource.go index 34444f92c2..c4b4e51597 100644 --- a/swarm/storage/resource.go +++ b/swarm/storage/resource.go @@ -93,7 +93,7 @@ type resource struct { // treated as a resource update chunk. type ResourceValidator interface { - isOwner(string) (bool, error) + isOwner(string, common.Address) (bool, error) nameHash(string) common.Hash } @@ -105,7 +105,6 @@ type ResourceHandler struct { hashLock sync.Mutex resourceLock sync.RWMutex hasher SwarmHash - privKey *ecdsa.PrivateKey maxChunkData int64 } @@ -127,7 +126,6 @@ func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore Cl rpcClient: rpcClient, resources: make(map[string]*resource), hasher: hasher(), - privKey: privKey, maxChunkData: DefaultBranches * int64(hasher().Size()), } @@ -187,9 +185,13 @@ func NewResource(name string, startBlock uint64, frequency uint64, nameHashFunc // Creates a new root entry for a mutable resource identified by `name` with the specified `frequency`. // // The start block of the resource update will be the actual current block height of the connected network. -func (self *ResourceHandler) NewResource(name string, frequency uint64) (*resource, error) { +func (self *ResourceHandler) NewResource(name string, frequency uint64, signature [signatureLength]byte) (*resource, error) { - ok, err := self.validator.isOwner(name) + addr, err := self.getAddressFromDataSig([]byte(name), signature) + if err != nil { + return nil, fmt.Errorf("Corrupt signature") + } + ok, err := self.validator.isOwner(name, addr) if err != nil { return nil, err } else if !ok { @@ -463,9 +465,13 @@ func parseUpdate(blob []byte) (period uint32, version uint32, ensname []byte, da // It is the caller's responsibility to make sure that this data is not stale. // // A resource update cannot span chunks, and thus has max length 4096 -func (self *ResourceHandler) Update(name string, data []byte) (Key, error) { +func (self *ResourceHandler) Update(name string, data []byte, signature [signatureLength]byte) (Key, error) { - ok, err := self.validator.isOwner(name) + addr, err := self.getAddressFromDataSig(data, signature) + if err != nil { + return nil, fmt.Errorf("Invalid data/signature: %v", err) + } + ok, err := self.validator.isOwner(name, addr) if err != nil { return nil, err } else if !ok { @@ -501,34 +507,36 @@ func (self *ResourceHandler) Update(name string, data []byte) (Key, error) { } version++ + // create the update chunk // prepend version and period to allow reverse lookups // data header length does NOT include the header length prefix bytes themselves headerlength := uint16(len(resource.nameHash) + 4 + 4) - fulldata := make([]byte, int(headerlength)+2+len(data)) - cursor := 0 - binary.LittleEndian.PutUint16(fulldata, headerlength) - cursor += 2 - - binary.LittleEndian.PutUint32(fulldata[cursor:], nextperiod) - cursor += 4 - - binary.LittleEndian.PutUint32(fulldata[cursor:], version) - cursor += 4 - - copy(fulldata[cursor:], resource.nameHash[:]) - cursor += len(resource.nameHash) - - copy(fulldata[cursor:], data) - - // create the update chunk and send it key := self.resourceHash(resource.nameHash, nextperiod, version) chunk := NewChunk(key, nil) - chunk.SData, err = self.signContent(fulldata) - if err != nil { - return nil, err - } - chunk.Size = int64(len(fulldata)) + chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data)) + + cursor := 0 + copy(chunk.SData, signature[:]) + cursor += signatureLength + + binary.LittleEndian.PutUint16(chunk.SData[cursor:], headerlength) + cursor += 2 + + binary.LittleEndian.PutUint32(chunk.SData[cursor:], nextperiod) + cursor += 4 + + binary.LittleEndian.PutUint32(chunk.SData[cursor:], version) + cursor += 4 + + copy(chunk.SData[cursor:], resource.nameHash[:]) + cursor += len(resource.nameHash) + + copy(chunk.SData[cursor:], data) + + chunk.Size = int64(len(chunk.SData)) + + // send the chunk self.Put(chunk) log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData) @@ -594,33 +602,33 @@ func (self *ResourceHandler) resourceHash(namehash common.Hash, period uint32, v return self.hasher.Sum(nil) } -func (self *ResourceHandler) signContent(data []byte) ([]byte, error) { +func (self *ResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) { + if len(chunkdata) <= signatureLength { + return common.Address{}, fmt.Errorf("zero-length data") + } + var signaturetype [signatureLength]byte + copy(signaturetype[:], chunkdata[:signatureLength]) + return self.getAddressFromDataSig(chunkdata[signatureLength:], signaturetype) +} + +func (self *ResourceHandler) getContentName(chunkdata []byte) (string, error) { + nameoffset := signatureLength + 2 + 4 + 4 + if len(chunkdata) < nameoffset { + return "", fmt.Errorf("invalid chunk data") + } + namelength := binary.LittleEndian.Uint16(chunkdata[signatureLength : signatureLength+2]) + namebytes := make([]byte, namelength) + copy(namebytes, chunkdata[nameoffset:nameoffset+int(namelength)-2-4-4]) + return string(namebytes), nil +} + +func (self *ResourceHandler) getAddressFromDataSig(data []byte, signature [signatureLength]byte) (common.Address, error) { self.hashLock.Lock() self.hasher.Reset() self.hasher.Write(data) datahash := self.hasher.Sum(nil) self.hashLock.Unlock() - - signature, err := crypto.Sign(datahash, self.privKey) - if err != nil { - return nil, err - } - datawithsign := make([]byte, len(data)+signatureLength) - copy(datawithsign[:signatureLength], signature) - copy(datawithsign[signatureLength:], data) - return datawithsign, nil -} - -func (self *ResourceHandler) getContentAccount(chunkdata []byte) (common.Address, error) { - if len(chunkdata) <= signatureLength { - return common.Address{}, fmt.Errorf("zero-length data") - } - self.hashLock.Lock() - self.hasher.Reset() - self.hasher.Write(chunkdata[signatureLength:]) - datahash := self.hasher.Sum(nil) - self.hashLock.Unlock() - pub, err := crypto.SigToPub(datahash, chunkdata[:signatureLength]) + pub, err := crypto.SigToPub(datahash, signature[:]) if err != nil { return common.Address{}, err } @@ -632,7 +640,16 @@ func (self *ResourceHandler) verifyContent(chunkdata []byte) error { if err != nil { return err } - log.Warn("ens owner lookup not implemented, verify will return true in all cases", "address", address) + name, err := self.getContentName(chunkdata) + if err != nil { + return err + } + ok, err := self.validator.isOwner(name, address) + if err != nil { + return err + } else if !ok { + return fmt.Errorf("not owner") + } return nil } diff --git a/swarm/storage/resource_ens.go b/swarm/storage/resource_ens.go index a82311b08f..8a742e2716 100644 --- a/swarm/storage/resource_ens.go +++ b/swarm/storage/resource_ens.go @@ -8,27 +8,25 @@ import ( // ENS validation of mutable resource owners type ENSValidator struct { - owner common.Address - api *ens.ENS + api *ens.ENS } -func NewENSValidator(owneraddress common.Address, contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts) (*ENSValidator, error) { +func NewENSValidator(contractaddress common.Address, backend bind.ContractBackend, transactOpts *bind.TransactOpts) (*ENSValidator, error) { var err error validator := &ENSValidator{} validator.api, err = ens.NewENS(transactOpts, contractaddress, backend) if err != nil { return nil, err } - validator.owner = owneraddress return validator, nil } -func (self *ENSValidator) isOwner(name string) (bool, error) { +func (self *ENSValidator) isOwner(name string, address common.Address) (bool, error) { owneraddr, err := self.api.Owner(self.nameHash(name)) if err != nil { return false, err } - return owneraddr == self.owner, nil + return owneraddr == address, nil } func (self *ENSValidator) nameHash(name string) common.Hash { @@ -45,7 +43,7 @@ func NewGenericValidator(hashFunc func(string) common.Hash) *GenericValidator { hashFunc: hashFunc, } } -func (self *GenericValidator) isOwner(name string) (bool, error) { +func (self *GenericValidator) isOwner(name string, address common.Address) (bool, error) { return true, nil } diff --git a/swarm/storage/resource_test.go b/swarm/storage/resource_test.go index 951899ebfa..bc1fe37634 100644 --- a/swarm/storage/resource_test.go +++ b/swarm/storage/resource_test.go @@ -141,7 +141,12 @@ func TestResourceReverseLookup(t *testing.T) { } // create a new resource - rsrc, err := rh.NewResource(domainName, resourceFrequency) + signature, err := signContent(privkey, []byte(domainName)) + if err != nil { + teardownTest(t, err) + } + + rsrc, err := rh.NewResource(domainName, resourceFrequency, signature) if err != nil { teardownTest(t, err) } @@ -149,7 +154,12 @@ func TestResourceReverseLookup(t *testing.T) { // update data fwdBlocks(int(resourceFrequency+1), backend) data := []byte("foo") - resourcekey, err := rh.Update(domainName, data) + signature, err = signContent(privkey, data) + if err != nil { + teardownTest(t, err) + } + + resourcekey, err := rh.Update(domainName, data, signature) if err != nil { teardownTest(t, err) } @@ -205,7 +215,8 @@ func TestResourceHandler(t *testing.T) { if err != nil { teardownTest(t, err) } - _, err = rh.NewResource(domainName, resourceFrequency) + signature, err := signContent(privkey, []byte(resourcevalidname)) + _, err = rh.NewResource(domainName, resourceFrequency, signature) if err != nil { teardownTest(t, err) } @@ -230,28 +241,48 @@ func TestResourceHandler(t *testing.T) { // update halfway to first period resourcekey := make(map[string]Key) fwdBlocks(int(resourceFrequency/2), backend) - resourcekey["blinky"], err = rh.Update(domainName, []byte("blinky")) + data := []byte("blinky") + signature, err = signContent(privkey, data) + if err != nil { + teardownTest(t, err) + } + resourcekey["blinky"], err = rh.Update(domainName, data, signature) if err != nil { teardownTest(t, err) } // update on first period fwdBlocks(int(resourceFrequency/2), backend) - resourcekey["pinky"], err = rh.Update(domainName, []byte("pinky")) + data = []byte("pinky") + signature, err = signContent(privkey, data) + if err != nil { + teardownTest(t, err) + } + resourcekey["pinky"], err = rh.Update(domainName, data, signature) if err != nil { teardownTest(t, err) } // update on second period fwdBlocks(int(resourceFrequency), backend) - resourcekey["inky"], err = rh.Update(domainName, []byte("inky")) + data = []byte("inky") + signature, err = signContent(privkey, data) + if err != nil { + teardownTest(t, err) + } + resourcekey["inky"], err = rh.Update(domainName, data, signature) if err != nil { teardownTest(t, err) } // update just after second period fwdBlocks(1, backend) - resourcekey["clyde"], err = rh.Update(domainName, []byte("clyde")) + data = []byte("clyde") + signature, err = signContent(privkey, data) + if err != nil { + teardownTest(t, err) + } + resourcekey["clyde"], err = rh.Update(domainName, data, signature) if err != nil { teardownTest(t, err) } @@ -350,7 +381,7 @@ func TestResourceENSOwner(t *testing.T) { t.Fatal(err) } - validator, err := NewENSValidator(addr, contractAddr, contractbackend, transactOpts) + validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts) if err != nil { t.Fatal(err) } @@ -361,28 +392,29 @@ func TestResourceENSOwner(t *testing.T) { teardownTest(t, err) } + signature, err := signContent(privkey, []byte(domainName)) + if err != nil { + teardownTest(t, err) + } // create new resource when we are owner = ok - _, err = rh.NewResource(domainName, 42) + _, err = rh.NewResource(domainName, 42, signature) if err != nil { teardownTest(t, fmt.Errorf("Create resource fail: %v", err)) } + data := []byte("foo") + signature, err = signContent(privkey, data) + // update resource when we are owner = ok - _, err = rh.Update(domainName, []byte("foo")) + _, err = rh.Update(domainName, data, signature) if err != nil { teardownTest(t, fmt.Errorf("Update resource fail: %v", err)) } // create new resource when we are NOT owner = !ok - addrtwo := crypto.PubkeyToAddress(privkeytwo.PublicKey) - validator.owner = addrtwo - - _, err = rh.NewResource(domainName, 42) - if err == nil { - teardownTest(t, fmt.Errorf("Expected resource create fail due to owner mismatch")) - } + signaturetwo, err := signContent(privkeytwo, data) // update resource when we are owner = ok - _, err = rh.Update(domainName, []byte("foo")) + _, err = rh.Update(domainName, data, signaturetwo) if err == nil { teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch")) } @@ -503,6 +535,20 @@ func setupENS(addr common.Address, transactOpts *bind.TransactOpts, sub string, return contractAddress, contractBackend, nil } +func signContent(privKey *ecdsa.PrivateKey, data []byte) ([signatureLength]byte, error) { + hasher.Reset() + hasher.Write(data) + datahash := hasher.Sum(nil) + + signature, err := crypto.Sign(datahash, privKey) + if err != nil { + return [signatureLength]byte{}, err + } + var signaturetype [signatureLength]byte + copy(signaturetype[:], signature) + return signaturetype, nil +} + type testCloudStore struct { }