diff --git a/politeiad/backendv2/tstorebe/plugins/comments/cmds.go b/politeiad/backendv2/tstorebe/plugins/comments/cmds.go index ebbb5ffc1..a6637c4b5 100644 --- a/politeiad/backendv2/tstorebe/plugins/comments/cmds.go +++ b/politeiad/backendv2/tstorebe/plugins/comments/cmds.go @@ -441,9 +441,16 @@ func (p *commentsPlugin) cmdNew(token []byte, payload string) (string, error) { return "", err } + // Ensure no extra data provided if not allowed + err = p.verifyExtraData(n.ExtraData, n.ExtraDataHint) + if err != nil { + return "", err + } + // Verify signature msg := strconv.FormatUint(uint64(n.State), 10) + n.Token + - strconv.FormatUint(uint64(n.ParentID), 10) + n.Comment + strconv.FormatUint(uint64(n.ParentID), 10) + n.Comment + + n.ExtraData + n.ExtraDataHint err = util.VerifySignature(n.Signature, n.PublicKey, msg) if err != nil { return "", convertSignatureError(err) @@ -560,9 +567,16 @@ func (p *commentsPlugin) cmdEdit(token []byte, payload string) (string, error) { return "", err } + // Ensure no extra data provided if not allowed + err = p.verifyExtraData(e.ExtraData, e.ExtraDataHint) + if err != nil { + return "", err + } + // Verify signature msg := strconv.FormatUint(uint64(e.State), 10) + e.Token + - strconv.FormatUint(uint64(e.ParentID), 10) + e.Comment + strconv.FormatUint(uint64(e.ParentID), 10) + e.Comment + + e.ExtraData + e.ExtraDataHint err = util.VerifySignature(e.Signature, e.PublicKey, msg) if err != nil { return "", convertSignatureError(err) @@ -687,6 +701,17 @@ func (p *commentsPlugin) cmdEdit(token []byte, payload string) (string, error) { return string(reply), nil } +// verifyExtraData ensures no extra data provided if it's not allowed. +func (p *commentsPlugin) verifyExtraData(extraData, extraDataHint string) error { + if !p.allowExtraData && (extraData != "" || extraDataHint != "") { + return backend.PluginError{ + PluginID: comments.PluginID, + ErrorCode: uint32(comments.ErrorCodeExtraDataNotAllowed), + } + } + return nil +} + // cmdDel deletes a comment. func (p *commentsPlugin) cmdDel(token []byte, payload string) (string, error) { // Decode payload diff --git a/politeiad/backendv2/tstorebe/plugins/comments/comments.go b/politeiad/backendv2/tstorebe/plugins/comments/comments.go index fdd674b27..82f5cd4b4 100644 --- a/politeiad/backendv2/tstorebe/plugins/comments/comments.go +++ b/politeiad/backendv2/tstorebe/plugins/comments/comments.go @@ -5,7 +5,6 @@ package comments import ( - "fmt" "os" "path/filepath" "strconv" @@ -15,6 +14,7 @@ import ( backend "github.com/decred/politeia/politeiad/backendv2" "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins" "github.com/decred/politeia/politeiad/plugins/comments" + "github.com/pkg/errors" ) var ( @@ -42,6 +42,7 @@ type commentsPlugin struct { // Plugin settings commentLengthMax uint32 voteChangesMax uint32 + allowExtraData bool } // Setup performs any plugin setup that is required. @@ -137,6 +138,7 @@ func New(tstore plugins.TstoreClient, settings []backend.PluginSetting, dataDir var ( commentLengthMax = comments.SettingCommentLengthMax voteChangesMax = comments.SettingVoteChangesMax + allowExtraData = comments.SettingAllowExtraData ) // Override defaults with any passed in settings @@ -145,19 +147,26 @@ func New(tstore plugins.TstoreClient, settings []backend.PluginSetting, dataDir case comments.SettingKeyCommentLengthMax: u, err := strconv.ParseUint(v.Value, 10, 64) if err != nil { - return nil, fmt.Errorf("invalid plugin setting %v '%v': %v", + return nil, errors.Errorf("invalid plugin setting %v '%v': %v", v.Key, v.Value, err) } commentLengthMax = uint32(u) case comments.SettingKeyVoteChangesMax: u, err := strconv.ParseUint(v.Value, 10, 64) if err != nil { - return nil, fmt.Errorf("invalid plugin setting %v '%v': %v", + return nil, errors.Errorf("invalid plugin setting %v '%v': %v", v.Key, v.Value, err) } voteChangesMax = uint32(u) + case comments.SettingKeyAllowExtraData: + b, err := strconv.ParseBool(v.Value) + if err != nil { + return nil, errors.Errorf("invalid plugin setting %v '%v': %v", + v.Key, v.Value, err) + } + allowExtraData = b default: - return nil, fmt.Errorf("invalid comments plugin setting '%v'", v.Key) + return nil, errors.Errorf("invalid comments plugin setting '%v'", v.Key) } } @@ -167,5 +176,6 @@ func New(tstore plugins.TstoreClient, settings []backend.PluginSetting, dataDir dataDir: dataDir, commentLengthMax: commentLengthMax, voteChangesMax: voteChangesMax, + allowExtraData: allowExtraData, }, nil } diff --git a/politeiad/plugins/comments/comments.go b/politeiad/plugins/comments/comments.go index d74387885..084d3318c 100644 --- a/politeiad/plugins/comments/comments.go +++ b/politeiad/plugins/comments/comments.go @@ -34,6 +34,10 @@ const ( // SettingKeyVoteChangesMax is the plugin setting key for the // SettingVoteChangesMax plugin setting. SettingKeyVoteChangesMax = "votechangesmax" + + // SettingKeyAllowExtraData is the plugin setting key for the + // SettingAllowExtraData plugin setting. + SettingKeyAllowExtraData = "allowextradata" ) // Plugin setting default values. These can be overridden by providing a plugin @@ -47,6 +51,10 @@ const ( // user can change their vote on a comment. This prevents a // malicious user from being able to spam comment votes. SettingVoteChangesMax uint32 = 5 + + // SettingAllowExtraData is the default value of the bool flag which + // determines whether posting extra data along with the comment is allowed. + SettingAllowExtraData = false ) // ErrorCodeT represents a error that was caused by the user. @@ -99,8 +107,12 @@ const ( // does not match the record state. ErrorCodeRecordStateInvalid ErrorCodeT = 11 + // ErrorCodeExtraDataNotAllowed is returned when comment extra data + // is found while comment plugin setting does not allow it. + ErrorCodeExtraDataNotAllowed = 12 + // ErrorCodeLast unit test only. - ErrorCodeLast ErrorCodeT = 12 + ErrorCodeLast ErrorCodeT = 13 ) var ( @@ -118,6 +130,7 @@ var ( ErrorCodeVoteInvalid: "vote invalid", ErrorCodeVoteChangesMaxExceeded: "vote changes max exceeded", ErrorCodeRecordStateInvalid: "record state invalid", + ErrorCodeExtraDataNotAllowed: "comment extra data not allowed", } ) @@ -151,7 +164,15 @@ const ( // the deleted comment. Everything else from the original comment is // permanently deleted. // -// Signature is the client signature of State+Token+ParentID+Comment. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + ParentID + Comment + ExtraData + ExtraDataHint +// +// Receipt is the server signature of the user signature. +// +// The PublicKey, Signature, and Receipt are all hex encoded and use the +// ed25519 signature scheme. type Comment struct { UserID string `json:"userid"` // Unique user ID State RecordStateT `json:"state"` // Record state @@ -178,7 +199,15 @@ type Comment struct { // CommentAdd is the structure that is saved to disk when a comment is created // or edited. // -// Signature is the client signature of State+Token+ParentID+Comment. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + ParentID + Comment + ExtraData + ExtraDataHint +// +// Receipt is the server signature of the user signature. +// +// The PublicKey, Signature, and Receipt are all hex encoded and use the +// ed25519 signature scheme. type CommentAdd struct { // Data generated by client UserID string `json:"userid"` // Unique user ID @@ -206,7 +235,13 @@ type CommentAdd struct { // additional fields to properly display the deleted comment in the comment // hierarchy. // -// Signature is the client signature of the State+Token+CommentID+Reason +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Reason +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type CommentDel struct { // Data generated by client Token string `json:"token"` // Record token @@ -240,7 +275,13 @@ const ( // CommentVote is the structure that is saved to disk when a comment is voted // on. // -// Signature is the client signature of the State+Token+CommentID+Vote. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Vote +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type CommentVote struct { // Data generated by client UserID string `json:"userid"` // Unique user ID @@ -261,7 +302,15 @@ type CommentVote struct { // The parent ID is used to reply to an existing comment. A parent ID of 0 // indicates that the comment is a base level comment and not a reply commment. // -// Signature is the client signature of State+Token+ParentID+Comment. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + ParentID + Comment + ExtraData + ExtraDataHint +// +// Receipt is the server signature of the user signature. +// +// The PublicKey, Signature, and Receipt are all hex encoded and use the +// ed25519 signature scheme. type New struct { UserID string `json:"userid"` // Unique user ID State RecordStateT `json:"state"` // Record state @@ -283,7 +332,15 @@ type NewReply struct { // Edit edits an existing comment. // -// Signature is the client signature of State+Token+ParentID+Comment. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + ParentID + Comment + ExtraData + ExtraDataHint +// +// Receipt is the server signature of the user signature. +// +// The PublicKey, Signature, and Receipt are all hex encoded and use the +// ed25519 signature scheme. type Edit struct { UserID string `json:"userid"` // Unique user ID State RecordStateT `json:"state"` // Record state @@ -306,7 +363,13 @@ type EditReply struct { // Del permanently deletes all versions of the provided comment. // -// Signature is the client signature of the State+Token+CommentID+Reason +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Reason +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type Del struct { State RecordStateT `json:"state"` // Record state Token string `json:"token"` // Record token @@ -329,7 +392,13 @@ type DelReply struct { // original upvote. The public key cannot be relied on to remain the same for // each user so a user ID must be included. // -// Signature is the client signature of the State+Token+CommentID+Vote. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Vote +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type Vote struct { UserID string `json:"userid"` // Unique user ID State RecordStateT `json:"state"` // Record state diff --git a/politeiawww/api/comments/v1/v1.go b/politeiawww/api/comments/v1/v1.go index b2eda3328..fc2d3a0fa 100644 --- a/politeiawww/api/comments/v1/v1.go +++ b/politeiawww/api/comments/v1/v1.go @@ -141,7 +141,15 @@ const ( // information for the deleted comment. Everything else from the original // comment is permanently deleted. // -// Signature is the client signature of State+Token+ParentID+Comment. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + ParentID + Comment + ExtraData + ExtraDataHint +// +// Receipt is the server signature of the user signature. +// +// The PublicKey, Signature, and Receipt are all hex encoded and use the +// ed25519 signature scheme. type Comment struct { UserID string `json:"userid"` // Unique user ID Username string `json:"username"` // Username @@ -167,7 +175,13 @@ type Comment struct { // CommentVote represents a comment vote (upvote/downvote). // -// Signature is the client signature of the State+Token+CommentID+Vote. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Vote +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type CommentVote struct { UserID string `json:"userid"` // Unique user ID Username string `json:"username"` // Username @@ -186,7 +200,13 @@ type CommentVote struct { // The parent ID is used to reply to an existing comment. A parent ID of 0 // indicates that the comment is a base level comment and not a reply commment. // -// Signature is the client signature of State+Token+ParentID+Comment. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + ParentID + Comment + ExtraData + ExtraDataHint +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type New struct { State RecordStateT `json:"state"` Token string `json:"token"` @@ -227,7 +247,13 @@ const ( // upvoted, the resulting vote score is 0 due to the second upvote removing the // original upvote. // -// Signature is the client signature of the State+Token+CommentID+Vote. +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Vote +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type Vote struct { State RecordStateT `json:"state"` Token string `json:"token"` @@ -248,7 +274,13 @@ type VoteReply struct { // Del permanently deletes the provided comment. Only admins can delete // comments. A reason must be given for the deletion. // -// Signature is the client signature of the State+Token+CommentID+Reason +// PublicKey is the user's public key that is used to verify the signature. +// +// Signature is the user signature of the: +// State + Token + CommentID + Reason +// +// The PublicKey and Signature are hex encoded and use the +// ed25519 signature scheme. type Del struct { State RecordStateT `json:"state"` Token string `json:"token"`