1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00

wip: secureservice with verifiers

This commit is contained in:
Sergey Cherepanov 2023-02-13 21:48:41 +03:00 committed by Mikhail Iudin
parent d91d0941f6
commit d30d79a110
No known key found for this signature in database
GPG key ID: FAAAA8BAABDFF1C0
8 changed files with 264 additions and 9 deletions

View file

@ -11,9 +11,13 @@ type contextKey uint
const (
contextKeyPeerId contextKey = iota
contextKeyIdentity
)
var ErrPeerIdNotFoundInContext = errors.New("peer id not found in context")
var (
ErrPeerIdNotFoundInContext = errors.New("peer id not found in context")
ErrIdentityNotFoundInContext = errors.New("identity not found in context")
)
// CtxPeerId first tries to get peer id under our own key, but if it is not found tries to get through DRPC key
func CtxPeerId(ctx context.Context) (string, error) {
@ -30,3 +34,16 @@ func CtxPeerId(ctx context.Context) (string, error) {
func CtxWithPeerId(ctx context.Context, peerId string) context.Context {
return context.WithValue(ctx, contextKeyPeerId, peerId)
}
// CtxIdentity returns identity from context
func CtxIdentity(ctx context.Context) ([]byte, error) {
if identity, ok := ctx.Value(contextKeyIdentity).([]byte); ok {
return identity, nil
}
return nil, ErrIdentityNotFoundInContext
}
// CtxWithIdentity sets identity in the context
func CtxWithIdentity(ctx context.Context, identity []byte) context.Context {
return context.WithValue(ctx, contextKeyIdentity, identity)
}

View file

@ -0,0 +1,72 @@
package secureservice
import (
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/net/secureservice/handshake"
"github.com/anytypeio/any-sync/net/secureservice/handshake/handshakeproto"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/libp2p/go-libp2p/core/sec"
"go.uber.org/zap"
)
func newNoVerifyChecker() handshake.CredentialChecker {
return &noVerifyChecker{cred: &handshakeproto.Credentials{Type: handshakeproto.CredentialsType_SkipVerify}}
}
type noVerifyChecker struct {
cred *handshakeproto.Credentials
}
func (n noVerifyChecker) MakeCredentials(sc sec.SecureConn) *handshakeproto.Credentials {
return n.cred
}
func (n noVerifyChecker) CheckCredential(sc sec.SecureConn, cred *handshakeproto.Credentials) (identity []byte, err error) {
return nil, nil
}
func newPeerSignVerifier(account *accountdata.AccountData) handshake.CredentialChecker {
return &peerSignVerifier{account: account}
}
type peerSignVerifier struct {
account *accountdata.AccountData
}
func (p *peerSignVerifier) MakeCredentials(sc sec.SecureConn) *handshakeproto.Credentials {
sign, err := p.account.SignKey.Sign([]byte(p.account.PeerId + sc.RemotePeer().String()))
if err != nil {
log.Warn("can't sign identity credentials", zap.Error(err))
}
msg := &handshakeproto.PayloadSignedPeerIds{
Identity: p.account.Identity,
Sign: sign,
}
payload, _ := msg.Marshal()
return &handshakeproto.Credentials{
Type: handshakeproto.CredentialsType_SignedPeerIds,
Payload: payload,
}
}
func (p *peerSignVerifier) CheckCredential(sc sec.SecureConn, cred *handshakeproto.Credentials) (identity []byte, err error) {
if cred.Type != handshakeproto.CredentialsType_SignedPeerIds {
return nil, handshake.ErrSkipVerifyNotAllowed
}
var msg = &handshakeproto.PayloadSignedPeerIds{}
if err = msg.Unmarshal(cred.Payload); err != nil {
return nil, handshake.ErrUnexpectedPayload
}
pubKey, err := signingkey.NewSigningEd25519PubKeyFromBytes(msg.Identity)
if err != nil {
return nil, handshake.ErrInvalidCredentials
}
ok, err := pubKey.Verify([]byte((sc.RemotePeer().String() + p.account.PeerId)), msg.Sign)
if err != nil {
return nil, err
}
if !ok {
return nil, handshake.ErrInvalidCredentials
}
return msg.Identity, nil
}

View file

@ -0,0 +1,77 @@
package secureservice
import (
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/net/secureservice/handshake"
"github.com/anytypeio/any-sync/testutil/accounttest"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/sec"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net"
"testing"
)
func TestPeerSignVerifier_CheckCredential(t *testing.T) {
a1 := newTestAccData(t)
a2 := newTestAccData(t)
cc1 := newPeerSignVerifier(a1)
cc2 := newPeerSignVerifier(a2)
c1 := newTestSC(a2.PeerId)
c2 := newTestSC(a1.PeerId)
cr1 := cc1.MakeCredentials(c1)
cr2 := cc2.MakeCredentials(c2)
id1, err := cc1.CheckCredential(c1, cr2)
assert.NoError(t, err)
assert.Equal(t, a2.Identity, id1)
id2, err := cc2.CheckCredential(c2, cr1)
assert.NoError(t, err)
assert.Equal(t, a1.Identity, id2)
_, err = cc1.CheckCredential(c1, cr1)
assert.EqualError(t, err, handshake.ErrInvalidCredentials.Error())
}
func newTestAccData(t *testing.T) *accountdata.AccountData {
as := accounttest.AccountTestService{}
require.NoError(t, as.Init(nil))
return as.Account()
}
func newTestSC(peerId string) sec.SecureConn {
pid, _ := peer.Decode(peerId)
return &testSc{
ID: pid,
}
}
type testSc struct {
net.Conn
peer.ID
}
func (t *testSc) LocalPeer() peer.ID {
return ""
}
func (t *testSc) LocalPrivateKey() crypto.PrivKey {
return nil
}
func (t *testSc) RemotePeer() peer.ID {
return t.ID
}
func (t *testSc) RemotePublicKey() crypto.PubKey {
return nil
}
func (t *testSc) ConnState() network.ConnectionState {
return network.ConnectionState{}
}

View file

@ -5,6 +5,9 @@ import (
commonaccount "github.com/anytypeio/any-sync/accountservice"
"github.com/anytypeio/any-sync/app"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/net/secureservice/handshake"
"github.com/anytypeio/any-sync/nodeconf"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/sec"
libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
@ -41,7 +44,13 @@ type SecureService interface {
}
type secureService struct {
key crypto.PrivKey
outboundTr *libp2ptls.Transport
account *accountdata.AccountData
key crypto.PrivKey
nodeconf nodeconf.Service
noVerifyChecker handshake.CredentialChecker
peerSignVerifier handshake.CredentialChecker
}
func (s *secureService) Init(a *app.App) (err error) {
@ -54,8 +63,12 @@ func (s *secureService) Init(a *app.App) (err error) {
return
}
log.Info("secure service init", zap.String("peerId", account.Account().PeerId))
s.noVerifyChecker = newNoVerifyChecker()
s.peerSignVerifier = newPeerSignVerifier(account.Account())
s.nodeconf = a.MustComponent(nodeconf.CName).(nodeconf.Service)
log.Info("secure service init", zap.String("peerId", account.Account().PeerId))
return nil
}
@ -72,9 +85,22 @@ func (s *secureService) BasicListener(lis net.Listener, timeoutMillis int) Conte
}
func (s *secureService) TLSConn(ctx context.Context, conn net.Conn) (sec.SecureConn, error) {
tr, err := libp2ptls.New(libp2ptls.ID, s.key, nil)
sc, err := s.outboundTr.SecureOutbound(ctx, conn, "")
if err != nil {
return nil, err
return nil, HandshakeError{err: err, remoteAddr: conn.RemoteAddr().String()}
}
return tr.SecureOutbound(ctx, conn, "")
peerId := sc.RemotePeer().String()
confTypes := s.nodeconf.GetLast().NodeTypes(peerId)
var checker handshake.CredentialChecker
if len(confTypes) > 0 {
checker = s.peerSignVerifier
} else {
checker = s.noVerifyChecker
}
// ignore identity for outgoing connection because we don't need it at this moment
_, err = handshake.OutgoingHandshake(sc, checker)
if err != nil {
return nil, HandshakeError{err: err, remoteAddr: conn.RemoteAddr().String()}
}
return sc, nil
}

View file

@ -0,0 +1,35 @@
package secureservice
import (
"context"
"github.com/anytypeio/any-sync/app"
"github.com/anytypeio/any-sync/testutil/accounttest"
"github.com/stretchr/testify/require"
"testing"
)
var ctx = context.Background()
func TestHandshake(t *testing.T) {
fx := newFixture(t)
defer fx.Finish(t)
}
func newFixture(t *testing.T) *fixture {
fx := &fixture{
secureService: New().(*secureService),
a: new(app.App),
}
fx.a.Register(&accounttest.AccountTestService{}).Register(fx.secureService)
require.NoError(t, fx.a.Start(ctx))
return fx
}
type fixture struct {
*secureService
a *app.App
}
func (fx *fixture) Finish(t *testing.T) {
require.NoError(t, fx.a.Close(ctx))
}

View file

@ -3,6 +3,7 @@ package secureservice
import (
"context"
"github.com/anytypeio/any-sync/net/peer"
"github.com/anytypeio/any-sync/net/secureservice/handshake"
"github.com/anytypeio/any-sync/net/timeoutconn"
"github.com/libp2p/go-libp2p/core/crypto"
libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
@ -22,9 +23,10 @@ type ContextListener interface {
Addr() net.Addr
}
func newTLSListener(key crypto.PrivKey, lis net.Listener, timeoutMillis int) ContextListener {
func newTLSListener(cc handshake.CredentialChecker, key crypto.PrivKey, lis net.Listener, timeoutMillis int) ContextListener {
tr, _ := libp2ptls.New(libp2ptls.ID, key, nil)
return &tlsListener{
cc: cc,
tr: tr,
Listener: lis,
timeoutMillis: timeoutMillis,
@ -35,6 +37,7 @@ type tlsListener struct {
net.Listener
tr *libp2ptls.Transport
timeoutMillis int
cc handshake.CredentialChecker
}
func (p *tlsListener) Accept(ctx context.Context) (context.Context, net.Conn, error) {
@ -54,6 +57,14 @@ func (p *tlsListener) upgradeConn(ctx context.Context, conn net.Conn) (context.C
err: err,
}
}
identity, err := handshake.IncomingHandshake(secure, p.cc)
if err != nil {
return nil, nil, HandshakeError{
remoteAddr: conn.RemoteAddr().String(),
err: err,
}
}
ctx = peer.CtxWithPeerId(ctx, secure.RemotePeer().String())
ctx = peer.CtxWithIdentity(ctx, identity)
return ctx, secure, nil
}

View file

@ -23,6 +23,8 @@ type Configuration interface {
CHash() chash.CHash
// Partition returns partition number by spaceId
Partition(spaceId string) (part int)
// NodeTypes returns list of known nodeTypes by nodeId, if node not registered in configuration will return empty list
NodeTypes(nodeId string) []NodeType
}
type configuration struct {
@ -82,6 +84,15 @@ func (c *configuration) Partition(spaceId string) (part int) {
return c.chash.GetPartition(ReplKey(spaceId))
}
func (c *configuration) NodeTypes(nodeId string) []NodeType {
for _, m := range c.allMembers {
if m.PeerId == nodeId {
return m.Types
}
}
return nil
}
func ReplKey(spaceId string) (replKey string) {
if i := strings.LastIndex(spaceId, "."); i != -1 {
return spaceId[i+1:]

View file

@ -34,15 +34,21 @@ func (s *AccountTestService) Init(a *app.App) (err error) {
return
}
peerId, err := peer.IdFromSigningPubKey(signKey.GetPublic())
peerKey, _, err := signingkey.GenerateRandomEd25519KeyPair()
if err != nil {
return err
}
peerId, err := peer.IdFromSigningPubKey(peerKey.GetPublic())
if err != nil {
return err
}
s.acc = &accountdata.AccountData{
PeerId: peerId.String(),
Identity: ident,
PeerKey: peerKey,
SignKey: signKey,
EncKey: encKey,
PeerId: peerId.String(),
}
return nil
}