/* 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) } } }