diff --git a/clientapi/routing/register.go b/clientapi/routing/register.go index feea3b049..2ac4f3c6a 100644 --- a/clientapi/routing/register.go +++ b/clientapi/routing/register.go @@ -19,7 +19,6 @@ import ( "context" "crypto/hmac" "crypto/sha1" - "crypto/tls" "encoding/json" "errors" "fmt" @@ -40,6 +39,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/clientapi/threepid" "github.com/matrix-org/dendrite/clientapi/userutil" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" @@ -161,33 +161,10 @@ type authDict struct { // Recaptcha Response string `json:"response"` // m.login.email.identity and m.login.msisdn - ThreePidCreds *threepidCreds `json:"threepidCreds"` + ThreePidCreds *threepid.Credentials `json:"threepidCreds"` // TODO: Lots of custom keys depending on the type } -type threepidCreds struct { - Sid string `json:"sid"` - ClientSecret string `json:"client_secret"` - IdServer string `json:"id_server"` - IdAccessToken string `json:"id_access_token"` -} - -func (c *threepidCreds) validate() *jsonerror.MatrixError { - if c.Sid == "" { - return jsonerror.BadJSON("sid field in threepidCreds is required") - } - if c.ClientSecret == "" { - return jsonerror.BadJSON("client_secret in threepidCreds is required") - } - if c.IdServer == "" { - return jsonerror.BadJSON("id_server in threepidCreds is required") - } - if c.IdAccessToken == "" { - return jsonerror.BadJSON("id_access_token in threepidCreds is required") - } - return nil -} - // http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#user-interactive-authentication-api type userInteractiveResponse struct { Flows []authtypes.Flow `json:"flows"` @@ -352,15 +329,19 @@ func validateRecaptcha( func validateEmailIdentity( ctx context.Context, - cred *threepidCreds, + cred *threepid.Credentials, + cfg *config.ClientAPI, ) *util.JSONResponse { + if err := isTrusted(cred.IDServer, cfg); err != nil { + return &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.NotTrusted(cred.IDServer), + } + } + util.GetLogger(ctx).Infof("conecting to identity server: %s", cred.IDServer) url := fmt.Sprintf( "https://%s/_matrix/identity/api/v1/3pid/getValidated3pid", - cred.IdServer) - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - client := &http.Client{Transport: tr} + cred.IDServer) req, err := http.NewRequestWithContext(ctx, "POST", url, nil) if err != nil { return &util.JSONResponse{ @@ -369,11 +350,11 @@ func validateEmailIdentity( } } q := req.URL.Query() - q.Add("client_secret", cred.ClientSecret) - q.Add("sid", cred.Sid) + q.Add("client_secret", cred.Secret) + q.Add("sid", cred.SID) req.URL.RawQuery = q.Encode() req.Header.Add("Authorization", "Bearer swordfish") - resp, err := client.Do(req) + resp, err := cfg.Derived.HttpClient.Do(req) if err != nil { util.GetLogger(ctx).WithError(err).Error("failed conecting to identity server") return &util.JSONResponse{ @@ -403,6 +384,18 @@ func validateEmailIdentity( } } +// isTrusted checks if a given identity server is part of the list of trusted +// identity servers in the configuration file. +// Returns an error if the server isn't trusted. +func isTrusted(idServer string, cfg *config.ClientAPI) error { + for _, server := range cfg.Matrix.TrustedIDServers { + if idServer == server { + return nil + } + } + return threepid.ErrNotTrusted +} + // UserIDIsWithinApplicationServiceNamespace checks to see if a given userID // falls within any of the namespaces of a given Application Service. If no // Application Service is given, it will check to see if it matches any @@ -759,13 +752,13 @@ func handleRegistrationFlow( JSON: jsonerror.BadJSON("threepidCreds not found in auth field"), } } - if err := r.Auth.ThreePidCreds.validate(); err != nil { + if err := r.Auth.ThreePidCreds.Validate(); err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: err, } } - if err := validateEmailIdentity(req.Context(), r.Auth.ThreePidCreds); err != nil { + if err := validateEmailIdentity(req.Context(), r.Auth.ThreePidCreds, cfg); err != nil { return *err } AddCompletedSessionStage(sessionID, authtypes.LoginTypeEmailIdentity) diff --git a/clientapi/threepid/threepid.go b/clientapi/threepid/threepid.go index 2f817ef42..49cb2ab29 100644 --- a/clientapi/threepid/threepid.go +++ b/clientapi/threepid/threepid.go @@ -15,15 +15,16 @@ package threepid import ( + "bytes" "context" "encoding/json" "errors" "fmt" "net/http" "net/url" - "strconv" "strings" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/setup/config" ) @@ -48,6 +49,19 @@ type Credentials struct { Secret string `json:"client_secret"` } +func (c *Credentials) Validate() *jsonerror.MatrixError { + if c.SID == "" { + return jsonerror.BadJSON("sid field in threepidCreds is required") + } + if c.Secret == "" { + return jsonerror.BadJSON("client_secret in threepidCreds is required") + } + if c.IDServer == "" { + return jsonerror.BadJSON("id_server in threepidCreds is required") + } + return nil +} + // CreateSession creates a session on an identity server. // Returns the session's ID. // Returns an error if there was a problem sending the request or decoding the @@ -62,22 +76,23 @@ func CreateSession( // Create a session on the ID server postURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/validate/email/requestToken", req.IDServer) - data := url.Values{} - data.Add("client_secret", req.Secret) - data.Add("email", req.Email) - data.Add("send_attempt", strconv.Itoa(req.SendAttempt)) - - request, err := http.NewRequest(http.MethodPost, postURL, strings.NewReader(data.Encode())) + b := bytes.Buffer{} + enc := json.NewEncoder(&b) + err := enc.Encode(req) if err != nil { return "", err } - request.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - client := http.Client{} - resp, err := client.Do(request.WithContext(ctx)) + request, err := http.NewRequest(http.MethodPost, postURL, &b) if err != nil { return "", err } + request.Header.Add("Content-Type", "application/json") + + resp, err := cfg.Derived.HttpClient.Do(request.WithContext(ctx)) + if err != nil { + return "", err + } + defer resp.Body.Close() // Error if the status isn't OK if resp.StatusCode != http.StatusOK { @@ -112,11 +127,11 @@ func CheckAssociation( if err != nil { return false, "", "", err } - resp, err := http.DefaultClient.Do(req.WithContext(ctx)) + resp, err := cfg.Derived.HttpClient.Do(req.WithContext(ctx)) if err != nil { return false, "", "", err } - + defer resp.Body.Close() var respBody struct { Medium string `json:"medium"` ValidatedAt int64 `json:"validated_at"` @@ -160,8 +175,7 @@ func PublishAssociation(creds Credentials, userID string, cfg *config.ClientAPI) } request.Header.Add("Content-Type", "application/x-www-form-urlencoded") - client := http.Client{} - resp, err := client.Do(request) + resp, err := cfg.Derived.HttpClient.Do(request) if err != nil { return err } diff --git a/setup/config/config.go b/setup/config/config.go index b91144078..b03ac2bda 100644 --- a/setup/config/config.go +++ b/setup/config/config.go @@ -16,10 +16,14 @@ package config import ( "bytes" + "crypto/tls" + "crypto/x509" "encoding/pem" + "errors" "fmt" "io" "io/ioutil" + "net/http" "net/url" "path/filepath" "regexp" @@ -97,6 +101,9 @@ type Derived struct { Params map[string]interface{} `json:"params"` } + // Used for request to identity server + HttpClient *http.Client + // Application services parsed from their config files // The paths of which were given above in the main config file ApplicationServices []ApplicationService @@ -289,6 +296,26 @@ func (config *Dendrite) Derive() error { return err } + if config.ClientAPI.CustomCaPath != "" { + // Add custom CA cert + rootCAs, err := x509.SystemCertPool() + if err != nil { + return err + } + certs, err := ioutil.ReadFile(config.ClientAPI.CustomCaPath) + if err != nil { + return err + } + if ok := rootCAs.AppendCertsFromPEM(certs); !ok { + return errors.New("failed to append custom certificate") + } + trConfig := &tls.Config{ + RootCAs: rootCAs, + } + tr := &http.Transport{TLSClientConfig: trConfig} + config.Derived.HttpClient = &http.Client{Transport: tr} + } + return nil } diff --git a/setup/config/config_clientapi.go b/setup/config/config_clientapi.go index 9341d5081..067dbdebf 100644 --- a/setup/config/config_clientapi.go +++ b/setup/config/config_clientapi.go @@ -11,6 +11,9 @@ type ClientAPI struct { Matrix *Global `yaml:"-"` Derived *Derived `yaml:"-"` // TODO: Nuke Derived from orbit + // The path to file of custom CA certificate to be added to root CA + CustomCaPath string `yaml:"custom_ca_path"` + InternalAPI InternalAPIOptions `yaml:"internal_api"` ExternalAPI ExternalAPIOptions `yaml:"external_api"`