Write a missing signing request and give up on a handshake that never returns.
A certificate already on disk still has to be renewable, and a reflector that never finishes TLS must not hold the session past the login timers. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -124,6 +124,9 @@ func prepareIdentity(dir, callsign string) (tls.Certificate, bool, string) {
|
|||||||
if msg != "" {
|
if msg != "" {
|
||||||
return tls.Certificate{}, false, msg
|
return tls.Certificate{}, false, msg
|
||||||
}
|
}
|
||||||
|
if msg := ensureCSR(dir, callsign); msg != "" {
|
||||||
|
return tls.Certificate{}, false, msg
|
||||||
|
}
|
||||||
return pair, true, ""
|
return pair, true, ""
|
||||||
}
|
}
|
||||||
if msg := ensureKeyAndCSR(dir, callsign, !keyExists); msg != "" {
|
if msg := ensureKeyAndCSR(dir, callsign, !keyExists); msg != "" {
|
||||||
@@ -155,6 +158,25 @@ func loadPair(certPath, keyPath, callsign string) (tls.Certificate, string) {
|
|||||||
}, ""
|
}, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureCSR(dir, callsign string) string {
|
||||||
|
csrPath := filepath.Join(dir, csrFileName)
|
||||||
|
_, err := os.Stat(csrPath)
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return "Cannot create " + csrPath
|
||||||
|
}
|
||||||
|
key, err := loadKey(filepath.Join(dir, keyFileName))
|
||||||
|
if err != nil {
|
||||||
|
return "Cannot create " + csrPath
|
||||||
|
}
|
||||||
|
if err := writeCSR(csrPath, key, callsign); err != nil {
|
||||||
|
return "Cannot create " + csrPath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func ensureKeyAndCSR(dir, callsign string, created bool) string {
|
func ensureKeyAndCSR(dir, callsign string, created bool) string {
|
||||||
keyPath := filepath.Join(dir, keyFileName)
|
keyPath := filepath.Join(dir, keyFileName)
|
||||||
var key *rsa.PrivateKey
|
var key *rsa.PrivateKey
|
||||||
|
|||||||
@@ -697,6 +697,11 @@ func TestTLSUsesTheCallsignCertificate(t *testing.T) {
|
|||||||
t.Fatalf("presented %#v", peer)
|
t.Fatalf("presented %#v", peer)
|
||||||
}
|
}
|
||||||
v.refl.writeFrame(t, 12, nil)
|
v.refl.writeFrame(t, 12, nil)
|
||||||
|
typ, body := v.refl.readFrame(t)
|
||||||
|
if typ != 17 {
|
||||||
|
t.Fatalf("type %d, want the signing request written because none was stored", typ)
|
||||||
|
}
|
||||||
|
checkCSR(t, body, v.certDir)
|
||||||
v.serverAndKey(t, []string{"OPUS"})
|
v.serverAndKey(t, []string{"OPUS"})
|
||||||
v.beat(t)
|
v.beat(t)
|
||||||
}
|
}
|
||||||
@@ -794,6 +799,53 @@ func TestHostMismatchRetriesOn3(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMissingSigningRequestIsWritten(t *testing.T) {
|
||||||
|
v := startV3(t, "")
|
||||||
|
if err := os.Mkdir(v.certDir, 0o700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeKey(t, filepath.Join(v.certDir, "key.pem"), key)
|
||||||
|
if err := os.WriteFile(filepath.Join(v.certDir, "cert.pem"), signClient(t, v.pki, key, "DL1ABC"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
v.throughBundle(t)
|
||||||
|
v.tls(t, v.pki.serverConfig())
|
||||||
|
v.refl.writeFrame(t, 16, nil)
|
||||||
|
typ, body := v.refl.readFrame(t)
|
||||||
|
if typ != 17 {
|
||||||
|
t.Fatalf("type %d, want the signing request", typ)
|
||||||
|
}
|
||||||
|
checkCSR(t, body, v.certDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandshakeThatDoesNotFinishRetriesOn3(t *testing.T) {
|
||||||
|
v := startV3(t, "")
|
||||||
|
v.throughBundle(t)
|
||||||
|
gen := v.clock.Generation()
|
||||||
|
v.refl.writeFrame(t, 15, nil)
|
||||||
|
waitClock(t, v.clock, gen)
|
||||||
|
v.clock.Advance(15 * time.Second)
|
||||||
|
if _, err := v.st.Wait(context.Background(), func(s station.State) bool {
|
||||||
|
return s.Detail == "TLS handshake failed"
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
v.clock.BlockUntilWaiting(t)
|
||||||
|
v.clock.Advance(2 * time.Second)
|
||||||
|
v.refl.accept(t)
|
||||||
|
typ, body := v.refl.readFrame(t)
|
||||||
|
if typ != 5 || !versionIs(body, 3, 0) {
|
||||||
|
t.Fatalf("retry type %d body %v, want 3.0", typ, body)
|
||||||
|
}
|
||||||
|
if v.st.State().Detail != "TLS handshake failed" {
|
||||||
|
t.Fatalf("Detail = %q", v.st.State().Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTLSHandshakeFailureRetriesOn3(t *testing.T) {
|
func TestTLSHandshakeFailureRetriesOn3(t *testing.T) {
|
||||||
v := startV3(t, "")
|
v := startV3(t, "")
|
||||||
v.throughBundle(t)
|
v.throughBundle(t)
|
||||||
|
|||||||
@@ -148,15 +148,25 @@ func (r *reflector) conn() net.Conn {
|
|||||||
|
|
||||||
func newReflector(t *testing.T) *reflector {
|
func newReflector(t *testing.T) *reflector {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
var ln net.Listener
|
||||||
if err != nil {
|
var udp *net.UDPConn
|
||||||
t.Fatal(err)
|
var port int
|
||||||
}
|
for range 8 {
|
||||||
port := ln.Addr().(*net.TCPAddr).Port
|
var err error
|
||||||
udp, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: port})
|
ln, err = net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
port = ln.Addr().(*net.TCPAddr).Port
|
||||||
|
udp, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: port})
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
ln.Close()
|
ln.Close()
|
||||||
t.Fatal(err)
|
ln = nil
|
||||||
|
}
|
||||||
|
if udp == nil {
|
||||||
|
t.Fatal("udp port stayed in use")
|
||||||
}
|
}
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if ln != nil {
|
if ln != nil {
|
||||||
|
|||||||
+28
-8
@@ -159,18 +159,34 @@ func (f *framed) Write(p []byte) (int, error) {
|
|||||||
|
|
||||||
func (f *framed) setMax(n uint32) { f.max.Store(n) }
|
func (f *framed) setMax(n uint32) { f.max.Store(n) }
|
||||||
|
|
||||||
func (f *framed) startTLS(ctx context.Context, cfg *tls.Config) error {
|
var errTLSTimeout = errors.New("tls timeout")
|
||||||
|
|
||||||
|
func (s *Station) startTLS(ctx context.Context, f *framed, cfg *tls.Config, until time.Time) error {
|
||||||
select {
|
select {
|
||||||
case f.upgrade <- cfg:
|
case f.upgrade <- cfg:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
_ = f.conn.SetReadDeadline(time.Now())
|
_ = f.conn.SetReadDeadline(time.Now())
|
||||||
|
wait := until.Sub(s.clock.Now())
|
||||||
|
if wait < 0 {
|
||||||
|
wait = 0
|
||||||
|
}
|
||||||
|
timer := s.clock.NewTimer(wait)
|
||||||
|
defer timer.Stop()
|
||||||
select {
|
select {
|
||||||
case err := <-f.up:
|
case err := <-f.up:
|
||||||
return err
|
return err
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
_ = f.conn.SetDeadline(time.Now())
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
case <-timer.C():
|
||||||
|
_ = f.conn.SetDeadline(time.Now())
|
||||||
|
select {
|
||||||
|
case <-f.up:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
return errTLSTimeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +364,11 @@ func (s *Station) session(ctx context.Context, conn net.Conn, major uint16) erro
|
|||||||
switch reason {
|
switch reason {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
tcpRx = now.Add(15 * time.Second)
|
tcpRx = now.Add(15 * time.Second)
|
||||||
err := s.onFrame(ctx, wire, major, tcpf, &ph, &authed, &client, &haveCert, done, &udpCh, &udpErr, &udpReady, &lastTCP)
|
limit := tcpRx
|
||||||
|
if udpRx.Before(limit) {
|
||||||
|
limit = udpRx
|
||||||
|
}
|
||||||
|
err := s.onFrame(ctx, wire, major, tcpf, &ph, &authed, &client, &haveCert, done, &udpCh, &udpErr, &udpReady, &lastTCP, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -428,7 +448,7 @@ func (s *Station) onClock(now time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Station) onFrame(ctx context.Context, wire *framed, major uint16, f frame, ph *phase, authed *bool, client *tls.Certificate, haveCert *bool, done chan struct{}, udpCh *chan []byte, udpErr *chan error, udpReady *bool, lastTCP *time.Time) error {
|
func (s *Station) onFrame(ctx context.Context, wire *framed, major uint16, f frame, ph *phase, authed *bool, client *tls.Certificate, haveCert *bool, done chan struct{}, udpCh *chan []byte, udpErr *chan error, udpReady *bool, lastTCP *time.Time, limit time.Time) error {
|
||||||
switch f.typ {
|
switch f.typ {
|
||||||
case tcpHeartbeat:
|
case tcpHeartbeat:
|
||||||
return nil
|
return nil
|
||||||
@@ -443,7 +463,7 @@ func (s *Station) onFrame(ctx context.Context, wire *framed, major uint16, f fra
|
|||||||
if major < 3 {
|
if major < 3 {
|
||||||
return s.onV2(wire, f, ph, authed, done, udpCh, udpErr, udpReady, lastTCP)
|
return s.onV2(wire, f, ph, authed, done, udpCh, udpErr, udpReady, lastTCP)
|
||||||
}
|
}
|
||||||
return s.onV3(ctx, wire, f, ph, authed, client, haveCert, done, udpCh, udpErr, udpReady, lastTCP)
|
return s.onV3(ctx, wire, f, ph, authed, client, haveCert, done, udpCh, udpErr, udpReady, lastTCP, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Station) onReflectorError(body []byte, major uint16) error {
|
func (s *Station) onReflectorError(body []byte, major uint16) error {
|
||||||
@@ -520,7 +540,7 @@ func (s *Station) answerChallenge(w io.Writer, body []byte, lastTCP *time.Time)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Station) onV3(ctx context.Context, wire *framed, f frame, ph *phase, authed *bool, client *tls.Certificate, haveCert *bool, done chan struct{}, udpCh *chan []byte, udpErr *chan error, udpReady *bool, lastTCP *time.Time) error {
|
func (s *Station) onV3(ctx context.Context, wire *framed, f frame, ph *phase, authed *bool, client *tls.Certificate, haveCert *bool, done chan struct{}, udpCh *chan []byte, udpErr *chan error, udpReady *bool, lastTCP *time.Time, limit time.Time) error {
|
||||||
switch f.typ {
|
switch f.typ {
|
||||||
case tcpCAInfo:
|
case tcpCAInfo:
|
||||||
if *ph != phCA {
|
if *ph != phCA {
|
||||||
@@ -536,7 +556,7 @@ func (s *Station) onV3(ctx context.Context, wire *framed, f frame, ph *phase, au
|
|||||||
if *ph != phStart {
|
if *ph != phStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.onStartEncryption(ctx, wire, ph, client, haveCert)
|
return s.onStartEncryption(ctx, wire, ph, client, haveCert, limit)
|
||||||
case tcpCsrReq:
|
case tcpCsrReq:
|
||||||
return s.sendCSR(wire, lastTCP)
|
return s.sendCSR(wire, lastTCP)
|
||||||
case tcpAuthChallenge:
|
case tcpAuthChallenge:
|
||||||
@@ -658,7 +678,7 @@ func (s *Station) onCABundle(w io.Writer, body []byte, ph *phase, lastTCP *time.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Station) onStartEncryption(ctx context.Context, wire *framed, ph *phase, client *tls.Certificate, haveCert *bool) error {
|
func (s *Station) onStartEncryption(ctx context.Context, wire *framed, ph *phase, client *tls.Certificate, haveCert *bool, limit time.Time) error {
|
||||||
pair, present, fatal := prepareIdentity(s.cfg.certificateDir, s.cfg.callsign)
|
pair, present, fatal := prepareIdentity(s.cfg.certificateDir, s.cfg.callsign)
|
||||||
if fatal != "" {
|
if fatal != "" {
|
||||||
s.endFatal(fatal, true)
|
s.endFatal(fatal, true)
|
||||||
@@ -675,7 +695,7 @@ func (s *Station) onStartEncryption(ctx context.Context, wire *framed, ph *phase
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return s.retry("TLS handshake failed")
|
return s.retry("TLS handshake failed")
|
||||||
}
|
}
|
||||||
if err := wire.startTLS(ctx, cfg); err != nil {
|
if err := s.startTLS(ctx, wire, cfg, limit); err != nil {
|
||||||
if errors.Is(err, errHostMismatch) {
|
if errors.Is(err, errHostMismatch) {
|
||||||
return s.retry("Reflector certificate does not match the host")
|
return s.retry("Reflector certificate does not match the host")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user