Pass custom certificates via config file to be added to system pool, fix CreateSession to be conformant to V1 api

This commit is contained in:
Piotr Kozimor 2021-04-21 12:39:38 +02:00
parent 01ce9abc5b
commit d975de3c05
4 changed files with 88 additions and 51 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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"`