commit 1257e5f6d6dc2d3d135cb0c442899f06dd025174
parent 7facfbd8054c54969d29aa8ea0cf9d93bfdaefae
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Sun, 29 Jan 2023 18:11:13 -0800
onion claim stuff
Diffstat:
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)