mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
![David Anderson](/assets/img/avatar_default.png)
This is so that we can plumb our client capability version through the protocol as the Noise version. The capability version increments more frequently than strictly required (the Noise version only needs to change when cryptographically-significant changes are made to the protocol, whereas the capability version also indicates changes in non-cryptographically-significant parts of the protocol), but this gives us a safe pre-auth way to determine if the client supports future protocol features, while still relying on Noise's strong assurance that the client and server have agreed on the same version. Currently, the server executes the same protocol regardless of the version number, and just presents the version to the caller so they can do capability-based things in the upper RPC protocol. In future, we may add a ratchet to disallow obsolete protocols, or vary the Noise handshake behavior based on requested version. Updates #3488 Signed-off-by: David Anderson <danderson@tailscale.com>
258 lines
6.8 KiB
Go
258 lines
6.8 KiB
Go
// Copyright (c) 2021 Tailscale Inc & 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 controlbase
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
|
|
tsnettest "tailscale.com/net/nettest"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
// Can a reference Noise IK client talk to our server?
|
|
func TestInteropClient(t *testing.T) {
|
|
var (
|
|
s1, s2 = tsnettest.NewConn("noise", 128000)
|
|
controlKey = key.NewMachine()
|
|
machineKey = key.NewMachine()
|
|
serverErr = make(chan error, 2)
|
|
serverBytes = make(chan []byte, 1)
|
|
c2s = "client>server"
|
|
s2c = "server>client"
|
|
)
|
|
|
|
go func() {
|
|
server, err := Server(context.Background(), s2, controlKey, testProtocolVersion, nil)
|
|
serverErr <- err
|
|
if err != nil {
|
|
return
|
|
}
|
|
var buf [1024]byte
|
|
_, err = io.ReadFull(server, buf[:len(c2s)])
|
|
serverBytes <- buf[:len(c2s)]
|
|
if err != nil {
|
|
serverErr <- err
|
|
return
|
|
}
|
|
_, err = server.Write([]byte(s2c))
|
|
serverErr <- err
|
|
}()
|
|
|
|
gotS2C, err := noiseExplorerClient(s1, controlKey.Public(), machineKey, []byte(c2s))
|
|
if err != nil {
|
|
t.Fatalf("failed client interop: %v", err)
|
|
}
|
|
if string(gotS2C) != s2c {
|
|
t.Fatalf("server sent unexpected data %q, want %q", string(gotS2C), s2c)
|
|
}
|
|
|
|
if err := <-serverErr; err != nil {
|
|
t.Fatalf("server handshake failed: %v", err)
|
|
}
|
|
if err := <-serverErr; err != nil {
|
|
t.Fatalf("server read/write failed: %v", err)
|
|
}
|
|
if got := string(<-serverBytes); got != c2s {
|
|
t.Fatalf("server received %q, want %q", got, c2s)
|
|
}
|
|
}
|
|
|
|
// Can our client talk to a reference Noise IK server?
|
|
func TestInteropServer(t *testing.T) {
|
|
var (
|
|
s1, s2 = tsnettest.NewConn("noise", 128000)
|
|
controlKey = key.NewMachine()
|
|
machineKey = key.NewMachine()
|
|
clientErr = make(chan error, 2)
|
|
clientBytes = make(chan []byte, 1)
|
|
c2s = "client>server"
|
|
s2c = "server>client"
|
|
)
|
|
|
|
go func() {
|
|
client, err := Client(context.Background(), s1, machineKey, controlKey.Public(), testProtocolVersion)
|
|
clientErr <- err
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = client.Write([]byte(c2s))
|
|
if err != nil {
|
|
clientErr <- err
|
|
return
|
|
}
|
|
var buf [1024]byte
|
|
_, err = io.ReadFull(client, buf[:len(s2c)])
|
|
clientBytes <- buf[:len(s2c)]
|
|
clientErr <- err
|
|
}()
|
|
|
|
gotC2S, err := noiseExplorerServer(s2, controlKey, machineKey.Public(), []byte(s2c))
|
|
if err != nil {
|
|
t.Fatalf("failed server interop: %v", err)
|
|
}
|
|
if string(gotC2S) != c2s {
|
|
t.Fatalf("server sent unexpected data %q, want %q", string(gotC2S), c2s)
|
|
}
|
|
|
|
if err := <-clientErr; err != nil {
|
|
t.Fatalf("client handshake failed: %v", err)
|
|
}
|
|
if err := <-clientErr; err != nil {
|
|
t.Fatalf("client read/write failed: %v", err)
|
|
}
|
|
if got := string(<-clientBytes); got != s2c {
|
|
t.Fatalf("client received %q, want %q", got, s2c)
|
|
}
|
|
}
|
|
|
|
// noiseExplorerClient uses the Noise Explorer implementation of Noise
|
|
// IK to handshake as a Noise client on conn, transmit payload, and
|
|
// read+return a payload from the peer.
|
|
func noiseExplorerClient(conn net.Conn, controlKey key.MachinePublic, machineKey key.MachinePrivate, payload []byte) ([]byte, error) {
|
|
var mk keypair
|
|
copy(mk.private_key[:], machineKey.UntypedBytes())
|
|
copy(mk.public_key[:], machineKey.Public().UntypedBytes())
|
|
var peerKey [32]byte
|
|
copy(peerKey[:], controlKey.UntypedBytes())
|
|
session := InitSession(true, protocolVersionPrologue(testProtocolVersion), mk, peerKey)
|
|
|
|
_, msg1 := SendMessage(&session, nil)
|
|
var hdr [initiationHeaderLen]byte
|
|
binary.BigEndian.PutUint16(hdr[:2], testProtocolVersion)
|
|
hdr[2] = msgTypeInitiation
|
|
binary.BigEndian.PutUint16(hdr[3:5], 96)
|
|
if _, err := conn.Write(hdr[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg1.ne[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg1.ns); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg1.ciphertext); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var buf [1024]byte
|
|
if _, err := io.ReadFull(conn, buf[:51]); err != nil {
|
|
return nil, err
|
|
}
|
|
// ignore the header for this test, we're only checking the noise
|
|
// implementation.
|
|
msg2 := messagebuffer{
|
|
ciphertext: buf[35:51],
|
|
}
|
|
copy(msg2.ne[:], buf[3:35])
|
|
_, p, valid := RecvMessage(&session, &msg2)
|
|
if !valid {
|
|
return nil, errors.New("handshake failed")
|
|
}
|
|
if len(p) != 0 {
|
|
return nil, errors.New("non-empty payload")
|
|
}
|
|
|
|
_, msg3 := SendMessage(&session, payload)
|
|
hdr[0] = msgTypeRecord
|
|
binary.BigEndian.PutUint16(hdr[1:3], uint16(len(msg3.ciphertext)))
|
|
if _, err := conn.Write(hdr[:3]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg3.ciphertext); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := io.ReadFull(conn, buf[:3]); err != nil {
|
|
return nil, err
|
|
}
|
|
// Ignore all of the header except the payload length
|
|
plen := int(binary.BigEndian.Uint16(buf[1:3]))
|
|
if _, err := io.ReadFull(conn, buf[:plen]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msg4 := messagebuffer{
|
|
ciphertext: buf[:plen],
|
|
}
|
|
_, p, valid = RecvMessage(&session, &msg4)
|
|
if !valid {
|
|
return nil, errors.New("transport message decryption failed")
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func noiseExplorerServer(conn net.Conn, controlKey key.MachinePrivate, wantMachineKey key.MachinePublic, payload []byte) ([]byte, error) {
|
|
var mk keypair
|
|
copy(mk.private_key[:], controlKey.UntypedBytes())
|
|
copy(mk.public_key[:], controlKey.Public().UntypedBytes())
|
|
session := InitSession(false, protocolVersionPrologue(testProtocolVersion), mk, [32]byte{})
|
|
|
|
var buf [1024]byte
|
|
if _, err := io.ReadFull(conn, buf[:101]); err != nil {
|
|
return nil, err
|
|
}
|
|
// Ignore the header, we're just checking the noise implementation.
|
|
msg1 := messagebuffer{
|
|
ns: buf[37:85],
|
|
ciphertext: buf[85:101],
|
|
}
|
|
copy(msg1.ne[:], buf[5:37])
|
|
_, p, valid := RecvMessage(&session, &msg1)
|
|
if !valid {
|
|
return nil, errors.New("handshake failed")
|
|
}
|
|
if len(p) != 0 {
|
|
return nil, errors.New("non-empty payload")
|
|
}
|
|
|
|
_, msg2 := SendMessage(&session, nil)
|
|
var hdr [headerLen]byte
|
|
hdr[0] = msgTypeResponse
|
|
binary.BigEndian.PutUint16(hdr[1:3], 48)
|
|
if _, err := conn.Write(hdr[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg2.ne[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg2.ciphertext[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := io.ReadFull(conn, buf[:3]); err != nil {
|
|
return nil, err
|
|
}
|
|
plen := int(binary.BigEndian.Uint16(buf[1:3]))
|
|
if _, err := io.ReadFull(conn, buf[:plen]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msg3 := messagebuffer{
|
|
ciphertext: buf[:plen],
|
|
}
|
|
_, p, valid = RecvMessage(&session, &msg3)
|
|
if !valid {
|
|
return nil, errors.New("transport message decryption failed")
|
|
}
|
|
|
|
_, msg4 := SendMessage(&session, payload)
|
|
hdr[0] = msgTypeRecord
|
|
binary.BigEndian.PutUint16(hdr[1:3], uint16(len(msg4.ciphertext)))
|
|
if _, err := conn.Write(hdr[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := conn.Write(msg4.ciphertext); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return p, nil
|
|
}
|