mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-11-22 10:40:27 +00:00
Initial support for pinning public keys in peering strings
This commit is contained in:
parent
b4d72dc604
commit
e849b3e119
@ -1,6 +1,7 @@
|
||||
package yggdrasil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -50,6 +51,7 @@ type linkInterface struct {
|
||||
name string
|
||||
link *link
|
||||
peer *peer
|
||||
options linkOptions
|
||||
msgIO linkInterfaceMsgIO
|
||||
info linkInfo
|
||||
incoming bool
|
||||
@ -67,6 +69,10 @@ type linkInterface struct {
|
||||
unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up)
|
||||
}
|
||||
|
||||
type linkOptions struct {
|
||||
pinningInfo *url.Userinfo
|
||||
}
|
||||
|
||||
func (l *link) init(c *Core) error {
|
||||
l.core = c
|
||||
l.mutex.Lock()
|
||||
@ -92,13 +98,19 @@ func (l *link) call(uri string, sintf string) error {
|
||||
return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err)
|
||||
}
|
||||
pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||
tcpOpts := tcpOptions{}
|
||||
if u.User != nil {
|
||||
tcpOpts.pinningInfo = u.User
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "tcp":
|
||||
l.tcp.call(u.Host, nil, sintf, nil)
|
||||
l.tcp.call(u.Host, tcpOpts, sintf)
|
||||
case "socks":
|
||||
l.tcp.call(pathtokens[0], u.Host, sintf, nil)
|
||||
tcpOpts.socksProxyAddr = u.Host
|
||||
l.tcp.call(pathtokens[0], tcpOpts, sintf)
|
||||
case "tls":
|
||||
l.tcp.call(u.Host, nil, sintf, l.tcp.tls.forDialer)
|
||||
tcpOpts.upgrade = l.tcp.tls.forDialer
|
||||
l.tcp.call(u.Host, tcpOpts, sintf)
|
||||
default:
|
||||
return errors.New("unknown call scheme: " + u.Scheme)
|
||||
}
|
||||
@ -122,12 +134,13 @@ func (l *link) listen(uri string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) {
|
||||
func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*linkInterface, error) {
|
||||
// Technically anything unique would work for names, but let's pick something human readable, just for debugging
|
||||
intf := linkInterface{
|
||||
name: name,
|
||||
link: l,
|
||||
msgIO: msgIO,
|
||||
name: name,
|
||||
link: l,
|
||||
options: options,
|
||||
msgIO: msgIO,
|
||||
info: linkInfo{
|
||||
linkType: linkType,
|
||||
local: local,
|
||||
@ -181,6 +194,36 @@ func (intf *linkInterface) handler() error {
|
||||
intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer))
|
||||
return errors.New("failed to connect: wrong version")
|
||||
}
|
||||
// Check if the remote side matches the keys we expected. This is a bit of a weak
|
||||
// check - in future versions we really should check a signature or something like that.
|
||||
if pinning := intf.options.pinningInfo; pinning != nil {
|
||||
allowed := true
|
||||
keytype := pinning.Username()
|
||||
if pubkey, ok := pinning.Password(); ok {
|
||||
switch keytype {
|
||||
case "curve25519":
|
||||
boxPub, err := hex.DecodeString(pubkey)
|
||||
if err != nil || len(boxPub) != crypto.BoxPubKeyLen {
|
||||
allowed = false
|
||||
break
|
||||
}
|
||||
allowed = bytes.Compare(boxPub, meta.box[:]) == 0
|
||||
case "ed25519":
|
||||
sigPub, err := hex.DecodeString(pubkey)
|
||||
if err != nil || len(sigPub) != crypto.SigPubKeyLen {
|
||||
allowed = false
|
||||
break
|
||||
}
|
||||
allowed = bytes.Compare(sigPub, meta.sig[:]) == 0
|
||||
}
|
||||
} else {
|
||||
allowed = false
|
||||
}
|
||||
if !allowed {
|
||||
intf.link.core.log.Errorf("Failed to connect to node: %q sent key that does not match pinned %q key", intf.name, keytype)
|
||||
return fmt.Errorf("failed to connect: host does not match pinned %q key", pinning.Username())
|
||||
}
|
||||
}
|
||||
// Check if we're authorized to connect to this key / IP
|
||||
if intf.incoming && !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) {
|
||||
intf.link.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
|
||||
|
@ -57,6 +57,12 @@ type TcpUpgrade struct {
|
||||
name string
|
||||
}
|
||||
|
||||
type tcpOptions struct {
|
||||
linkOptions
|
||||
upgrade *TcpUpgrade
|
||||
socksProxyAddr string
|
||||
}
|
||||
|
||||
func (l *TcpListener) Stop() {
|
||||
defer func() { recover() }()
|
||||
close(l.stop)
|
||||
@ -221,7 +227,10 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) {
|
||||
return
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
go t.handler(sock, true, nil, l.upgrade)
|
||||
options := tcpOptions{
|
||||
upgrade: l.upgrade,
|
||||
}
|
||||
go t.handler(sock, true, options)
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,12 +248,12 @@ func (t *tcp) startCalling(saddr string) bool {
|
||||
// If the dial is successful, it launches the handler.
|
||||
// When finished, it removes the outgoing call, so reconnection attempts can be made later.
|
||||
// This all happens in a separate goroutine that it spawns.
|
||||
func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *TcpUpgrade) {
|
||||
func (t *tcp) call(saddr string, options tcpOptions, sintf string) {
|
||||
go func() {
|
||||
callname := saddr
|
||||
callproto := "TCP"
|
||||
if upgrade != nil {
|
||||
callproto = strings.ToUpper(upgrade.name)
|
||||
if options.upgrade != nil {
|
||||
callproto = strings.ToUpper(options.upgrade.name)
|
||||
}
|
||||
if sintf != "" {
|
||||
callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf)
|
||||
@ -263,12 +272,11 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp
|
||||
}()
|
||||
var conn net.Conn
|
||||
var err error
|
||||
socksaddr, issocks := options.(string)
|
||||
if issocks {
|
||||
if options.socksProxyAddr != "" {
|
||||
if sintf != "" {
|
||||
return
|
||||
}
|
||||
dialerdst, er := net.ResolveTCPAddr("tcp", socksaddr)
|
||||
dialerdst, er := net.ResolveTCPAddr("tcp", options.socksProxyAddr)
|
||||
if er != nil {
|
||||
return
|
||||
}
|
||||
@ -282,7 +290,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp
|
||||
return
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
t.handler(conn, false, saddr, nil)
|
||||
t.handler(conn, false, options)
|
||||
} else {
|
||||
dst, err := net.ResolveTCPAddr("tcp", saddr)
|
||||
if err != nil {
|
||||
@ -348,19 +356,19 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp
|
||||
return
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
t.handler(conn, false, nil, upgrade)
|
||||
t.handler(conn, false, options)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) {
|
||||
func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) {
|
||||
defer t.waitgroup.Done() // Happens after sock.close
|
||||
defer sock.Close()
|
||||
t.setExtraOptions(sock)
|
||||
var upgraded bool
|
||||
if upgrade != nil {
|
||||
if options.upgrade != nil {
|
||||
var err error
|
||||
if sock, err = upgrade.upgrade(sock); err != nil {
|
||||
if sock, err = options.upgrade.upgrade(sock); err != nil {
|
||||
t.link.core.log.Errorln("TCP handler upgrade failed:", err)
|
||||
return
|
||||
} else {
|
||||
@ -370,14 +378,14 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade
|
||||
stream := stream{}
|
||||
stream.init(sock)
|
||||
var name, proto, local, remote string
|
||||
if socksaddr, issocks := options.(string); issocks {
|
||||
name = "socks://" + sock.RemoteAddr().String() + "/" + socksaddr
|
||||
if options.socksProxyAddr != "" {
|
||||
name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksProxyAddr
|
||||
proto = "socks"
|
||||
local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
|
||||
remote, _, _ = net.SplitHostPort(socksaddr)
|
||||
remote, _, _ = net.SplitHostPort(options.socksProxyAddr)
|
||||
} else {
|
||||
if upgraded {
|
||||
proto = upgrade.name
|
||||
proto = options.upgrade.name
|
||||
name = proto + "://" + sock.RemoteAddr().String()
|
||||
} else {
|
||||
proto = "tcp"
|
||||
@ -387,7 +395,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade
|
||||
remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
|
||||
}
|
||||
force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast()
|
||||
link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force)
|
||||
link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force, options.linkOptions)
|
||||
if err != nil {
|
||||
t.link.core.log.Println(err)
|
||||
panic(err)
|
||||
|
Loading…
Reference in New Issue
Block a user