Implement EIP-4361 sign in with Ethereum (#5)

* Blacklist some sytest tests that are failing in our environment

* Commenting out test that isn't reliably passing or failing, probably a race

* refresh latest dendrite main

* refresh latest dendrite main

* dendrite implementation of eip-4361

* simplify nonce generation

Co-authored-by: Brian Meek <brian@hntlabs.com>
Co-authored-by: Tak Wai Wong <takwaiw@gmail.com>
This commit is contained in:
Tak Wai Wong 2022-06-09 13:03:04 -04:00 committed by Tak Wai Wong
parent 76e663a046
commit da9334831a
14 changed files with 179 additions and 209 deletions

View file

@ -16,51 +16,28 @@ package auth
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/userutil"
"github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/tidwall/gjson"
"github.com/spruceid/siwe-go"
)
type LoginPublicKeyEthereum struct {
// https://github.com/tak-hntlabs/matrix-spec-proposals/blob/main/proposals/3782-matrix-publickey-login-spec.md#client-sends-login-request-with-authentication-data
Type string `json:"type"`
Address string `json:"address"`
Session string `json:"session"`
Message string `json:"message"`
Signature string `json:"signature"`
HashFields publicKeyEthereumHashFields `json:"hashFields"`
HashFieldsRaw string // Raw base64 encoded string of MessageFields for hash verification
Type string `json:"type"`
Address string `json:"address"`
Session string `json:"session"`
Message string `json:"message"`
Signature string `json:"signature"`
userAPI userapi.ClientUserAPI
config *config.ClientAPI
}
type publicKeyEthereumHashFields struct {
// Todo: See https://...
Domain string `json:"domain"` // home server domain
Address string `json:"address"` // Ethereum address. 0x...
Nonce string `json:"nonce"` // session ID
Version string `json:"version"` // version of the Matrix public key spec that the client is complying with
ChainId string `json:"chainId"` // blockchain network ID.
}
type publicKeyEthereumRequiredFields struct {
From string // Sender
To string // Recipient
Hash string // Hash of JSON representation of the message fields
}
func CreatePublicKeyEthereumHandler(
reqBytes []byte,
userAPI userapi.ClientUserAPI,
@ -71,15 +48,8 @@ func CreatePublicKeyEthereumHandler(
return nil, jsonerror.BadJSON("auth")
}
hashFields := gjson.GetBytes(reqBytes, "hashFields")
if !hashFields.Exists() {
return nil, jsonerror.BadJSON("auth.hashFields")
}
pk.config = config
pk.userAPI = userAPI
// Save raw bytes for hash verification later.
pk.HashFieldsRaw = hashFields.Raw
// Case-insensitive
pk.Address = strings.ToLower(pk.Address)
@ -116,41 +86,20 @@ func (pk LoginPublicKeyEthereum) AccountExists(ctx context.Context) (string, *js
}
func (pk LoginPublicKeyEthereum) ValidateLoginResponse() (bool, *jsonerror.MatrixError) {
// Check signature to verify message was not tempered
isVerified := verifySignature(pk.Address, []byte(pk.Message), pk.Signature)
if !isVerified {
return false, jsonerror.InvalidSignature("")
}
// Extract the required message fields for validation
requiredFields, err := extractRequiredMessageFields(pk.Message)
// Parse the message to extract all the fields.
message, err := siwe.ParseMessage(pk.Message)
if err != nil {
return false, jsonerror.MissingParam("message does not contain domain, address, or hash")
return false, jsonerror.InvalidParam("auth.message")
}
// Verify that the hash is valid for the message fields.
if !verifyHash(pk.HashFieldsRaw, requiredFields.Hash) {
return false, jsonerror.Forbidden("error verifying message hash")
}
// Unmarshal the hashFields for further validation
var authData publicKeyEthereumHashFields
if err := json.Unmarshal([]byte(pk.HashFieldsRaw), &authData); err != nil {
return false, jsonerror.BadJSON("auth.hashFields")
}
// Error if the message is not from the expected public address
if pk.Address != requiredFields.From || requiredFields.From != pk.HashFields.Address {
return false, jsonerror.Forbidden("address")
}
// Error if the message is not for the home server
if requiredFields.To != pk.HashFields.Domain {
return false, jsonerror.Forbidden("domain")
// Check signature to verify message was not tempered
_, err = message.Verify(pk.Signature, (*string)(&pk.config.Matrix.ServerName), nil, nil)
if err != nil {
return false, jsonerror.InvalidSignature(err.Error())
}
// Error if the chainId is not supported by the server.
if !contains(pk.config.PublicKeyAuthentication.Ethereum.ChainIDs, authData.ChainId) {
if !contains(pk.config.PublicKeyAuthentication.Ethereum.ChainIDs, message.GetChainID()) {
return false, jsonerror.Forbidden("chainId")
}
@ -169,75 +118,7 @@ func (pk LoginPublicKeyEthereum) CreateLogin() *Login {
return &login
}
// The required fields in the signed message are:
// 1. Domain -- home server. First non-whitespace characters in the first line.
// 2. Address -- public address of the user. Starts with 0x... in the second line on its own.
// 3. Hash -- Base64-encoded hash string of the metadata that represents the message.
// The rest of the fields are informational, and will be used in the future.
var regexpAuthority = regexp.MustCompile(`^\S+`)
var regexpAddress = regexp.MustCompile(`\n(?P<address>0x\w+)\n`)
var regexpHash = regexp.MustCompile(`\nHash: (?P<hash>.*)\n`)
func extractRequiredMessageFields(message string) (*publicKeyEthereumRequiredFields, error) {
var requiredFields publicKeyEthereumRequiredFields
/*
service.org wants you to sign in with your account:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
I accept the ServiceOrg Terms of Service: https://service.org/tos
Hash: yfSIwarByPfKFxeYSCWN3XoIgNgeEFJffbwFA+JxYbA=
*/
requiredFields.To = regexpAuthority.FindString(message)
from := regexpAddress.FindStringSubmatch(message)
if len(from) == 2 {
requiredFields.From = from[1]
}
hash := regexpHash.FindStringSubmatch(message)
if len(hash) == 2 {
requiredFields.Hash = hash[1]
}
if len(requiredFields.To) == 0 || len(requiredFields.From) == 0 || len(requiredFields.Hash) == 0 {
return nil, errors.New("required message fields are missing")
}
// Make these fields case-insensitive
requiredFields.From = strings.ToLower(requiredFields.From)
requiredFields.To = strings.ToLower(requiredFields.To)
return &requiredFields, nil
}
func verifySignature(from string, message []byte, signature string) bool {
decodedSig := hexutil.MustDecode(signature)
message = accounts.TextHash(message)
// Issue: https://stackoverflow.com/questions/49085737/geth-ecrecover-invalid-signature-recovery-id
// Fix: https://gist.github.com/dcb9/385631846097e1f59e3cba3b1d42f3ed#file-eth_sign_verify-go
decodedSig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1
recovered, err := crypto.SigToPub(message, decodedSig)
if err != nil {
return false
}
recoveredAddr := crypto.PubkeyToAddress(*recovered)
addressStr := strings.ToLower(recoveredAddr.Hex())
return from == addressStr
}
func verifyHash(rawStr string, expectedHash string) bool {
hash := crypto.Keccak256([]byte(rawStr))
hashStr := base64.StdEncoding.EncodeToString(hash)
return expectedHash == hashStr
}
func contains(list []string, element string) bool {
func contains(list []int, element int) bool {
for _, i := range list {
if i == element {
return true

View file

@ -62,11 +62,10 @@ func TestLoginFromJSONReader(t *testing.T) {
},
}
userInteractive := UserInteractive{
Completed: []string{},
Flows: []userInteractiveFlow{},
Types: make(map[string]Type),
Sessions: make(map[string][]string),
Params: make(map[string]interface{}),
Flows: []userInteractiveFlow{},
Types: make(map[string]Type),
Sessions: make(map[string][]string),
Params: make(map[string]interface{}),
}
for _, tst := range tsts {
@ -148,11 +147,10 @@ func TestBadLoginFromJSONReader(t *testing.T) {
},
}
userInteractive := UserInteractive{
Completed: []string{},
Flows: []userInteractiveFlow{},
Types: make(map[string]Type),
Sessions: make(map[string][]string),
Params: make(map[string]interface{}),
Flows: []userInteractiveFlow{},
Types: make(map[string]Type),
Sessions: make(map[string][]string),
Params: make(map[string]interface{}),
}
for _, tst := range tsts {

View file

@ -21,6 +21,7 @@ import (
"sync"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/internal/mapsutil"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/util"
@ -179,17 +180,27 @@ type Challenge struct {
// Challenge returns an HTTP 401 with the supported flows for authenticating
func (u *UserInteractive) challenge(sessionID string) *util.JSONResponse {
u.RLock()
paramsCopy := mapsutil.MapCopy(u.Params)
completed := u.Sessions[sessionID]
flows := u.Flows
u.RUnlock()
for key, element := range paramsCopy {
p := GetAuthParams(element)
if p != nil {
// If an auth flow has params,
// send it as part of the challenge.
paramsCopy[key] = p
}
}
return &util.JSONResponse{
Code: 401,
JSON: Challenge{
Completed: completed,
Flows: flows,
Session: sessionID,
Params: u.Params,
Params: paramsCopy,
},
}
}
@ -235,7 +246,7 @@ func (u *UserInteractive) ResponseWithChallenge(sessionID string, response inter
// Verify returns an error/challenge response to send to the client, or nil if the user is authenticated.
// `bodyBytes` is the HTTP request body which must contain an `auth` key.
// Returns the login that was verified for additional checks if required.
func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte) (*Login, *util.JSONResponse) {
func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *api.Device) (*Login, *util.JSONResponse) {
// TODO: rate limit
// "A client should first make a request with no auth parameter. The homeserver returns an HTTP 401 response, with a JSON body"
@ -286,3 +297,12 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte) (*Login,
// TODO: Check if there's more stages to go and return an error
return login, nil
}
func GetAuthParams(params interface{}) interface{} {
v, ok := params.(config.AuthParams)
if ok {
p := v.GetParams()
return p
}
return nil
}

View file

@ -17,6 +17,11 @@ var (
serverName = gomatrixserverlib.ServerName("example.com")
// space separated localpart+password -> account
lookup = make(map[string]*api.Account)
device = &api.Device{
AccessToken: "flibble",
DisplayName: "My Device",
ID: "device_id_goes_here",
}
)
type fakeAccountDatabase struct {
@ -55,7 +60,7 @@ func setup() *UserInteractive {
func TestUserInteractiveChallenge(t *testing.T) {
uia := setup()
// no auth key results in a challenge
_, errRes := uia.Verify(ctx, []byte(`{}`))
_, errRes := uia.Verify(ctx, []byte(`{}`), device)
if errRes == nil {
t.Fatalf("Verify succeeded with {} but expected failure")
}
@ -95,7 +100,7 @@ func TestUserInteractivePasswordLogin(t *testing.T) {
}`),
}
for _, tc := range testCases {
_, errRes := uia.Verify(ctx, tc)
_, errRes := uia.Verify(ctx, tc, device)
if errRes != nil {
t.Errorf("Verify failed but expected success for request: %s - got %+v", string(tc), errRes)
}
@ -176,7 +181,7 @@ func TestUserInteractivePasswordBadLogin(t *testing.T) {
},
}
for _, tc := range testCases {
_, errRes := uia.Verify(ctx, tc.body)
_, errRes := uia.Verify(ctx, tc.body, device)
if errRes == nil {
t.Errorf("Verify succeeded but expected failure for request: %s", string(tc.body))
continue

View file

@ -28,7 +28,7 @@ func Deactivate(
}
}
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes)
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, deviceAPI)
if errRes != nil {
return *errRes
}

View file

@ -198,7 +198,7 @@ func DeleteDeviceById(
sessionID = s
}
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes)
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device)
if errRes != nil {
switch data := errRes.JSON.(type) {
case auth.Challenge:

View file

@ -68,6 +68,13 @@ func handlePublicKeyRegistration(
return false, "", nil
}
if _, ok := sessions.sessions[authHandler.GetSession()]; !ok {
return false, "", &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.Unknown("the session ID is missing or unknown."),
}
}
isCompleted, jerr := authHandler.ValidateLoginResponse()
if jerr != nil {
return false, "", &util.JSONResponse{

View file

@ -165,6 +165,16 @@ client_api:
# of whether registration is otherwise disabled.
registration_shared_secret: ""
# Disable password authentication.
password_authentication_disabled: false
# public key authentication
public_key_authentication:
ethereum:
enabled: false
version: 1
chain_ids: []
# Whether to require reCAPTCHA for registration. If you have enabled registration
# then this is HIGHLY RECOMMENDED to reduce the risk of your homeserver being used
# for coordinated spam attacks.

5
go.mod
View file

@ -35,6 +35,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.2
github.com/sirupsen/logrus v1.8.1
github.com/spruceid/siwe-go v0.2.0
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.7.1
github.com/tidwall/gjson v1.14.1
@ -53,7 +54,7 @@ require (
nhooyr.io/websocket v1.8.7
)
require github.com/ethereum/go-ethereum v1.10.15
require github.com/ethereum/go-ethereum v1.10.15 // indirect
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@ -64,6 +65,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
@ -99,6 +101,7 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/relvacode/iso8601 v1.1.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect

19
go.sum
View file

@ -80,9 +80,7 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/RyanCarrier/dijkstra v1.0.0/go.mod h1:5agGUBNEtUAGIANmbw09fuO3a2htPEkc1jNH01qxCWA=
github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
@ -169,6 +167,8 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
@ -243,7 +243,6 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
@ -256,7 +255,6 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@ -307,7 +305,6 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
@ -504,7 +501,6 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
@ -562,7 +558,6 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -629,11 +624,11 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/relvacode/iso8601 v1.1.0 h1:2nV8sp0eOjpoKQ2vD3xSDygsjAx37NHG2UlZiCkDH4I=
github.com/relvacode/iso8601 v1.1.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -646,7 +641,6 @@ github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5P
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
@ -686,6 +680,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spruceid/siwe-go v0.2.0 h1:MkBZ/TpPlh1mBhul3h/XLSNZJAbbaHF587Q/VQbhPI0=
github.com/spruceid/siwe-go v0.2.0/go.mod h1:rvV+8/z/ryBKqdw9RcexFgtcsrDlESOGR38sPdVWbSI=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -700,7 +696,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -713,9 +708,7 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso
github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=

View file

@ -27,3 +27,18 @@ func MapsUnion(a map[string]interface{}, b map[string]interface{}) map[string]in
return c
}
// Make a copy of the map
func MapCopy(m map[string]interface{}) map[string]interface{} {
cp := make(map[string]interface{})
for k, v := range m {
vm, ok := v.(map[string]interface{})
if ok {
cp[k] = MapCopy(vm)
} else {
cp[k] = v
}
}
return cp
}

View file

@ -280,7 +280,7 @@ func (config *Dendrite) Derive() error {
config.Derived.Registration.Params[authtypes.LoginTypeRecaptcha] = map[string]string{"public_key": config.ClientAPI.RecaptchaPublicKey}
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows,
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeRecaptcha}})
} else {
} else if !config.ClientAPI.PasswordAuthenticationDisabled {
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows,
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}})
}

View file

@ -3,8 +3,6 @@ package config
import (
"fmt"
"time"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
)
type ClientAPI struct {
@ -160,44 +158,3 @@ func (r *RateLimiting) Defaults() {
r.Threshold = 5
r.CooloffMS = 500
}
type ethereumAuthParams struct {
Version uint32 `json:"version"`
ChainIDs []string `json:"chain_ids"`
}
type ethereumAuthConfig struct {
Enabled bool `yaml:"enabled"`
Version uint32 `yaml:"version"`
ChainIDs []string `yaml:"chain_ids"`
}
type publicKeyAuthentication struct {
Ethereum ethereumAuthConfig `yaml:"ethereum"`
}
func (pk *publicKeyAuthentication) Enabled() bool {
return pk.Ethereum.Enabled
}
func (pk *publicKeyAuthentication) GetPublicKeyRegistrationFlows() []authtypes.Flow {
var flows []authtypes.Flow
if pk.Ethereum.Enabled {
flows = append(flows, authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypePublicKeyEthereum}})
}
return flows
}
func (pk *publicKeyAuthentication) GetPublicKeyRegistrationParams() map[string]interface{} {
params := make(map[string]interface{})
if pk.Ethereum.Enabled {
p := ethereumAuthParams{
Version: pk.Ethereum.Version,
ChainIDs: pk.Ethereum.ChainIDs,
}
params[authtypes.LoginTypePublicKeyEthereum] = p
}
return params
}

View file

@ -0,0 +1,81 @@
package config
import (
"math/rand"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
)
var nonceLength = 32
type AuthParams interface {
GetParams() interface{}
GetNonce() string
}
type EthereumAuthParams struct {
Version uint `json:"version"`
ChainIDs []int `json:"chain_ids"`
Nonce string `json:"nonce"`
}
func (p EthereumAuthParams) GetParams() interface{} {
copyP := p
copyP.ChainIDs = make([]int, len(p.ChainIDs))
copy(copyP.ChainIDs, p.ChainIDs)
copyP.Nonce = newNonce(nonceLength)
return copyP
}
func (p EthereumAuthParams) GetNonce() string {
return p.Nonce
}
type ethereumAuthConfig struct {
Enabled bool `yaml:"enabled"`
Version uint `yaml:"version"`
ChainIDs []int `yaml:"chain_ids"`
}
type publicKeyAuthentication struct {
Ethereum ethereumAuthConfig `yaml:"ethereum"`
}
func (pk *publicKeyAuthentication) Enabled() bool {
return pk.Ethereum.Enabled
}
func (pk *publicKeyAuthentication) GetPublicKeyRegistrationFlows() []authtypes.Flow {
var flows []authtypes.Flow
if pk.Ethereum.Enabled {
flows = append(flows, authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypePublicKeyEthereum}})
}
return flows
}
func (pk *publicKeyAuthentication) GetPublicKeyRegistrationParams() map[string]interface{} {
params := make(map[string]interface{})
if pk.Ethereum.Enabled {
p := EthereumAuthParams{
Version: pk.Ethereum.Version,
ChainIDs: pk.Ethereum.ChainIDs,
Nonce: "",
}
params[authtypes.LoginTypePublicKeyEthereum] = p
}
return params
}
const lettersAndNumbers = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func newNonce(n int) string {
nonce := make([]byte, n)
for i := range nonce {
nonce[i] = lettersAndNumbers[rand.Int63()%int64(len(lettersAndNumbers))]
}
return string(nonce)
}