Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
5.7 KB go
/* SPDX-License-Identifier: MIT
 *
 * Phobos
 */

package conf

import (
	"encoding/base64"
	"strings"
	"testing"

	"golang.zx2c4.com/wireguard/windows/phobos"
)

const specWireGuardLink = "phobos://W0ludGVyZmFjZV0KUHJpdmF0ZUtleSA9IE1Ccm5ab1RkeVQvTFI0WHBCN3RFbFN4eVZUUWRYRncwdHZWSk9NU0wvR0k9CkFkZHJlc3MgPSAxMC44LjAuNC8zMiwgZmRjYzphZDk0OmJhY2Y6NjFhNDo6Y2FmZTo0LzEyOApNVFUgPSAxNDIwCkROUyA9IDguOC44LjgsIDIwMDE6NDg2MDo0ODYwOjo4ODg4CgpbUGVlcl0KUHVibGljS2V5ID0gZy9HNHkyWGtUWTVtUExNWVlYWENhcnZ5eFVTSFV6TTF2cElZUkh3d0ZUND0KUHJlc2hhcmVkS2V5ID0gbDVURldNM3RJUjBEazg3dVBFblZrcXFsNExta2NQb2VXbU9LWktmeWVmWT0KQWxsb3dlZElQcyA9IDAuMC4wLjAvMCwgOjovMApQZXJzaXN0ZW50S2VlcGFsaXZlID0gMApFbmRwb2ludCA9IDEyNy4wLjAuMToxMzI1NQoKW2luc3RhbmNlXQpzb3VyY2UtaWYgPSAxMjcuMC4wLjEKc291cmNlLWxwb3J0ID0gMTMyNTUKdGFyZ2V0ID0gMTMwLjQ5LjE4NS4xMzY6NTE4MjQKa2V5ID0gWFIwTkVmOE1oR0FHY0NwYwptYXNraW5nID0gU1RVTgp2ZXJib3NlID0gSU5GTwppZGxlLXRpbWVvdXQgPSAzMDAKbWF4LWR1bW15ID0gNDU#Mobil-phone"

const specSocks5Link = "phobos://W2luc3RhbmNlXQptb2RlID0gc29ja3M1CnJvbGUgPSBjbGllbnQKc291cmNlLWlmID0gMTI3LjAuMC4xCnNvdXJjZS1scG9ydCA9IDEwODAKdGFyZ2V0ID0gdnBuLmV4YW1wbGUuY29tOjUxODI0CmtleSA9IFhSME5FZjhNaEdBR2NDcGMKbWFza2luZyA9IE1FRElBCnZlcmJvc2UgPSBlcnJvcgptZWRpYS1zc3JjID0gMzA1NDE5ODk2Cgpbc29ja3M1XQpsb2dpbiA9IEFiM0txCnBhc3N3b3JkID0gWng5TG0#Mobil-phone"

func linkFrom(config, fragment string) string {
	return PhobosLinkScheme + base64.RawURLEncoding.EncodeToString([]byte(config)) + "#" + fragment
}

func TestDecodeSpecWireGuardLink(t *testing.T) {
	text, name, err := DecodePhobosLink(specWireGuardLink)
	if err != nil {
		t.Fatalf("unable to decode: %v", err)
	}
	if name != "Mobil-phone" {
		t.Errorf("name = %q", name)
	}

	config, err := FromWgQuick(text, name)
	if err != nil {
		t.Fatalf("unable to parse the decoded configuration: %v", err)
	}
	if len(config.Peers) != 1 {
		t.Fatalf("expected one peer, got %d", len(config.Peers))
	}
	o := config.Peers[0].Obfuscation
	if o == nil {
		t.Fatal("the peer carries no obfuscation")
	}
	if o.Target.Host != "130.49.185.136" || o.Target.Port != 51824 {
		t.Errorf("target = %v", o.Target.String())
	}
	if o.Masking != phobos.MaskingSTUN {
		t.Errorf("masking = %v", o.Masking)
	}
	if o.MaxDummy != 45 || o.SourceListenPort != 13255 {
		t.Errorf("max-dummy = %d, source-lport = %d", o.MaxDummy, o.SourceListenPort)
	}
	if config.Interface.MTU != 1420 || len(config.Interface.DNS) != 2 {
		t.Errorf("interface = %+v", config.Interface)
	}
}

