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 }