swarm/storage: External signing, chunk data verification

This commit is contained in:
lash 2018-01-17 06:04:49 +01:00
parent c7ce92fe46
commit 56c59d57b1
3 changed files with 138 additions and 77 deletions

View file

@ -93,7 +93,7 @@ type resource struct {
// treated as a resource update chunk. // treated as a resource update chunk.
type ResourceValidator interface { type ResourceValidator interface {
isOwner(string) (bool, error) isOwner(string, common.Address) (bool, error)
nameHash(string) common.Hash nameHash(string) common.Hash
} }
@ -105,7 +105,6 @@ type ResourceHandler struct {
hashLock sync.Mutex hashLock sync.Mutex
resourceLock sync.RWMutex resourceLock sync.RWMutex
hasher SwarmHash hasher SwarmHash
privKey *ecdsa.PrivateKey
maxChunkData int64 maxChunkData int64
} }
@ -127,7 +126,6 @@ func NewResourceHandler(privKey *ecdsa.PrivateKey, datadir string, cloudStore Cl
rpcClient: rpcClient, rpcClient: rpcClient,
resources: make(map[string]*resource), resources: make(map[string]*resource),
hasher: hasher(), hasher: hasher(),
privKey: privKey,
maxChunkData: DefaultBranches * int64(hasher().Size()), 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`. // 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. // 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 { if err != nil {
return nil, err return nil, err
} else if !ok { } 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. // 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 // 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 { if err != nil {
return nil, err return nil, err
} else if !ok { } else if !ok {
@ -501,34 +507,36 @@ func (self *ResourceHandler) Update(name string, data []byte) (Key, error) {
} }
version++ version++
// create the update chunk
// prepend version and period to allow reverse lookups // prepend version and period to allow reverse lookups
// data header length does NOT include the header length prefix bytes themselves // data header length does NOT include the header length prefix bytes themselves
headerlength := uint16(len(resource.nameHash) + 4 + 4) 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) key := self.resourceHash(resource.nameHash, nextperiod, version)
chunk := NewChunk(key, nil) chunk := NewChunk(key, nil)
chunk.SData, err = self.signContent(fulldata) chunk.SData = make([]byte, signatureLength+int(headerlength)+2+len(data))
if err != nil {
return nil, err cursor := 0
} copy(chunk.SData, signature[:])
chunk.Size = int64(len(fulldata)) 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) self.Put(chunk)
log.Trace("resource update", "name", resource.name, "key", key, "currentblock", currentblock, "lastperiod", nextperiod, "version", version, "data", chunk.SData) 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) 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.hashLock.Lock()
self.hasher.Reset() self.hasher.Reset()
self.hasher.Write(data) self.hasher.Write(data)
datahash := self.hasher.Sum(nil) datahash := self.hasher.Sum(nil)
self.hashLock.Unlock() self.hashLock.Unlock()
pub, err := crypto.SigToPub(datahash, signature[:])
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])
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
@ -632,7 +640,16 @@ func (self *ResourceHandler) verifyContent(chunkdata []byte) error {
if err != nil { if err != nil {
return err 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 return nil
} }

View file

@ -8,27 +8,25 @@ import (
// ENS validation of mutable resource owners // ENS validation of mutable resource owners
type ENSValidator struct { 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 var err error
validator := &ENSValidator{} validator := &ENSValidator{}
validator.api, err = ens.NewENS(transactOpts, contractaddress, backend) validator.api, err = ens.NewENS(transactOpts, contractaddress, backend)
if err != nil { if err != nil {
return nil, err return nil, err
} }
validator.owner = owneraddress
return validator, nil 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)) owneraddr, err := self.api.Owner(self.nameHash(name))
if err != nil { if err != nil {
return false, err return false, err
} }
return owneraddr == self.owner, nil return owneraddr == address, nil
} }
func (self *ENSValidator) nameHash(name string) common.Hash { func (self *ENSValidator) nameHash(name string) common.Hash {
@ -45,7 +43,7 @@ func NewGenericValidator(hashFunc func(string) common.Hash) *GenericValidator {
hashFunc: hashFunc, hashFunc: hashFunc,
} }
} }
func (self *GenericValidator) isOwner(name string) (bool, error) { func (self *GenericValidator) isOwner(name string, address common.Address) (bool, error) {
return true, nil return true, nil
} }

View file

@ -141,7 +141,12 @@ func TestResourceReverseLookup(t *testing.T) {
} }
// create a new resource // 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 { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
@ -149,7 +154,12 @@ func TestResourceReverseLookup(t *testing.T) {
// update data // update data
fwdBlocks(int(resourceFrequency+1), backend) fwdBlocks(int(resourceFrequency+1), backend)
data := []byte("foo") 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 { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
@ -205,7 +215,8 @@ func TestResourceHandler(t *testing.T) {
if err != nil { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
_, err = rh.NewResource(domainName, resourceFrequency) signature, err := signContent(privkey, []byte(resourcevalidname))
_, err = rh.NewResource(domainName, resourceFrequency, signature)
if err != nil { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
@ -230,28 +241,48 @@ func TestResourceHandler(t *testing.T) {
// update halfway to first period // update halfway to first period
resourcekey := make(map[string]Key) resourcekey := make(map[string]Key)
fwdBlocks(int(resourceFrequency/2), backend) 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 { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
// update on first period // update on first period
fwdBlocks(int(resourceFrequency/2), backend) 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 { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
// update on second period // update on second period
fwdBlocks(int(resourceFrequency), backend) 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 { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
// update just after second period // update just after second period
fwdBlocks(1, backend) 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 { if err != nil {
teardownTest(t, err) teardownTest(t, err)
} }
@ -350,7 +381,7 @@ func TestResourceENSOwner(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
validator, err := NewENSValidator(addr, contractAddr, contractbackend, transactOpts) validator, err := NewENSValidator(contractAddr, contractbackend, transactOpts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -361,28 +392,29 @@ func TestResourceENSOwner(t *testing.T) {
teardownTest(t, err) teardownTest(t, err)
} }
signature, err := signContent(privkey, []byte(domainName))
if err != nil {
teardownTest(t, err)
}
// create new resource when we are owner = ok // create new resource when we are owner = ok
_, err = rh.NewResource(domainName, 42) _, err = rh.NewResource(domainName, 42, signature)
if err != nil { if err != nil {
teardownTest(t, fmt.Errorf("Create resource fail: %v", err)) teardownTest(t, fmt.Errorf("Create resource fail: %v", err))
} }
data := []byte("foo")
signature, err = signContent(privkey, data)
// update resource when we are owner = ok // update resource when we are owner = ok
_, err = rh.Update(domainName, []byte("foo")) _, err = rh.Update(domainName, data, signature)
if err != nil { if err != nil {
teardownTest(t, fmt.Errorf("Update resource fail: %v", err)) teardownTest(t, fmt.Errorf("Update resource fail: %v", err))
} }
// create new resource when we are NOT owner = !ok // create new resource when we are NOT owner = !ok
addrtwo := crypto.PubkeyToAddress(privkeytwo.PublicKey) signaturetwo, err := signContent(privkeytwo, data)
validator.owner = addrtwo
_, err = rh.NewResource(domainName, 42)
if err == nil {
teardownTest(t, fmt.Errorf("Expected resource create fail due to owner mismatch"))
}
// update resource when we are owner = ok // update resource when we are owner = ok
_, err = rh.Update(domainName, []byte("foo")) _, err = rh.Update(domainName, data, signaturetwo)
if err == nil { if err == nil {
teardownTest(t, fmt.Errorf("Expected resource update fail due to owner mismatch")) 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 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 { type testCloudStore struct {
} }