Move receipt logic to any-sync, write tests

This commit is contained in:
mcrakhman 2023-04-25 09:54:12 +02:00 committed by Mikhail Iudin
parent 0e00cf4ebb
commit 447272a1f1
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
3 changed files with 230 additions and 85 deletions

View File

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

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

View 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)
}