Files

260 lines
6.1 KiB
Go

package station
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
func (s *Station) onV3(ctx context.Context, wire *framed, f frame, ex *exchange) error {
switch f.typ {
case tcpCAInfo:
if ex.ph != phCA {
return nil
}
return s.onCAInfo(wire, f.body, ex)
case tcpCABundle:
if ex.ph != phBundle {
return nil
}
return s.onCABundle(wire, f.body, ex)
case tcpStartEnc:
if ex.ph != phStart {
return nil
}
return s.onStartEncryption(ctx, wire, ex)
case tcpCsrReq:
return s.sendCSR(wire, ex)
case tcpAuthChallenge:
return s.answerChallenge(wire, f.body, ex)
case tcpAuthOK:
ex.authed = true
ex.ph = phServer
newer, err := csrNewerThanCert(s.cfg.certificateDir)
if err != nil {
s.endFatalCause("Cannot create "+s.cfg.certificateDir, err, true)
return errStopped
}
if newer {
if err := s.sendCSR(wire, ex); err != nil {
return err
}
}
return nil
case tcpClientCert:
return s.saveCert(f.body)
case tcpServerInfo:
if !ex.authed {
return s.retry("Protocol error", nil)
}
return s.finishV3Server(wire, f.body, ex)
case tcpStartUDPEnc:
if ex.ph != phUDP {
return nil
}
return s.finishV3UDP(wire.conn, ex)
case tcpTalkerStart:
s.onTalker(f.body, true)
return nil
case tcpTalkerStop:
s.onTalker(f.body, false)
return nil
case tcpNodeList, tcpNodeJoined, tcpNodeLeft, tcpRequestQSY, tcpStateEvent, tcpSignal, tcpTxStatus:
if err := unpackIgnored(f); err != nil {
return s.retry("Protocol error", err)
}
return nil
default:
return nil
}
}
func (s *Station) onCAInfo(w io.Writer, body []byte, ex *exchange) error {
_, digest, err := parseCAInfo(body)
if err != nil {
return s.retry("Protocol error", err)
}
if err := ensureCertDir(s.cfg.certificateDir); err != nil {
s.endFatalCause("Cannot create "+s.cfg.certificateDir, err, true)
return errStopped
}
sum, ok, err := caDigest(s.cfg.certificateDir)
if err != nil {
s.endFatalCause("Cannot create "+s.cfg.certificateDir, err, true)
return errStopped
}
if ok {
if !bytes.Equal(sum[:], digest) {
s.mu.Lock()
s.writeLog(s.clock.Now(), "CA digest differs")
s.mu.Unlock()
}
if err := writeFrame(w, tcpStartEncReq, nil); err != nil {
return err
}
ex.lastTCP = s.clock.Now()
ex.ph = phStart
return nil
}
if err := writeFrame(w, tcpCABundleReq, nil); err != nil {
return err
}
ex.lastTCP = s.clock.Now()
ex.ph = phBundle
return nil
}
func parseCAInfo(b []byte) (uint16, []byte, error) {
size, rest, err := parseU16(b)
if err != nil {
return 0, nil, err
}
digest, rest, err := parseBlob(rest)
if err != nil {
return 0, nil, err
}
if len(digest) != sha256Len || len(rest) != 0 {
return 0, nil, io.ErrUnexpectedEOF
}
return size, digest, nil
}
const sha256Len = 32
func (s *Station) onCABundle(w io.Writer, body []byte, ex *exchange) error {
caPEM, rest, err := parseString(body)
if err != nil {
return s.retry("CA signature rejected", err)
}
sig, rest, err := parseBlob(rest)
if err != nil {
return s.retry("CA signature rejected", err)
}
certPEM, rest, err := parseString(rest)
if err != nil {
return s.retry("CA signature rejected", err)
}
if len(rest) != 0 {
return s.retry("CA signature rejected", nil)
}
if err := verifyCABundle(caPEM, sig, certPEM); err != nil {
return s.retry("CA signature rejected", err)
}
if err := writeCABundle(s.cfg.certificateDir, caPEM); err != nil {
s.endFatalCause("Cannot create "+s.cfg.certificateDir, err, true)
return errStopped
}
if err := writeFrame(w, tcpStartEncReq, nil); err != nil {
return err
}
ex.lastTCP = s.clock.Now()
ex.ph = phStart
return nil
}
func (s *Station) onStartEncryption(ctx context.Context, wire *framed, ex *exchange) error {
pair, present, fatal, cause := prepareIdentity(s.cfg.certificateDir, s.cfg.callsign)
if fatal != "" {
s.endFatalCause(fatal, cause, true)
return errStopped
}
ex.client = pair
ex.haveCert = present
caPath := filepath.Join(s.cfg.certificateDir, caFileName)
caPEM, err := os.ReadFile(caPath)
if err != nil {
s.endFatalCause("Cannot create "+caPath, err, true)
return errStopped
}
cfg, err := tlsConfig(s.cfg.host, caPEM, pair, present)
if err != nil {
return s.retry("TLS handshake failed", err)
}
if err := s.startTLS(ctx, wire, cfg, ex.limit); err != nil {
if errors.Is(err, errHostMismatch) {
return s.retry("Reflector certificate does not match the host", err)
}
if ctx.Err() != nil {
return ctx.Err()
}
return s.retry("TLS handshake failed", err)
}
ex.ph = phIdent
return nil
}
func (s *Station) sendCSR(w io.Writer, ex *exchange) error {
csrPath := filepath.Join(s.cfg.certificateDir, csrFileName)
pem, err := readCSR(s.cfg.certificateDir)
if err != nil {
s.endFatalCause("Cannot create "+csrPath, err, true)
return errStopped
}
if err := writeFrame(w, tcpCsr, appendString(nil, pem)); err != nil {
return err
}
ex.lastTCP = s.clock.Now()
return nil
}
func (s *Station) saveCert(body []byte) error {
pem, _, err := parseString(body)
if err != nil || pem == "" {
return s.retry("Protocol error", err)
}
certPath := filepath.Join(s.cfg.certificateDir, certFileName)
if err := writeClientCert(s.cfg.certificateDir, pem); err != nil {
s.endFatalCause("Cannot create "+certPath, err, true)
return errStopped
}
return errReconnect
}
func (s *Station) finishV3Server(wire *framed, body []byte, ex *exchange) error {
id, enc, dec, err := s.acceptServer(body)
if err != nil {
return err
}
if enc == nil {
return errStopped
}
iv := make([]byte, 6)
key := make([]byte, 16)
if _, err := rand.Read(iv); err != nil {
enc.Close()
dec.Close()
return fmt.Errorf("udp key: %w", err)
}
if _, err := rand.Read(key); err != nil {
enc.Close()
dec.Close()
return fmt.Errorf("udp key: %w", err)
}
info, err := identityV3(iv, key)
if err != nil {
enc.Close()
dec.Close()
return err
}
if err := writeFrame(wire, tcpNodeInfo, info); err != nil {
enc.Close()
dec.Close()
return err
}
ex.lastTCP = s.clock.Now()
s.mu.Lock()
s.clientID = id
s.enc = enc
s.dec = dec
s.ivPrefix = iv
s.udpKey = key
s.secure = true
s.mu.Unlock()
ex.ph = phUDP
return nil
}