commit 4a6b8ad450dff8143005ea9544684882e2ad7eac
parent bc408f802a4e20b24a244ca1060b7c673449326c
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Tue, 24 Jan 2023 01:25:41 -0800
allow clearsign forum messages
Diffstat:
6 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/cmd/dkf/migrations/121.sql b/cmd/dkf/migrations/121.sql
@@ -0,0 +1,4 @@
+-- +migrate Up
+ALTER TABLE forum_messages ADD COLUMN is_signed TINYINT(1) NOT NULL DEFAULT 0;
+
+-- +migrate Down
diff --git a/pkg/database/table_forum_threads.go b/pkg/database/table_forum_threads.go
@@ -2,6 +2,7 @@ package database
import (
"dkforest/pkg/utils"
+ "github.com/ProtonMail/go-crypto/openpgp/clearsign"
html2 "html"
"regexp"
"strings"
@@ -61,6 +62,7 @@ type ForumMessage struct {
Message string
UserID UserID
ThreadID ForumThreadID
+ IsSigned bool
CreatedAt time.Time
User User
}
@@ -94,7 +96,13 @@ func (u *ForumMessage) DoSave() {
}
func (m *ForumMessage) Escape() string {
- res := strings.Replace(m.Message, "\r", "", -1)
+ msg := m.Message
+ if m.IsSigned {
+ if b, _ := clearsign.Decode([]byte(msg)); b != nil {
+ msg = string(b.Plaintext)
+ }
+ }
+ res := strings.Replace(msg, "\r", "", -1)
res = html2.EscapeString(res)
resBytes := bf.Run([]byte(res), bf.WithRenderer(utils.MyRenderer(true, true)), bf.WithExtensions(bf.CommonExtensions|bf.HardLineBreak))
res = string(resBytes)
@@ -135,6 +143,13 @@ func (m *ForumMessage) CanEdit() bool {
return true
}
+func (m *ForumMessage) ValidateSignature(pkey string) bool {
+ if pkey == "" {
+ return false
+ }
+ return utils.PgpCheckClearSignMessage(pkey, m.Message)
+}
+
func GetForumThread(threadID ForumThreadID) (out ForumThread, err error) {
err = DB.First(&out, "id = ? AND is_club = 1", threadID).Error
return
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
@@ -21,6 +21,7 @@ import (
"fmt"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
+ "github.com/ProtonMail/go-crypto/openpgp/clearsign"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/alecthomas/chroma/formatters/html"
"github.com/asaskevich/govalidator"
@@ -391,6 +392,21 @@ func getGCMKeyBytes(keyBytes []byte) (cipher.AEAD, int, error) {
return gcm, nonceSize, nil
}
+func PgpCheckClearSignMessage(pkey, msg string) bool {
+ keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pkey))
+ if err != nil {
+ return false
+ }
+ b, _ := clearsign.Decode([]byte(msg))
+ if b == nil {
+ return false
+ }
+ if _, err = b.VerifySignature(keyring, nil); err != nil {
+ return false
+ }
+ return true
+}
+
func PgpCheckSignMessage(pkey, msg, signature string) bool {
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pkey))
if err != nil {
diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go
@@ -1785,6 +1785,7 @@ func ThreadReplyHandler(c echo.Context) error {
return c.Render(http.StatusOK, "thread-reply", data)
}
message := database.ForumMessage{UUID: database.ForumMessageUUID(uuid.New().String()), Message: data.Message, UserID: authUser.ID, ThreadID: thread.ID}
+ message.IsSigned = message.ValidateSignature(authUser.GPGPublicKey)
if err := database.DB.Create(&message).Error; err != nil {
logrus.Error(err)
}
@@ -2044,6 +2045,7 @@ func ThreadEditMessageHandler(c echo.Context) error {
return c.Render(http.StatusOK, "thread-reply", data)
}
msg.Message = data.Message
+ msg.IsSigned = msg.ValidateSignature(authUser.GPGPublicKey)
msg.DoSave()
return c.Redirect(http.StatusFound, "/t/"+string(thread.UUID))
}
@@ -2051,6 +2053,18 @@ func ThreadEditMessageHandler(c echo.Context) error {
return c.Render(http.StatusOK, "thread-reply", data)
}
+func ThreadRawMessageHandler(c echo.Context) error {
+ if config.ForumEnabled.IsFalse() {
+ return c.Render(http.StatusOK, "flash", FlashResponse{Message: "Forum is temporarily disabled", Redirect: "/", Type: "alert-danger"})
+ }
+ messageUUID := database.ForumMessageUUID(c.Param("messageUUID"))
+ msg, err := database.GetForumMessageByUUID(messageUUID)
+ if err != nil {
+ return c.Redirect(http.StatusFound, "/")
+ }
+ return c.String(http.StatusOK, msg.Message)
+}
+
func ClubThreadEditMessageHandler(c echo.Context) error {
authUser := c.Get("authUser").(*database.User)
threadID := database.ForumThreadID(utils.DoParseInt64(c.Param("threadID")))
@@ -2110,6 +2124,7 @@ func NewThreadHandler(c echo.Context) error {
thread := database.ForumThread{UUID: database.ForumThreadUUID(uuid.New().String()), Name: data.ThreadName, UserID: authUser.ID, CategoryID: 1}
database.DB.Create(&thread)
message := database.ForumMessage{UUID: database.ForumMessageUUID(uuid.New().String()), Message: data.Message, UserID: authUser.ID, ThreadID: thread.ID}
+ message.IsSigned = message.ValidateSignature(authUser.GPGPublicKey)
database.DB.Create(&message)
_ = database.SubscribeToForumThread(authUser.ID, thread.ID)
return c.Redirect(http.StatusFound, "/t/"+string(thread.UUID))
diff --git a/pkg/web/public/views/pages/thread.gohtml b/pkg/web/public/views/pages/thread.gohtml
@@ -70,7 +70,12 @@
{{ end }}
</div>
<div style="display: flex; flex-direction: column;">
- <div style="flex: 1;"><a {{ .User.GenerateChatStyle | attr }} href="/u/{{ .User.Username }}">{{ .User.Username }}</a></div>
+ <div style="flex: 1;">
+ <a {{ .User.GenerateChatStyle | attr }} href="/u/{{ .User.Username }}">{{ .User.Username }}</a>
+ {{- if .IsSigned -}}
+ <a href="/t/{{ $.Data.Thread.UUID }}/messages/{{ .UUID }}/raw" class="ml-2" title="PGP signed" rel="noopener noreferrer" target="_blank">✅</a>
+ {{- end -}}
+ </div>
<div style="flex: 1;"><a href="#{{ .UUID }}">{{ .CreatedAt.Format "Jan 02, 2006 15:04:05" }}</a></div>
<div>
{{ if $.AuthUser }}
diff --git a/pkg/web/web.go b/pkg/web/web.go
@@ -189,6 +189,7 @@ func getMainServer() echo.HandlerFunc {
authGroup.POST("/t/:threadUUID/delete", handlers.ThreadDeleteHandler, middlewares.AuthRateLimitMiddleware(1*time.Second, 2))
authGroup.GET("/t/:threadUUID/reply", handlers.ThreadReplyHandler)
authGroup.POST("/t/:threadUUID/reply", handlers.ThreadReplyHandler, middlewares.AuthRateLimitMiddleware(1*time.Second, 2))
+ authGroup.GET("/t/:threadUUID/messages/:messageUUID/raw", handlers.ThreadRawMessageHandler)
authGroup.GET("/t/:threadUUID/messages/:messageUUID/edit", handlers.ThreadEditMessageHandler)
authGroup.POST("/t/:threadUUID/messages/:messageUUID/edit", handlers.ThreadEditMessageHandler, middlewares.AuthRateLimitMiddleware(1*time.Second, 2))
authGroup.GET("/t/:threadUUID/messages/:messageUUID/delete", handlers.ThreadDeleteMessageHandler)