dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 1257e5f6d6dc2d3d135cb0c442899f06dd025174
parent 7facfbd8054c54969d29aa8ea0cf9d93bfdaefae
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Sun, 29 Jan 2023 18:11:13 -0800

onion claim stuff

Diffstat:
Mpkg/database/tableLinks.go | 7+++++--
Mpkg/web/handlers/data.go | 2++
Mpkg/web/handlers/handlers.go | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mpkg/web/public/views/pages/link-claim.gohtml | 21++++++++++++---------
Mpkg/web/public/views/pages/link.gohtml | 12++++++++++++
Apkg/web/public/views/pages/links-claim-instructions.gohtml | 21+++++++++++++++++++++
Mpkg/web/web.go | 3+++
7 files changed, 118 insertions(+), 23 deletions(-)

diff --git a/pkg/database/tableLinks.go b/pkg/database/tableLinks.go @@ -23,17 +23,20 @@ type Link struct { UpdatedAt time.Time DeletedAt *time.Time Mirrors []LinksMirror + OwnerUser *User } -func (l Link) GenOwnershipCert() string { +func (l Link) GenOwnershipCert(signerUsername string) string { return fmt.Sprintf(""+ "DarkForest ownership certificate\n"+ "\n"+ "For the following onion address:\n"+ "%s\n"+ "\n"+ + "Signed by: @%s\n"+ "Signed on: %s", l.GetOnionAddr(), + signerUsername, time.Now().UTC().Format("January 02, 2006")) } @@ -85,7 +88,7 @@ func GetLinkByShorthand(shorthand string) (out Link, err error) { } func GetLinkByUUID(linkUUID string) (out Link, err error) { - err = DB.First(&out, "uuid = ?", linkUUID).Error + err = DB.Preload("OwnerUser").First(&out, "uuid = ?", linkUUID).Error return } diff --git a/pkg/web/handlers/data.go b/pkg/web/handlers/data.go @@ -235,6 +235,8 @@ type editLinkData struct { type claimLinkData struct { Link database.Link Certificate string + Signature string + Error string } type linkData struct { diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "encoding/csv" "encoding/json" + "encoding/pem" "errors" "fmt" "github.com/jinzhu/gorm" @@ -1338,6 +1339,10 @@ func LinkPgpDownloadHandler(c echo.Context) error { return c.Stream(http.StatusOK, "application/octet-stream", strings.NewReader(linkPgp.PgpPublicKey)) } +func LinksClaimInstructionsHandler(c echo.Context) error { + return c.Render(http.StatusOK, "links-claim-instructions", nil) +} + func LinkHandler(c echo.Context) error { shorthand := c.Param("shorthand") linkUUID := c.Param("linkUUID") @@ -1558,7 +1563,10 @@ func EditLinkHandler(c echo.Context) error { _ = database.DeleteLinkCategories(link.ID) _ = database.DeleteLinkTags(link.ID) - data.Link = c.Request().PostFormValue("link") + // If link is signed, we can no longer edit the link URL + if link.SignedCertificate == "" { + data.Link = c.Request().PostFormValue("link") + } data.Title = c.Request().PostFormValue("title") data.Description = c.Request().PostFormValue("description") data.Shorthand = c.Request().PostFormValue("shorthand") @@ -1671,6 +1679,7 @@ func EditLinkHandler(c echo.Context) error { } func ClaimLinkHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) linkUUID := c.Param("linkUUID") link, err := database.GetLinkByUUID(linkUUID) if err != nil { @@ -1678,25 +1687,67 @@ func ClaimLinkHandler(c echo.Context) error { } var data claimLinkData data.Link = link - data.Certificate = link.GenOwnershipCert() - - // test := `-----BEGIN SIGNED MESSAGE----- - //message by n0tr1v - //-----BEGIN SIGNATURE----- - //EEU5ZQ7le6IH8ehZsbZaQbHlu/JRkSK72j4KpUep3nIB0akErBusv/t2STNRfGc/ - //j6JmMytRgTWOlq1nMFrkAg== - //-----END SIGNATURE-----` + data.Certificate = link.GenOwnershipCert(authUser.Username) if c.Request().Method == http.MethodGet { return c.Render(http.StatusOK, "link-claim", data) } - pemSig := c.Request().PostFormValue("signature") - isValid := utils.VerifyTorSign(link.GetOnionAddr(), data.Certificate, pemSig) - fmt.Println("valid?", isValid) + data.Signature = c.Request().PostFormValue("signature") + + b64Sig, err := base64.StdEncoding.DecodeString(data.Signature) + if err != nil { + data.Error = "invalid signature" + return c.Render(http.StatusOK, "link-claim", data) + } + pemSign := string(pem.EncodeToMemory(&pem.Block{Type: "SIGNATURE", Bytes: b64Sig})) + + isValid := utils.VerifyTorSign(link.GetOnionAddr(), data.Certificate, pemSign) + if !isValid { + data.Error = "invalid signature" + return c.Render(http.StatusOK, "link-claim", data) + } + + signedCert := "-----BEGIN SIGNED MESSAGE-----\n" + + data.Certificate + "\n" + + pemSign + + link.SignedCertificate = signedCert + link.OwnerUserID = authUser.ID + link.DoSave() + return c.Redirect(http.StatusFound, "/links/"+link.UUID) } +func ClaimDownloadCertificateLinkHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + + linkUUID := c.Param("linkUUID") + link, err := database.GetLinkByUUID(linkUUID) + if err != nil { + return c.Redirect(http.StatusFound, "/") + } + + fileName := "certificate.txt" + + // Keep track of user downloads + if _, err := database.CreateDownload(authUser.ID, fileName); err != nil { + logrus.Error(err) + } + + c.Response().Header().Set("Content-Disposition", `attachment; filename="`+fileName+`"`) + return c.Stream(http.StatusOK, "application/octet-stream", strings.NewReader(link.GenOwnershipCert(authUser.Username))) +} + +func ClaimCertificateLinkHandler(c echo.Context) error { + linkUUID := c.Param("linkUUID") + link, err := database.GetLinkByUUID(linkUUID) + if err != nil { + return c.Redirect(http.StatusFound, "/") + } + return c.String(http.StatusOK, link.SignedCertificate) +} + func ForumHandler(c echo.Context) error { authUser := c.Get("authUser").(*database.User) var data forumData diff --git a/pkg/web/public/views/pages/link-claim.gohtml b/pkg/web/public/views/pages/link-claim.gohtml @@ -9,20 +9,23 @@ <p> You can claim ownership of an onion address by using your onion private key to sign the following certificate.<br /> Once done, send the signature here.<br /> - + </p> + <div class="form-group"> + <textarea name="certificate" class="form-control" rows="7" readonly>{{ .Data.Certificate }}</textarea> + <form method="post" action="/links/{{ .Data.Link.UUID }}/claim/download-certificate"> + <input type="hidden" name="csrf" value="{{ $.CSRF }}" /> + <button class="btn btn-secondary mt-2">Downlaod certificate file</button> + </form> + </div> + <p> + Use the <a href="/links/claim-instructions">following instructions</a> to sign the certificate file. </p> <form method="post"> <input type="hidden" name="csrf" value="{{ .CSRF }}" /> <div class="form-group"> - <textarea name="certificate" class="form-control" rows="6" readonly>{{ .Data.Certificate }}</textarea> - <button class="btn btn-secondary mt-2">Downlaod certificate file</button> - </div> - <p> - Use the <a href="/">following python script</a> to sign the certificate file. - </p> - <div class="form-group"> <label for="signature">Signature:</label> - <textarea name="signature" id="signature" class="form-control" rows="5" placeholder="-----BEGIN SIGNATURE----- ..."></textarea> + <input type="text" name="signature" id="signature" class="form-control {{ if .Data.Error }} is-invalid{{ end }}" placeholder="base64 signature" value="{{ .Data.Signature }}" autofocus /> + {{ if .Data.Error }}<div class="invalid-feedback">{{ .Data.Error }}</div>{{ end }} </div> <button class="btn btn-primary">Claim ownership</button> <a class="btn btn-secondary" href="/links/{{ .Data.Link.UUID }}">Cancel</a> diff --git a/pkg/web/public/views/pages/link.gohtml b/pkg/web/public/views/pages/link.gohtml @@ -21,6 +21,18 @@ </div> </div> + <strong>Owner:</strong> + {{ if .Data.Link.OwnerUserID }} + <a href="/u/{{ .Data.Link.OwnerUser.Username }}" {{ .Data.Link.OwnerUser.GenerateChatStyle | attr }}>@{{ .Data.Link.OwnerUser.Username }}</a> + ( + <a href="/links/{{ .Data.Link.UUID }}/claim-certificate">certificate</a> + {{ if eq .AuthUser.ID .Data.Link.OwnerUserID }} + | <a href="/links/{{ .Data.Link.UUID }}/claim">re-sign</a> + {{ end }} + ) + {{ else }} + <a href="/links/{{ .Data.Link.UUID }}/claim">claim ownership</a> + {{ end }}<br /> <strong>Created at:</strong> {{ .Data.Link.CreatedAt.Format "Jan 02, 2006 15:04:05" }}<br /> <strong>Link:</strong> <a href="{{ .Data.Link.URL }}" rel="noopener noreferrer" target="_blank">{{ .Data.Link.URL }}</a><br /> <strong>Description:</strong><br /> diff --git a/pkg/web/public/views/pages/links-claim-instructions.gohtml b/pkg/web/public/views/pages/links-claim-instructions.gohtml @@ -0,0 +1,20 @@ +{{ define "title" }}dkf - Link claim instructions{{ end }} + +{{ define "content" }} + +<div class="container mb-5"> + <ul> + <li>Download the certificate file you want to sign</li> + <li>Use the following Golang script to sign the file <code>./torsign -s /path/to/hs_ed25519_secret_key certificate.txt</code></li> + <li>Alternatively, you can use python <code>python3 torsign.py -s /path/to/hs_ed25519_secret_key certificate.txt</code></li> + </ul> + <div> + Golang script:<br /> + <a href="http://yylovpz7taca7jfrub3wltxabzzjp34fngj5lpwl6eo47ekt5cxs6mid.onion/n0tr1v/dkforest/src/master/cmd/torsign/main.go">http://dkfgit.onion/n0tr1v/dkforest/src/master/cmd/torsign/main.go</a> + </div> + <div> + Python script:<br /> + <a href="http://yylovpz7taca7jfrub3wltxabzzjp34fngj5lpwl6eo47ekt5cxs6mid.onion/n0tr1v/dkforest/src/master/cmd/torsign/torsign.py">http://dkfgit.onion/n0tr1v/dkforest/src/master/cmd/torsign/torsign.py</a> + </div> +</div> +{{ end }} +\ No newline at end of file diff --git a/pkg/web/web.go b/pkg/web/web.go @@ -167,10 +167,13 @@ func getMainServer(i18nBundle *i18n.Bundle, renderer *tmp.Templates) echo.Handle authGroup.GET("/links/download", handlers.LinksDownloadHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.POST("/links/download", handlers.LinksDownloadHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.GET("/l/:shorthand", handlers.LinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) + authGroup.GET("/links/claim-instructions", handlers.LinksClaimInstructionsHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.GET("/links/:linkUUID", handlers.LinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.POST("/links/:linkUUID/restore", handlers.RestoreLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.GET("/links/:linkUUID/claim", handlers.ClaimLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.POST("/links/:linkUUID/claim", handlers.ClaimLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) + authGroup.POST("/links/:linkUUID/claim/download-certificate", handlers.ClaimDownloadCertificateLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) + authGroup.GET("/links/:linkUUID/claim-certificate", handlers.ClaimCertificateLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.GET("/links/:linkUUID/edit", handlers.EditLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.POST("/links/:linkUUID/edit", handlers.EditLinkHandler, middlewares.AuthRateLimitMiddleware(time.Second, 2)) authGroup.GET("/links/:linkUUID/delete", handlers.LinkDeleteHandler)