From 45b773eade3b10e45c532671550cce244b559090 Mon Sep 17 00:00:00 2001
From: Neil Alexander <neilalexander@users.noreply.github.com>
Date: Wed, 11 Oct 2023 18:25:35 +0100
Subject: [PATCH] Remove TLS root validation

This is just too complicated compared to the per-peer/per-listener/per-interface password
approach.
---
 cmd/yggdrasil/main.go    |  50 --------------------
 contrib/mobile/mobile.go |   7 +--
 src/config/config.go     | 100 +++++----------------------------------
 src/core/core.go         |  11 +----
 src/core/link_tcp.go     |   6 ---
 src/core/options.go      |   9 ----
 src/core/tls.go          |  42 +++++-----------
 7 files changed, 29 insertions(+), 196 deletions(-)

diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go
index a770e4e2..09d2f315 100644
--- a/cmd/yggdrasil/main.go
+++ b/cmd/yggdrasil/main.go
@@ -3,7 +3,6 @@ package main
 import (
 	"context"
 	"crypto/ed25519"
-	"crypto/sha1"
 	"encoding/hex"
 	"encoding/json"
 	"flag"
@@ -45,8 +44,6 @@ func main() {
 	useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
 	normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
 	exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format")
-	exportcsr := flag.Bool("exportcsr", false, "use in combination with either -useconf or -useconffile, outputs your self-signed certificate request in PEM format")
-	exportcert := flag.Bool("exportcert", false, "use in combination with either -useconf or -useconffile, outputs your self-signed certificate in PEM format")
 	confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
 	autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
 	ver := flag.Bool("version", false, "prints the version of this build")
@@ -177,30 +174,10 @@ func main() {
 		}
 		fmt.Println(string(pem))
 		return
-
-	case *exportcsr:
-		pem, err := cfg.GenerateCertificateSigningRequest()
-		if err != nil {
-			panic(err)
-		}
-		fmt.Println(string(pem))
-		return
-
-	case *exportcert:
-		pem, err := cfg.MarshalPEMCertificate()
-		if err != nil {
-			panic(err)
-		}
-		fmt.Println(string(pem))
-		return
 	}
 
 	n := &node{}
 
-	// Track certificate fingerprints for configured roots, so
-	// that we can match them using the multicast discriminator.
-	fingerprints := map[[20]byte]struct{}{}
-
 	// Setup the Yggdrasil node itself.
 	{
 		options := []core.SetupOption{
@@ -218,10 +195,6 @@ func main() {
 				options = append(options, core.Peer{URI: peer, SourceInterface: intf})
 			}
 		}
-		for _, root := range cfg.RootCertificates {
-			options = append(options, core.RootCertificate(*root))
-			fingerprints[sha1.Sum(root.Raw[:])] = struct{}{}
-		}
 		for _, allowed := range cfg.AllowedPublicKeys {
 			k, err := hex.DecodeString(allowed)
 			if err != nil {
@@ -259,29 +232,6 @@ func main() {
 				Priority: uint8(intf.Priority),
 			})
 		}
-		if len(fingerprints) > 0 {
-			var matcher multicast.DiscriminatorMatch = func(b []byte) bool {
-				// Break apart the discriminator into 20-byte chunks and
-				// see whether any of them match the configured root CA
-				// fingerprints. If any of them match, we'll return true.
-				var f [20]byte
-				for len(b) >= len(f) {
-					b = b[copy(f[:], b):]
-					if _, ok := fingerprints[f]; ok {
-						return true
-					}
-				}
-				return false
-			}
-			// Populate our own discriminator with the fingerprints of our
-			// configured root CAs.
-			var discriminator multicast.Discriminator
-			for f := range fingerprints {
-				discriminator = append(discriminator, f[:]...)
-			}
-			options = append(options, matcher)
-			options = append(options, discriminator)
-		}
 		if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
 			panic(err)
 		}
diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go
index 6ceccb30..be1f5ff7 100644
--- a/contrib/mobile/mobile.go
+++ b/contrib/mobile/mobile.go
@@ -42,8 +42,8 @@ func (m *Yggdrasil) StartAutoconfigure() error {
 // StartJSON starts a node with the given JSON config. You can get JSON config
 // (rather than HJSON) by using the GenerateConfigJSON() function
 func (m *Yggdrasil) StartJSON(configjson []byte) error {
-  setMemLimitIfPossible()
-  
+	setMemLimitIfPossible()
+
 	logger := log.New(m.log, "", 0)
 	logger.EnableLevel("error")
 	logger.EnableLevel("warn")
@@ -70,9 +70,6 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
 			}
 			options = append(options, core.AllowedPublicKey(k[:]))
 		}
-		for _, root := range m.config.RootCertificates {
-			options = append(options, core.RootCertificate(*root))
-		}
 		var err error
 		m.core, err = core.New(m.config.Certificate, logger, options...)
 		if err != nil {
diff --git a/src/config/config.go b/src/config/config.go
index fe55e827..e818f703 100644
--- a/src/config/config.go
+++ b/src/config/config.go
@@ -40,22 +40,19 @@ import (
 // options that are necessary for an Yggdrasil node to run. You will need to
 // supply one of these structs to the Yggdrasil core when starting a node.
 type NodeConfig struct {
-	PrivateKey           KeyBytes                   `comment:"Your private key. DO NOT share this with anyone!"`
-	PrivateKeyPath       string                     `json:",omitempty"`
-	Certificate          *tls.Certificate           `json:"-"`
-	CertificatePath      string                     `json:",omitempty"`
-	RootCertificates     []*x509.Certificate        `json:"-"`
-	RootCertificatePaths []string                   `json:",omitempty"`
-	Peers                []string                   `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
-	InterfacePeers       map[string][]string        `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
-	Listen               []string                   `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
-	AdminListen          string                     `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
-	MulticastInterfaces  []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."`
-	AllowedPublicKeys    []string                   `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
-	IfName               string                     `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
-	IfMTU                uint64                     `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
-	NodeInfoPrivacy      bool                       `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
-	NodeInfo             map[string]interface{}     `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
+	PrivateKey          KeyBytes                   `comment:"Your private key. DO NOT share this with anyone!"`
+	PrivateKeyPath      string                     `json:",omitempty"`
+	Certificate         *tls.Certificate           `json:"-"`
+	Peers               []string                   `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
+	InterfacePeers      map[string][]string        `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
+	Listen              []string                   `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
+	AdminListen         string                     `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
+	MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."`
+	AllowedPublicKeys   []string                   `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
+	IfName              string                     `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
+	IfMTU               uint64                     `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
+	NodeInfoPrivacy     bool                       `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
+	NodeInfo            map[string]interface{}     `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
 }
 
 type MulticastInterfaceConfig struct {
@@ -138,19 +135,6 @@ func (cfg *NodeConfig) postprocessConfig() error {
 			return err
 		}
 	}
-	if cfg.CertificatePath != "" {
-		if cfg.PrivateKeyPath == "" {
-			return fmt.Errorf("CertificatePath requires PrivateKeyPath")
-		}
-		cfg.Certificate = nil
-		f, err := os.ReadFile(cfg.CertificatePath)
-		if err != nil {
-			return err
-		}
-		if err := cfg.UnmarshalPEMCertificate(f); err != nil {
-			return err
-		}
-	}
 	switch {
 	case cfg.Certificate == nil:
 		// No self-signed certificate has been generated yet.
@@ -163,35 +147,6 @@ func (cfg *NodeConfig) postprocessConfig() error {
 			return err
 		}
 	}
-	cfg.RootCertificates = cfg.RootCertificates[:0]
-	for _, path := range cfg.RootCertificatePaths {
-		f, err := os.ReadFile(path)
-		if err != nil {
-			return err
-		}
-		if err := cfg.UnmarshalRootCertificate(f); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (cfg *NodeConfig) UnmarshalRootCertificate(b []byte) error {
-	p, _ := pem.Decode(b)
-	if p == nil {
-		return fmt.Errorf("failed to parse PEM file")
-	}
-	if p.Type != "CERTIFICATE" {
-		return fmt.Errorf("unexpected PEM type %q", p.Type)
-	}
-	cert, err := x509.ParseCertificate(p.Bytes)
-	if err != nil {
-		return fmt.Errorf("failed to load X.509 keypair: %w", err)
-	}
-	if !cert.IsCA {
-		return fmt.Errorf("supplied root certificate is not a certificate authority")
-	}
-	cfg.RootCertificates = append(cfg.RootCertificates, cert)
 	return nil
 }
 
@@ -215,26 +170,6 @@ func (cfg *NodeConfig) GenerateSelfSignedCertificate() error {
 	return nil
 }
 
-func (cfg *NodeConfig) GenerateCertificateSigningRequest() ([]byte, error) {
-	template := &x509.CertificateRequest{
-		Subject: pkix.Name{
-			CommonName: hex.EncodeToString(cfg.PrivateKey),
-		},
-		SignatureAlgorithm: x509.PureEd25519,
-	}
-
-	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, template, ed25519.PrivateKey(cfg.PrivateKey))
-	if err != nil {
-		return nil, err
-	}
-
-	pemBytes := bytes.NewBuffer(nil)
-	if err := pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}); err != nil {
-		return nil, err
-	}
-	return pemBytes.Bytes(), nil
-}
-
 func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
 	privateKey := ed25519.PrivateKey(cfg.PrivateKey)
 	publicKey := privateKey.Public().(ed25519.PublicKey)
@@ -263,15 +198,6 @@ func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
 	return pem.EncodeToMemory(block), nil
 }
 
-func (cfg *NodeConfig) UnmarshalPEMCertificate(b []byte) error {
-	tlsCert, err := tls.LoadX509KeyPair(cfg.CertificatePath, cfg.PrivateKeyPath)
-	if err != nil {
-		return fmt.Errorf("failed to load X.509 keypair: %w", err)
-	}
-	cfg.Certificate = &tlsCert
-	return nil
-}
-
 func (cfg *NodeConfig) NewPrivateKey() {
 	_, spriv, err := ed25519.GenerateKey(nil)
 	if err != nil {
diff --git a/src/core/core.go b/src/core/core.go
index dfc18704..e641c8cc 100644
--- a/src/core/core.go
+++ b/src/core/core.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"crypto/ed25519"
 	"crypto/tls"
-	"crypto/x509"
 	"encoding/hex"
 	"fmt"
 	"io"
@@ -39,8 +38,7 @@ type Core struct {
 	log          Logger
 	addPeerTimer *time.Timer
 	config       struct {
-		tls   *tls.Config    // immutable after startup
-		roots *x509.CertPool // immutable after startup
+		tls *tls.Config // immutable after startup
 		//_peers             map[Peer]*linkInfo         // configurable after startup
 		_listeners         map[ListenAddress]struct{} // configurable after startup
 		nodeinfo           NodeInfo                   // immutable after startup
@@ -110,9 +108,6 @@ func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, erro
 	c.log.Infof("Your public key is %s", hex.EncodeToString(c.public))
 	c.log.Infof("Your IPv6 address is %s", address.String())
 	c.log.Infof("Your IPv6 subnet is %s", subnet.String())
-	if c.config.roots != nil {
-		c.log.Println("Yggdrasil is running in TLS-only mode")
-	}
 	c.proto.init(c)
 	if err := c.links.init(c); err != nil {
 		return nil, fmt.Errorf("error initialising links: %w", err)
@@ -169,10 +164,6 @@ func (c *Core) _close() error {
 	return err
 }
 
-func (c *Core) isTLSOnly() bool {
-	return c.config.roots != nil
-}
-
 func (c *Core) MTU() uint64 {
 	const sessionTypeOverhead = 1
 	MTU := c.PacketConn.MTU() - sessionTypeOverhead
diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go
index 889051b9..f595aeb9 100644
--- a/src/core/link_tcp.go
+++ b/src/core/link_tcp.go
@@ -69,9 +69,6 @@ func (l *linkTCP) dialersFor(url *url.URL, info linkInfo) ([]*tcpDialer, error)
 }
 
 func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
-	if l.core.isTLSOnly() {
-		return nil, fmt.Errorf("TCP peer prohibited in TLS-only mode")
-	}
 	dialers, err := l.dialersFor(url, info)
 	if err != nil {
 		return nil, err
@@ -92,9 +89,6 @@ func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options
 }
 
 func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
-	if l.core.isTLSOnly() {
-		return nil, fmt.Errorf("TCP listener prohibited in TLS-only mode")
-	}
 	hostport := url.Host
 	if sintf != "" {
 		if host, port, err := net.SplitHostPort(hostport); err == nil {
diff --git a/src/core/options.go b/src/core/options.go
index e294896c..ffbdae73 100644
--- a/src/core/options.go
+++ b/src/core/options.go
@@ -2,19 +2,12 @@ package core
 
 import (
 	"crypto/ed25519"
-	"crypto/x509"
 	"fmt"
 	"net/url"
 )
 
 func (c *Core) _applyOption(opt SetupOption) (err error) {
 	switch v := opt.(type) {
-	case RootCertificate:
-		cert := x509.Certificate(v)
-		if c.config.roots == nil {
-			c.config.roots = x509.NewCertPool()
-		}
-		c.config.roots.AddCert(&cert)
 	case Peer:
 		u, err := url.Parse(v.URI)
 		if err != nil {
@@ -39,7 +32,6 @@ type SetupOption interface {
 	isSetupOption()
 }
 
-type RootCertificate x509.Certificate
 type ListenAddress string
 type Peer struct {
 	URI             string
@@ -49,7 +41,6 @@ type NodeInfo map[string]interface{}
 type NodeInfoPrivacy bool
 type AllowedPublicKey ed25519.PublicKey
 
-func (a RootCertificate) isSetupOption()  {}
 func (a ListenAddress) isSetupOption()    {}
 func (a Peer) isSetupOption()             {}
 func (a NodeInfo) isSetupOption()         {}
diff --git a/src/core/tls.go b/src/core/tls.go
index 08d0bd1e..3538334e 100644
--- a/src/core/tls.go
+++ b/src/core/tls.go
@@ -17,46 +17,30 @@ func (c *Core) generateTLSConfig(cert *tls.Certificate) (*tls.Config, error) {
 		VerifyConnection:      c.verifyTLSConnection,
 		InsecureSkipVerify:    true,
 		MinVersion:            tls.VersionTLS13,
-		NextProtos:            []string{"yggdrasil/0.5"},
+		NextProtos: []string{
+			fmt.Sprintf("yggdrasil/%d.%d", ProtocolVersionMajor, ProtocolVersionMinor),
+		},
 	}
 	return config, nil
 }
 
 func (c *Core) verifyTLSCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
-	if c.config.roots == nil {
-		// If there's no certificate pool configured then we will
-		// accept all TLS certificates.
-		return nil
-	}
-	if len(rawCerts) == 0 {
-		return fmt.Errorf("expected at least one certificate")
+	if len(rawCerts) != 1 {
+		return fmt.Errorf("expected one certificate")
 	}
 
-	opts := x509.VerifyOptions{
-		Roots: c.config.roots,
-	}
-
-	for i, rawCert := range rawCerts {
-		if i == 0 {
-			// The first certificate is the leaf certificate. All other
-			// certificates in the list are intermediates, so add them
-			// into the VerifyOptions.
-			continue
-		}
-		cert, err := x509.ParseCertificate(rawCert)
+	/*
+		opts := x509.VerifyOptions{}
+		cert, err := x509.ParseCertificate(rawCerts[0])
 		if err != nil {
-			return fmt.Errorf("failed to parse intermediate certificate: %w", err)
+			return fmt.Errorf("failed to parse leaf certificate: %w", err)
 		}
-		opts.Intermediates.AddCert(cert)
-	}
 
-	cert, err := x509.ParseCertificate(rawCerts[0])
-	if err != nil {
-		return fmt.Errorf("failed to parse leaf certificate: %w", err)
-	}
+		_, err = cert.Verify(opts)
+		return err
+	*/
 
-	_, err = cert.Verify(opts)
-	return err
+	return nil
 }
 
 func (c *Core) verifyTLSConnection(cs tls.ConnectionState) error {