2d46d313d1
CI / check-and-test (pull_request) Successful in 11s
TCP forwardData copied plaintext after the handshake. Frame each chunk as uint32 length plus AES-GCM ciphertext in both client and server, both directions.
121 lines
3.0 KiB
Go
121 lines
3.0 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"teleport/pkg/config"
|
|
"teleport/pkg/encryption"
|
|
)
|
|
|
|
func testClient(t *testing.T) *TeleportClient {
|
|
t.Helper()
|
|
return NewTeleportClient(&config.Config{
|
|
EncryptionKey: "a0e3dd20a761b118ca234160dd8b87230a001e332a97c9cfe3b8b9c99efaae03",
|
|
})
|
|
}
|
|
|
|
func TestForwardDataHidesHTTPPlaintextOnTunnel(t *testing.T) {
|
|
tc := testClient(t)
|
|
defer tc.Stop()
|
|
|
|
localA, localB := net.Pipe()
|
|
tunnelA, tunnelB := net.Pipe()
|
|
defer localA.Close()
|
|
defer localB.Close()
|
|
defer tunnelA.Close()
|
|
defer tunnelB.Close()
|
|
|
|
go tc.forwardData(localA, tunnelA, true)
|
|
|
|
req := []byte("GET /secret HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
|
writeErr := make(chan error, 1)
|
|
go func() {
|
|
_, err := localB.Write(req)
|
|
writeErr <- err
|
|
}()
|
|
|
|
tunnelB.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
var length uint32
|
|
if err := binary.Read(tunnelB, binary.BigEndian, &length); err != nil {
|
|
t.Fatalf("failed to read frame length from tunnel: %v", err)
|
|
}
|
|
if length == 0 || length > encryption.MaxFrameSize {
|
|
t.Fatalf("unexpected frame length %d", length)
|
|
}
|
|
ciphertext := make([]byte, length)
|
|
if _, err := io.ReadFull(tunnelB, ciphertext); err != nil {
|
|
t.Fatalf("failed to read ciphertext from tunnel: %v", err)
|
|
}
|
|
|
|
if bytes.Contains(ciphertext, []byte("GET /")) ||
|
|
bytes.Contains(ciphertext, []byte("Host:")) ||
|
|
bytes.Contains(ciphertext, []byte("example.com")) {
|
|
t.Fatal("HTTP plaintext visible on client->server tunnel")
|
|
}
|
|
|
|
var frame bytes.Buffer
|
|
if err := binary.Write(&frame, binary.BigEndian, length); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
frame.Write(ciphertext)
|
|
plain, err := encryption.ReadEncryptedFrame(&frame, tc.derivedKey)
|
|
if err != nil {
|
|
t.Fatalf("decrypt of tunneled frame failed: %v", err)
|
|
}
|
|
if !bytes.Equal(plain, req) {
|
|
t.Errorf("decrypted payload mismatch: got %q want %q", plain, req)
|
|
}
|
|
|
|
select {
|
|
case err := <-writeErr:
|
|
if err != nil {
|
|
t.Fatalf("write to local conn failed: %v", err)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("timed out waiting for local write")
|
|
}
|
|
}
|
|
|
|
func TestForwardDataDecryptsTunnelToLocal(t *testing.T) {
|
|
tc := testClient(t)
|
|
defer tc.Stop()
|
|
|
|
tunnelA, tunnelB := net.Pipe()
|
|
localA, localB := net.Pipe()
|
|
defer tunnelA.Close()
|
|
defer tunnelB.Close()
|
|
defer localA.Close()
|
|
defer localB.Close()
|
|
|
|
go tc.forwardData(tunnelA, localA, false)
|
|
|
|
payload := []byte("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello")
|
|
writeErr := make(chan error, 1)
|
|
go func() {
|
|
writeErr <- encryption.WriteEncryptedFrame(tunnelB, payload, tc.derivedKey)
|
|
}()
|
|
|
|
localB.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
got := make([]byte, len(payload))
|
|
if _, err := io.ReadFull(localB, got); err != nil {
|
|
t.Fatalf("failed to read decrypted payload: %v", err)
|
|
}
|
|
if !bytes.Equal(got, payload) {
|
|
t.Errorf("got %q want %q", got, payload)
|
|
}
|
|
|
|
select {
|
|
case err := <-writeErr:
|
|
if err != nil {
|
|
t.Fatalf("WriteEncryptedFrame failed: %v", err)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("timed out waiting for tunnel write")
|
|
}
|
|
}
|