github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4quic/matcher.go (about)

     1  // Copyright 2024 VNXME
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package l4quic
    16  
    17  import (
    18  	"context"
    19  	"crypto/ecdsa"
    20  	"crypto/elliptic"
    21  	"crypto/rand"
    22  	"crypto/tls"
    23  	"crypto/x509"
    24  	"crypto/x509/pkix"
    25  	"encoding/json"
    26  	"fmt"
    27  	"io"
    28  	"math"
    29  	"math/big"
    30  	"net"
    31  	"time"
    32  
    33  	"github.com/caddyserver/caddy/v2"
    34  	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    35  	"github.com/caddyserver/caddy/v2/modules/caddytls"
    36  	"github.com/quic-go/quic-go"
    37  
    38  	"github.com/mholt/caddy-l4/layer4"
    39  	"github.com/mholt/caddy-l4/modules/l4tls"
    40  )
    41  
    42  func init() {
    43  	caddy.RegisterModule(&MatchQUIC{})
    44  }
    45  
    46  // MatchQUIC is able to match QUIC connections. Its structure
    47  // is different from the auto-generated documentation. This
    48  // value should be a map of matcher names to their values.
    49  type MatchQUIC struct {
    50  	MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=tls.handshake_match"`
    51  
    52  	matchers []caddytls.ConnectionMatcher
    53  	quicConf *quic.Config
    54  	tlsConf  *tls.Config
    55  }
    56  
    57  // CaddyModule returns the Caddy module information.
    58  func (m *MatchQUIC) CaddyModule() caddy.ModuleInfo {
    59  	return caddy.ModuleInfo{
    60  		ID:  "layer4.matchers.quic",
    61  		New: func() caddy.Module { return new(MatchQUIC) },
    62  	}
    63  }
    64  
    65  // Match returns true if the connection looks like QUIC.
    66  func (m *MatchQUIC) Match(cx *layer4.Connection) (bool, error) {
    67  	// Check if the protocol is UDP
    68  	if _, isUDP := cx.LocalAddr().(*net.UDPAddr); !isUDP {
    69  		return false, nil
    70  	}
    71  
    72  	// Read one byte
    73  	buf := make([]byte, 1)
    74  	n, err := io.ReadAtLeast(cx, buf, 1)
    75  	if err != nil {
    76  		return false, err
    77  	}
    78  
    79  	// Ensure the second bit of the first byte is set, i.e. continue if
    80  	// github.com/quic-go/quic-go/internal/wire.IsPotentialQUICPacket(buf[0]).
    81  	qFirstByte := buf[0]
    82  	if qFirstByte&QUICMagicBitValue == 0 {
    83  		return false, nil
    84  	}
    85  
    86  	// Ensure the first bit of the first byte is set, i.e. continue if
    87  	// github.com/quic-go/quic-go/internal/wire.IsLongHeaderPacket(buf[0]).
    88  	// Note: this behaviour may be changed in the future if there are packets
    89  	// that should be considered valid despite having the first bit unset.
    90  	if qFirstByte&QUICLongHeaderBitValue == 0 {
    91  		return false, nil
    92  	}
    93  
    94  	// Read the remaining bytes
    95  	buf = make([]byte, QUICPacketBytesMax+1)
    96  	buf[0] = qFirstByte
    97  	n, err = io.ReadAtLeast(cx, buf[1:], 1)
    98  	if err != nil || n < QUICPacketBytesMin-1 || n == QUICPacketBytesMax {
    99  		return false, err
   100  	}
   101  
   102  	// Use a workaround to match ALPNs. This way quic.EarlyListener.Accept() exits on deadline
   103  	// if it receives a packet having an ALPN other than those present in tls.Config.NextProtos.
   104  	repl := cx.Context.Value(layer4.ReplacerCtxKey).(*caddy.Replacer)
   105  	tlsConf := &tls.Config{Certificates: m.tlsConf.Certificates}
   106  	for _, matcher := range m.matchers {
   107  		if alpnMatcher, ok := matcher.(*l4tls.MatchALPN); ok {
   108  			for _, alpnValue := range *alpnMatcher {
   109  				alpnValue = repl.ReplaceAll(alpnValue, "")
   110  				if len(alpnValue) > 0 {
   111  					tlsConf.NextProtos = append(tlsConf.NextProtos, alpnValue)
   112  				}
   113  			}
   114  		}
   115  	}
   116  
   117  	// Create a new fakePacketConn pipe
   118  	serverFPC, clientFPC := newFakePacketConnPipe(&fakePipeAddr{ID: newRand(), TS: time.Now()}, nil)
   119  	defer func() { _ = serverFPC.Close() }()
   120  	defer func() { _ = clientFPC.Close() }()
   121  
   122  	// Create a new quic.Transport
   123  	qTransport := quic.Transport{
   124  		Conn: serverFPC,
   125  	}
   126  
   127  	// Launch a new quic.EarlyListener
   128  	var qListener *quic.EarlyListener
   129  	qListener, err = qTransport.ListenEarly(tlsConf, m.quicConf)
   130  	if err != nil {
   131  		return false, err
   132  	}
   133  	defer func() { _ = qListener.Close() }()
   134  
   135  	// Write the buffered bytes into the pipe
   136  	n, err = clientFPC.WriteTo(buf[:n+1], nil)
   137  	if err != nil {
   138  		return false, nil
   139  	}
   140  
   141  	// Prepare a context with a deadline
   142  	qContext, qCancel := context.WithDeadline(context.Background(), time.Now().Add(QUICAcceptTimeout))
   143  	defer qCancel()
   144  
   145  	// Accept a new quic.EarlyConnection
   146  	var qConn quic.EarlyConnection
   147  	qConn, err = qListener.Accept(qContext)
   148  	if err != nil {
   149  		return false, nil
   150  	}
   151  
   152  	// Obtain a quic.ConnectionState
   153  	qState := qConn.ConnectionState()
   154  
   155  	// Add values to the replacer
   156  	repl.Set("l4.quic.tls.server_name", qState.TLS.ServerName)
   157  	repl.Set("l4.quic.tls.version", qState.TLS.Version)
   158  	repl.Set("l4.quic.version", qState.Version.String())
   159  
   160  	// Fill a tls.ClientHelloInfo structure
   161  	chi := &tls.ClientHelloInfo{
   162  		CipherSuites:      []uint16{qState.TLS.CipherSuite},
   163  		ServerName:        qState.TLS.ServerName,
   164  		SupportedCurves:   nil,
   165  		SupportedPoints:   nil,
   166  		SignatureSchemes:  nil,
   167  		SupportedProtos:   []string{qState.TLS.NegotiatedProtocol}, // Empty if no ALPNs are provided
   168  		SupportedVersions: []uint16{tls.VersionTLS13},              // Presumed to always be TLS 1.3
   169  		Conn:              cx,
   170  	}
   171  
   172  	// Check TLS matchers if any
   173  	for _, matcher := range m.matchers {
   174  		// ALPN matching is implicitly done above when the QUIC listener is initialised,
   175  		// as quic.EarlyConnection.ConnectionState().TLS.NegotiatedProtocol is only filled
   176  		// when the client's ALPN matches one of the values set in tls.Config.NextProtos.
   177  		if _, isMatchALPN := matcher.(*l4tls.MatchALPN); isMatchALPN {
   178  			continue
   179  		}
   180  
   181  		// TODO: even though we have more data than the standard lib's
   182  		// ClientHelloInfo lets us fill, the matcher modules we use do
   183  		// not accept our own type; but the advantage of this is that
   184  		// we can reuse TLS connection matchers from the tls app - but
   185  		// it would be nice if we found a way to give matchers all
   186  		// the information
   187  		if !matcher.Match(chi) {
   188  			return false, nil
   189  		}
   190  	}
   191  
   192  	return true, nil
   193  }
   194  
   195  // Provision prepares m's internal structures.
   196  func (m *MatchQUIC) Provision(ctx caddy.Context) error {
   197  	// Load TLS matchers
   198  	mods, err := ctx.LoadModule(m, "MatchersRaw")
   199  	if err != nil {
   200  		return fmt.Errorf("loading TLS matchers: %v", err)
   201  	}
   202  	for _, modAny := range mods.(map[string]any) {
   203  		m.matchers = append(m.matchers, modAny.(caddytls.ConnectionMatcher))
   204  	}
   205  
   206  	// Generate a new private key
   207  	var key *ecdsa.PrivateKey
   208  	key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   209  	if err != nil {
   210  		return fmt.Errorf("generating a private key: %v", err)
   211  	}
   212  
   213  	// Compose a new x509 certificate template
   214  	template := &x509.Certificate{
   215  		SerialNumber: newRand(),
   216  		Subject: pkix.Name{
   217  			CommonName:   QUICCertificateCommonName,
   218  			Organization: []string{QUICCertificateOrganization},
   219  		},
   220  		NotBefore:             time.Now(),
   221  		NotAfter:              time.Now().Add(QUICCertificateValidityPeriod),
   222  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   223  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   224  		BasicConstraintsValid: true,
   225  		DNSNames:              []string{QUICCertificateSubjectAltName},
   226  	}
   227  
   228  	// Create a new x509 certificate
   229  	var cert []byte
   230  	cert, err = x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
   231  	if err != nil {
   232  		return fmt.Errorf("generating a x509 ceriticate: %v", err)
   233  	}
   234  
   235  	// Initialize a new TLS config
   236  	m.tlsConf = &tls.Config{
   237  		Certificates: []tls.Certificate{{Certificate: [][]byte{cert}, PrivateKey: key}},
   238  	}
   239  
   240  	// Initialize a new QUIC config
   241  	m.quicConf = &quic.Config{}
   242  
   243  	return nil
   244  }
   245  
   246  // UnmarshalCaddyfile sets up the MatchQUIC from Caddyfile tokens. Syntax:
   247  //
   248  //	quic {
   249  //		<matcher> [<args...>]
   250  //		<matcher> [<args...>]
   251  //	}
   252  //	quic <matcher> [<args...>]
   253  //	quic
   254  func (m *MatchQUIC) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
   255  	d.Next() // consume wrapper name
   256  
   257  	matcherSet, err := l4tls.ParseCaddyfileNestedMatcherSet(d)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	m.MatchersRaw = matcherSet
   262  
   263  	return nil
   264  }
   265  
   266  // UnmarshalJSON satisfies the json.Unmarshaler interface.
   267  func (m *MatchQUIC) UnmarshalJSON(b []byte) error {
   268  	return json.Unmarshal(b, &m.MatchersRaw)
   269  }
   270  
   271  // MarshalJSON satisfies the json.Marshaler interface.
   272  func (m *MatchQUIC) MarshalJSON() ([]byte, error) {
   273  	return json.Marshal(m.MatchersRaw)
   274  }
   275  
   276  // fakePipeAddr satisfies net.Addr and uses a random number and a timestamp
   277  // to avoid panic in quic.connMultiplexer.AddConn() due to the same local address.
   278  type fakePipeAddr struct {
   279  	ID *big.Int
   280  	TS time.Time
   281  }
   282  
   283  func (fpa fakePipeAddr) Network() string {
   284  	return "pipe"
   285  }
   286  
   287  func (fpa fakePipeAddr) String() string {
   288  	return fmt.Sprintf("fake_%v_%v", fpa.ID.String(), fpa.TS.UnixNano())
   289  }
   290  
   291  // fakePacketConn wraps around net.Conn and satisfies net.PacketConn.
   292  type fakePacketConn struct {
   293  	net.Conn
   294  
   295  	Local  net.Addr
   296  	Remote net.Addr
   297  }
   298  
   299  func (fpc *fakePacketConn) LocalAddr() net.Addr {
   300  	if fpc.Local != nil {
   301  		return fpc.Local
   302  	}
   303  	return fpc.Conn.LocalAddr()
   304  }
   305  
   306  func (fpc *fakePacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
   307  	n, err := fpc.Conn.Read(p)
   308  	return n, fpc.RemoteAddr(), err
   309  }
   310  
   311  func (fpc *fakePacketConn) RemoteAddr() net.Addr {
   312  	if fpc.Remote != nil {
   313  		return fpc.Remote
   314  	}
   315  	return fpc.Conn.RemoteAddr()
   316  }
   317  
   318  func (fpc *fakePacketConn) SetReadBuffer(_ int) error {
   319  	return nil
   320  }
   321  
   322  func (fpc *fakePacketConn) SetWriteBuffer(_ int) error {
   323  	return nil
   324  }
   325  
   326  func (fpc *fakePacketConn) WriteTo(p []byte, _ net.Addr) (int, error) {
   327  	return fpc.Conn.Write(p)
   328  }
   329  
   330  // Interface guards
   331  var (
   332  	_ caddy.Provisioner     = (*MatchQUIC)(nil)
   333  	_ caddyfile.Unmarshaler = (*MatchQUIC)(nil)
   334  	_ layer4.ConnMatcher    = (*MatchQUIC)(nil)
   335  
   336  	_ net.Addr = (*fakePipeAddr)(nil)
   337  
   338  	_ net.PacketConn                         = (*fakePacketConn)(nil)
   339  	_ interface{ SetReadBuffer(int) error }  = (*fakePacketConn)(nil)
   340  	_ interface{ SetWriteBuffer(int) error } = (*fakePacketConn)(nil)
   341  )
   342  
   343  const (
   344  	QUICAcceptTimeout = 100 * time.Millisecond
   345  
   346  	QUICCertificateCommonName     = "layer4"
   347  	QUICCertificateOrganization   = "caddy"
   348  	QUICCertificateSubjectAltName = "*"
   349  	QUICCertificateValidityPeriod = time.Hour * 24 * 365 * 20
   350  
   351  	QUICLongHeaderBitValue uint8 = 0x80 // github.com/quic-go/quic-go/internal/wire.IsLongHeaderPacket()
   352  	QUICMagicBitValue      uint8 = 0x40 // github.com/quic-go/quic-go/internal/wire.IsPotentialQUICPacket()
   353  
   354  	QUICPacketBytesMax = 1452 // github.com/quic-go/quic-go/internal/protocol.MaxPacketBufferSize
   355  	QUICPacketBytesMin = 1200 // github.com/quic-go/quic-go/internal/protocol.MinInitialPacketSize
   356  )
   357  
   358  func newFakePacketConnPipe(local, remote net.Addr) (*fakePacketConn, *fakePacketConn) {
   359  	server, client := net.Pipe()
   360  	serverFPC, clientFPC := &fakePacketConn{Conn: server}, &fakePacketConn{Conn: client}
   361  	if local != nil || remote != nil {
   362  		if local == nil {
   363  			local = remote
   364  		}
   365  		if remote == nil {
   366  			remote = local
   367  		}
   368  		serverFPC.Local, clientFPC.Remote = local, local
   369  		serverFPC.Remote, clientFPC.Local = remote, remote
   370  	}
   371  	return serverFPC, clientFPC
   372  }
   373  
   374  func newRand() *big.Int {
   375  	rnd, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
   376  	return rnd
   377  }