In Cosmos, transaction's signature(s) are validated against public keys (addresses) taken from the transaction itself,
where locations of the keys are specified in GetSigners
methods.
In the simplest case there is just one signer required, and its address is simple to use correctly. However, in more complex scenarios like when multiple signatures are required or a delegation schema is implemented, it is possible to make mistakes about what addresses in the transaction (the message) are actually authenticated.
Fortunately, mistakes in GetSigners
should make part of application's intended functionality not working,
making it easy to spot the bug.
The example application allows an author to create posts. A post can be created with a MsgCreatePost
message, which has signer
and author
fields.
service Msg {
rpc CreatePost(MsgCreatePost) returns (MsgCreatePostResponse);
}
message MsgCreatePost {
string signer = 1;
string author = 2;
string title = 3;
string body = 4;
}
message MsgCreatePostResponse {
uint64 id = 1;
}
message Post {
string author = 1;
uint64 id = 2;
string title = 3;
string body = 4;
}
The signer
field is used for signature verification - as can be seen in GetSigners
method below.
func (msg *MsgCreatePost) GetSigners() []sdk.AccAddress {
signer, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
panic(err)
}
return []sdk.AccAddress{Signer}
}
func (msg *MsgCreatePost) GetSignBytes() []byte {
bz := ModuleCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
func (msg *MsgCreatePost) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(msg.Signer)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err)
}
return nil
}
The author
field is saved along with the post's content:
func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
var post = types.Post{
Author: msg.Author,
Title: msg.Title,
Body: msg.Body,
}
id := k.AppendPost(ctx, post)
return &types.MsgCreatePostResponse{Id: id}, nil
}
The bug here - mismatch between the message signer address and the stored address - allows users to impersonate other users by sending an arbitrary author
field.
- Keep signers-related logic simple
- Implement basic sanity tests for all functionalities