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:
parent
d91d0941f6
commit
d30d79a110
8 changed files with 264 additions and 9 deletions
|
@ -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)
|
||||
}
|
||||
|
|
72
net/secureservice/credential.go
Normal file
72
net/secureservice/credential.go
Normal 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
|
||||
}
|
77
net/secureservice/credential_test.go
Normal file
77
net/secureservice/credential_test.go
Normal 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{}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
35
net/secureservice/secureservice_test.go
Normal file
35
net/secureservice/secureservice_test.go
Normal 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))
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue