yggdrasil-go/src/core/link_tls.go

171 lines
4.2 KiB
Go
Raw Normal View History

package core
import (
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/url"
"strings"
"time"
"github.com/Arceliar/phony"
)
type linkTLS struct {
phony.Inbox
*links
tcp *linkTCP
listener *net.ListenConfig
config *tls.Config
_listeners map[*Listener]context.CancelFunc
}
func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
lt := &linkTLS{
links: l,
tcp: tcp,
listener: &net.ListenConfig{
Control: tcp.tcpContext,
KeepAlive: -1,
},
_listeners: map[*Listener]context.CancelFunc{},
}
var err error
lt.config, err = lt.generateConfig()
if err != nil {
panic(err)
}
return lt
}
func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error {
info := linkInfoFor("tls", sintf, strings.SplitN(url.Host, "%", 2)[0])
if l.links.isConnectedTo(info) {
return nil
}
addr, err := net.ResolveTCPAddr("tcp", url.Host)
if err != nil {
return err
}
2022-10-21 19:49:15 +01:00
dialer, err := l.tcp.dialerFor(addr, sintf)
if err != nil {
return err
}
tlsconfig := l.config.Clone()
tlsconfig.ServerName = sni
tlsdialer := &tls.Dialer{
NetDialer: dialer,
Config: tlsconfig,
}
conn, err := tlsdialer.DialContext(l.core.ctx, "tcp", addr.String())
if err != nil {
return err
}
2022-10-22 16:23:25 +01:00
return l.handler(url.String(), info, conn, options, false, false)
}
func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) {
ctx, cancel := context.WithCancel(l.core.ctx)
hostport := url.Host
if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil {
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
}
}
listener, err := l.listener.Listen(ctx, "tcp", hostport)
if err != nil {
cancel()
return nil, err
}
tlslistener := tls.NewListener(listener, l.config)
entry := &Listener{
Listener: tlslistener,
closed: make(chan struct{}),
}
phony.Block(l, func() {
l._listeners[entry] = cancel
})
l.core.log.Printf("TLS listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := tlslistener.Accept()
if err != nil {
cancel()
break
}
addr := conn.RemoteAddr().(*net.TCPAddr)
name := fmt.Sprintf("tls://%s", addr)
info := linkInfoFor("tls", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0])
2022-10-22 16:23:25 +01:00
if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
2022-09-24 17:05:44 +01:00
_ = tlslistener.Close()
close(entry.closed)
l.core.log.Printf("TLS listener stopped on %s", listener.Addr())
}()
return entry, nil
}
func (l *linkTLS) generateConfig() (*tls.Config, error) {
certBuf := &bytes.Buffer{}
// TODO: because NotAfter is finite, we should add some mechanism to
// regenerate the certificate and restart the listeners periodically
// for nodes with very high uptimes. Perhaps regenerate certs and restart
// listeners every few months or so.
cert := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: hex.EncodeToString(l.links.core.public[:]),
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
certbytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, l.links.core.public, l.links.core.secret)
if err != nil {
return nil, err
}
if err := pem.Encode(certBuf, &pem.Block{
Type: "CERTIFICATE",
Bytes: certbytes,
}); err != nil {
return nil, err
}
rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(certbytes)
return &tls.Config{
RootCAs: rootCAs,
Certificates: []tls.Certificate{
{
Certificate: [][]byte{certbytes},
PrivateKey: l.links.core.secret,
},
},
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
}, nil
}
2022-10-22 16:23:25 +01:00
func (l *linkTLS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
return l.tcp.handler(name, info, conn, options, incoming, force)
}