func TestDecodeSpecSocks5Link(t *testing.T) {
	text, name, err := DecodePhobosLink(specSocks5Link)
	if err != nil {
		t.Fatalf("unable to decode: %v", err)
	}
	config, err := FromWgQuick(text, name)
	if err != nil {
		t.Fatalf("unable to parse the decoded configuration: %v", err)
	}
	if !config.IsSocks5() {
		t.Fatal("expected a SOCKS5 tunnel")
	}
	o := config.Obfuscation
	if o.Target.Host != "vpn.example.com" || o.SourceListenPort != 1080 {
		t.Errorf("target = %v, source-lport = %d", o.Target.String(), o.SourceListenPort)
	}
	if o.Masking != phobos.MaskingMEDIA || o.MediaSSRC != 305419896 {
		t.Errorf("masking = %v, media-ssrc = %d", o.Masking, o.MediaSSRC)
	}
	if o.Login != "Ab3Kq" || o.Password != "Zx9Lm" {
		t.Errorf("credentials = %q/%q", o.Login, o.Password)
	}
}

func TestDecodeTreatsNoneAsUnset(t *testing.T) {
	text, _, err := DecodePhobosLink(linkFrom(`[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
Address = 10.8.0.2/32
MTU = none
DNS = none

[Peer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
PresharedKey = none
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 0
Endpoint = 127.0.0.1:51822

[instance]
source-if = 127.0.0.1
source-lport = 51822
target = vpn.example.com:51823
key = secret
masking = none
verbose = none
idle-timeout = none
max-dummy = none
`, "peer"))
	if err != nil {
		t.Fatalf("unable to decode: %v", err)
	}
	if strings.Contains(text, "none") {
		t.Fatalf("unset fields survived decoding:\n%s", text)
	}

	config, err := FromWgQuick(text, "peer")
	if err != nil {
		t.Fatalf("unable to parse: %v", err)
	}
	if config.Interface.MTU != 0 || len(config.Interface.DNS) != 0 {
		t.Errorf("unset interface fields leaked: %+v", config.Interface)
	}
	if !config.Peers[0].PresharedKey.IsZero() {
		t.Error("unset preshared key leaked")
	}
	if config.Peers[0].Obfuscation.Masking != phobos.MaskingNone {
		t.Error("masking should fall back to none")
	}
}

func TestDecodeNames(t *testing.T) {
	for fragment, want := range map[string]string{
		"Mobil-phone":              "Mobil-phone",
		"none":                     "phobos",
		"":                         "phobos",
		"My%20Laptop":              "My-Laptop",
		"%D0%A2%D0%B5%D1%81%D1%82": "phobos",
		"a/b\\c":                   "a-b-c",
		"...":                      "phobos",
		strings.Repeat("x", 64):    strings.Repeat("x", 32),
	} {
		_, name, err := DecodePhobosLink(linkFrom("[Interface]\nPrivateKey = x\n", fragment))
		if err != nil {
			t.Fatalf("unable to decode %q: %v", fragment, err)
		}
		if name != want {
			t.Errorf("fragment %q gave name %q, want %q", fragment, name, want)
		}
		if !TunnelNameIsValid(name) {
			t.Errorf("fragment %q produced an invalid tunnel name %q", fragment, name)
		}
	}
}

func TestDecodeRejectsBadLinks(t *testing.T) {
	for name, link := range map[string]string{
		"wrong scheme": "https://example.com/#x",
		"no payload":   "phobos://#x",
		"bad base64":   "phobos://!!!!#x",
		"empty":        "",
	} {
		if _, _, err := DecodePhobosLink(link); err == nil {
			t.Errorf("%s: expected an error", name)
		}
	}
}

func TestIsPhobosLink(t *testing.T) {
	for link, want := range map[string]bool{
		"phobos://abc#n":    true,
		"  phobos://abc#n ": true,
		"PHOBOS://abc#n":    true,
		"phobos:/abc":       false,
		"http://abc":        false,
		"":                  false,
	} {
		if got := IsPhobosLink(link); got != want {
			t.Errorf("IsPhobosLink(%q) = %v", link, got)
		}
	}
}