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 {
AccountExists(ctx context.Context) (string, *jsonerror.MatrixError)
IsValidUserIdForRegistration(userId string) bool
CreateLogin() *Login
GetSession() string
GetType() string

View file

@ -17,6 +17,8 @@ package auth
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
@ -85,6 +87,24 @@ func (pk LoginPublicKeyEthereum) AccountExists(ctx context.Context) (string, *js
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) {
// Parse the message to extract all the fields.
message, err := siwe.ParseMessage(pk.Message)
@ -98,6 +118,12 @@ func (pk LoginPublicKeyEthereum) ValidateLoginResponse() (bool, *jsonerror.Matri
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.
if !contains(pk.config.PublicKeyAuthentication.Ethereum.ChainIDs, message.GetChainID()) {
return false, jsonerror.Forbidden("chainId")
@ -118,6 +144,15 @@ func (pk LoginPublicKeyEthereum) CreateLogin() *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 {
for _, i := range list {
if i == element {

View file

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

View file

@ -37,6 +37,7 @@ func newPublicKeyAuthSession(request *registerRequest, sessions *sessionsDict, s
func handlePublicKeyRegistration(
cfg *config.ClientAPI,
reqBytes []byte,
r *registerRequest,
userAPI userapi.ClientUserAPI,
) (bool, authtypes.LoginType, *util.JSONResponse) {
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()
if jerr != nil {
return false, "", &util.JSONResponse{