Separate the window sentence from the wrapped error so a bad certificate file is not reported as a key mismatch.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user