| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- package pia
- import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net"
- "net/http"
- "net/netip"
- "net/url"
- "strconv"
- "time"
- )
- type RegistrationClient struct {
- CAPEM []byte
- Port uint16
- MaxBody int64
- Timeout time.Duration
- UserAgent string
- }
- func NewRegistrationClient(caPEM []byte) *RegistrationClient {
- return &RegistrationClient{
- CAPEM: caPEM, Port: DefaultAddKeyPort, MaxBody: DefaultMaxResponseBody,
- Timeout: DefaultRequestTimeout, UserAgent: DefaultUserAgent,
- }
- }
- func (c *RegistrationClient) RegisterKey(ctx context.Context, server WireGuardServer, token string, publicKey string) (Registration, error) {
- if !server.IP.IsValid() || !server.IP.Is4() || !validHostname(server.Hostname) {
- return Registration{}, NewError(CodeInvalidInput, "The selected PIA WireGuard server is invalid.")
- }
- if !validSecret([]byte(token), 16, 4096) {
- return Registration{}, NewError(CodeTokenRejected, "The PIA authentication token is invalid.")
- }
- if !validWGKey(publicKey) {
- return Registration{}, NewError(CodeInvalidInput, "The WireGuard public key is invalid.")
- }
- roots := x509.NewCertPool()
- if !roots.AppendCertsFromPEM(c.CAPEM) {
- return Registration{}, NewError(CodeTLSValidation, "The built-in PIA certificate authority is invalid.")
- }
- port := c.Port
- if port == 0 {
- port = DefaultAddKeyPort
- }
- dialer := &net.Dialer{Timeout: 8 * time.Second, KeepAlive: 30 * time.Second}
- transport := &http.Transport{
- Proxy: nil,
- DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
- return dialer.DialContext(ctx, network, net.JoinHostPort(server.IP.String(), strconv.Itoa(int(port))))
- },
- TLSClientConfig: &tls.Config{ServerName: server.Hostname, RootCAs: roots, MinVersion: tls.VersionTLS12},
- TLSHandshakeTimeout: 8 * time.Second, ResponseHeaderTimeout: 12 * time.Second, ForceAttemptHTTP2: true,
- }
- defer transport.CloseIdleConnections()
- client := &http.Client{Transport: transport, Timeout: c.Timeout, CheckRedirect: noRedirect}
- endpoint := url.URL{Scheme: "https", Host: net.JoinHostPort(server.Hostname, strconv.Itoa(int(port))), Path: "/addKey"}
- query := endpoint.Query()
- query.Set("pt", token)
- query.Set("pubkey", publicKey)
- endpoint.RawQuery = query.Encode()
- request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
- if err != nil {
- return Registration{}, WrapError(CodeRegistrationRejected, "Could not prepare PIA key registration.", err)
- }
- request.Header.Set("Accept", "application/json")
- request.Header.Set("User-Agent", c.UserAgent)
- response, err := client.Do(request)
- if err != nil {
- return Registration{}, classifyNetworkError(ctx, CodeNetworkUnavailable, "The selected PIA WireGuard server could not be reached.", err)
- }
- defer response.Body.Close()
- if response.StatusCode == http.StatusUnauthorized || response.StatusCode == http.StatusForbidden {
- return Registration{}, NewError(CodeTokenRejected, "The PIA authentication token was rejected.")
- }
- if response.StatusCode != http.StatusOK {
- return Registration{}, NewError(CodeRegistrationRejected, fmt.Sprintf("PIA key registration returned HTTP %d.", response.StatusCode))
- }
- if !expectedContentType(response.Header.Get("Content-Type"), "application/json") {
- return Registration{}, NewError(CodeRegistrationInvalid, "PIA key registration returned an unexpected content type.")
- }
- raw, err := readLimitedBody(response.Body, c.MaxBody)
- if err != nil {
- return Registration{}, WrapError(CodeRegistrationInvalid, "PIA key registration returned an invalid response.", err)
- }
- return parseRegistration(raw)
- }
- func parseRegistration(raw []byte) (Registration, error) {
- var payload struct {
- Status string `json:"status"`
- PeerIP string `json:"peer_ip"`
- ServerKey string `json:"server_key"`
- ServerIP string `json:"server_ip"`
- ServerPort int `json:"server_port"`
- DNSServers []string `json:"dns_servers"`
- }
- if err := decodeSingleJSON(raw, &payload); err != nil {
- return Registration{}, NewError(CodeRegistrationInvalid, "PIA key registration returned malformed JSON.")
- }
- if payload.Status != "OK" {
- return Registration{}, NewError(CodeRegistrationRejected, "The PIA server rejected WireGuard key registration.")
- }
- peerIP, err := parsePeerIP(payload.PeerIP)
- if err != nil {
- return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard peer address.")
- }
- if !validWGKey(payload.ServerKey) {
- return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard server key.")
- }
- serverIP, err := netip.ParseAddr(payload.ServerIP)
- if err != nil || !serverIP.Is4() || serverIP.IsUnspecified() {
- return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard server address.")
- }
- if payload.ServerPort < 1 || payload.ServerPort > 65535 {
- return Registration{}, NewError(CodeRegistrationInvalid, "PIA returned an invalid WireGuard server port.")
- }
- dns := make([]netip.Addr, 0, len(payload.DNSServers))
- for _, value := range payload.DNSServers {
- address, err := netip.ParseAddr(value)
- if err != nil || !address.Is4() || address.IsUnspecified() {
- continue
- }
- if len(dns) == 8 {
- break
- }
- dns = append(dns, address)
- }
- return Registration{PeerIP: peerIP, ServerKey: payload.ServerKey, ServerIP: serverIP, ServerPort: uint16(payload.ServerPort), DNSServers: dns}, nil
- }
- func parsePeerIP(value string) (netip.Prefix, error) {
- if address, err := netip.ParseAddr(value); err == nil {
- if !address.Is4() || address.IsUnspecified() {
- return netip.Prefix{}, fmt.Errorf("peer address is not a usable IPv4 address")
- }
- return netip.PrefixFrom(address, 32), nil
- }
- prefix, err := netip.ParsePrefix(value)
- if err != nil || !prefix.Addr().Is4() || prefix.Addr().IsUnspecified() || prefix.Bits() != 32 {
- return netip.Prefix{}, fmt.Errorf("peer address is not an IPv4 host prefix")
- }
- return prefix, nil
- }
|