diff --git a/internal/httpauth/auth.go b/internal/httpauth/auth.go deleted file mode 100644 index a3417facac..0000000000 --- a/internal/httpauth/auth.go +++ /dev/null @@ -1,82 +0,0 @@ -package httpauth - -import ( - "errors" - "fmt" - "net/http" - "strconv" - - "github.com/skycoin/dmsg/cipher" -) - -// Nonce is used to sign requests in order to avoid replay attack -type Nonce uint64 - -func (n Nonce) String() string { return fmt.Sprintf("%d", n) } - -// Auth holds authentication mandatory values -type Auth struct { - Key cipher.PubKey - Nonce Nonce - Sig cipher.Sig -} - -// AuthFromHeaders attempts to extract auth from request header -func AuthFromHeaders(hdr http.Header) (*Auth, error) { - a := &Auth{} - var v string - - if v = hdr.Get("SW-Public"); v == "" { - return nil, errors.New("SW-Public missing") - } - key := cipher.PubKey{} - if err := key.UnmarshalText([]byte(v)); err != nil { - return nil, fmt.Errorf("error parsing SW-Public: %s", err.Error()) - } - a.Key = key - - if v = hdr.Get("SW-Sig"); v == "" { - return nil, errors.New("SW-Sig missing") - } - sig := cipher.Sig{} - if err := sig.UnmarshalText([]byte(v)); err != nil { - return nil, fmt.Errorf("error parsing SW-Sig:'%s': %s", v, err.Error()) - } - a.Sig = sig - - nonceStr := hdr.Get("SW-Nonce") - if nonceStr == "" { - return nil, errors.New("SW-Nonce missing") - } - nonceUint, err := strconv.ParseUint(nonceStr, 10, 64) - if err != nil { - if numErr, ok := err.(*strconv.NumError); ok { - return nil, fmt.Errorf("error parsing SW-Nonce: %s", numErr.Err.Error()) - } - - return nil, fmt.Errorf("error parsing SW-Nonce: %s", err.Error()) - } - a.Nonce = Nonce(nonceUint) - - return a, nil -} - -// Verify verifies signature of a payload. -func (a *Auth) Verify(in []byte) error { - return Verify(in, a.Nonce, a.Key, a.Sig) -} - -// PayloadWithNonce returns the concatenation of payload and nonce. -func PayloadWithNonce(payload []byte, nonce Nonce) []byte { - return []byte(fmt.Sprintf("%s%d", string(payload), nonce)) -} - -// Sign signs the Hash of payload and nonce -func Sign(payload []byte, nonce Nonce, sec cipher.SecKey) (cipher.Sig, error) { - return cipher.SignPayload(PayloadWithNonce(payload, nonce), sec) -} - -// Verify verifies the signature of the hash of payload and nonce -func Verify(payload []byte, nonce Nonce, pub cipher.PubKey, sig cipher.Sig) error { - return cipher.VerifyPubKeySignedPayload(pub, sig, PayloadWithNonce(payload, nonce)) -} diff --git a/internal/httpauth/client.go b/internal/httpauth/client.go index e10082f1cf..c115f73e9c 100644 --- a/internal/httpauth/client.go +++ b/internal/httpauth/client.go @@ -23,6 +23,11 @@ const ( invalidNonceErrorMessage = "SW-Nonce does not match" ) +// Error is the object returned to the client when there's an error. +type Error struct { + Error string `json:"error"` +} + // NextNonceResponse represents a ServeHTTP response for json encoding type NextNonceResponse struct { Edge cipher.PubKey `json:"edge"` @@ -42,14 +47,12 @@ type HTTPError struct { } // Client implements Client for auth services. -// As Client needs to dial both with reusing address and without it, it uses two http clients: reuseClient and client. type Client struct { // atomic requires 64-bit alignment for struct field access nonce uint64 mu sync.Mutex reqMu sync.Mutex client *http.Client - reuseClient *http.Client key cipher.PubKey sec cipher.SecKey addr string // sanitized address of the client, which may differ from addr used in NewClient @@ -67,7 +70,6 @@ func NewClient(ctx context.Context, addr string, key cipher.PubKey, sec cipher.S mLog *logging.MasterLogger) (*Client, error) { c := &Client{ client: client, - reuseClient: client, key: key, sec: sec, addr: sanitizedAddr(addr), @@ -85,28 +87,6 @@ func NewClient(ctx context.Context, addr string, key cipher.PubKey, sec cipher.S return c, nil } -// Header returns headers for httpauth. -func (c *Client) Header() (http.Header, error) { - nonce := c.getCurrentNonce() - body := make([]byte, 0) - sign, err := Sign(body, nonce, c.sec) - if err != nil { - return nil, err - } - - header := make(http.Header) - - // use nonce, later, if no err from req update such nonce - header.Set("SW-Nonce", strconv.FormatUint(uint64(nonce), 10)) - header.Set("SW-Sig", sign.Hex()) - header.Set("SW-Public", c.key.Hex()) - if c.clientPublicIP != "" { - header.Set("SW-PublicIP", c.clientPublicIP) - } - - return header, nil -} - // Do performs a new authenticated Request and returns the response. Internally, if the request was // successful nonce is incremented func (c *Client) Do(req *http.Request) (*http.Response, error) { @@ -189,7 +169,7 @@ func (c *Client) Nonce(ctx context.Context, key cipher.PubKey) (Nonce, error) { }() if resp.StatusCode != http.StatusOK { - return 0, fmt.Errorf("error getting current nonce: status: %d <- %v", resp.StatusCode, extractError(resp.Body)) + return 0, fmt.Errorf("error getting current nonce: status: %d <- %v", resp.StatusCode, extractHTTPError(resp.Body)) } var nr NextNonceResponse @@ -200,22 +180,6 @@ func (c *Client) Nonce(ctx context.Context, key cipher.PubKey) (Nonce, error) { return nr.NextNonce, nil } -// ReuseClient returns HTTP client that reuses port for dialing. -func (c *Client) ReuseClient() *http.Client { - c.mu.Lock() - defer c.mu.Unlock() - - return c.reuseClient -} - -// SetTransport sets transport for HTTP client that reuses port for dialing. -func (c *Client) SetTransport(transport http.RoundTripper) { - c.mu.Lock() - defer c.mu.Unlock() - - c.reuseClient.Transport = transport -} - // SetNonce sets client current nonce to given nonce func (c *Client) SetNonce(n Nonce) { atomic.StoreUint64(&c.nonce, uint64(n)) @@ -301,8 +265,8 @@ func sanitizedAddr(addr string) string { return u.String() } -// extractError returns the decoded error message from Body. -func extractError(r io.Reader) error { +// extractHTTPError returns the decoded error message from Body. +func extractHTTPError(r io.Reader) error { var serverError HTTPResponse body, err := ioutil.ReadAll(r) @@ -316,3 +280,19 @@ func extractError(r io.Reader) error { return errors.New(serverError.Error.Message) } + +// ExtractError returns the decoded error message from Body. +func ExtractError(r io.Reader) error { + var apiError Error + + body, err := ioutil.ReadAll(r) + if err != nil { + return err + } + + if err := json.Unmarshal(body, &apiError); err != nil { + return errors.New(string(body)) + } + + return errors.New(apiError.Error) +} diff --git a/internal/httpauth/nonce.go b/internal/httpauth/nonce.go new file mode 100644 index 0000000000..98c45bd5b2 --- /dev/null +++ b/internal/httpauth/nonce.go @@ -0,0 +1,22 @@ +package httpauth + +import ( + "fmt" + + "github.com/skycoin/dmsg/cipher" +) + +// Nonce is used to sign requests in order to avoid replay attack +type Nonce uint64 + +func (n Nonce) String() string { return fmt.Sprintf("%d", n) } + +// PayloadWithNonce returns the concatenation of payload and nonce. +func PayloadWithNonce(payload []byte, nonce Nonce) []byte { + return []byte(fmt.Sprintf("%s%d", string(payload), nonce)) +} + +// Sign signs the Hash of payload and nonce +func Sign(payload []byte, nonce Nonce, sec cipher.SecKey) (cipher.Sig, error) { + return cipher.SignPayload(PayloadWithNonce(payload, nonce), sec) +} diff --git a/internal/testhelpers/testhelpers.go b/internal/testhelpers/testhelpers.go index 18c1ed661f..55d58b78de 100644 --- a/internal/testhelpers/testhelpers.go +++ b/internal/testhelpers/testhelpers.go @@ -3,34 +3,10 @@ package testhelpers import ( "errors" - "testing" - "time" - - "github.com/stretchr/testify/require" ) -const timeout = 5 * time.Second - // NoErr is used with the mock interface to return from its methods. var NoErr error // Err is used with the mock interface to return some error from its methods. var Err = errors.New("error") - -// WithinTimeout tries to read an error from error channel within timeout and returns it. -// If timeout exceeds, nil value is returned. -func WithinTimeout(ch <-chan error) error { - select { - case err := <-ch: - return err - case <-time.After(timeout): - return nil - } -} - -// NoErrorN performs require.NoError on multiple errors -func NoErrorN(t *testing.T, errs ...error) { - for _, err := range errs { - require.NoError(t, err) - } -} diff --git a/internal/utclient/client.go b/internal/utclient/client.go index 849fed8fc1..efeec8b448 100644 --- a/internal/utclient/client.go +++ b/internal/utclient/client.go @@ -4,11 +4,7 @@ package utclient import ( "bytes" "context" - "encoding/json" - "errors" "fmt" - "io" - "io/ioutil" "net/http" "time" @@ -21,11 +17,6 @@ import ( //go:generate mockery -name APIClient -case underscore -inpkg -// Error is the object returned to the client when there's an error. -type Error struct { - Error string `json:"error"` -} - // APIClient implements uptime tracker API client. type APIClient interface { UpdateVisorUptime(context.Context) error @@ -102,24 +93,8 @@ func (c *httpClient) UpdateVisorUptime(ctx context.Context) error { }() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("status: %d, error: %w", resp.StatusCode, extractError(resp.Body)) + return fmt.Errorf("status: %d, error: %w", resp.StatusCode, httpauth.ExtractError(resp.Body)) } return nil } - -// extractError returns the decoded error message from Body. -func extractError(r io.Reader) error { - var apiError Error - - body, err := ioutil.ReadAll(r) - if err != nil { - return err - } - - if err := json.Unmarshal(body, &apiError); err != nil { - return errors.New(string(body)) - } - - return errors.New(apiError.Error) -} diff --git a/internal/vpn/net.go b/internal/vpn/net.go index 8dd9b89c36..e70b8f9365 100644 --- a/internal/vpn/net.go +++ b/internal/vpn/net.go @@ -3,14 +3,8 @@ package vpn import ( "encoding/json" "fmt" - "io" "net" "time" - - "github.com/skycoin/dmsg/cipher" - "github.com/skycoin/dmsg/noise" - - "github.com/skycoin/skywire/pkg/app/appnet" ) // WriteJSONWithTimeout marshals `data` and sends it over the `conn` with the specified write `timeout`. @@ -92,29 +86,3 @@ func ReadJSON(conn net.Conn, data interface{}) error { return nil } - -// WrapRWWithNoise wraps `conn` with noise. -func WrapRWWithNoise(conn net.Conn, initiator bool, pk cipher.PubKey, sk cipher.SecKey) (io.ReadWriter, error) { - remoteAddr, isAppConn := conn.RemoteAddr().(appnet.Addr) - if isAppConn { - ns, err := noise.New(noise.HandshakeKK, noise.Config{ - LocalPK: pk, - LocalSK: sk, - RemotePK: remoteAddr.PubKey, - Initiator: initiator, - }) - if err != nil { - return nil, fmt.Errorf("failed to prepare stream noise object: %w", err) - } - - rw := noise.NewReadWriter(conn, ns) - if err := rw.Handshake(HSTimeout); err != nil { - return nil, fmt.Errorf("error performing noise handshake: %w", err) - } - - return rw, nil - } - - // shouldn't happen, but no encryption in this case - return conn, nil -} diff --git a/internal/vpn/noise_credentials.go b/internal/vpn/noise_credentials.go deleted file mode 100644 index 63b5fb118e..0000000000 --- a/internal/vpn/noise_credentials.go +++ /dev/null @@ -1,42 +0,0 @@ -package vpn - -import ( - "fmt" - "time" - - "github.com/skycoin/dmsg/cipher" -) - -const ( - // HSTimeout is a timeout for noise handshake. - HSTimeout = 5 * time.Second -) - -// NoiseCredentials encapsulates sec and pub keys for noise usage. -type NoiseCredentials struct { - PK cipher.PubKey - SK cipher.SecKey -} - -// NewNoiseCredentials creates creds out of sec key and pub key pair. -func NewNoiseCredentials(sk cipher.SecKey, pk cipher.PubKey) NoiseCredentials { - return NoiseCredentials{ - PK: pk, - SK: sk, - } -} - -// NewNoiseCredentialsFromSK creates creds out of sec key deriving pub key. -func NewNoiseCredentialsFromSK(sk cipher.SecKey) (NoiseCredentials, error) { - pk, err := sk.PubKey() - if err != nil { - return NoiseCredentials{}, fmt.Errorf("error deriving pub key from sec key: %w", err) - } - - return NewNoiseCredentials(sk, pk), nil -} - -// IsValid returns true only if PK and SK are valid. -func (c NoiseCredentials) IsValid() bool { - return !c.PK.Null() && !c.SK.Null() -} diff --git a/pkg/transport/network/addrresolver/client.go b/pkg/transport/network/addrresolver/client.go index 9857c700b5..6165deade2 100644 --- a/pkg/transport/network/addrresolver/client.go +++ b/pkg/transport/network/addrresolver/client.go @@ -243,7 +243,7 @@ func (c *httpClient) BindSTCPR(ctx context.Context, port string) error { }() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("status: %d, error: %w", resp.StatusCode, extractError(resp.Body)) + return fmt.Errorf("status: %d, error: %w", resp.StatusCode, httpauth.ExtractError(resp.Body)) } return nil @@ -270,7 +270,7 @@ func (c *httpClient) delBindSTCPR(ctx context.Context) error { }() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("status: %d, error: %w", resp.StatusCode, extractError(resp.Body)) + return fmt.Errorf("status: %d, error: %w", resp.StatusCode, httpauth.ExtractError(resp.Body)) } c.log.Debugf("delBindSTCPR: Deleted bind pk: %v from Address resolver successfully", c.pk.String()) @@ -368,7 +368,7 @@ func (c *httpClient) Resolve(ctx context.Context, tType string, pk cipher.PubKey } if resp.StatusCode != http.StatusOK { - return VisorData{}, fmt.Errorf("status: %d, error: %w", resp.StatusCode, extractError(resp.Body)) + return VisorData{}, fmt.Errorf("status: %d, error: %w", resp.StatusCode, httpauth.ExtractError(resp.Body)) } rawBody, err := ioutil.ReadAll(resp.Body) @@ -562,19 +562,3 @@ func (c *httpClient) isClosed() bool { return false } } - -// extractError returns the decoded error message from Body. -func extractError(r io.Reader) error { - var apiError Error - - body, err := ioutil.ReadAll(r) - if err != nil { - return err - } - - if err := json.Unmarshal(body, &apiError); err != nil { - return errors.New(string(body)) - } - - return errors.New(apiError.Error) -}