Move receipt logic to any-sync, write tests
This commit is contained in:
parent
0e00cf4ebb
commit
447272a1f1
@ -1,85 +0,0 @@
|
|||||||
package coordinatorclient
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anytypeio/any-sync/coordinator/coordinatorproto"
|
|
||||||
"github.com/anytypeio/any-sync/net/peer"
|
|
||||||
"github.com/anytypeio/any-sync/nodeconf"
|
|
||||||
"github.com/anytypeio/any-sync/util/crypto"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errReceiptSignatureIncorrect = errors.New("receipt signature is incorrect")
|
|
||||||
errNoSuchCoordinatorNode = errors.New("no such control node")
|
|
||||||
errReceiptSpaceIdIncorrect = errors.New("receipt space id is incorrect")
|
|
||||||
errReceiptPeerIdIncorrect = errors.New("receipt peer id is incorrect")
|
|
||||||
errReceiptAccountIncorrect = errors.New("receipt account id is incorrect")
|
|
||||||
errReceiptExpired = errors.New("receipt is expired")
|
|
||||||
)
|
|
||||||
|
|
||||||
func CheckReceipt(ctx context.Context, confService nodeconf.Service, request *spacesyncproto.SpacePushRequest) (err error) {
|
|
||||||
peerId, err := peer.CtxPeerId(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
accountIdentity, err := peer.CtxIdentity(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
credential := &coordinatorproto.SpaceReceiptWithSignature{}
|
|
||||||
err = proto.Unmarshal(request.GetCredential(), credential)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := &coordinatorproto.SpaceReceipt{}
|
|
||||||
err = proto.Unmarshal(credential.GetSpaceReceiptPayload(), payload)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if payload.GetSpaceId() != request.GetPayload().GetSpaceHeader().GetId() {
|
|
||||||
return errReceiptSpaceIdIncorrect
|
|
||||||
}
|
|
||||||
if payload.GetPeerId() != peerId {
|
|
||||||
return errReceiptPeerIdIncorrect
|
|
||||||
}
|
|
||||||
if !bytes.Equal(payload.GetAccountIdentity(), accountIdentity) {
|
|
||||||
return errReceiptAccountIncorrect
|
|
||||||
}
|
|
||||||
err = checkCoordinator(
|
|
||||||
confService,
|
|
||||||
payload.GetControlNodeIdentity(),
|
|
||||||
credential.GetSpaceReceiptPayload(),
|
|
||||||
credential.GetSignature())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if payload.GetValidUntil() < uint64(time.Now().Unix()) {
|
|
||||||
return errReceiptExpired
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCoordinator(confService nodeconf.Service, identity []byte, payload, signature []byte) (err error) {
|
|
||||||
cooordinatorKey, err := crypto.UnmarshalEd25519PublicKey(identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nodeTypes := confService.NodeTypes(cooordinatorKey.PeerId())
|
|
||||||
if len(nodeTypes) == 0 || !slices.Contains(nodeTypes, nodeconf.NodeTypeCoordinator) {
|
|
||||||
return errNoSuchCoordinatorNode
|
|
||||||
}
|
|
||||||
res, err := cooordinatorKey.Verify(payload, signature)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !res {
|
|
||||||
return errReceiptSignatureIncorrect
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
100
coordinator/coordinatorproto/receipt.go
Normal file
100
coordinator/coordinatorproto/receipt.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package coordinatorproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errReceiptSignatureIncorrect = errors.New("receipt signature is incorrect")
|
||||||
|
errNoSuchCoordinatorNode = errors.New("no such control node")
|
||||||
|
errReceiptSpaceIdIncorrect = errors.New("receipt space id is incorrect")
|
||||||
|
errReceiptPeerIdIncorrect = errors.New("receipt peer id is incorrect")
|
||||||
|
errReceiptAccountIncorrect = errors.New("receipt account is incorrect")
|
||||||
|
errReceiptExpired = errors.New("receipt is expired")
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrepareSpaceReceipt(spaceId, peerId string, validPeriod time.Duration, accountPubKey crypto.PubKey, nodeKey crypto.PrivKey) (signedReceipt *SpaceReceiptWithSignature, err error) {
|
||||||
|
marshalledAccount, err := accountPubKey.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
marshalledNode, err := nodeKey.GetPublic().Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
receipt := &SpaceReceipt{
|
||||||
|
SpaceId: spaceId,
|
||||||
|
PeerId: peerId,
|
||||||
|
AccountIdentity: marshalledAccount,
|
||||||
|
ControlNodeIdentity: marshalledNode,
|
||||||
|
ValidUntil: uint64(time.Now().Add(validPeriod).Unix()),
|
||||||
|
}
|
||||||
|
receiptData, err := receipt.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sign, err := nodeKey.Sign(receiptData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return &SpaceReceiptWithSignature{
|
||||||
|
SpaceReceiptPayload: receiptData,
|
||||||
|
Signature: sign,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckReceipt(peerId, spaceId string, accountIdentity []byte, coordinators []string, receipt *SpaceReceiptWithSignature) (err error) {
|
||||||
|
payload := &SpaceReceipt{}
|
||||||
|
err = proto.Unmarshal(receipt.GetSpaceReceiptPayload(), payload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if payload.SpaceId != spaceId {
|
||||||
|
return errReceiptSpaceIdIncorrect
|
||||||
|
}
|
||||||
|
if payload.PeerId != peerId {
|
||||||
|
return errReceiptPeerIdIncorrect
|
||||||
|
}
|
||||||
|
protoIdentity, err := crypto.UnmarshalEd25519PublicKeyProto(payload.AccountIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !bytes.Equal(protoIdentity.Storage(), accountIdentity) {
|
||||||
|
return errReceiptAccountIncorrect
|
||||||
|
}
|
||||||
|
err = checkCoordinator(
|
||||||
|
coordinators,
|
||||||
|
payload.ControlNodeIdentity,
|
||||||
|
receipt.GetSpaceReceiptPayload(),
|
||||||
|
receipt.GetSignature())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if payload.GetValidUntil() <= uint64(time.Now().Unix()) {
|
||||||
|
return errReceiptExpired
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCoordinator(coordinators []string, identity []byte, payload, signature []byte) (err error) {
|
||||||
|
coordinatorKey, err := crypto.UnmarshalEd25519PublicKeyProto(identity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !slices.Contains(coordinators, coordinatorKey.PeerId()) {
|
||||||
|
return errNoSuchCoordinatorNode
|
||||||
|
}
|
||||||
|
res, err := coordinatorKey.Verify(payload, signature)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
return errReceiptSignatureIncorrect
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
130
coordinator/coordinatorproto/receipt_test.go
Normal file
130
coordinator/coordinatorproto/receipt_test.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package coordinatorproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fixture struct {
|
||||||
|
coordinatorKey crypto.PrivKey
|
||||||
|
accountKey crypto.PubKey
|
||||||
|
accountIdentity []byte
|
||||||
|
ctx context.Context
|
||||||
|
originalReceipt *SpaceReceipt
|
||||||
|
signedReceipt *SpaceReceiptWithSignature
|
||||||
|
spaceId string
|
||||||
|
peerId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFixture(t *testing.T) *fixture {
|
||||||
|
coordinatorKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signKeyRaw, err := signKey.GetPublic().Raw()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return &fixture{
|
||||||
|
spaceId: "spaceId",
|
||||||
|
peerId: "peerId",
|
||||||
|
accountIdentity: signKeyRaw,
|
||||||
|
coordinatorKey: coordinatorKey,
|
||||||
|
accountKey: signKey.GetPublic(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fx *fixture) prepareReceipt(t *testing.T, validPeriod time.Duration) {
|
||||||
|
var err error
|
||||||
|
fx.signedReceipt, err = PrepareSpaceReceipt(fx.spaceId, fx.peerId, validPeriod, fx.accountKey, fx.coordinatorKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fx.originalReceipt = &SpaceReceipt{}
|
||||||
|
err = proto.Unmarshal(fx.signedReceipt.SpaceReceiptPayload, fx.originalReceipt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fx *fixture) updateReceipt(t *testing.T, update func(t *testing.T, receipt *SpaceReceipt)) {
|
||||||
|
update(t, fx.originalReceipt)
|
||||||
|
marshalled, err := proto.Marshal(fx.originalReceipt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
signature, err := fx.coordinatorKey.Sign(marshalled)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fx.signedReceipt = &SpaceReceiptWithSignature{
|
||||||
|
SpaceReceiptPayload: marshalled,
|
||||||
|
Signature: signature,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptValid(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
err := CheckReceipt(fx.peerId, fx.spaceId, fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptIncorrectSpaceId(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
err := CheckReceipt(fx.peerId, "otherId", fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, errReceiptSpaceIdIncorrect, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptIncorrectPeerId(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
err := CheckReceipt("otherId", fx.spaceId, fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, errReceiptPeerIdIncorrect, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptIncorrectAccountIdentity(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
err := CheckReceipt(fx.peerId, fx.spaceId, []byte("some identity"), []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, errReceiptAccountIncorrect, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptIncorrectCoordinatorKey(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
|
||||||
|
t.Run("random key payload", func(t *testing.T) {
|
||||||
|
fx.updateReceipt(t, func(t *testing.T, receipt *SpaceReceipt) {
|
||||||
|
receipt.AccountIdentity = []byte("some random stuff")
|
||||||
|
})
|
||||||
|
err := CheckReceipt(fx.peerId, fx.spaceId, fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("random incorrect key", func(t *testing.T) {
|
||||||
|
fx.updateReceipt(t, func(t *testing.T, receipt *SpaceReceipt) {
|
||||||
|
randomKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
keyBytes, err := randomKey.GetPublic().Marshall()
|
||||||
|
require.NoError(t, err)
|
||||||
|
receipt.AccountIdentity = keyBytes
|
||||||
|
})
|
||||||
|
err := CheckReceipt(fx.peerId, fx.spaceId, fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, errNoSuchCoordinatorNode, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptIncorrectSignature(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
fx.signedReceipt.Signature = []byte("random sig")
|
||||||
|
err := CheckReceipt(fx.peerId, fx.spaceId, fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, errReceiptSignatureIncorrect, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptExpired(t *testing.T) {
|
||||||
|
fx := newFixture(t)
|
||||||
|
fx.prepareReceipt(t, time.Second)
|
||||||
|
fx.updateReceipt(t, func(t *testing.T, receipt *SpaceReceipt) {
|
||||||
|
receipt.ValidUntil = uint64(time.Now().Add(-time.Second).Unix())
|
||||||
|
})
|
||||||
|
err := CheckReceipt(fx.peerId, fx.spaceId, fx.accountIdentity, []string{fx.coordinatorKey.GetPublic().PeerId()}, fx.signedReceipt)
|
||||||
|
require.Error(t, errReceiptExpired, err)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user