From 83c1a810b57d99e68ebe9e04955c290b6b4fac1b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 18 Mar 2023 12:14:32 +0000 Subject: [PATCH] New handshake, use `softcrdt` upstream --- go.mod | 2 +- go.sum | 4 +- src/core/api.go | 4 +- src/core/link.go | 16 +++--- src/core/version.go | 110 +++++++++++++++++++++++++++------------ src/core/version_test.go | 34 ++++++++++++ 6 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 src/core/version_test.go diff --git a/go.mod b/go.mod index ca18ef04..4b4e19f9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 + github.com/Arceliar/ironwood v0.0.0-20230318003210-65aa386cab13 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 diff --git a/go.sum b/go.sum index 2292b71b..e6e4a3ed 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 h1:eOW6/XIs06TnUn9GPCnfv71CQZw8edP3u3mH3lZt6iM= -github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20230318003210-65aa386cab13 h1:z0PVz7aDDW5c+JVEW7b00N2JMGAfV6BHtTcOJ8zHKcU= +github.com/Arceliar/ironwood v0.0.0-20230318003210-65aa386cab13/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= diff --git a/src/core/api.go b/src/core/api.go index 5accdeee..fc06b9c6 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -98,7 +98,7 @@ func (c *Core) GetDHT() []DHTEntryInfo { var info DHTEntryInfo info.Key = d.Key info.Port = d.Port - info.Rest = d.Rest + //info.Rest = d.Rest dhts = append(dhts, info) } return dhts @@ -110,7 +110,7 @@ func (c *Core) GetPaths() []PathEntryInfo { for _, p := range ps { var info PathEntryInfo info.Key = p.Key - info.Path = p.Path + //info.Path = p.Path paths = append(paths, info) } return paths diff --git a/src/core/link.go b/src/core/link.go index 933e3983..06776618 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -278,7 +278,7 @@ func (intf *link) handler(dial *linkDial) error { }) meta := version_getBaseMetadata() - meta.key = intf.links.core.public + meta.publicKey = intf.links.core.public metaBytes := meta.encode() if err := intf.conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil { return fmt.Errorf("failed to set handshake deadline: %w", err) @@ -311,8 +311,8 @@ func (intf *link) handler(dial *linkDial) error { intf.links.core.log.Debugf("%s: %s is incompatible version (local %s, remote %s)", connectError, intf.lname, - fmt.Sprintf("%d.%d", base.ver, base.minorVer), - fmt.Sprintf("%d.%d", meta.ver, meta.minorVer), + fmt.Sprintf("%d.%d", base.majorVer, base.minorVer), + fmt.Sprintf("%d.%d", meta.majorVer, meta.minorVer), ) return errors.New("remote node is incompatible version") } @@ -320,7 +320,7 @@ func (intf *link) handler(dial *linkDial) error { // check - in future versions we really should check a signature or something like that. if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { var key keyArray - copy(key[:], meta.key) + copy(key[:], meta.publicKey) if _, allowed := pinned[key]; !allowed { return fmt.Errorf("node public key that does not match pinned keys") } @@ -329,14 +329,14 @@ func (intf *link) handler(dial *linkDial) error { allowed := intf.links.core.config._allowedPublicKeys isallowed := len(allowed) == 0 for k := range allowed { - if bytes.Equal(k[:], meta.key) { + if bytes.Equal(k[:], meta.publicKey) { isallowed = true break } } if intf.incoming && !intf.force && !isallowed { _ = intf.close() - return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.key)) + return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey)) } phony.Block(intf.links, func() { @@ -347,13 +347,13 @@ func (intf *link) handler(dial *linkDial) error { if intf.incoming { dir = "inbound" } - remoteAddr := net.IP(address.AddrForKey(meta.key)[:]).String() + remoteAddr := net.IP(address.AddrForKey(meta.publicKey)[:]).String() remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote) localStr := intf.conn.LocalAddr() intf.links.core.log.Infof("Connected %s %s: %s, source %s", dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr) - err = intf.links.core.HandleConn(meta.key, intf.conn, intf.options.priority) + err = intf.links.core.HandleConn(meta.publicKey, intf.conn, intf.options.priority) switch err { case io.EOF, net.ErrClosed, nil: intf.links.core.log.Infof("Disconnected %s %s: %s, source %s", diff --git a/src/core/version.go b/src/core/version.go index 0bfbbcbc..3787d1e0 100644 --- a/src/core/version.go +++ b/src/core/version.go @@ -4,65 +4,107 @@ package core // Used in the initial connection setup and key exchange // Some of this could arguably go in wire.go instead -import "crypto/ed25519" +import ( + "bytes" + "crypto/ed25519" + "encoding/binary" +) // This is the version-specific metadata exchanged at the start of a connection. // It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number. // The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection. type version_metadata struct { - meta [4]byte - ver uint8 // 1 byte in this version - // Everything after this point potentially depends on the version number, and is subject to change in future versions - minorVer uint8 // 1 byte in this version - key ed25519.PublicKey + majorVer uint16 + minorVer uint16 + publicKey ed25519.PublicKey + priority uint8 } +const ( + ProtocolVersionMajor uint16 = 0 + ProtocolVersionMinor uint16 = 5 +) + +const ( + metaVersionMajor uint16 = iota // uint16 + metaVersionMinor // uint16 + metaPublicKey // [32]byte + metaPriority // uint8 +) + // Gets a base metadata with no keys set, but with the correct version numbers. func version_getBaseMetadata() version_metadata { return version_metadata{ - meta: [4]byte{'m', 'e', 't', 'a'}, - ver: 0, - minorVer: 4, + majorVer: ProtocolVersionMajor, + minorVer: ProtocolVersionMinor, } } -// Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection. -func version_getMetaLength() (mlen int) { - mlen += 4 // meta - mlen++ // ver, as long as it's < 127, which it is in this version - mlen++ // minorVer, as long as it's < 127, which it is in this version - mlen += ed25519.PublicKeySize // key - return -} - // Encodes version metadata into its wire format. func (m *version_metadata) encode() []byte { - bs := make([]byte, 0, version_getMetaLength()) - bs = append(bs, m.meta[:]...) - bs = append(bs, m.ver) - bs = append(bs, m.minorVer) - bs = append(bs, m.key[:]...) - if len(bs) != version_getMetaLength() { - panic("Inconsistent metadata length") - } + bs := make([]byte, 0, 64) + bs = append(bs, 'm', 'e', 't', 'a') + + bs = binary.BigEndian.AppendUint16(bs, metaVersionMajor) + bs = binary.BigEndian.AppendUint16(bs, 2) + bs = binary.BigEndian.AppendUint16(bs, m.majorVer) + + bs = binary.BigEndian.AppendUint16(bs, metaVersionMinor) + bs = binary.BigEndian.AppendUint16(bs, 2) + bs = binary.BigEndian.AppendUint16(bs, m.minorVer) + + bs = binary.BigEndian.AppendUint16(bs, metaPublicKey) + bs = binary.BigEndian.AppendUint16(bs, ed25519.PublicKeySize) + bs = append(bs, m.publicKey[:]...) + + bs = binary.BigEndian.AppendUint16(bs, metaPriority) + bs = binary.BigEndian.AppendUint16(bs, 1) + bs = append(bs, m.priority) + return bs } // Decodes version metadata from its wire format into the struct. func (m *version_metadata) decode(bs []byte) bool { - if len(bs) != version_getMetaLength() { + meta := [4]byte{'m', 'e', 't', 'a'} + if !bytes.Equal(bs[:4], meta[:]) { return false } - offset := 0 - offset += copy(m.meta[:], bs[offset:]) - m.ver, offset = bs[offset], offset+1 - m.minorVer, offset = bs[offset], offset+1 - m.key = append([]byte(nil), bs[offset:]...) + for bs = bs[4:]; len(bs) >= 4; { + op := binary.BigEndian.Uint16(bs[:2]) + oplen := binary.BigEndian.Uint16(bs[2:4]) + if bs = bs[4:]; len(bs) < int(oplen) { + break + } + switch op { + case metaVersionMajor: + m.majorVer = binary.BigEndian.Uint16(bs[:2]) + + case metaVersionMinor: + m.minorVer = binary.BigEndian.Uint16(bs[:2]) + + case metaPublicKey: + m.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize) + copy(m.publicKey, bs[:ed25519.PublicKeySize]) + + case metaPriority: + m.priority = bs[0] + } + bs = bs[oplen:] + } return true } // Checks that the "meta" bytes and the version numbers are the expected values. func (m *version_metadata) check() bool { - base := version_getBaseMetadata() - return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer + switch { + case m.majorVer != ProtocolVersionMajor: + return false + case m.minorVer != ProtocolVersionMinor: + return false + case len(m.publicKey) != ed25519.PublicKeySize: + return false + default: + return true + } } diff --git a/src/core/version_test.go b/src/core/version_test.go new file mode 100644 index 00000000..6fb7895c --- /dev/null +++ b/src/core/version_test.go @@ -0,0 +1,34 @@ +package core + +import ( + "crypto/ed25519" + "math/rand" + "reflect" + "testing" +) + +func TestVersionRoundtrip(t *testing.T) { + for _, test := range []*version_metadata{ + {majorVer: 1}, + {majorVer: 256}, + {majorVer: 2, minorVer: 4}, + {majorVer: 2, minorVer: 257}, + {majorVer: 258, minorVer: 259}, + {majorVer: 3, minorVer: 5, priority: 6}, + {majorVer: 260, minorVer: 261, priority: 7}, + } { + // Generate a random public key for each time, since it is + // a required field. + test.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize) + rand.Read(test.publicKey) + + encoded := test.encode() + decoded := &version_metadata{} + if !decoded.decode(encoded) { + t.Fatalf("failed to decode") + } + if !reflect.DeepEqual(test, decoded) { + t.Fatalf("round-trip failed\nwant: %+v\n got: %+v", test, decoded) + } + } +}