Skip to content

Commit 3b790e8

Browse files
committed
A client authenticator + test for path reflection
1 parent 21fe878 commit 3b790e8

7 files changed

Lines changed: 330 additions & 35 deletions

File tree

authenticator/pathreflection.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package authenticator
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/google/gopacket"
7+
"github.com/google/gopacket/layers"
8+
"github.com/willscott/sp3"
9+
"golang.org/x/net/proxy"
10+
11+
"log"
12+
"math/rand"
13+
"net"
14+
"net/http"
15+
16+
"strings"
17+
)
18+
19+
type PathReflectionAuth struct {
20+
Dialer proxy.Dialer
21+
servers map[string]string
22+
clientIP net.IP
23+
conn net.Conn
24+
done chan<- string
25+
}
26+
27+
//This should be kept in-sync with server/lib/pathreflection
28+
type pathReflectionState struct {
29+
ServerIP net.IP
30+
ServerPort uint16
31+
ClientIP net.IP
32+
ClientPort uint16
33+
SequenceNumber uint32
34+
AcknowledgementNumber uint32
35+
}
36+
37+
// The default local port range for debian jessie
38+
const IP_LOCAL_PORT_LOW = 32768
39+
const IP_LOCAL_PORT_HIGH = 60999
40+
41+
func CreatePathReflectionAuthFromURL(sp3Url string, clientIP net.IP) (*PathReflectionAuth, error) {
42+
resp, err := http.Get(sp3Url)
43+
if err != nil {
44+
return nil, err
45+
}
46+
defer resp.Body.Close()
47+
48+
servers := map[string]interface{}{}
49+
err = json.NewDecoder(resp.Body).Decode(&servers)
50+
if err != nil {
51+
return nil, err
52+
}
53+
typedServers := make(map[string]string)
54+
for k := range servers {
55+
typedServers[k] = k
56+
}
57+
return CreatePathReflectionAuth(typedServers, clientIP), nil
58+
}
59+
60+
func CreatePathReflectionAuth(servers map[string]string, clientIP net.IP) *PathReflectionAuth {
61+
pra := new(PathReflectionAuth)
62+
pra.clientIP = clientIP
63+
pra.servers = servers
64+
return pra
65+
}
66+
67+
func (p *PathReflectionAuth) Authenticate(done chan<- string) (sp3.AuthenticationMethod, []byte, error) {
68+
p.done = done
69+
70+
// Choose an allowed host/ip
71+
var addr string
72+
var err error
73+
if len(p.servers) == 0 {
74+
return sp3.PATHREFLECTION, nil, errors.New("No servers configured for reflection.")
75+
} else {
76+
pos := rand.Int() % len(p.servers)
77+
for k := range p.servers {
78+
if pos == 0 {
79+
addr = k
80+
break
81+
}
82+
pos -= 1
83+
}
84+
}
85+
log.Printf("Connection will be to %s", addr)
86+
// Connect
87+
if p.Dialer == nil {
88+
p.Dialer = &net.Dialer{}
89+
}
90+
p.conn, err = p.Dialer.Dial("ip4:tcp", addr)
91+
if err != nil {
92+
return sp3.PATHREFLECTION, nil, err
93+
}
94+
95+
// TCP Handshake
96+
state := pathReflectionState{
97+
p.clientIP,
98+
uint16(IP_LOCAL_PORT_LOW + rand.Int()%(IP_LOCAL_PORT_HIGH-IP_LOCAL_PORT_LOW)),
99+
net.ParseIP(addr),
100+
uint16(80),
101+
uint32(rand.Int()),
102+
0,
103+
}
104+
105+
iplayer := &layers.IPv4{
106+
Version: 4,
107+
IHL: 5,
108+
TTL: 64,
109+
Protocol: 6,
110+
SrcIP: state.ClientIP,
111+
DstIP: state.ServerIP,
112+
}
113+
114+
tcplayer := &layers.TCP{
115+
SrcPort: layers.TCPPort(state.ClientPort),
116+
DstPort: layers.TCPPort(state.ServerPort),
117+
Window: 4380,
118+
Seq: state.SequenceNumber,
119+
SYN: true,
120+
}
121+
122+
buf := gopacket.NewSerializeBuffer()
123+
tcplayer.SetNetworkLayerForChecksum(iplayer)
124+
err = gopacket.SerializeLayers(buf, gopacket.SerializeOptions{
125+
ComputeChecksums: true,
126+
FixLengths: true,
127+
}, tcplayer)
128+
if err != nil {
129+
return sp3.PATHREFLECTION, nil, err
130+
}
131+
log.Printf("About to write SYN")
132+
if _, err = p.conn.Write(buf.Bytes()); err != nil {
133+
return sp3.PATHREFLECTION, nil, err
134+
}
135+
136+
// Wait for a syn-ack to learn server's squence number.
137+
log.Printf("Waiting for SYN-ACK")
138+
synackbytes := make([]byte, 2048)
139+
respn, err := p.conn.Read(synackbytes)
140+
if err != nil {
141+
return sp3.PATHREFLECTION, nil, err
142+
}
143+
rpkt := gopacket.NewPacket(synackbytes[0:respn], layers.LayerTypeTCP, gopacket.Lazy)
144+
if tcpLayer := rpkt.Layer(layers.LayerTypeTCP); tcpLayer != nil {
145+
tcp, _ := tcpLayer.(*layers.TCP)
146+
state.AcknowledgementNumber = tcp.Seq + 1
147+
} else {
148+
return sp3.PATHREFLECTION, nil, errors.New("SYNACK not understood.")
149+
}
150+
151+
// Set up the listener for server response to injected query.
152+
go p.listen()
153+
154+
// Leak State
155+
data, err := json.Marshal(state)
156+
if err != nil {
157+
return sp3.PATHREFLECTION, nil, err
158+
}
159+
return sp3.PATHREFLECTION, data, nil
160+
}
161+
162+
func (p *PathReflectionAuth) listen() {
163+
bufbytes := make([]byte, 2048)
164+
respn, err := p.conn.Read(bufbytes)
165+
log.Printf("Path Reflection got an incoming packet.")
166+
if err != nil {
167+
log.Printf("Couldn't read path reflection packet: %v", err)
168+
p.done <- ""
169+
return
170+
}
171+
rpkt := gopacket.NewPacket(bufbytes[0:respn], layers.LayerTypeTCP, gopacket.Default)
172+
if payload := rpkt.ApplicationLayer(); payload != nil {
173+
strpayload := string(payload.Payload())
174+
idx := strings.Index(strpayload, "sp3.")
175+
if idx == -1 {
176+
p.done <- ""
177+
return
178+
}
179+
idx += 4
180+
end := strings.IndexFunc(strpayload[idx:], isbase64)
181+
if end == -1 {
182+
p.done <- ""
183+
return
184+
}
185+
p.done <- strpayload[idx:idx + end]
186+
} else {
187+
err := rpkt.ErrorLayer()
188+
log.Printf("Couldn't parse packet!", err)
189+
p.done <- ""
190+
}
191+
}
192+
193+
func isbase64(c rune) bool {
194+
val := (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
195+
return !val
196+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package authenticator
2+
3+
import (
4+
"encoding/json"
5+
"github.com/willscott/sp3"
6+
"github.com/willscott/sp3/server/lib"
7+
"net"
8+
"testing"
9+
)
10+
11+
type MockDialer struct {
12+
net.Conn
13+
}
14+
15+
func (m *MockDialer) Dial(network, address string) (net.Conn, error) {
16+
return m.Conn, nil
17+
}
18+
19+
func TestAuthenticate(t *testing.T) {
20+
servconf := server.Config{8888, "eth0", "0", "0", "../server/pathreflection.json"}
21+
serv := server.NewServer(servconf)
22+
if serv == nil {
23+
t.Fatal("Could not start server.")
24+
}
25+
go serv.Serve()
26+
27+
// Create path reflection authenticator.
28+
auth, err := CreatePathReflectionAuthFromURL("http://localhost:8888/pathreflection.json", net.IP{0, 0, 0, 0})
29+
if err != nil {
30+
t.Fatal("Could not create reflection auth from local server.", err)
31+
}
32+
33+
// Make a dummy dialer.
34+
authClientConn, authConnServer := net.Pipe()
35+
auth.Dialer = &MockDialer{authClientConn}
36+
37+
// Receive initial syn which authenticator should send out.
38+
connBuf := make([]byte, 2048)
39+
go func() {
40+
n, err := authConnServer.Read(connBuf)
41+
if err != nil {
42+
t.Fatal("Authenticator failed to establish connection.")
43+
}
44+
connBuf = connBuf[0:n]
45+
// Send the syn-ack.
46+
authConnServer.Write(connBuf)
47+
}()
48+
49+
// Authenticate
50+
done := make(chan string, 1)
51+
mode, opts, err := auth.Authenticate(done)
52+
if err != nil || mode != sp3.PATHREFLECTION || opts == nil || len(connBuf) == 0 {
53+
t.Fatal("Could not begin auth process", err)
54+
}
55+
56+
// Send the injected packet.
57+
server.TestSpoofChannel = make(chan []byte, 1)
58+
decodeopts := &server.PathReflectionState{}
59+
if err = json.Unmarshal(opts, decodeopts); err != nil {
60+
t.Fatal("Could not understand auth opts", err)
61+
}
62+
challenge, err := server.SendPathReflectionChallenge(servconf, decodeopts)
63+
if err != nil {
64+
t.Fatal("Could not generate auth pkg", err)
65+
}
66+
67+
// Strip off the ip header.
68+
injectPkt := <- server.TestSpoofChannel
69+
authConnServer.Write(injectPkt[20:])
70+
71+
// auth hsould now be done
72+
token := <-done
73+
if token != challenge {
74+
t.Fatal("Authentication extracted wrong token from response.")
75+
}
76+
}

client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func Dial(sp3server url.URL, destination net.IP, auth Authenticator, dialer *web
4747
go conn.readLoop()
4848

4949
// Wait for Authenticator to finish challenge.
50-
AuthLoop:
50+
AuthLoop:
5151
for {
5252
select {
5353
case challenge := <-finished:
@@ -149,7 +149,7 @@ func (s *Sp3Conn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
149149
return 0, nil, errors.New("SP3 Connections do not receive data.")
150150
}
151151

152-
func extractHost(addr net.Addr) (string) {
152+
func extractHost(addr net.Addr) string {
153153
host, _, err := net.SplitHostPort(addr.String())
154154
if err != nil || len(host) == 0 {
155155
return addr.String()

server/lib/pathreflection.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ import (
2020
)
2121

2222
type PathReflectionState struct {
23-
serverIP net.IP
24-
serverPort uint16
25-
clientIP net.IP
26-
clientPort uint16
27-
sequenceNumber uint32
23+
ServerIP net.IP
24+
ServerPort uint16
25+
ClientIP net.IP
26+
ClientPort uint16
27+
SequenceNumber uint32
28+
AcknowledgementNumber uint32
2829
}
2930

30-
func getPathReflectionServers() map[string]string {
31-
data, err := ioutil.ReadFile("pathreflection.json")
31+
func getPathReflectionServers(path string) map[string]string {
32+
data, err := ioutil.ReadFile(path)
3233
if err != nil {
3334
log.Fatalf("Couldn't read path reflection config: %s", err)
3435
return nil
@@ -49,15 +50,23 @@ func genToken() (string, error) {
4950
return "", err
5051
}
5152
encoded := base64.StdEncoding.EncodeToString(b)
52-
return encoded, nil
53+
// remove non alphanumeric characters
54+
output := ""
55+
for _, code := range encoded {
56+
if code == '+' || code == '/' || code == '=' {
57+
continue
58+
}
59+
output = output + string(code)
60+
}
61+
return output, nil
5362
}
5463

55-
func PathReflectionServerTrusted(state *PathReflectionState) bool {
56-
_, ok := getPathReflectionServers()[state.serverIP.String()]
64+
func PathReflectionServerTrusted(conf Config, state *PathReflectionState) bool {
65+
_, ok := getPathReflectionServers(conf.PathReflectionFile)[state.ServerIP.String()]
5766
return ok
5867
}
5968

60-
func SendPathReflectionChallenge(state *PathReflectionState) (string, error) {
69+
func SendPathReflectionChallenge(conf Config, state *PathReflectionState) (string, error) {
6170
token, err := genToken()
6271
if err != nil {
6372
return "", err
@@ -71,17 +80,20 @@ func SendPathReflectionChallenge(state *PathReflectionState) (string, error) {
7180
IHL: 5,
7281
TTL: 64,
7382
Protocol: 6,
74-
SrcIP: state.clientIP,
75-
DstIP: state.serverIP,
83+
SrcIP: state.ClientIP,
84+
DstIP: state.ServerIP,
7685
}
7786
tcp := &layers.TCP{
78-
SrcPort: layers.TCPPort(state.clientPort),
79-
DstPort: layers.TCPPort(state.serverPort),
87+
SrcPort: layers.TCPPort(state.ClientPort),
88+
DstPort: layers.TCPPort(state.ServerPort),
8089
Window: 4380,
81-
Seq: state.sequenceNumber,
90+
Seq: state.SequenceNumber,
91+
Ack: state.AcknowledgementNumber,
92+
ACK: true,
93+
DataOffset: 5,
8294
}
8395
tcp.SetNetworkLayerForChecksum(ip)
84-
host := getPathReflectionServers()[state.serverIP.String()]
96+
host := getPathReflectionServers(conf.PathReflectionFile)[state.ServerIP.String()]
8597
request := "GET /sp3." + token + "/ HTTP/1.0\r\nHost: " + host + "\r\n\r\n"
8698
ip.Length = 20 + 20 + uint16(len(request))
8799
payload := gopacket.Payload([]byte(request))
@@ -90,7 +102,7 @@ func SendPathReflectionChallenge(state *PathReflectionState) (string, error) {
90102
}
91103

92104
//send.
93-
if err = SpoofIPv4Message(buf.Bytes(), state.clientIP, state.serverIP); err != nil {
105+
if err = SpoofIPv4Message(buf.Bytes(), state.ClientIP, state.ServerIP); err != nil {
94106
return "", err
95107
}
96108

server/lib/pathreflection_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ func TestGenPacket(t *testing.T) {
1515
net.IP{127, 0, 0, 1},
1616
uint16(8081),
1717
0,
18+
0,
1819
}
19-
challenge, err := SendPathReflectionChallenge(state)
20+
conf := Config{0, "", "", "", "../pathreflection.json"}
21+
challenge, err := SendPathReflectionChallenge(conf, state)
2022
if err != nil {
2123
t.Fatal("Error sending challenge", err)
2224
}

0 commit comments

Comments
 (0)