During registration, verify that the user ID passes the grammar of CAIP-10, and the Matrix ID. Also verify that the ID matches the authentication data. Then during login authentication, verify that the user ID in the auth request matches the fields in the signed message.

This commit is contained in:
Tak Wai Wong 2022-06-16 10:18:01 -07:00
parent e10f54d7f4
commit 3115998616
4 changed files with 46 additions and 1 deletions

View file

@ -30,6 +30,7 @@ import (
type LoginPublicKeyHandler interface { type LoginPublicKeyHandler interface {
AccountExists(ctx context.Context) (string, *jsonerror.MatrixError) AccountExists(ctx context.Context) (string, *jsonerror.MatrixError)
IsValidUserIdForRegistration(userId string) bool
CreateLogin() *Login CreateLogin() *Login
GetSession() string GetSession() string
GetType() string GetType() string

View file

@ -17,6 +17,8 @@ package auth
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"regexp"
"strings" "strings"
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
@ -85,6 +87,24 @@ func (pk LoginPublicKeyEthereum) AccountExists(ctx context.Context) (string, *js
return localPart, nil return localPart, nil
} }
var validChainAgnosticIdRegex = regexp.MustCompile("^eip155=3a[0-9]+=3a0x[0-9a-fA-F]+$")
func (pk LoginPublicKeyEthereum) IsValidUserIdForRegistration(userId string) bool {
// Verify that the user ID is a valid one according to spec.
// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md
// Matrix ID has additional grammar requirements for user ID.
// https://spec.matrix.org/v1.1/appendices/#user-identifiers
// Make sure disallowed characters are escaped.
// E.g. ":" is replaced with "=3a".
isValid := validChainAgnosticIdRegex.MatchString(userId)
// In addition, double check that the user ID for registration
// matches the authentication data in the request.
return isValid && userId == pk.UserId
}
func (pk LoginPublicKeyEthereum) ValidateLoginResponse() (bool, *jsonerror.MatrixError) { func (pk LoginPublicKeyEthereum) ValidateLoginResponse() (bool, *jsonerror.MatrixError) {
// Parse the message to extract all the fields. // Parse the message to extract all the fields.
message, err := siwe.ParseMessage(pk.Message) message, err := siwe.ParseMessage(pk.Message)
@ -98,6 +118,12 @@ func (pk LoginPublicKeyEthereum) ValidateLoginResponse() (bool, *jsonerror.Matri
return false, jsonerror.InvalidSignature(err.Error()) return false, jsonerror.InvalidSignature(err.Error())
} }
// Error if the user ID does not match the signed message.
isVerifiedUserId := pk.verifyMessageUserId(message)
if !isVerifiedUserId {
return false, jsonerror.InvalidUsername(pk.UserId)
}
// Error if the chainId is not supported by the server. // Error if the chainId is not supported by the server.
if !contains(pk.config.PublicKeyAuthentication.Ethereum.ChainIDs, message.GetChainID()) { if !contains(pk.config.PublicKeyAuthentication.Ethereum.ChainIDs, message.GetChainID()) {
return false, jsonerror.Forbidden("chainId") return false, jsonerror.Forbidden("chainId")
@ -118,6 +144,15 @@ func (pk LoginPublicKeyEthereum) CreateLogin() *Login {
return &login return &login
} }
func (pk LoginPublicKeyEthereum) verifyMessageUserId(message *siwe.Message) bool {
// Use info in the signed message to derive the expected user ID.
expectedUserId := fmt.Sprintf("eip155=3a%d=3a%s", message.GetChainID(), message.GetAddress())
// Case-insensitive comparison to make sure the user ID matches the expected
// one derived from the signed message.
return pk.UserId == strings.ToLower(expectedUserId)
}
func contains(list []int, element int) bool { func contains(list []int, element int) bool {
for _, i := range list { for _, i := range list {
if i == element { if i == element {

View file

@ -767,7 +767,7 @@ func handleRegistrationFlow(
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeDummy) sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeDummy)
case authtypes.LoginTypePublicKey: case authtypes.LoginTypePublicKey:
isCompleted, authType, err := handlePublicKeyRegistration(cfg, reqBody, userAPI) isCompleted, authType, err := handlePublicKeyRegistration(cfg, reqBody, &r, userAPI)
if err != nil { if err != nil {
return *err return *err
} }

View file

@ -37,6 +37,7 @@ func newPublicKeyAuthSession(request *registerRequest, sessions *sessionsDict, s
func handlePublicKeyRegistration( func handlePublicKeyRegistration(
cfg *config.ClientAPI, cfg *config.ClientAPI,
reqBytes []byte, reqBytes []byte,
r *registerRequest,
userAPI userapi.ClientUserAPI, userAPI userapi.ClientUserAPI,
) (bool, authtypes.LoginType, *util.JSONResponse) { ) (bool, authtypes.LoginType, *util.JSONResponse) {
if !cfg.PublicKeyAuthentication.Enabled() { if !cfg.PublicKeyAuthentication.Enabled() {
@ -76,6 +77,14 @@ func handlePublicKeyRegistration(
} }
} }
isValidUserId := authHandler.IsValidUserIdForRegistration(r.Username)
if !isValidUserId {
return false, "", &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(r.Username),
}
}
isCompleted, jerr := authHandler.ValidateLoginResponse() isCompleted, jerr := authHandler.ValidateLoginResponse()
if jerr != nil { if jerr != nil {
return false, "", &util.JSONResponse{ return false, "", &util.JSONResponse{