mirror of
https://github.com/cloudflare/cloudflared.git
synced 2026-08-07 07:14:57 +00:00
156 lines
4.2 KiB
Go
156 lines
4.2 KiB
Go
// Copyright 2026 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/cryptobyte"
|
|
)
|
|
|
|
const (
|
|
muxProtocolVersion = 4
|
|
|
|
muxMsgHello = 0x00000001
|
|
muxCProxy = 0x1000000f
|
|
muxSProxy = 0x8000000f
|
|
)
|
|
|
|
const controlProxyRequestID = 0
|
|
|
|
// handshakeControlProxy attempts to establish a transport connection with an
|
|
// OpenSSH ControlMaster socket in proxy mode. For details see:
|
|
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.mux
|
|
func handshakeControlProxy(rw io.ReadWriteCloser) (connTransport, error) {
|
|
if err := controlProxyWritePacket(rw, func(b *cryptobyte.Builder) {
|
|
b.AddUint32(muxMsgHello)
|
|
b.AddUint32(muxProtocolVersion)
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("mux hello write failed: %w", err)
|
|
}
|
|
if err := controlProxyWritePacket(rw, func(b *cryptobyte.Builder) {
|
|
b.AddUint32(muxCProxy)
|
|
b.AddUint32(controlProxyRequestID)
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("mux client proxy write failed: %w", err)
|
|
}
|
|
|
|
messageType, body, err := controlProxyReadMessage(rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mux hello read failed: %w", err)
|
|
}
|
|
if messageType != muxMsgHello {
|
|
return nil, fmt.Errorf("expected hello response, got %v", messageType)
|
|
}
|
|
var v uint32
|
|
if !body.ReadUint32(&v) {
|
|
return nil, errors.New("EOF reading mux protocol version")
|
|
}
|
|
if v != muxProtocolVersion {
|
|
return nil, fmt.Errorf("mux server has unsupported version %v", v)
|
|
}
|
|
messageType, body, err = controlProxyReadMessage(rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mux server proxy read failed: %w", err)
|
|
}
|
|
if messageType != muxSProxy {
|
|
return nil, fmt.Errorf("expected server proxy response, got %v", messageType)
|
|
}
|
|
var reqID uint32
|
|
if !body.ReadUint32(&reqID) {
|
|
return nil, errors.New("EOF reading request id")
|
|
}
|
|
if reqID != controlProxyRequestID {
|
|
return nil, fmt.Errorf("expected request id %v, got %v", controlProxyRequestID, reqID)
|
|
}
|
|
return &controlProxyTransport{rw}, nil
|
|
}
|
|
|
|
// controlProxyTransport implements the connTransport interface for
|
|
// ControlMaster connections. Each controlMessage has zero length padding and
|
|
// no MAC.
|
|
type controlProxyTransport struct {
|
|
rw io.ReadWriteCloser
|
|
}
|
|
|
|
func (p *controlProxyTransport) Close() error {
|
|
return p.rw.Close()
|
|
}
|
|
|
|
func (p *controlProxyTransport) writePacket(controlMessage []byte) error {
|
|
return controlProxyWritePacket(p.rw, func(b *cryptobyte.Builder) {
|
|
b.AddUint8(0) // Padding length.
|
|
b.AddBytes(controlMessage)
|
|
})
|
|
}
|
|
|
|
func (p *controlProxyTransport) readPacket() ([]byte, error) {
|
|
buf, err := controlProxyReadPacket(p.rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ssh: error reading control message: %w", err)
|
|
}
|
|
// Discard the padding length.
|
|
if len(buf) < 1 {
|
|
return nil, errors.New("ssh: EOF reading padding length")
|
|
}
|
|
if buf[0] != 0 {
|
|
return nil, errors.New("ssh: unexpected non-zero padding in control message")
|
|
}
|
|
return buf[1:], nil
|
|
}
|
|
|
|
func (p *controlProxyTransport) getAlgorithms() NegotiatedAlgorithms {
|
|
return NegotiatedAlgorithms{}
|
|
}
|
|
|
|
func (p *controlProxyTransport) getSessionID() []byte {
|
|
return nil
|
|
}
|
|
|
|
func (p *controlProxyTransport) waitSession() error {
|
|
return nil
|
|
}
|
|
|
|
func controlProxyWritePacket(w io.Writer, f cryptobyte.BuilderContinuation) error {
|
|
var buf []byte
|
|
b := cryptobyte.NewBuilder(buf)
|
|
b.AddUint32LengthPrefixed(f)
|
|
out, err := b.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(out)
|
|
return err
|
|
}
|
|
|
|
func controlProxyReadPacket(r io.Reader) (cryptobyte.String, error) {
|
|
var l uint32
|
|
if err := binary.Read(r, binary.BigEndian, &l); err != nil {
|
|
return nil, err
|
|
}
|
|
if l > maxPacket {
|
|
return nil, fmt.Errorf("message length %v exceeds maximum %v", l, maxPacket)
|
|
}
|
|
buf := make([]byte, l)
|
|
if _, err := io.ReadFull(r, buf); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
func controlProxyReadMessage(r io.Reader) (messageType uint32, body cryptobyte.String, err error) {
|
|
body, err = controlProxyReadPacket(r)
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("error reading message body: %w", err)
|
|
}
|
|
if !body.ReadUint32(&messageType) {
|
|
return 0, nil, errors.New("EOF reading message type")
|
|
}
|
|
return messageType, body, nil
|
|
}
|