mirror of
https://github.com/yggdrasil-network/yggdrasil-go
synced 2024-11-09 23:20:26 +03:00
Merge branch 'neil/password' into future
This commit is contained in:
commit
6dc847de31
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
)
|
)
|
||||||
|
|
||||||
type linkType int
|
type linkType int
|
||||||
@ -65,6 +66,7 @@ type linkOptions struct {
|
|||||||
pinnedEd25519Keys map[keyArray]struct{}
|
pinnedEd25519Keys map[keyArray]struct{}
|
||||||
priority uint8
|
priority uint8
|
||||||
tlsSNI string
|
tlsSNI string
|
||||||
|
password []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
@ -129,6 +131,7 @@ func (e linkError) Error() string { return string(e) }
|
|||||||
const ErrLinkAlreadyConfigured = linkError("peer is already configured")
|
const ErrLinkAlreadyConfigured = linkError("peer is already configured")
|
||||||
const ErrLinkPriorityInvalid = linkError("priority value is invalid")
|
const ErrLinkPriorityInvalid = linkError("priority value is invalid")
|
||||||
const ErrLinkPinnedKeyInvalid = linkError("pinned public key is invalid")
|
const ErrLinkPinnedKeyInvalid = linkError("pinned public key is invalid")
|
||||||
|
const ErrLinkPasswordInvalid = linkError("password is invalid")
|
||||||
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
||||||
|
|
||||||
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
||||||
@ -166,6 +169,13 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
|||||||
}
|
}
|
||||||
options.priority = uint8(pi)
|
options.priority = uint8(pi)
|
||||||
}
|
}
|
||||||
|
if p := u.Query().Get("password"); p != "" {
|
||||||
|
if len(p) > blake2b.Size {
|
||||||
|
retErr = ErrLinkPasswordInvalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
options.password = []byte(p)
|
||||||
|
}
|
||||||
|
|
||||||
// If we think we're already connected to this peer, load up
|
// If we think we're already connected to this peer, load up
|
||||||
// the existing peer state. Try to kick the peer if possible,
|
// the existing peer state. Try to kick the peer if possible,
|
||||||
@ -351,6 +361,12 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
|
|||||||
}
|
}
|
||||||
options.priority = uint8(pi)
|
options.priority = uint8(pi)
|
||||||
}
|
}
|
||||||
|
if p := u.Query().Get("password"); p != "" {
|
||||||
|
if len(p) > blake2b.Size {
|
||||||
|
return nil, ErrLinkPasswordInvalid
|
||||||
|
}
|
||||||
|
options.password = []byte(p)
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
l.core.log.Printf("%s listener started on %s", strings.ToUpper(u.Scheme), listener.Addr())
|
l.core.log.Printf("%s listener started on %s", strings.ToUpper(u.Scheme), listener.Addr())
|
||||||
@ -476,7 +492,10 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
|
|||||||
meta := version_getBaseMetadata()
|
meta := version_getBaseMetadata()
|
||||||
meta.publicKey = l.core.public
|
meta.publicKey = l.core.public
|
||||||
meta.priority = options.priority
|
meta.priority = options.priority
|
||||||
metaBytes := meta.encode()
|
metaBytes, err := meta.encode(l.core.secret, options.password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate handshake: %w", err)
|
||||||
|
}
|
||||||
if err := conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
|
if err := conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
|
||||||
return fmt.Errorf("failed to set handshake deadline: %w", err)
|
return fmt.Errorf("failed to set handshake deadline: %w", err)
|
||||||
}
|
}
|
||||||
@ -489,7 +508,7 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
|
|||||||
}
|
}
|
||||||
meta = version_metadata{}
|
meta = version_metadata{}
|
||||||
base := version_getBaseMetadata()
|
base := version_getBaseMetadata()
|
||||||
if !meta.decode(conn) {
|
if !meta.decode(conn, options.password) {
|
||||||
return conn.Close()
|
return conn.Close()
|
||||||
}
|
}
|
||||||
if !meta.check() {
|
if !meta.check() {
|
||||||
|
@ -8,7 +8,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is the version-specific metadata exchanged at the start of a connection.
|
// This is the version-specific metadata exchanged at the start of a connection.
|
||||||
@ -26,6 +29,8 @@ const (
|
|||||||
ProtocolVersionMinor uint16 = 5
|
ProtocolVersionMinor uint16 = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Once a major/minor version is released, it is not safe to change any of these
|
||||||
|
// (including their ordering), it is only safe to add new ones.
|
||||||
const (
|
const (
|
||||||
metaVersionMajor uint16 = iota // uint16
|
metaVersionMajor uint16 = iota // uint16
|
||||||
metaVersionMinor // uint16
|
metaVersionMinor // uint16
|
||||||
@ -42,7 +47,7 @@ func version_getBaseMetadata() version_metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encodes version metadata into its wire format.
|
// Encodes version metadata into its wire format.
|
||||||
func (m *version_metadata) encode() []byte {
|
func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte) ([]byte, error) {
|
||||||
bs := make([]byte, 0, 64)
|
bs := make([]byte, 0, 64)
|
||||||
bs = append(bs, 'm', 'e', 't', 'a')
|
bs = append(bs, 'm', 'e', 't', 'a')
|
||||||
bs = append(bs, 0, 0) // Remaining message length
|
bs = append(bs, 0, 0) // Remaining message length
|
||||||
@ -63,12 +68,26 @@ func (m *version_metadata) encode() []byte {
|
|||||||
bs = binary.BigEndian.AppendUint16(bs, 1)
|
bs = binary.BigEndian.AppendUint16(bs, 1)
|
||||||
bs = append(bs, m.priority)
|
bs = append(bs, m.priority)
|
||||||
|
|
||||||
|
hasher, err := blake2b.New512(password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n, err := hasher.Write(m.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != ed25519.PublicKeySize {
|
||||||
|
return nil, fmt.Errorf("hash writer only wrote %d bytes", n)
|
||||||
|
}
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
bs = append(bs, ed25519.Sign(privateKey, hash)...)
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(bs[4:6], uint16(len(bs)-6))
|
binary.BigEndian.PutUint16(bs[4:6], uint16(len(bs)-6))
|
||||||
return bs
|
return bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decodes version metadata from its wire format into the struct.
|
// Decodes version metadata from its wire format into the struct.
|
||||||
func (m *version_metadata) decode(r io.Reader) bool {
|
func (m *version_metadata) decode(r io.Reader, password []byte) bool {
|
||||||
bh := [6]byte{}
|
bh := [6]byte{}
|
||||||
if _, err := io.ReadFull(r, bh[:]); err != nil {
|
if _, err := io.ReadFull(r, bh[:]); err != nil {
|
||||||
return false
|
return false
|
||||||
@ -81,6 +100,10 @@ func (m *version_metadata) decode(r io.Reader) bool {
|
|||||||
if _, err := io.ReadFull(r, bs); err != nil {
|
if _, err := io.ReadFull(r, bs); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sig := bs[len(bs)-ed25519.SignatureSize:]
|
||||||
|
bs = bs[:len(bs)-ed25519.SignatureSize]
|
||||||
|
|
||||||
for len(bs) >= 4 {
|
for len(bs) >= 4 {
|
||||||
op := binary.BigEndian.Uint16(bs[:2])
|
op := binary.BigEndian.Uint16(bs[:2])
|
||||||
oplen := binary.BigEndian.Uint16(bs[2:4])
|
oplen := binary.BigEndian.Uint16(bs[2:4])
|
||||||
@ -103,7 +126,17 @@ func (m *version_metadata) decode(r io.Reader) bool {
|
|||||||
}
|
}
|
||||||
bs = bs[oplen:]
|
bs = bs[oplen:]
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
hasher, err := blake2b.New512(password)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n, err := hasher.Write(m.publicKey)
|
||||||
|
if err != nil || n != ed25519.PublicKeySize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
return ed25519.Verify(m.publicKey, hash, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks that the "meta" bytes and the version numbers are the expected values.
|
// Checks that the "meta" bytes and the version numbers are the expected values.
|
||||||
|
@ -3,33 +3,76 @@ package core
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVersionRoundtrip(t *testing.T) {
|
func TestVersionPasswordAuth(t *testing.T) {
|
||||||
for _, test := range []*version_metadata{
|
for _, tt := range []struct {
|
||||||
{majorVer: 1},
|
password1 []byte // The password on node 1
|
||||||
{majorVer: 256},
|
password2 []byte // The password on node 2
|
||||||
{majorVer: 2, minorVer: 4},
|
allowed bool // Should the connection have been allowed?
|
||||||
{majorVer: 2, minorVer: 257},
|
}{
|
||||||
{majorVer: 258, minorVer: 259},
|
{nil, nil, true}, // Allow: No passwords (both nil)
|
||||||
{majorVer: 3, minorVer: 5, priority: 6},
|
{nil, []byte(""), true}, // Allow: No passwords (mixed nil and empty string)
|
||||||
{majorVer: 260, minorVer: 261, priority: 7},
|
{nil, []byte("foo"), false}, // Reject: One node has a password, the other doesn't
|
||||||
|
{[]byte("foo"), []byte(""), false}, // Reject: One node has a password, the other doesn't
|
||||||
|
{[]byte("foo"), []byte("foo"), true}, // Allow: Same password
|
||||||
|
{[]byte("foo"), []byte("bar"), false}, // Reject: Different passwords
|
||||||
} {
|
} {
|
||||||
// Generate a random public key for each time, since it is
|
pk1, sk1, err := ed25519.GenerateKey(nil)
|
||||||
// a required field.
|
if err != nil {
|
||||||
test.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
|
t.Fatalf("Node 1 failed to generate key: %s", err)
|
||||||
_, _ = rand.Read(test.publicKey)
|
|
||||||
|
|
||||||
encoded := bytes.NewBuffer(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)
|
metadata1 := &version_metadata{
|
||||||
|
publicKey: pk1,
|
||||||
|
}
|
||||||
|
encoded, err := metadata1.encode(sk1, tt.password1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Node 1 failed to encode metadata: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded version_metadata
|
||||||
|
if allowed := decoded.decode(bytes.NewBuffer(encoded), tt.password2); allowed != tt.allowed {
|
||||||
|
t.Fatalf("Permutation %q -> %q should have been %v but was %v", tt.password1, tt.password2, tt.allowed, allowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionRoundtrip(t *testing.T) {
|
||||||
|
for _, password := range [][]byte{
|
||||||
|
nil, []byte(""), []byte("foo"),
|
||||||
|
} {
|
||||||
|
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.
|
||||||
|
pk, sk, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
test.publicKey = pk
|
||||||
|
meta, err := test.encode(sk, password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
encoded := bytes.NewBuffer(meta)
|
||||||
|
decoded := &version_metadata{}
|
||||||
|
if !decoded.decode(encoded, password) {
|
||||||
|
t.Fatalf("failed to decode")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(test, decoded) {
|
||||||
|
t.Fatalf("round-trip failed\nwant: %+v\n got: %+v", test, decoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user