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.
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
}

View file

@ -8,27 +8,25 @@ import (
// ENS validation of mutable resource owners
type ENSValidator struct {
owner common.Address
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
}

View file

@ -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 {
}