diff --git a/.gitignore b/.gitignore index b6dea61c..e864ca4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +__debug_bin* +selfIdentity.plist +cmd/cdc-ncm/cdc-ncm node_modules *.mobileprovision *.p12 diff --git a/README.md b/README.md index a023fdb5..2ef680d9 100644 --- a/README.md +++ b/README.md @@ -125,3 +125,5 @@ ios listen [options] Keeps a persi ios diskspace [options] Prints disk space info. ``` +For Windows install: https://build.openvpn.net/downloads/releases +use wireguard-go \ No newline at end of file diff --git a/go.mod b/go.mod index 6ec88410..4affcde8 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.21 require ( github.com/Masterminds/semver v1.5.0 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 - go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 github.com/google/gopacket v1.1.19 github.com/google/uuid v1.1.2 github.com/grandcat/zeroconf v1.0.0 @@ -16,11 +15,14 @@ require ( github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/stretchr/testify v1.6.1 github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 - golang.org/x/crypto v0.15.0 - software.sslmate.com/src/go-pkcs12 v0.2.0 + go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 + golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20221205204356-47842c84f3db - golang.org/x/net v0.18.0 + golang.org/x/net v0.26.0 + golang.org/x/sys v0.21.0 + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 + software.sslmate.com/src/go-pkcs12 v0.2.0 ) require ( @@ -36,9 +38,9 @@ require ( github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/stretchr/objx v0.1.0 // indirect go.uber.org/mock v0.3.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.15.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a2f763ce..1ddbeafe 100644 --- a/go.sum +++ b/go.sum @@ -13,16 +13,15 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= @@ -75,42 +74,54 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -122,3 +133,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc= howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..df049953 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,37 @@ +github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= diff --git a/ios/connect.go b/ios/connect.go index 1c6abab8..9e64927c 100755 --- a/ios/connect.go +++ b/ios/connect.go @@ -2,6 +2,7 @@ package ios import ( "fmt" + "io" "net" "time" @@ -333,3 +334,51 @@ func initializeXpcConnection(h *http.HttpConnection) error { return nil } + +func initializeXpcConnection2(c io.ReadWriteCloser) error { + csWriter := c + ssWriter := c + err := xpc.EncodeMessage(csWriter, xpc.Message{ + Flags: xpc.AlwaysSetFlag, + Body: map[string]interface{}{}, + Id: 0, + }) + if err != nil { + return fmt.Errorf("initializeXpcConnection: failed to encode message: %w", err) + } + + _, err = xpc.DecodeMessage(csWriter) // TODO : figure out if need to act on this frame + if err != nil { + return fmt.Errorf("initializeXpcConnection: failed to decode message: %w", err) + } + + err = xpc.EncodeMessage(ssWriter, xpc.Message{ + Flags: xpc.InitHandshakeFlag | xpc.AlwaysSetFlag, + Body: nil, + Id: 0, + }) + if err != nil { + return fmt.Errorf("initializeXpcConnection: failed to encode message 2: %w", err) + } + + _, err = xpc.DecodeMessage(ssWriter) // TODO : figure out if need to act on this frame + if err != nil { + return fmt.Errorf("initializeXpcConnection: failed to decode message 2: %w", err) + } + + err = xpc.EncodeMessage(csWriter, xpc.Message{ + Flags: 0x201, // alwaysSetFlag | 0x200 + Body: nil, + Id: 0, + }) + if err != nil { + return fmt.Errorf("initializeXpcConnection: failed to encode message 3: %w", err) + } + + _, err = xpc.DecodeMessage(csWriter) // TODO : figure out if need to act on this frame + if err != nil { + return fmt.Errorf("initializeXpcConnection: failed to decode message 3: %w", err) + } + + return nil +} diff --git a/ios/discover.go b/ios/discover.go index de21a710..8ccbe324 100644 --- a/ios/discover.go +++ b/ios/discover.go @@ -4,11 +4,25 @@ import ( "context" "fmt" "net" + "sync" + "time" "github.com/grandcat/zeroconf" log "github.com/sirupsen/logrus" ) +type SDResponse struct { + HandshakeResponse RsdHandshakeResponse + InterfaceName string + ServiceEntry *zeroconf.ServiceEntry + Ipv6 net.IP + Err error +} + +func (r SDResponse) Addr() string { + return fmt.Sprintf("%s%%%s", r.Ipv6.String(), r.InterfaceName) +} + // FindDeviceInterfaceAddress tries to find the address of the device by browsing through all network interfaces. // It uses mDNS to discover the "_remoted._tcp" service on the local. domain. Then tries to connect to the RemoteServiceDiscovery // and checks if the udid of the device matches the udid of the device we are looking for. @@ -18,7 +32,7 @@ func FindDeviceInterfaceAddress(ctx context.Context, device DeviceEntry) (string return "", fmt.Errorf("FindDeviceInterfaceAddress: failed to get network interfaces: %w", err) } - result := make(chan string) + result := make(chan SDResponse) defer close(result) for _, iface := range ifaces { @@ -31,7 +45,9 @@ func FindDeviceInterfaceAddress(ctx context.Context, device DeviceEntry) (string } entries := make(chan *zeroconf.ServiceEntry) resolver.Browse(ctx, "_remoted._tcp", "local.", entries) - go checkEntry(ctx, device, iface.Name, entries, result) + wg := &sync.WaitGroup{} + wg.Add(1) + go checkEntry(ctx, iface.Name, entries, result, wg) } @@ -40,14 +56,64 @@ func FindDeviceInterfaceAddress(ctx context.Context, device DeviceEntry) (string return "", ctx.Err() case r := <-result: log.WithField("udid", device.Properties.SerialNumber).WithField("address", r).Debug("found device address") - return r, nil + addr := fmt.Sprintf("%s%%%s", r.Ipv6.String(), r.InterfaceName) + return addr, nil } } +const RemoteDLocal = "_remoted._tcp" + +func FindDevicesForService(ctx context.Context, service string) ([]SDResponse, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("FindDeviceInterfaceAddress: failed to get network interfaces: %w", err) + } + + result := make(chan SDResponse) + defer close(result) + wg := sync.WaitGroup{} + for _, iface := range ifaces { + resolver, err := zeroconf.NewResolver(zeroconf.SelectIfaces([]net.Interface{iface}), zeroconf.SelectIPTraffic(zeroconf.IPv6)) + if err != nil { + log.WithField("interface", iface.Name). + WithField("err", err). + Debug("failed to initialize resolver") + continue + } + entries := make(chan *zeroconf.ServiceEntry) + resolver.Browse(ctx, service, "local.", entries) + wg.Add(1) + go checkEntry(ctx, iface.Name, entries, result, &wg) + + } + results := []SDResponse{} + go func() { + for { + select { + case <-ctx.Done(): + return + case r, ok := <-result: + if !ok { + return + } + results = append(results, r) + //log.WithField("udid", device.Properties.SerialNumber).WithField("address", r).Debug("found device address") + + } + } + }() + + wg.Wait() + return results, nil +} + // checkEntry connects to all remote service discoveries and tests which one belongs to this device' udid. -func checkEntry(ctx context.Context, device DeviceEntry, interfaceName string, entries chan *zeroconf.ServiceEntry, result chan<- string) { +func checkEntry(ctx context.Context, interfaceName string, entries chan *zeroconf.ServiceEntry, result chan<- SDResponse, wg *sync.WaitGroup) { + defer wg.Done() for { select { + case <-time.After(time.Second): + return case <-ctx.Done(): return case entry := <-entries: @@ -55,24 +121,43 @@ func checkEntry(ctx context.Context, device DeviceEntry, interfaceName string, e continue } for _, ip6 := range entry.AddrIPv6 { - tryHandshake(ip6, interfaceName, device.Properties.SerialNumber, result) + tryHandshake(ip6, entry, interfaceName, result, wg) } } } + } -func tryHandshake(ip6 net.IP, interfaceName, udid string, result chan<- string) { +var NewTCP func(string, int) (RsdService, error) + +func tryHandshake(ip6 net.IP, svcEntry *zeroconf.ServiceEntry, interfaceName string, result chan<- SDResponse, wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + resp := SDResponse{ + Err: nil, + InterfaceName: interfaceName, + Ipv6: ip6, + ServiceEntry: svcEntry, + } addr := fmt.Sprintf("%s%%%s", ip6.String(), interfaceName) - s, err := NewWithAddr(addr) + port := svcEntry.Port + ff := NewTCP + if ff == nil { + ff = NewWithAddrPort + } + s, err := ff(addr, port) if err != nil { + resp.Err = err + result <- resp return } defer s.Close() h, err := s.Handshake() if err != nil { + resp.Err = err + result <- resp return } - if udid == h.Udid { - result <- addr - } + resp.HandshakeResponse = h + result <- resp } diff --git a/ios/discover_test.go b/ios/discover_test.go new file mode 100644 index 00000000..9d5ee389 --- /dev/null +++ b/ios/discover_test.go @@ -0,0 +1,28 @@ +package ios_test + +import ( + "context" + "log" + "testing" + + "github.com/danielpaulus/go-ios/ios" + "github.com/danielpaulus/go-ios/ios/tunnel" +) + +func TestDiscover(t *testing.T) { + + const amod = "_apple-mobdev2._tcp" + const r = "_remotepairing._tcp" + const rmp = "_remotepairing-manual-pairing._tcp.local." + var c = func(a string, p int) (ios.RsdService, error) { + return tunnel.NewTCP(a, p) + } + ios.NewTCP = c + rsp, err := ios.FindDevicesForService(context.Background(), r) + if err != nil { + t.Fatal(err) + } + //d := rsp[0] + + log.Fatalf("%v", rsp) +} diff --git a/ios/tunnel/codec.go b/ios/tunnel/codec.go index c821983a..7e43d3b4 100644 --- a/ios/tunnel/codec.go +++ b/ios/tunnel/codec.go @@ -2,6 +2,7 @@ package tunnel import ( "crypto/cipher" + "encoding/base64" "encoding/binary" "encoding/json" "fmt" @@ -40,6 +41,12 @@ func (p *pairingData) Decode(e map[string]interface{}) error { if data, ok := pd["data"].([]byte); ok { p.data = data } + if data, ok := pd["data"].(string); ok { + p.data, err = base64.StdEncoding.DecodeString(data) + if err != nil { + return fmt.Errorf("Decode: failed to decode base64 data: %w", err) + } + } if kind, ok := pd["kind"].(string); ok { p.kind = kind } @@ -184,16 +191,16 @@ func (c *controlChannelReadWriter) read() (map[string]interface{}, error) { // the host. This message pair uses the same nonce before that counter is increased for the next message from the host // to the device type cipherStream struct { - controlChannel *controlChannelReadWriter + controlChannel pairingService clientCipher cipher.AEAD serverCipher cipher.AEAD nonce []byte sequence uint64 } -func newCipherStream(controlChannel *controlChannelReadWriter, clientCipher, serverCipher cipher.AEAD) *cipherStream { +func newCipherStream(p pairingService, clientCipher, serverCipher cipher.AEAD) *cipherStream { return &cipherStream{ - controlChannel: controlChannel, + controlChannel: p, clientCipher: clientCipher, serverCipher: serverCipher, nonce: make([]byte, clientCipher.NonceSize()), @@ -209,7 +216,7 @@ func (c *cipherStream) write(p map[string]interface{}) error { } encrypted := c.clientCipher.Seal(nil, c.nonce, marshalled, nil) c.sequence += 1 - return c.controlChannel.write(map[string]interface{}{ + return c.controlChannel.writeEncrypted(map[string]interface{}{ "streamEncrypted": map[string]interface{}{ "_0": encrypted, }, @@ -217,18 +224,30 @@ func (c *cipherStream) write(p map[string]interface{}) error { } func (c *cipherStream) read(p *map[string]interface{}) error { - m, err := c.controlChannel.read() + m, err := c.controlChannel.readEncrypted() if err != nil { return err } if streamEncr, err := getChildMap(m, "streamEncrypted"); err == nil { - if cip, ok := streamEncr["_0"].([]byte); ok { - plain, err := c.serverCipher.Open(nil, c.nonce, cip, nil) + var cip []byte + var ok bool + if cip, ok = streamEncr["_0"].([]byte); !ok { + cips, ok := streamEncr["_0"].(string) + if !ok { + return fmt.Errorf("read: failed to get encrypted payload") + } + cip, err = base64.StdEncoding.DecodeString(cips) if err != nil { - return err + return fmt.Errorf("read: failed to decode base64 data: %w", err) } - return json.Unmarshal(plain, p) } + + plain, err := c.serverCipher.Open(nil, c.nonce, cip, nil) + if err != nil { + return err + } + return json.Unmarshal(plain, p) + } return fmt.Errorf("not implemented") } diff --git a/ios/tunnel/remotepairing.go b/ios/tunnel/remotepairing.go new file mode 100644 index 00000000..0d7c3a04 --- /dev/null +++ b/ios/tunnel/remotepairing.go @@ -0,0 +1,315 @@ +package tunnel + +import ( + "context" + "crypto/sha512" + "crypto/tls" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net" + "strings" + "sync" + "time" + + "github.com/danielpaulus/go-ios/ios" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" +) + +type RemoteTunnelService struct { + pairRecords PairRecordManager + connSequence *ConnSequence + cipher *cipherStream +} + +func (r *RemoteTunnelService) readEncrypted() (map[string]interface{}, error) { + m, err := receiveRemotePair(r.connSequence.Conn) + if err != nil { + return nil, fmt.Errorf("readEncrypted: failed to read message: %w", err) + } + return getChildMap(m, "message") +} + +func (r *RemoteTunnelService) writeEncrypted(data map[string]interface{}) error { + return sendRemotePair(data, r.connSequence) +} + +func (r *RemoteTunnelService) readResponse() (map[string]interface{}, error) { + return receiveResponse(r.connSequence.Conn) +} + +func (r *RemoteTunnelService) getCipher() *cipherStream { + return r.cipher +} + +func (r *RemoteTunnelService) writeEvent(event eventCodec) error { + return writeEvent(event, r.connSequence) +} + +func (r *RemoteTunnelService) readEvent(event eventCodec) error { + return readEvent(event, r.connSequence) +} + +func (r *RemoteTunnelService) setupCiphers(sessionKey []byte) error { + clientKey := make([]byte, 32) + _, err := hkdf.New(sha512.New, sessionKey, nil, []byte("ClientEncrypt-main")).Read(clientKey) + if err != nil { + return err + } + serverKey := make([]byte, 32) + _, err = hkdf.New(sha512.New, sessionKey, nil, []byte("ServerEncrypt-main")).Read(serverKey) + if err != nil { + return err + } + server, err := chacha20poly1305.New(serverKey) + if err != nil { + return err + } + client, err := chacha20poly1305.New(clientKey) + if err != nil { + return err + } + + r.cipher = newCipherStream(r, client, server) + + return nil +} + +func (t *RemoteTunnelService) writeRequest(req map[string]interface{}) error { + return writeRequest(req, t.connSequence) +} + +func (r *RemoteTunnelService) getPairRecords() PairRecordManager { + return r.pairRecords +} + +type ConnSequence struct { + Conn io.ReadWriteCloser + Sequence uint64 + mux sync.Mutex +} + +func (c *ConnSequence) GetAndIncrement() uint64 { + c.mux.Lock() + defer c.mux.Unlock() + c.Sequence++ + return c.Sequence +} + +var remotePairingMagic = []byte("RPPairing") + +func Verify(conn *ConnSequence) error { + data := map[string]interface{}{ + "handshake": map[string]interface{}{ + "_0": map[string]interface{}{ + "hostOptions": map[string]interface{}{ + "attemptPairVerify": true, + }, + "wireProtocolVersion": int64(19), + }, + }, + } + err := writeRequest(data, conn) + if err != nil { + return fmt.Errorf("could not send remote pairing handshake: %w", err) + } + //data, err = receiveRemotePair(conn.Conn) + //data, err = extractResponse(data) + data, _ = receiveResponse(conn.Conn) + print(data) + return nil +} + +func receiveResponse(conn io.Reader) (map[string]interface{}, error) { + data, err := receiveRemotePair(conn) + if err != nil { + return nil, fmt.Errorf("could not receive remote pairing response: %w", err) + } + return extractResponse(data) +} + +func extractResponse(data map[string]interface{}) (map[string]interface{}, error) { + message, err := getChildMap(data, "message") + if err != nil { + return nil, fmt.Errorf("could not extract message: %w", err) + } + message, err = getChildMap(message, "plain") + if err != nil { + return nil, fmt.Errorf("could not extract message: %w", err) + } + message, err = getChildMap(message, "_0") + if err != nil { + return nil, fmt.Errorf("could not extract message: %w", err) + } + + return message, nil +} + +func writeEvent(e eventCodec, conn *ConnSequence) error { + encoded := map[string]interface{}{ + "plain": map[string]interface{}{ + "_0": map[string]interface{}{ + "event": map[string]interface{}{ + "_0": e.Encode(), + }, + }, + }, + } + return sendRemotePair(encoded, conn) +} + +func readEvent(e eventCodec, conn *ConnSequence) error { + m, err := receiveRemotePair(conn.Conn) + if err != nil { + return fmt.Errorf("readEvent: failed to read message: %w", err) + } + event, err := getChildMap(m, "message", "plain", "_0", "event", "_0") + if err != nil { + return fmt.Errorf("readEvent: failed to get event payload: %w", err) + } + return e.Decode(event) +} + +func writeRequest(req map[string]interface{}, conn *ConnSequence) error { + err := sendRemotePair(map[string]interface{}{ + "plain": map[string]interface{}{ + "_0": map[string]interface{}{ + "request": map[string]interface{}{ + "_0": req, + }, + }, + }, + }, conn) + if err != nil { + return fmt.Errorf("writeRequest: failed to write message: %w", err) + } + return nil +} + +func sendRemotePair(data map[string]interface{}, conn *ConnSequence) error { + d := map[string]interface{}{ + "message": data, + "originatedBy": "host", + "sequenceNumber": conn.GetAndIncrement(), + } + b, err := json.Marshal(d) + print(string(b) + "\n") + if err != nil { + return fmt.Errorf("could not marshal remote pairing data: %w", err) + } + + _, err = conn.Conn.Write(remotePairingMagic) + l := make([]byte, 2) + binary.BigEndian.PutUint16(l, uint16(len(b))) + _, err2 := conn.Conn.Write(l) + _, err3 := conn.Conn.Write(b) + return errors.Join(err, err2, err3) +} + +func receiveRemotePair(conn io.Reader) (map[string]interface{}, error) { + magic := make([]byte, 9) + _, err := conn.Read(magic) + if err != nil { + return nil, fmt.Errorf("could not read remote pairing magic: %w", err) + } + if string(magic) != string(remotePairingMagic) { + return nil, fmt.Errorf("invalid remote pairing magic: %s", magic) + } + + var length uint16 + err = binary.Read(conn, binary.BigEndian, &length) + if err != nil { + return nil, fmt.Errorf("could not read remote pairing length: %w", err) + } + + data := make([]byte, length) + _, err = conn.Read(data) + if err != nil { + return nil, fmt.Errorf("could not read remote pairing data: %w", err) + } + + var decoded map[string]interface{} + err = json.Unmarshal(data, &decoded) + if err != nil { + return nil, fmt.Errorf("could not unmarshal remote pairing data: %w", err) + } + + return decoded, nil +} + +func ConnectRemotePairingTunnel(ctx context.Context, device ios.DeviceEntry, p PairRecordManager) (Tunnel, error) { + const amod = "_apple-mobdev2._tcp" + const r = "_remotepairing._tcp" + const rmp = "_remotepairing-manual-pairing._tcp.local." + var c = func(a string, port int) (ios.RsdService, error) { + return NewTCP(a, port, p) + } + ios.NewTCP = c + _, err := ios.FindDevicesForService(context.Background(), r) + if err != nil { + return Tunnel{}, err + } + time.Sleep(15 * time.Second) + + if err != nil { + return Tunnel{}, fmt.Errorf("ManualPairAndConnectToTunnel: failed to create tunnel listener: %w", err) + } + + return Tunnel{}, nil +} + +func NewTCP(addr1 string, port1 int, p PairRecordManager) (ios.RsdService, error) { + addr, err := net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", addr1, port1)) + if err != nil { + return ios.RsdService{}, fmt.Errorf("ConnectToHttp2WithAddr: failed to resolve address: %w", err) + } + + /* + ctx = SSLPSKContext(ssl.PROTOCOL_TLSv1_2) + ctx.psk = self.encryption_key + ctx.set_ciphers('PSK') + */ + conn, err := net.DialTCP("tcp", nil, addr) + if err != nil { + return ios.RsdService{}, fmt.Errorf("ConnectToHttp2WithAddr: failed to dial: %w", err) + } + + err = conn.SetKeepAlive(true) + if err != nil { + return ios.RsdService{}, fmt.Errorf("ConnectToHttp2WithAddr: failed to set keepalive: %w", err) + } + err = conn.SetKeepAlivePeriod(1 * time.Second) + if err != nil { + return ios.RsdService{}, fmt.Errorf("ConnectToHttp2WithAddr: failed to set keepalive period: %w", err) + } + c := ConnSequence{ + Conn: conn, + mux: sync.Mutex{}, + } + //Verify(&c) + remoteTunnelService := RemoteTunnelService{ + pairRecords: p, + connSequence: &c, + } + err = ManualPair(&remoteTunnelService) + if err != nil { + slog.Error("failed to verify pair", "err", err) + return ios.RsdService{}, fmt.Errorf("ConnectToHttp2WithAddr: failed to verify pair: %w", err) + } + tunnelInfo, err := createTunnelListener(&remoteTunnelService, "tcp") + conf, err := createTlsConfig(tunnelInfo) + if err != nil { + return ios.RsdService{}, err + } + addr1 = strings.Replace(addr1, "%en12", "%en0", -1) + cs, err := net.Dial("tcp", fmt.Sprintf("[%s]:%d", addr1, tunnelInfo.TunnelPort)) + tlsconn, err := tls.Dial("tcp", fmt.Sprintf("[%s]:%d", addr1, tunnelInfo.TunnelPort), conf) + tun, err := connectToTunnelLockdown(context.Background(), ios.DeviceEntry{Properties: ios.DeviceProperties{SerialNumber: "d"}}, tlsconn) + + slog.Info("sdf", tun, cs) + return ios.RsdService{}, nil +} diff --git a/ios/tunnel/tun_windows.go b/ios/tunnel/tun_windows.go new file mode 100644 index 00000000..03326547 --- /dev/null +++ b/ios/tunnel/tun_windows.go @@ -0,0 +1,296 @@ +//go:build windows + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved. + */ +// taken from package wireguard-go/tun/tun_windows.go to minimize useless dependencies and transparently drop +// ipv4 packets +package tunnel + +import ( + "errors" + "fmt" + "os" + "sync" + "sync/atomic" + "time" + _ "unsafe" + + "golang.org/x/sys/windows" + "golang.zx2c4.com/wintun" +) + +type Event int + +const ( + EventUp = 1 << iota + EventDown + EventMTUUpdate +) + +type Device interface { + // File returns the file descriptor of the device. + File() *os.File + + // Read one or more packets from the Device (without any additional headers). + // On a successful read it returns the number of packets read, and sets + // packet lengths within the sizes slice. len(sizes) must be >= len(bufs). + // A nonzero offset can be used to instruct the Device on where to begin + // reading into each element of the bufs slice. + Read(bufs [][]byte, sizes []int, offset int) (n int, err error) + + // Write one or more packets to the device (without any additional headers). + // On a successful write it returns the number of packets written. A nonzero + // offset can be used to instruct the Device on where to begin writing from + // each packet contained within the bufs slice. + Write(bufs [][]byte, offset int) (int, error) + + // MTU returns the MTU of the Device. + MTU() (int, error) + + // Name returns the current name of the Device. + Name() (string, error) + + // Events returns a channel of type Event, which is fed Device events. + Events() <-chan Event + + // Close stops the Device and closes the Event channel. + Close() error + + // BatchSize returns the preferred/max number of packets that can be read or + // written in a single read/write call. BatchSize must not change over the + // lifetime of a Device. + BatchSize() int +} + +const ( + rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond) + spinloopRateThreshold = 800000000 / 8 // 800mbps + spinloopDuration = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s +) + +type rateJuggler struct { + current atomic.Uint64 + nextByteCount atomic.Uint64 + nextStartTime atomic.Int64 + changing atomic.Bool +} + +type NativeTun struct { + wt *wintun.Adapter + name string + handle windows.Handle + rate rateJuggler + session wintun.Session + readWait windows.Handle + events chan Event + running sync.WaitGroup + closeOnce sync.Once + close atomic.Bool + forcedMTU int + outSizes []int +} + +var ( + WintunTunnelType = "WireGuard" + WintunStaticRequestedGUID *windows.GUID +) + +//go:linkname procyield runtime.procyield +func procyield(cycles uint32) + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +// CreateTUN creates a Wintun interface with the given name. Should a Wintun +// interface with the same name exist, it is reused. +func CreateTUN(ifname string, mtu int) (Device, error) { + return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu) +} + +// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and +// a requested GUID. Should a Wintun interface with the same name exist, it is reused. +func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (Device, error) { + wt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID) + if err != nil { + return nil, fmt.Errorf("Error creating interface: %w", err) + } + + forcedMTU := 1420 + if mtu > 0 { + forcedMTU = mtu + } + + tun := &NativeTun{ + wt: wt, + name: ifname, + handle: windows.InvalidHandle, + events: make(chan Event, 10), + forcedMTU: forcedMTU, + } + + tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB + if err != nil { + tun.wt.Close() + close(tun.events) + return nil, fmt.Errorf("Error starting session: %w", err) + } + tun.readWait = tun.session.ReadWaitEvent() + return tun, nil +} + +func (tun *NativeTun) Name() (string, error) { + return tun.name, nil +} + +func (tun *NativeTun) File() *os.File { + return nil +} + +func (tun *NativeTun) Events() <-chan Event { + return tun.events +} + +func (tun *NativeTun) Close() error { + var err error + tun.closeOnce.Do(func() { + tun.close.Store(true) + windows.SetEvent(tun.readWait) + tun.running.Wait() + tun.session.End() + if tun.wt != nil { + tun.wt.Close() + } + close(tun.events) + }) + return err +} + +func (tun *NativeTun) MTU() (int, error) { + return tun.forcedMTU, nil +} + +// TODO: This is a temporary hack. We really need to be monitoring the interface in real time and adapting to MTU changes. +func (tun *NativeTun) ForceMTU(mtu int) { + if tun.close.Load() { + return + } + update := tun.forcedMTU != mtu + tun.forcedMTU = mtu + if update { + tun.events <- EventMTUUpdate + } +} + +func (tun *NativeTun) BatchSize() int { + // TODO: implement batching with wintun + return 1 +} + +// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking. + +func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) { + tun.running.Add(1) + defer tun.running.Done() +retry: + if tun.close.Load() { + return 0, os.ErrClosed + } + start := nanotime() + shouldSpin := tun.rate.current.Load() >= spinloopRateThreshold && uint64(start-tun.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2 + for { + if tun.close.Load() { + return 0, os.ErrClosed + } + + packet, err := tun.session.ReceivePacket() + + switch err { + case nil: + + n := copy(bufs[0][offset:], packet) + sizes[0] = n + tun.session.ReleaseReceivePacket(packet) + tun.rate.update(uint64(n)) + if packet[0]>>4 != 6 { + continue + } + return 1, nil + case windows.ERROR_NO_MORE_ITEMS: + + if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration { + windows.WaitForSingleObject(tun.readWait, windows.INFINITE) + + goto retry + } + procyield(1) + + continue + case windows.ERROR_HANDLE_EOF: + return 0, os.ErrClosed + case windows.ERROR_INVALID_DATA: + return 0, errors.New("Send ring corrupt") + } + return 0, fmt.Errorf("Read failed: %w", err) + } +} + +func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) { + tun.running.Add(1) + defer tun.running.Done() + if tun.close.Load() { + return 0, os.ErrClosed + } + + for i, buf := range bufs { + packetSize := len(buf) - offset + tun.rate.update(uint64(packetSize)) + + packet, err := tun.session.AllocateSendPacket(packetSize) + switch err { + case nil: + // TODO: Explore options to eliminate this copy. + copy(packet, buf[offset:]) + tun.session.SendPacket(packet) + continue + case windows.ERROR_HANDLE_EOF: + return i, os.ErrClosed + case windows.ERROR_BUFFER_OVERFLOW: + continue // Dropping when ring is full. + default: + return i, fmt.Errorf("Write failed: %w", err) + } + } + return len(bufs), nil +} + +// LUID returns Windows interface instance ID. +func (tun *NativeTun) LUID() uint64 { + tun.running.Add(1) + defer tun.running.Done() + if tun.close.Load() { + return 0 + } + return tun.wt.LUID() +} + +// RunningVersion returns the running version of the Wintun driver. +func (tun *NativeTun) RunningVersion() (version uint32, err error) { + return wintun.RunningVersion() +} + +func (rate *rateJuggler) update(packetLen uint64) { + now := nanotime() + total := rate.nextByteCount.Add(packetLen) + period := uint64(now - rate.nextStartTime.Load()) + if period >= rateMeasurementGranularity { + if !rate.changing.CompareAndSwap(false, true) { + return + } + rate.nextStartTime.Store(now) + rate.current.Store(total * uint64(time.Second/time.Nanosecond) / period) + rate.nextByteCount.Store(0) + rate.changing.Store(false) + } +} diff --git a/ios/tunnel/tun_windows_shim.go b/ios/tunnel/tun_windows_shim.go new file mode 100644 index 00000000..a22f71d2 --- /dev/null +++ b/ios/tunnel/tun_windows_shim.go @@ -0,0 +1,9 @@ +//go:build !windows + +package tunnel + +import "io" + +func setupWindowsTUN(tunnelInfo tunnelParameters) (io.ReadWriteCloser, error) { + panic("this should never be called, it only exists so the build system can compile the code without errors") +} diff --git a/ios/tunnel/tun_windows_wrapper.go b/ios/tunnel/tun_windows_wrapper.go new file mode 100644 index 00000000..3383f181 --- /dev/null +++ b/ios/tunnel/tun_windows_wrapper.go @@ -0,0 +1,94 @@ +//go:build windows + +package tunnel + +import ( + "fmt" + "io" + "os/exec" + + log "github.com/sirupsen/logrus" +) + +type tunWrapper struct { + device Device + + buffer [][]byte +} + +func initTUNwrapper(device Device) *tunWrapper { + t := &tunWrapper{} + + t.device = device + if device.BatchSize() != 1 { + panic("batch size not 1") + } + + mtu, _ := t.device.MTU() + log.Infof("batch size: %d mtu:%d", device.BatchSize(), mtu) + + t.buffer = make([][]byte, 1) + t.buffer[0] = make([]byte, mtu) + go func() { + for { + e := <-device.Events() + log.Infof("event: %v", e) + } + }() + return t +} + +func (t *tunWrapper) Close() error { + return t.device.Close() +} + +func (t *tunWrapper) Write(p []byte) (int, error) { + + bufs := [][]byte{p} // Create a slice of one byte slice + written, err := t.device.Write(bufs, 0) // Use offset 0 + if written > 0 { + return len(p), err // Assume the entire slice was written + } + return 0, err +} + +func (t *tunWrapper) Read(p []byte) (int, error) { + + sizes := make([]int, 1) + _, err := t.device.Read(t.buffer, sizes, 0) + + if err != nil { + return 0, err + } + + buf := t.buffer[0] + size := sizes[0] + copy(p, buf[:size]) + return size, err + +} + +func setupWindowsTUN(tunnelInfo tunnelParameters) (io.ReadWriteCloser, error) { + name := "tun0" + + tunDevice, err := CreateTUN(name, int(tunnelInfo.ClientParameters.Mtu)) + if err != nil { + fmt.Println("Error creating TUN device:", err) + return &tunWrapper{}, err + } + tunname, err := tunDevice.Name() + + if err != nil { + return nil, fmt.Errorf("setupTunnelInterface: failed to get interface name: %w", err) + } + const prefixLength = 64 + setIpAddr := exec.Command("netsh", "interface", "ipv6", "set", "address", tunname, fmt.Sprintf("%s/%d", tunnelInfo.ClientParameters.Address, prefixLength)) + err = runCmd(setIpAddr) + if err != nil { + return nil, fmt.Errorf("setupTunnelInterface: failed to set IP address for interface: %w", err) + } + log.Info("windows cmd") + log.Info(setIpAddr.String()) + + return initTUNwrapper(tunDevice), nil +} diff --git a/ios/tunnel/tunnel.go b/ios/tunnel/tunnel.go index 2b737dcb..5bac8697 100644 --- a/ios/tunnel/tunnel.go +++ b/ios/tunnel/tunnel.go @@ -21,6 +21,7 @@ import ( "github.com/quic-go/quic-go" "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" "github.com/songgao/water" ) @@ -32,17 +33,18 @@ type Tunnel struct { RsdPort int `json:"rsdPort"` // Udid is the id of the device for this tunnel Udid string `json:"udid"` - closer io.Closer + closer func() error } // Close closes the connection to the device and removes the virtual network interface from the host func (t Tunnel) Close() error { - return t.closer.Close() + return t.closer() } // ManualPairAndConnectToTunnel tries to verify an existing pairing, and if this fails it triggers a new manual pairing process. // After a successful pairing a tunnel for this device gets started and the tunnel information is returned func ManualPairAndConnectToTunnel(ctx context.Context, device ios.DeviceEntry, p PairRecordManager) (Tunnel, error) { + log.Info("ManualPairAndConnectToTunnel: starting manual pairing and tunnel connection, dont forget to stop remoted first with 'sudo pkill -SIGSTOP remoted' and run this with sudo.") addr, err := ios.FindDeviceInterfaceAddress(ctx, device) if err != nil { return Tunnel{}, fmt.Errorf("ManualPairAndConnectToTunnel: failed to find device ethernet interface: %w", err) @@ -63,11 +65,11 @@ func ManualPairAndConnectToTunnel(ctx context.Context, device ios.DeviceEntry, p } ts := newTunnelServiceWithXpc(xpcConn, h, p) - err = ts.ManualPair() + err = ManualPair(ts) if err != nil { return Tunnel{}, fmt.Errorf("ManualPairAndConnectToTunnel: failed to pair device: %w", err) } - tunnelInfo, err := ts.createTunnelListener() + tunnelInfo, err := createTunnelListener(ts, "quic") if err != nil { return Tunnel{}, fmt.Errorf("ManualPairAndConnectToTunnel: failed to create tunnel listener: %w", err) } @@ -117,12 +119,18 @@ func connectToTunnel(ctx context.Context, info tunnelListener, addr string, devi return Tunnel{}, err } - tunnelInfo, err := exchangeCoreTunnelParameters(conn) + stream, err := conn.OpenStream() + if err != nil { + return Tunnel{}, err + } + + tunnelInfo, err := exchangeCoreTunnelParameters(stream) + stream.Close() if err != nil { return Tunnel{}, fmt.Errorf("could not exchange tunnel parameters. %w", err) } - utunIface, err := setupTunnelInterface(err, tunnelInfo) + utunIface, err := setupTunnelInterface(tunnelInfo) if err != nil { return Tunnel{}, fmt.Errorf("could not setup tunnel interface. %w", err) } @@ -145,27 +153,34 @@ func connectToTunnel(ctx context.Context, info tunnelListener, addr string, devi } }() + closeFunc := func() error { + cancel() + quicErr := conn.CloseWithError(0, "") + utunErr := utunIface.Close() + return errors.Join(quicErr, utunErr) + } + return Tunnel{ Address: tunnelInfo.ServerAddress, RsdPort: int(tunnelInfo.ServerRSDPort), Udid: device.Properties.SerialNumber, - closer: tunnelCloser{ - quicConn: conn, - utunCloser: utunIface, - ctxCancel: cancel, - }, + closer: closeFunc, }, nil } -func setupTunnelInterface(err error, tunnelInfo tunnelParameters) (*water.Interface, error) { +func setupTunnelInterface(tunnelInfo tunnelParameters) (io.ReadWriteCloser, error) { + if runtime.GOOS == "windows" { + return setupWindowsTUN(tunnelInfo) + } ifce, err := water.New(water.Config{ DeviceType: water.TUN, }) if err != nil { - logrus.Fatal(err) + return nil, fmt.Errorf("setupTunnelInterface: failed creating TUN device %w", err) } const prefixLength = 64 // TODO: this could be calculated from the netmask provided by the device + setIpAddr := exec.Command("ifconfig", ifce.Name(), "inet6", "add", fmt.Sprintf("%s/%d", tunnelInfo.ClientParameters.Address, prefixLength)) err = runCmd(setIpAddr) if err != nil { @@ -277,13 +292,7 @@ func forwardDataToInterface(ctx context.Context, conn quic.Connection, w io.Writ } } -func exchangeCoreTunnelParameters(conn quic.Connection) (tunnelParameters, error) { - stream, err := conn.OpenStream() - if err != nil { - return tunnelParameters{}, err - } - defer stream.Close() - +func exchangeCoreTunnelParameters(stream io.ReadWriteCloser) (tunnelParameters, error) { rq, err := json.Marshal(map[string]interface{}{ "type": "clientHandshakeRequest", "mtu": 1280, @@ -324,16 +333,3 @@ func exchangeCoreTunnelParameters(conn quic.Connection) (tunnelParameters, error } return parameters, nil } - -type tunnelCloser struct { - quicConn quic.Connection - utunCloser io.Closer - ctxCancel context.CancelFunc -} - -func (t tunnelCloser) Close() error { - t.ctxCancel() - quicErr := t.quicConn.CloseWithError(0, "") - utunErr := t.utunCloser.Close() - return errors.Join(quicErr, utunErr) -} diff --git a/ios/tunnel/tunnel_api.go b/ios/tunnel/tunnel_api.go index 0a5f8ba0..e119b85d 100644 --- a/ios/tunnel/tunnel_api.go +++ b/ios/tunnel/tunnel_api.go @@ -7,8 +7,10 @@ import ( "io" "net/http" "strings" + "sync" "time" + "github.com/Masterminds/semver" "github.com/danielpaulus/go-ios/ios" log "github.com/sirupsen/logrus" "golang.org/x/exp/maps" @@ -121,10 +123,10 @@ func ListRunningTunnels(tunnelInfoPort int) ([]Tunnel, error) { // TunnelManager starts tunnels for devices when needed (if no tunnel is running yet) and stores the information // how those tunnels are reachable (address and remote service discovery port) type TunnelManager struct { - ts tunnelStarter - dl deviceLister - pm PairRecordManager - + ts tunnelStarter + dl deviceLister + pm PairRecordManager + mux sync.Mutex tunnels map[string]Tunnel startTunnelTimeout time.Duration } @@ -143,15 +145,22 @@ func NewTunnelManager(pm PairRecordManager) *TunnelManager { // UpdateTunnels checks for connected devices and starts a new tunnel if needed // On device disconnects the tunnel resources get cleaned up func (m *TunnelManager) UpdateTunnels(ctx context.Context) error { + + m.mux.Lock() + localTunnels := map[string]Tunnel{} + maps.Copy(localTunnels, m.tunnels) + m.mux.Unlock() + devices, err := m.dl.ListDevices() if err != nil { return fmt.Errorf("UpdateTunnels: failed to get list of devices: %w", err) } for _, d := range devices.DeviceList { udid := d.Properties.SerialNumber - if _, exists := m.tunnels[udid]; exists { + if _, exists := localTunnels[udid]; exists { continue } + t, err := m.startTunnel(ctx, d) if err != nil { log.WithField("udid", udid). @@ -159,9 +168,12 @@ func (m *TunnelManager) UpdateTunnels(ctx context.Context) error { Warn("failed to start tunnel") continue } + m.mux.Lock() + localTunnels[udid] = t m.tunnels[udid] = t + m.mux.Unlock() } - for udid, tun := range m.tunnels { + for udid, tun := range localTunnels { idx := slices.ContainsFunc(devices.DeviceList, func(entry ios.DeviceEntry) bool { return entry.Properties.SerialNumber == udid }) @@ -173,6 +185,8 @@ func (m *TunnelManager) UpdateTunnels(ctx context.Context) error { } func (m *TunnelManager) stopTunnel(t Tunnel) error { + m.mux.Lock() + defer m.mux.Unlock() log.WithField("udid", t.Udid).Info("stopping tunnel") delete(m.tunnels, t.Udid) @@ -183,7 +197,11 @@ func (m *TunnelManager) startTunnel(ctx context.Context, device ios.DeviceEntry) log.WithField("udid", device.Properties.SerialNumber).Info("start tunnel") startTunnelCtx, cancel := context.WithTimeout(ctx, m.startTunnelTimeout) defer cancel() - t, err := m.ts.StartTunnel(startTunnelCtx, device, m.pm) + version, err := ios.GetProductVersion(device) + if err != nil { + return Tunnel{}, fmt.Errorf("startTunnel: failed to get device version: %w", err) + } + t, err := m.ts.StartTunnel(startTunnelCtx, device, m.pm, version) if err != nil { return Tunnel{}, err } @@ -192,6 +210,8 @@ func (m *TunnelManager) startTunnel(ctx context.Context, device ios.DeviceEntry) // ListTunnels provides all currently running device tunnels func (m *TunnelManager) ListTunnels() ([]Tunnel, error) { + m.mux.Lock() + defer m.mux.Unlock() return maps.Values(m.tunnels), nil } @@ -211,7 +231,7 @@ func (m *TunnelManager) FindTunnel(udid string) (Tunnel, error) { } type tunnelStarter interface { - StartTunnel(ctx context.Context, device ios.DeviceEntry, p PairRecordManager) (Tunnel, error) + StartTunnel(ctx context.Context, device ios.DeviceEntry, p PairRecordManager, version *semver.Version) (Tunnel, error) } type deviceLister interface { @@ -221,8 +241,17 @@ type deviceLister interface { type manualPairingTunnelStart struct { } -func (m manualPairingTunnelStart) StartTunnel(ctx context.Context, device ios.DeviceEntry, p PairRecordManager) (Tunnel, error) { - return ManualPairAndConnectToTunnel(ctx, device, p) +func (m manualPairingTunnelStart) StartTunnel(ctx context.Context, device ios.DeviceEntry, p PairRecordManager, version *semver.Version) (Tunnel, error) { + //return ManualPairAndConnectToTunnel(ctx, device, p) + return ConnectRemotePairingTunnel(ctx, device, p) + + if version.Major() >= 17 && version.Minor() >= 4 { + return ConnectTunnelLockdown(device) + } + if version.Major() >= 17 { + return ManualPairAndConnectToTunnel(ctx, device, p) + } + return Tunnel{}, fmt.Errorf("manualPairingTunnelStart: unsupported iOS version %s", version.String()) } type deviceList struct { diff --git a/ios/tunnel/tunnel_lockdown.go b/ios/tunnel/tunnel_lockdown.go new file mode 100644 index 00000000..75d5bf75 --- /dev/null +++ b/ios/tunnel/tunnel_lockdown.go @@ -0,0 +1,109 @@ +package tunnel + +import ( + "context" + "errors" + "fmt" + "io" + + "github.com/danielpaulus/go-ios/ios" + "github.com/sirupsen/logrus" +) + +const coreDeviceProxy = "com.apple.internal.devicecompute.CoreDeviceProxy" + +func ConnectTunnelLockdown(device ios.DeviceEntry) (Tunnel, error) { + conn, err := ios.ConnectToService(device, coreDeviceProxy) + if err != nil { + return Tunnel{}, err + } + return connectToTunnelLockdown(context.TODO(), device, conn) +} + +func connectToTunnelLockdown(ctx context.Context, device ios.DeviceEntry, connToDevice io.ReadWriteCloser) (Tunnel, error) { + logrus.Info("connect to lockdown tunnel endpoint on device") + + tunnelInfo, err := exchangeCoreTunnelParameters(connToDevice) + if err != nil { + return Tunnel{}, fmt.Errorf("could not exchange tunnel parameters. %w", err) + } + + utunIface, err := setupTunnelInterface(tunnelInfo) + if err != nil { + return Tunnel{}, fmt.Errorf("could not setup tunnel interface. %w", err) + } + + // we want a copy of the parent ctx here, but it shouldn't time out/be cancelled at the same time. + // doing it like this allows us to have a context with a timeout for the tunnel creation, but the tunnel itself + tunnelCtx, cancel := context.WithCancel(context.WithoutCancel(ctx)) + + go func() { + err := forwardTCPToInterface(tunnelCtx, connToDevice, utunIface) + if err != nil { + logrus.WithError(err).Error("failed to forward data to tunnel interface") + } + }() + + go func() { + err := forwardTUNToDevice(tunnelCtx, tunnelInfo.ClientParameters.Mtu, utunIface, connToDevice) + if err != nil { + logrus.WithError(err).Error("failed to forward data to the device") + } + }() + + closeFunc := func() error { + cancel() + return errors.Join(utunIface.Close(), connToDevice.Close()) + } + return Tunnel{ + Address: tunnelInfo.ServerAddress, + RsdPort: int(tunnelInfo.ServerRSDPort), + Udid: device.Properties.SerialNumber, + closer: closeFunc, + }, nil +} + +func forwardTUNToDevice(ctx context.Context, mtu uint64, tun io.Reader, deviceConn io.Writer) error { + packet := make([]byte, mtu) + for { + + select { + case <-ctx.Done(): + return nil + default: + + n, err := tun.Read(packet) + + if err != nil { + return fmt.Errorf("could not read packet. %w", err) + } + + _, err = deviceConn.Write(packet[:n]) + if err != nil { + return fmt.Errorf("could not write packet. %w", err) + } + } + + } +} + +func forwardTCPToInterface(ctx context.Context, deviceConn io.Reader, tun io.Writer) error { + b := make([]byte, 20000) + for { + + select { + case <-ctx.Done(): + return nil + default: + n, err := deviceConn.Read(b) + if err != nil { + return fmt.Errorf("failed to read datagram. %w", err) + } + _, err = tun.Write(b[:n]) + if err != nil { + return fmt.Errorf("failed to forward data. %w", err) + } + } + + } +} diff --git a/ios/tunnel/untrusted.go b/ios/tunnel/untrusted.go index c6aada0e..2e9db6bb 100644 --- a/ios/tunnel/untrusted.go +++ b/ios/tunnel/untrusted.go @@ -34,6 +34,19 @@ func newTunnelServiceWithXpc(xpcConn *xpc.Connection, c io.Closer, pairRecords P } } +// need to find a nicer name, my interface to decouple the implementation from the ncm xpc tunnel and the remote tunnel +type pairingService interface { + getPairRecords() PairRecordManager + setupCiphers(sharedSecret []byte) error + writeEvent(event eventCodec) error + readEvent(event eventCodec) error + getCipher() *cipherStream + writeRequest(req map[string]interface{}) error + readResponse() (map[string]interface{}, error) + writeEncrypted(msg map[string]interface{}) error + readEncrypted() (map[string]interface{}, error) +} + type tunnelService struct { xpcConn *xpc.Connection c io.Closer @@ -48,12 +61,43 @@ func (t *tunnelService) Close() error { return t.c.Close() } +func (t *tunnelService) readEncrypted() (map[string]interface{}, error) { + return t.controlChannel.read() +} + +func (t *tunnelService) writeEncrypted(msg map[string]interface{}) error { + return t.controlChannel.write(msg) +} + +func (t *tunnelService) getCipher() *cipherStream { + return t.cipher +} + +func (t *tunnelService) writeEvent(event eventCodec) error { + return t.controlChannel.writeEvent(event) +} +func (t *tunnelService) readEvent(event eventCodec) error { + return t.controlChannel.readEvent(event) +} + +func (t *tunnelService) writeRequest(req map[string]interface{}) error { + return t.controlChannel.writeRequest(req) +} + +func (t *tunnelService) readResponse() (map[string]interface{}, error) { + return t.controlChannel.read() +} + +func (t *tunnelService) getPairRecords() PairRecordManager { + return t.pairRecords +} + // ManualPair triggers a device pairing that requires the user to press the 'Trust' button on the device that appears // when this operation is triggered // If there is already an active pairing with the credentials stored in PairRecordManager this call does not trigger // anything on the device and returns with an error -func (t *tunnelService) ManualPair() error { - err := t.controlChannel.writeRequest(map[string]interface{}{ +func ManualPair(t pairingService) error { + err := t.writeRequest(map[string]interface{}{ "handshake": map[string]interface{}{ "_0": map[string]interface{}{ "hostOptions": map[string]interface{}{ @@ -68,28 +112,29 @@ func (t *tunnelService) ManualPair() error { return fmt.Errorf("ManualPair: failed to send 'attemptPairVerify' request: %w", err) } // ignore the response for now - _, err = t.controlChannel.read() + r, err := t.readResponse() if err != nil { return fmt.Errorf("ManualPair: failed to read 'attemptPairVerify' response: %w", err) } + print(r) - err = t.verifyPair() + err = verifyPair(t) if err == nil { return nil } log.WithError(err).Info("pair verify failed") - err = t.setupManualPairing() + err = setupManualPairing(t) if err != nil { return fmt.Errorf("ManualPair: failed to initiate manual pairing: %w", err) } - sessionKey, err := t.setupSessionKey() + sessionKey, err := setupSessionKey(t) if err != nil { return fmt.Errorf("ManualPair: failed to setup SRP session key: %w", err) } - err = t.exchangeDeviceInfo(sessionKey) + err = exchangeDeviceInfo(t, sessionKey) if err != nil { return fmt.Errorf("ManualPair: failed to exchange device info: %w", err) } @@ -99,7 +144,7 @@ func (t *tunnelService) ManualPair() error { return fmt.Errorf("ManualPair: failed to setup session ciphers: %w", err) } - _, err = t.createUnlockKey() + _, err = createUnlockKey(t) if err != nil { return fmt.Errorf("ManualPair: failed to create unlock key: %w", err) } @@ -107,7 +152,7 @@ func (t *tunnelService) ManualPair() error { return nil } -func (t *tunnelService) createTunnelListener() (tunnelListener, error) { +func createTunnelListener(t pairingService, protocol string) (tunnelListener, error) { log.Info("create tunnel listener") privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -119,12 +164,12 @@ func (t *tunnelService) createTunnelListener() (tunnelListener, error) { return tunnelListener{}, err } - err = t.cipher.write(map[string]interface{}{ + err = t.getCipher().write(map[string]interface{}{ "request": map[string]interface{}{ "_0": map[string]interface{}{ "createListener": map[string]interface{}{ "key": der, - "transportProtocolType": "quic", + "transportProtocolType": protocol, }, }, }, @@ -134,7 +179,7 @@ func (t *tunnelService) createTunnelListener() (tunnelListener, error) { } var listenerRes map[string]interface{} - err = t.cipher.read(&listenerRes) + err = t.getCipher().read(&listenerRes) if err != nil { return tunnelListener{}, err } @@ -180,12 +225,12 @@ func (t *tunnelService) setupCiphers(sessionKey []byte) error { return err } - t.cipher = newCipherStream(t.controlChannel, client, server) + t.cipher = newCipherStream(t, client, server) return nil } -func (t *tunnelService) setupManualPairing() error { +func setupManualPairing(t pairingService) error { buf := newTlvBuffer() buf.writeByte(typeMethod, 0x00) buf.writeByte(typeState, pairStateStartRequest) @@ -196,20 +241,21 @@ func (t *tunnelService) setupManualPairing() error { startNewSession: true, } - err := t.controlChannel.writeEvent(&event) + err := t.writeEvent(&event) if err != nil { return err } - _, err = t.controlChannel.read() + r, err := t.readResponse() if err != nil { return err } + print(r) return err } -func (t *tunnelService) readDeviceKey() (publicKey []byte, salt []byte, err error) { +func readDeviceKey(t pairingService) (publicKey []byte, salt []byte, err error) { var pairingData pairingData - err = t.controlChannel.readEvent(&pairingData) + err = t.readEvent(&pairingData) if err != nil { return } @@ -224,8 +270,8 @@ func (t *tunnelService) readDeviceKey() (publicKey []byte, salt []byte, err erro return } -func (t *tunnelService) createUnlockKey() ([]byte, error) { - err := t.cipher.write(map[string]interface{}{ +func createUnlockKey(t pairingService) ([]byte, error) { + err := t.getCipher().write(map[string]interface{}{ "request": map[string]interface{}{ "_0": map[string]interface{}{ "createRemoteUnlockKey": map[string]interface{}{}, @@ -237,7 +283,7 @@ func (t *tunnelService) createUnlockKey() ([]byte, error) { } var res map[string]interface{} - err = t.cipher.read(&res) + err = t.getCipher().read(&res) if err != nil { return nil, err } @@ -245,7 +291,7 @@ func (t *tunnelService) createUnlockKey() ([]byte, error) { return nil, err } -func (t *tunnelService) verifyPair() error { +func verifyPair(t pairingService) error { key, _ := ecdh.X25519().GenerateKey(rand.Reader) tlv := newTlvBuffer() tlv.writeByte(typeState, pairStateStartRequest) @@ -257,12 +303,14 @@ func (t *tunnelService) verifyPair() error { startNewSession: true, } - selfId := t.pairRecords.selfId - - err := t.controlChannel.writeEvent(&event) + selfId := t.getPairRecords().selfId + err := t.writeEvent(&event) + if err != nil { + return err + } var devP pairingData - err = t.controlChannel.readEvent(&devP) + err = t.readEvent(&devP) if err != nil { return err } @@ -273,7 +321,7 @@ func (t *tunnelService) verifyPair() error { } if devicePublicKeyBytes == nil { - _ = t.controlChannel.writeEvent(pairVerifyFailed{}) + _ = t.writeEvent(&pairVerifyFailed{}) return fmt.Errorf("verifyPair: did not get public key from device. Can not verify pairing") } devicePublicKey, err := ecdh.X25519().NewPublicKey(devicePublicKeyBytes) @@ -323,13 +371,13 @@ func (t *tunnelService) verifyPair() error { startNewSession: false, } - err = t.controlChannel.writeEvent(&pd) + err = t.writeEvent(&pd) if err != nil { return err } var responseEvent pairingData - err = t.controlChannel.readEvent(&responseEvent) + err = t.readEvent(&responseEvent) if err != nil { return err } @@ -340,7 +388,7 @@ func (t *tunnelService) verifyPair() error { } if len(errRes) > 0 { log.Debug("send pair verify failed event") - err := t.controlChannel.writeEvent(pairVerifyFailed{}) + err := t.writeEvent(pairVerifyFailed{}) if err != nil { return err } @@ -371,8 +419,8 @@ type tunnelParameters struct { } } -func (t *tunnelService) setupSessionKey() ([]byte, error) { - devicePublicKey, deviceSalt, err := t.readDeviceKey() +func setupSessionKey(t pairingService) ([]byte, error) { + devicePublicKey, deviceSalt, err := readDeviceKey(t) if err != nil { return nil, fmt.Errorf("setupSessionKey: failed to read device public key and salt value: %w", err) } @@ -387,7 +435,7 @@ func (t *tunnelService) setupSessionKey() ([]byte, error) { proofTlv.writeData(typePublicKey, srp.ClientPublic) proofTlv.writeData(typeProof, srp.ClientProof) - err = t.controlChannel.writeEvent(&pairingData{ + err = t.writeEvent(&pairingData{ data: proofTlv.bytes(), kind: "setupManualPairing", }) @@ -396,7 +444,7 @@ func (t *tunnelService) setupSessionKey() ([]byte, error) { } var proofPairingData pairingData - err = t.controlChannel.readEvent(&proofPairingData) + err = t.readEvent(&proofPairingData) if err != nil { return nil, fmt.Errorf("setupSessionKey: failed to read device SRP proof: %w", err) } @@ -412,21 +460,21 @@ func (t *tunnelService) setupSessionKey() ([]byte, error) { return srp.SessionKey, nil } -func (t *tunnelService) exchangeDeviceInfo(sessionKey []byte) error { +func exchangeDeviceInfo(t pairingService, sessionKey []byte) error { hkdfPairSetup := hkdf.New(sha512.New, sessionKey, []byte("Pair-Setup-Controller-Sign-Salt"), []byte("Pair-Setup-Controller-Sign-Info")) buf := bytes.NewBuffer(nil) // Write on bytes.Buffer never returns an error _, _ = io.CopyN(buf, hkdfPairSetup, 32) - _, _ = buf.WriteString(t.pairRecords.selfId.Identifier) - _, _ = buf.Write(t.pairRecords.selfId.publicKey()) + _, _ = buf.WriteString(t.getPairRecords().selfId.Identifier) + _, _ = buf.Write(t.getPairRecords().selfId.publicKey()) - signature := ed25519.Sign(t.pairRecords.selfId.privateKey(), buf.Bytes()) + signature := ed25519.Sign(t.getPairRecords().selfId.privateKey(), buf.Bytes()) // this represents the device info of this host that is stored on the device on a successful pairing. // The only relevant field is 'accountID' as it's used earlier in the pairing process already. // Everything else can be random data and is not needed later in any communication. deviceInfo, err := opack.Encode(map[string]interface{}{ - "accountID": t.pairRecords.selfId.Identifier, + "accountID": t.getPairRecords().selfId.Identifier, "altIRK": []byte{0x5e, 0xca, 0x81, 0x91, 0x92, 0x02, 0x82, 0x00, 0x11, 0x22, 0x33, 0x44, 0xbb, 0xf2, 0x4a, 0xc8}, "btAddr": "FF:DD:99:66:BB:AA", "mac": []byte{0xff, 0x44, 0x88, 0x66, 0x33, 0x99}, @@ -437,8 +485,8 @@ func (t *tunnelService) exchangeDeviceInfo(sessionKey []byte) error { deviceInfoTlv := newTlvBuffer() deviceInfoTlv.writeData(typeSignature, signature) - deviceInfoTlv.writeData(typePublicKey, t.pairRecords.selfId.publicKey()) - deviceInfoTlv.writeData(typeIdentifier, []byte(t.pairRecords.selfId.Identifier)) + deviceInfoTlv.writeData(typePublicKey, t.getPairRecords().selfId.publicKey()) + deviceInfoTlv.writeData(typeIdentifier, []byte(t.getPairRecords().selfId.Identifier)) deviceInfoTlv.writeData(typeInfo, deviceInfo) sessionKeyBuf := bytes.NewBuffer(nil) @@ -461,7 +509,7 @@ func (t *tunnelService) exchangeDeviceInfo(sessionKey []byte) error { encryptedTlv.writeByte(typeState, 0x05) encryptedTlv.writeData(typeEncryptedData, x) - err = t.controlChannel.writeEvent(&pairingData{ + err = t.writeEvent(&pairingData{ data: encryptedTlv.bytes(), kind: "setupManualPairing", sendingHost: "SL-1876", @@ -471,7 +519,7 @@ func (t *tunnelService) exchangeDeviceInfo(sessionKey []byte) error { } var encRes pairingData - err = t.controlChannel.readEvent(&encRes) + err = t.readEvent(&encRes) if err != nil { return err } diff --git a/ios/utils.go b/ios/utils.go index d329392e..846e3921 100755 --- a/ios/utils.go +++ b/ios/utils.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "runtime" "strings" "github.com/Masterminds/semver" @@ -13,6 +14,24 @@ import ( plist "howett.net/plist" ) +// CheckRoot checks if the current user is root or has elevated privileges on Windows. +func CheckRoot() error { + // On Windows, check if the process has elevated privileges + if runtime.GOOS == "windows" { + _, err := os.Open("\\\\.\\PHYSICALDRIVE0") + if err != nil { + return fmt.Errorf("this program needs elevated privileges. Run as administrator.") + } + } else { + // Non-Windows platforms, check if the user is root + u := os.Geteuid() + if u != 0 { + return fmt.Errorf("this program needs root privileges. Run with sudo.") + } + } + return nil +} + // ToPlist converts a given struct to a Plist using the // github.com/DHowett/go-plist library. Make sure your struct is exported. // It returns a string containing the plist. diff --git a/main.go b/main.go index 79102b41..b2382972 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,7 @@ const version = "local-build" // Main Exports main for testing func Main() { + usage := fmt.Sprintf(`go-ios %s Usage: @@ -239,7 +240,8 @@ The commands work as following: ios diskspace [options] Prints disk space info. ios batterycheck [options] Prints battery info. ios tunnel start [options] [--pair-record-path=] Creates a tunnel connection to the device. If the device was not paired with the host yet, device pairing will also be executed. - > On systems with System Integrity Protection enabled the argument '--pair-record-path' is required as we can not access the default path for the pair record + > On systems with System Integrity Protection enabled the argument '--pair-record-path=default' can be used to point to /var/db/lockdown/RemotePairing/user_501. + > If nothing is specified, the current dir is used for the pair record. > This command needs to be executed with admin privileges. > (On MacOS the process 'remoted' must be paused before starting a tunnel is possible 'sudo pkill -SIGSTOP remoted', and 'sudo pkill -SIGCONT remoted' to resume) ios tunnel ls List currently started tunnels @@ -1007,10 +1009,19 @@ The commands work as following: if tunnelCommand { startCommand, _ := arguments.Bool("start") + + if startCommand { + err := ios.CheckRoot() + exitIfError("Run this with 'sudo' or in as admin on windows", err) + } + listCommand, _ := arguments.Bool("ls") if startCommand { pairRecordsPath, _ := arguments.String("--pair-record-path") if len(pairRecordsPath) == 0 { + pairRecordsPath = "." + } + if strings.ToLower(pairRecordsPath) == "default" { pairRecordsPath = "/var/db/lockdown/RemotePairing/user_501" } startTunnel(context.TODO(), pairRecordsPath, tunnelInfoPort) @@ -2015,7 +2026,7 @@ func startTunnel(ctx context.Context, recordsPath string, tunnelInfoPort int) { exitIfError("failed to start tunnel server", err) } }() - + log.Info("Tunnel server started") <-ctx.Done() }