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