will allow clients to discover the public key automatically, using:
gpg --locate-keys outgoing@sr.ht
Internet-Draft https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service#name-web-key-directory
---
Initial discussion: https://lists.sr.ht/~sircmpwn/sr.ht-discuss/%3Cdd965bea-a130-4675-8cdf-742286607079%40app.fastmail.com%3E
First patch (on meta.sr.ht frontend): https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/40794
Second patch (on meta.sr.ht backend): https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/40799
Current patch: hub.sr.ht backend.
It fixes the discrepancy noted by ~bitfehler (with an added test)
To be publicly accessible, adjustements must be made to sr.ht-nginx/hub.sr.ht.conf, like (untested):
location /.well-known/openpgpkey {
proxy_pass http://127.0.0.1:5114;
}
---
api/openpgpkey/opengpgkey_test.go | 78 +++++++++++
api/openpgpkey/openpgpkey.go | 141 ++++++++++++++++++++
api/openpgpkey/testdata/expected_served_key | Bin 0 -> 1169 bytes
api/openpgpkey/testdata/pubkey | 80 +++++++++++
api/server.go | 8 ++
go.mod | 9 +-
6 files changed, 313 insertions(+), 3 deletions(-)
create mode 100644 api/openpgpkey/opengpgkey_test.go
create mode 100644 api/openpgpkey/openpgpkey.go
create mode 100644 api/openpgpkey/testdata/expected_served_key
create mode 100644 api/openpgpkey/testdata/pubkey
diff --git a/api/openpgpkey/opengpgkey_test.go b/api/openpgpkey/opengpgkey_test.go
new file mode 100644
index 0000000..67d6306
--- /dev/null
+++ b/api/openpgpkey/opengpgkey_test.go
@@ -0,0 +1,78 @@
+package openpgpkey
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/go-chi/chi"
+ "github.com/stretchr/testify/assert"
+ "github.com/vaughan0/go-ini"
+)
+
+func TestNewWellKnown(t *testing.T) {
+ router := chi.NewRouter()
+ err := MountWebKeyDirectoryRoutes(ini.File{
+ "sr.ht": ini.Section{
+ "global-domain": "sr.ht",
+ },
+ "mail": ini.Section{
+ "pgp-pubkey": "testdata/pubkey",
+ },
+ }, router)
+ assert.NoError(t, err)
+
+ resp := httptest.NewRecorder()
+ router.ServeHTTP(resp, httptest.NewRequest("GET", "/.well-known/openpgpkey/hu/4y36rkzdjnzmk3oxaekyi5biowgr5kcz", nil)) // admin in zbase32
+ assert.Equal(t, http.StatusOK, resp.Result().StatusCode)
+ assert.Equal(t, "application/octet-stream", resp.Header().Get("Content-Type"))
+
+ // reference file created with $(gpgconf --list-dirs libexecdir)/gpg-wks-client -v --install-key -C . ./pubkey admin@sr.ht
+ expectedKey, err := os.ReadFile("testdata/expected_served_key")
+ assert.NoError(t, err)
+ servedKey := resp.Body.Bytes()
+ assert.NoError(t, os.WriteFile("testdata/served_key", servedKey, 0o666))
+ if assert.Len(t, servedKey, len(expectedKey)) {
+ // 13 bytes are different because gpg used the "new-format tag byte"
+ // https://superuser.com/a/1610202
+ diff := 0
+ for i, got := range servedKey {
+ expected := expectedKey[i]
+ if got != expected {
+ diff++
+ }
+ }
+ if assert.Equal(t, 13, diff) {
+ // everything look fine, remove the testdata/served_key
+ assert.NoError(t, os.Remove("testdata/served_key"))
+ } else {
+ // assert display a nice hex diff
+ assert.Equal(t, expectedKey, servedKey)
+ }
+ }
+}
+
+func TestEncodeLocalPart(t *testing.T) {
+ assert.Equal(t, "4y36rkzdjnzmk3oxaekyi5biowgr5kcz", encodeLocalPart("admin"))
+ assert.Equal(t, "4y36rkzdjnzmk3oxaekyi5biowgr5kcz", encodeLocalPart("Admin"))
+ assert.Equal(t, "e5a4bxki1ktx1jncwco5nkcofedmkxod", encodeLocalPart("git"))
+}
+
+func TestGetGlobalDomain(t *testing.T) {
+ globalDomain, err := getGlobalDomain(ini.File{
+ "sr.ht": ini.Section{
+ "global-domain": "sr.ht",
+ },
+ }, "meta.sr.ht")
+ assert.NoError(t, err)
+ assert.Equal(t, "sr.ht", globalDomain)
+
+ globalDomain, err = getGlobalDomain(ini.File{
+ "meta.sr.ht": ini.Section{
+ "origin": "http://meta.sr.ht.local",
+ },
+ }, "meta.sr.ht")
+ assert.NoError(t, err)
+ assert.Equal(t, "sr.ht.local", globalDomain)
+}
diff --git a/api/openpgpkey/openpgpkey.go b/api/openpgpkey/openpgpkey.go
new file mode 100644
index 0000000..a43aeed
--- /dev/null
+++ b/api/openpgpkey/openpgpkey.go
@@ -0,0 +1,141 @@
+package openpgpkey
+
+import (
+ "crypto/sha1"
+ "encoding/base32"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+
+ "github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/go-chi/chi"
+ "github.com/vaughan0/go-ini"
+)
+
+// MountWebKeyDirectoryRoutes mounts the routes to serve the public key (binary encoded) under the well-known URLs:
+// - /.well-known/openpgpkey/hu/<zbase32-hash>
+// - /.well-known/openpgpkey/hu/policy
+//
+// See https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service
+func MountWebKeyDirectoryRoutes(conf ini.File, router chi.Router) error {
+ pubKeyPath, ok := conf.Get("mail", "pgp-pubkey")
+ if !ok {
+ return errors.New("expected [mail]pgp-pubkey in config")
+ }
+
+ pubKeyFile, err := os.Open(pubKeyPath)
+ if err != nil {
+ return fmt.Errorf("failed to open [mail]pgp-pubkey: %v", err)
+ }
+ defer pubKeyFile.Close()
+
+ keyring, err := openpgp.ReadArmoredKeyRing(pubKeyFile)
+ if err != nil {
+ return err
+ }
+
+ globalDomain, err := getGlobalDomain(conf, "meta.sr.ht")
+ if err != nil {
+ return err
+ }
+
+ for _, entity := range keyring {
+ for _, identidy := range entity.Identities {
+ local, domain, found := stringsCut(identidy.UserId.Email, "@")
+ if !found || domain != globalDomain {
+ continue
+ }
+ serializePublicKey := wkdSerializer(entity, identidy)
+ router.Get("/.well-known/openpgpkey/hu/"+encodeLocalPart(local), func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/octet-stream")
+ serializePublicKey(w)
+ })
+ }
+ }
+ router.Get("/.well-known/openpgpkey/hu/policy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.Write([]byte("# Policy flags for the OpenPGP Web Key Directory"))
+ }))
+ return nil
+}
+
+func wkdSerializer(entity *openpgp.Entity, identidy *openpgp.Identity) func(io.Writer) error {
+ return func(w io.Writer) error {
+ if err := entity.PrimaryKey.Serialize(w); err != nil {
+ return err
+ }
+ if err := identidy.UserId.Serialize(w); err != nil {
+ return err
+ }
+ for _, sig := range identidy.Signatures {
+ if err := sig.Serialize(w); err != nil {
+ return err
+ }
+ }
+ for _, subkey := range entity.Subkeys {
+ if err := subkey.PublicKey.Serialize(w); err != nil {
+ return err
+ }
+ for _, revocation := range subkey.Revocations {
+ if err := revocation.Serialize(w); err != nil {
+ return err
+ }
+ }
+ if err := subkey.Sig.Serialize(w); err != nil {
+ return err
+ }
+
+ }
+ return nil
+ }
+}
+
+var zbase32Encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769").WithPadding(base32.NoPadding)
+
+func encodeLocalPart(s string) string {
+ hash := sha1.New()
+ hash.Write([]byte(strings.ToLower(s)))
+ shaOne := hash.Sum(nil)
+ return zbase32Encoding.EncodeToString(shaOne)
+}
+
+// Gets the global domain from the config. If it's not defined, assume that
+// the given site is a sub-domain of the global domain, i.e. it is of the
+// form `blah.globaldomain.com`.
+func getGlobalDomain(conf ini.File, site string) (string, error) {
+ globalDomain, ok := conf.Get("sr.ht", "global-domain")
+ if ok {
+ return globalDomain, nil
+ }
+ serviceOrigin, ok := conf.Get(site, "origin")
+ if !ok {
+ return "", fmt.Errorf("expected [sr.ht]global-domain or [%s]origin in config", site)
+ }
+ serviceURL, err := url.Parse(serviceOrigin)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse URL [%s]origin in config: %v", site, err)
+ }
+ _, globalDomain, _ = stringsCut(serviceURL.Hostname(), ".")
+ return globalDomain, nil
+}
+
+// strings.Cut is available starting with go1.18
+// Copied from https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/strings/strings.go;l=1262
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license.
+//
+// stringsCut slices s around the first instance of sep,
+// returning the text before and after sep.
+// The found result reports whether sep appears in s.
+// If sep does not appear in s, cut returns s, "", false.
+func stringsCut(s, sep string) (before, after string, found bool) {
+ if i := strings.Index(s, sep); i >= 0 {
+ return s[:i], s[i+len(sep):], true
+ }
+ return s, "", false
+}
diff --git a/api/openpgpkey/testdata/expected_served_key b/api/openpgpkey/testdata/expected_served_key
new file mode 100644
index 0000000000000000000000000000000000000000..0650ef3865a8a907fe71bdb19ca7b0978a29be49
GIT binary patch
literal 1169
zcmV;C1aAA80SyFL3eeO62mrjW#%(2aeVVDQPfuTXRMLH@P(mU}#bq0>%LAM3x7F^G
zqdA!-Si!~^Wa|bYx3q)|3_*%I9n@y&8r4d3h6QDY_n=p)^E&W2@haX9GVNb$u5C&E
zB?g+PWoh-$0|6M))e2^douh}W<|U?ERnPdG3g6hf3Z+Q*wFEPRNxH5b(7t+A9=ikf
znWz`*@Zxl17j>FPb|!F9S;7Ig{<DXcgF+f$-XvP(0w=1y$~MAAT<=N?bF^Dy!8Xf~
zas-@J_hNmmPEUISx$z-jN&e6vk_LjGlOZt32Cc(BkC)&wg)7>N;#rjhrW$N|R0u%;
z(vqEtwp-V~BaS7Kaq9pP0RREC6LWGdXmlVvVPtJ-Za{N#E@*T<i2*kR69EVSAq4_h
z3eeO78v_Lk2?z%Q1{DYi3JU@R76JnS0v-VZ7k~f?2@qwM1kt04ldgtb2melFzvfjw
zBtWw@<nKoHfnx4jhG}$eB_on1#h6I?QR{=ex%nG)4+j)TjWtAE^+;9=qeDJqk@>!7
zd`q&{!R7ijoK^6OKzr}v<XFV%Tq!alqmu9-ak79QA(p16_kSyvGZI~@A4uBfRmlH1
z<eMouju`q~p@U?f#>#0t>!l<k1u{px{=(lCLn?2c-zNW}@=ce{+-CCcg}`Y&7eay`
zUQz3=VM8e{rXw86D`oeH#Y%p;CxmX3#8XC-yZyzH{=ttnXrtmgZmJ;B#x3kQ>?N!W
zuwJ^#<@a8JQaK~GVP6x5R~-j_LMmO%Q#x<;NKmHx&A-M6Ik8z0KO4CL4Fp&U(9{74
z0LoH4T40{<vdU#z^HiLEVUg42rlOrgnP+5kB=$zCM-d`;JLP-Hdml(b)RFqzQ3-_)
zrPy9)j1?JHVL~(-3f8=Uz0VlfO}=urF)*L2@0YDKyk`RUsgNcg@|z|32-@F<>wSru
zPKg$NQAB}}cs;nthIYY3dCMpzhtB>bw%n{!vS<|dwK=8uWiam3(l|aOiEqcG6##;9
z)fV;scF>B&)w~fP{?LVWlVU`9ky2V2ozGk3HUaE~!_n3ZLI7gNwk&8mzLVa*dN9{U
zZ@D|$cTcKYGbgS0DqCz-=|5m*^t_wJ)gk-dU*PgDgq;JM1Gt^A-pz6^C6?3P0{{^L
z00D^s9|RZy2mlEM0$2*r)B+m}0162ZWtRleql%NR466tLv!lJb9I<Jq{Luu;loJ|Z
zh}>!~WHMUPL}#cGM~|zes^MIzRL^aJC|75$)JK<zwloRMQDTfBqu`fp1Z6+H>mRXd
zSUI<r{h!tVVtQ{PxZqx3FQSQI|3C7bw+lEKz0RR@U~}=u-Afx8L72=VmCKJOgVN+b
zLvk~j2X~vA{7B?&LL@aWiy)J;#5ndtH|M@w<}&E%P2~5_6k&mpg96vRZ@%FOhfa+8
zbt;z^18lqOdkE$qY@-(Jj0B%^yI-{ii;50GO72Y8m{Dq2B%MVyVdv4boas<VFVjY{
j{Cw%3E7_NImj#fO)ZM0?L)bW-EI|b3r%2Loooy2*?miwD
literal 0
HcmV?d00001
diff --git a/api/openpgpkey/testdata/pubkey b/api/openpgpkey/testdata/pubkey
new file mode 100644
index 0000000..27c1a2c
--- /dev/null
+++ b/api/openpgpkey/testdata/pubkey
@@ -0,0 +1,80 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFgK0NQBCAC8sMZtJXV9mqmtT09feFTSfahQQiJJxWUbr8sDm+231e6TozmZ
+JljBxhhk6wYht7SEDAxBijod1GbpGtVKc4YFZYb3oFep8zrwOPEq3g4y7V9rrm1J
+/SUGmqhlafXQAwEY0tUKZoudo4es5iWmW1XP+JwK39i7CqVI97UEM4NJuq4d0L56
+VR67A/aZqBfr8OJ0Yxd1mkd2JnBRWcIBt/6zh5aDQhpg3iRa5QInqr3KNsJGXO9K
+C3O0W2PBNsuQcgScVfdifa1OT3sDufEhYEn+0CCSBoKfkyEwyAatwz6Pl+AyhSva
+i+JZlQSmGmx6VAhBANKSnYm2W9e/I44lknHrABEBAAG0E3NyLmh0IDxhZG1pbkBz
+ci5odD6JATcEEwEIACEFAlgK0NQCGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AA
+CgkQZZcE0aOKk66GXAf/TmS/5lU+JECzNeTvRvWBYu5ahml0biUjkibFmEj6UeuD
+vLn5G3UPBxRIjTVEXPVIVgujQz5kkfm+Z3xLstbB5fo1nFXwikB77+LkWMTpXCky
+IqOS8CBxsoAgIZamp/d/K5YzEl2qH0ja5lXI/zjkmyk5jhj6XaGDZJ/Gymk866Uk
+IwUyR7z+wt8VQypvnt8m/6LyTZfO3Gby74XAaT0XQoIfXlHrrmFDKS6mIxzKK2X3
+iMVKfrknhG6TxFNGk7v9xZH+wY82aKPiO26qINHGLew57CWsDLBeusrl916BUjkj
+tmFfE4ZXHQd+QipdzFM6b/VIUKb7zb/GBzmxWRE/G7RIc3IuaHQgb3V0Z29pbmcg
+KEdlbmVyYWwgcHVycG9zZSBvdXRnb2luZyBzci5odCBhZGRyZXNzKSA8b3V0Z29p
+bmdAc3IuaHQ+iQFOBBMBCAA4FiEERHtp5LNL6QvIKaDpZZcE0aOKk64FAlwreIwC
+GwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQZZcE0aOKk64s7Af/RHO4hxQi
+rx4+W1Kqe3f6An9KcrFQ1hDUzWDm5KKZE7RztD1/9BoFXK4UOyPB1F+Ng0gCknLI
+65ISSsiC9CbS0adOf5nAO5cUbwSv2A1D/TgznVf1dZvdBQ1YuCyV53UXFryZJ4d7
+NgPQPOrzaQE7rQutbk/wlNSKn7t3rkJjVaSPAofdjG0HCb2Uc/oFSCNG6aY3SqBv
+yr0ys57CrdhKqg2DVQcsZk8elSEQu4DIFZm5xsM7wxN57qA3Xshm347t4tWH+IpT
+i9sqjIBCa+TEy5frhkgj/ykIl9AKrIWs27KEvCogXy6aiwbQS8K9+Mp4d8TKmv12
+nO79CM5j7vFLa7RGc3IuaHQgdG9kbyAoQWRkcmVzcyBmb3IgYXV0b21hdGVkIGVt
+YWlscyBmcm9tIHRvZG8uc3IuaHQpIDx0b2RvQHNyLmh0PokBTgQTAQgAOBYhBER7
+aeSzS+kLyCmg6WWXBNGjipOuBQJcK3ihAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B
+AheAAAoJEGWXBNGjipOuexMH/jBckRZTSVT9CciFzAslsuqRRehJ4Tae5EW4X5Ou
+JGnmFVDb+nn1T14I+I+GhA3URYuCe1pEFiJfg368pXqGDbh/9JSmzWd9PZtGGlI6
+JApQlPYxapfkv3UrXtF0T7Rj0yTTgf6vkjSaHdp05bCvatTO7UAD01s9/TCHloeP
+Mxkx9e4BKfhO2y/aVeNFW9EASKG46q/2c5UyUEjoVCYCV14L3vMcbXNO0hQOeY6o
+WQD245tfNRPUMNumCfdE+FF8PWYGkhypPf9Os4FfsHQsj12bi3CNEa7wrVuMwRU3
+qTWqkc+0c/HMU471DIh6YH7dxFgLTqDtZ7SqoFufmwJ+qTS0THNyLmh0IGJ1aWxk
+cyAoQWRkcmVzcyBmb3IgYXV0b21hdGVkIGVtYWlscyBmcm9tIGJ1aWxkcy5zci5o
+dCkgPGJ1aWxkc0Bzci5odD6JAU4EEwEIADgWIQREe2nks0vpC8gpoOlllwTRo4qT
+rgUCXCt4sgIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBllwTRo4qTrjzo
+B/oCCFAGAhrQJADDH7ALYd6K7kQgtHjOE++u2ZAjbbu5CBgIcao5UKe0t3u8xVDK
+E0gy/v2Y/vyu1VacaXWhvV52dm+YA6UYMDzlyTngQ6zXP2MmmOXYm9o7hks83Acj
+8zyVxtuoSiTEcXBxyGbcFa4TN8ptuiEJEKnRnpi67xMrniaXfBTAE4BDgD3qpfRd
+zh3Ya+/pqd/f6yWCok8rQeHennqbyrdSrjzlwrpwHj63aTp0CsbLjsv7JToo8AIo
+GoyGgXaMj1991DJFE2kxmd3aSFVEPGH2bgLk21wadjSMBfVgWQy/d0hBpU0h77+e
+iGkNWsmQXE0TrNKs96YSPktUtENzci5odCBnaXQgKEFkZHJlc3MgZm9yIGF1dG9t
+YXRlZCBlbWFpbHMgZnJvbSBnaXQuc3IuaHQpIDxnaXRAc3IuaHQ+iQFOBBMBCAA4
+FiEERHtp5LNL6QvIKaDpZZcE0aOKk64FAlwreMMCGwMFCwkIBwIGFQoJCAsCBBYC
+AwECHgECF4AACgkQZZcE0aOKk67CfggAlkf1xyVLFBNC/UyaYq6iYotcVQ9oW82O
+dLZ00NckXllixvmcd9s+l5xmKluGPOivY2/tM2ongxfe3orPrr4/JIQXAeZIcq7z
+uz8OJDI/efPFC+x/BMb2eb0bpDGamcTMp6AiJGZd4fF2IcBGcWUpFSbaXzy8fE/d
+iVEIk970HGSxB8NchA7TY1AKzGty5qy4lslfGc7eA9G8ZfoNQ96MGJFIPvY4jT4L
+71tH+baqRN3/8By0mmxLCiW8rQDVGgcPicZENWWioCIYLlj7TixqTLLtK8nCLQ8a
+y8uc/9Yk+whJDah+t8wBAuIjCysa/lZPcxoWekDyZ6pLGyI2T/QRSbRGc3IuaHQg
+bWV0YSAoQWRkcmVzcyBmb3IgYXV0b21hdGVkIGVtYWlscyBmcm9tIG1ldGEuc3Iu
+aHQpIDxtZXRhQHNyLmh0PokBTgQTAQgAOBYhBER7aeSzS+kLyCmg6WWXBNGjipOu
+BQJcK3jVAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEGWXBNGjipOuS9AH
+/RVimfI35uuI4zGu5v4T/FNO7uSvLiKtG64iYoQCBJSOw0wx/rU/wWfEZ5w8wPZB
+TdcFVB/qEVCkE8vISmvK70fLiBtOKq8tmZvgFd9f7V9LWUDyoh4xOShFM+nPWpJW
+aweKK/tfclXAU4NIfgtFEn+XuSqrtt3VmQWeYiR8+ToRb3lcO8CxLW/AFZrVzRZu
+X9nQMyCr6mK6Xxg8c17CXSHJRSfFEvRzES7q/spSJT26xlaY8X1V82GFRpBPdeIV
+EEd/6vbPqNJ6kyZioYB/PyEkTxMLh7sRM8l2xKYcqLgW7vXKW2L82vmFdzdPCQMg
+xxWhdUhYWD9RIdoT/n8CEHW0UnNyLmh0IGRpc3BhdGNoIChBZGRyZXNzIGZvciBh
+dXRvbWF0ZWQgZW1haWxzIGZyb20gZGlzcGF0Y2guc3IuaHQpIDxkaXNwYXRjaEBz
+ci5odD6JAU4EEwEIADgWIQREe2nks0vpC8gpoOlllwTRo4qTrgUCXCt46gIbAwUL
+CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBllwTRo4qTruoEB/9UJapNcs9gci37
+XJCEHB/G0+9HL7lcGjbBSfMhA2BCuirfS3k347Ug+hLzld23wqFSP2wbr+muKHxL
+1rQ9CIJ5lVwWOmZQLovl/8W/irQRh4SRnJtxWpCpXKoYhDH0bPoUbYjR9rezWWb0
+rKw6KpWiEhYAOZWZ/vnv9uT70P7RP0+D5biI5snuS/O2sJLVqt0YDpOPcyF3nFg5
+KHre7DzzBNoIZjrMOld8KbQ9YjOG14kokhXFQRbkrPhisx2uQdaCJN1fn4nbn8EU
+DMXk28ZtutEAJjoGnaWksYi1VtYm/4Z8dWMhRByAx0fNKbl3FhzEUuL8ef2nxfcA
+2KMtUu2IuQENBFgK0NQBCADKUjxaYJ7vssplWvNUnH5hkdPlpqKdQ5lnZHMk9kaq
+RxEieDvle8l7H0hC1JH621EJhQ+l2F5njBUZVmFCNBoK1ryAvc8Y2E2+crYxMJ+r
+75etNLxnAvepkCYf8psl+Qja34brfYmaTokWflFEgZF4PbjIhnbBRHnLKCWHzv4l
+ttysU7JoFPe1OaX4ZTDu09I4PiSJb8ekFQCCcdUW9f520IrF1bwRIP7QhXWTYkR5
+kVJaGZ3PW+Q2AeyFw9HWDEIAYse2LGg6vpPevnow10ZvuTvbd0+qWzMnrfcqW2xV
+6T9gZvS8m8XVIfveX+DyL4SdA5sDuJ2w3s1yLyWW094DABEBAAGJAR8EGAEIAAkF
+AlgK0NQCGwwACgkQZZcE0aOKk64MqwgAs6O9uhyxaaf80QTKlBMaYYjcai9kMlrS
+RGeoEUePq6Wq4VypVM9tgShXZ67UR5eJtjQJzFFijCCj4JdsBGU/vesfsWpYObeW
+/Z/WAGJ6byK44F5gL6KJYf8/8p23CzgZvc6hdGBz8cfdSxsZQZjMI5XLjyeD0uQ/
+Q3IzmQd3m5r8SORtQiQ1L4sgk7PEOPZDN+e+XOYy6OlN5PfPFGGBkYMC171vvuEI
+h06M+nUqlxcDbLvsewjmH2yjFuyMBJ9zu1+1BouKDkFK7kzXmFFqWCSdRTVh59G0
+nOlQSC/TRrL8fOmfK9mXdZcFkJXU3aadQ9g4nSxBBOanSNJvnW0TJw==
+=zmAw
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/api/server.go b/api/server.go
index 3a38b97..7684f5e 100644
--- a/api/server.go
+++ b/api/server.go
@@ -1,6 +1,8 @@
package main
import (
+ "log"
+
"git.sr.ht/~sircmpwn/core-go/config"
"git.sr.ht/~sircmpwn/core-go/server"
work "git.sr.ht/~sircmpwn/dowork"
@@ -8,6 +10,7 @@ import (
"git.sr.ht/~sircmpwn/hub.sr.ht/api/account"
"git.sr.ht/~sircmpwn/hub.sr.ht/api/graph"
"git.sr.ht/~sircmpwn/hub.sr.ht/api/graph/api"
+ "git.sr.ht/~sircmpwn/hub.sr.ht/api/openpgpkey"
)
func main() {
@@ -30,5 +33,10 @@ func main() {
WithSchema(schema, scopes).
WithQueues(accountQueue)
+ err := openpgpkey.MountWebKeyDirectoryRoutes(appConfig, gsrv.Router())
+ if err != nil {
+ log.Fatalf("openpgpkey.MountWebKeyDirectoryRoutes failed: %v", err)
+ }
+
gsrv.Run()
}
diff --git a/go.mod b/go.mod
index 0010975..d2afaae 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,10 @@ require (
git.sr.ht/~sircmpwn/core-go v0.0.0-20221025082458-3e69641ef307
git.sr.ht/~sircmpwn/dowork v0.0.0-20210820133136-d3970e97def3
github.com/99designs/gqlgen v0.17.20
+ github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3
+ github.com/go-chi/chi v4.1.2+incompatible
+ github.com/stretchr/testify v1.7.1
+ github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
github.com/vektah/gqlparser/v2 v2.5.1
)
@@ -13,11 +17,11 @@ require (
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 // indirect
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210227202403-5dae5c48f917 // indirect
github.com/Masterminds/squirrel v1.4.0 // indirect
- github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emersion/go-message v0.15.0 // indirect
github.com/emersion/go-pgpmail v0.2.0 // indirect
@@ -25,7 +29,6 @@ require (
github.com/emersion/go-smtp v0.15.1-0.20211103212524-30169acc42e7 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 // indirect
- github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-redis/redis/v8 v8.11.4 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
@@ -36,13 +39,13 @@ require (
github.com/lib/pq v1.8.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.3.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.7.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.10.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.8.1 // indirect
- github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect
github.com/vektah/gqlparser v1.3.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect
--
2.42.0