register.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package pia
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "fmt"
  7. "net"
  8. "net/http"
  9. "net/netip"
  10. "net/url"
  11. "strconv"
  12. "time"
  13. )
  14. type RegistrationClient struct {
  15. CAPEM []byte
  16. Port uint16
  17. MaxBody int64
  18. Timeout time.Duration
  19. UserAgent string
  20. }
  21. func NewRegistrationClient(caPEM []byte) *RegistrationClient {
  22. return &RegistrationClient{
  23. CAPEM: caPEM, Port: DefaultAddKeyPort, MaxBody: DefaultMaxResponseBody,
  24. Timeout: DefaultRequestTimeout, UserAgent: DefaultUserAgent,
  25. }
  26. }
  27. func (c *RegistrationClient) RegisterKey(ctx context.Context, server WireGuardServer, token string, publicKey string) (Registration, error) {
  28. if !server.IP.IsValid() || !server.IP.Is4() || !validHostname(server.Hostname) {
  29. return Registration{}, NewError(CodeInvalidInput, "The selected PIA WireGuard server is invalid.")
  30. }
  31. if !validSecret([]byte(token), 16, 4096) {
  32. return Registration{}, NewError(CodeTokenRejected, "The PIA authentication token is invalid.")
  33. }
  34. if !validWGKey(publicKey) {
  35. return Registration{}, NewError(CodeInvalidInput, "The WireGuard public key is invalid.")
  36. }
  37. roots := x509.NewCertPool()
  38. if !roots.AppendCertsFromPEM(c.CAPEM) {
  39. return Registration{}, NewError(CodeTLSValidation, "The built-in PIA certificate authority is invalid.")
  40. }
  41. port := c.Port
  42. if port == 0 {
  43. port = DefaultAddKeyPort
  44. }
  45. dialer := &net.Dialer{Timeout: 8 * time.Second, KeepAlive: 30 * time.Second}
  46. transport := &http.Transport{
  47. Proxy: nil,
  48. DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
  49. return dialer.DialContext(ctx, network, net.JoinHostPort(server.IP.String(), strconv.Itoa(int(port))))
  50. },
  51. TLSClientConfig: &tls.Config{ServerName: server.Hostname, RootCAs: roots, MinVersion: tls.VersionTLS12},
  52. TLSHandshakeTimeout: 8 * time.Second, ResponseHeaderTimeout: 12 * time.Second, ForceAttemptHTTP2: true,
  53. }
  54. defer transport.CloseIdleConnections()
  55. client := &http.Client{Transport: transport, Timeout: c.Timeout, CheckRedirect: noRedirect}
  56. endpoint := url.URL{Scheme: "https", Host: net.JoinHostPort(server.Hostname, strconv.Itoa(int(port))), Path: "/addKey"}
  57. query := endpoint.Query()
  58. query.Set("pt", token)
  59. query.Set("pubkey", publicKey)
  60. endpoint.RawQuery = query.Encode()
  61. request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
  62. if err != nil {
  63. return Registration{}, WrapError(CodeRegistrationRejected, "Could not prepare PIA key registration.", err)
  64. }
  65. request.Header.Set("Accept", "application/json")
  66. request.Header.Set("User-Agent", c.UserAgent)
  67. response, err := client.Do(request)
  68. if err != nil {
  69. return Registration{}, classifyNetworkError(ctx, CodeNetworkUnavailable, "The selected PIA WireGuard server could not be reached.", err)
  70. }
  71. defer response.Body.Close()
  72. if response.StatusCode == http.StatusUnauthorized || response.StatusCode == http.StatusForbidden {
  73. return Registration{}, NewError(CodeTokenRejected, "The PIA authentication token was rejected.")
  74. }
  75. if response.StatusCode != http.StatusOK {
  76. return Registration{}, NewError(CodeRegistrationRejected, fmt.Sprintf("PIA key registration returned HTTP %d.", response.StatusCode))
  77. }
  78. if !expectedContentType(response.Header.Get("Content-Type"), "application/json") {
  79. return Registration{}, NewError(CodeRegistrationInvalid, "PIA key registration returned an unexpected content type.")
  80. }
  81. raw, err := readLimitedBody(response.Body, c.MaxBody)
  82. if err != nil {
  83. return Registration{}, WrapError(CodeRegistrationInvalid, "PIA key registration returned an invalid response.", err)
  84. }
  85. return parseRegistration(raw)
  86. }
  87. func parseRegistration(raw []byte) (Registration, error) {
  88. var payload struct {
  89. Status string `json:"status"`
  90. PeerIP string `json:"peer_ip"`
  91. ServerKey string `json:"server_key"`
  92. ServerIP string `json:"server_ip"`
  93. ServerPort int `json:"server_port"`
  94. DNSServers []string `json:"dns_servers"`
  95. }
  96. if err := decodeSingleJSON(raw, &payload); err != nil {
  97. return Registration{}, NewError(CodeRegistrationInvalid, "PIA key registration returned malformed JSON.")
  98. }
  99. if payload.Status != "OK" {
  100. return Registration{}, NewError(CodeRegistrationRejected, "The PIA server rejected WireGuard key registration.")
  101. }
  102. peerIP, err := parsePeerIP(payload.PeerIP)
  103. if err != nil {
  104. return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard peer address.")
  105. }
  106. if !validWGKey(payload.ServerKey) {
  107. return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard server key.")
  108. }
  109. serverIP, err := netip.ParseAddr(payload.ServerIP)
  110. if err != nil || !serverIP.Is4() || serverIP.IsUnspecified() {
  111. return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard server address.")
  112. }
  113. if payload.ServerPort < 1 || payload.ServerPort > 65535 {
  114. return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard server port.")
  115. }
  116. dns := make([]netip.Addr, 0, len(payload.DNSServers))
  117. for _, value := range payload.DNSServers {
  118. address, err := netip.ParseAddr(value)
  119. if err != nil || !address.Is4() || address.IsUnspecified() {
  120. continue
  121. }
  122. if len(dns) == 8 {
  123. break
  124. }
  125. dns = append(dns, address)
  126. }
  127. return Registration{PeerIP: peerIP, ServerKey: payload.ServerKey, ServerIP: serverIP, ServerPort: uint16(payload.ServerPort), DNSServers: dns}, nil
  128. }
  129. func parsePeerIP(value string) (netip.Prefix, error) {
  130. if address, err := netip.ParseAddr(value); err == nil {
  131. if !address.Is4() || address.IsUnspecified() {
  132. return netip.Prefix{}, fmt.Errorf("peer address is not a usable IPv4 address")
  133. }
  134. return netip.PrefixFrom(address, 32), nil
  135. }
  136. prefix, err := netip.ParsePrefix(value)
  137. if err != nil || !prefix.Addr().Is4() || prefix.Addr().IsUnspecified() || prefix.Bits() != 32 {
  138. return netip.Prefix{}, fmt.Errorf("peer address is not an IPv4 host prefix")
  139. }
  140. return prefix, nil
  141. }