From e62e4f32d68c933168a0ff83114facce4b73d223 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Thu, 29 Jul 2021 15:31:49 +0200 Subject: [PATCH] Automatic ActivityPub key generation, doesn't need config anymore --- activityPub.go | 49 ++++++++++++++++++++++++++++++++++++--------- activityPub_test.go | 44 ++++++++++++++++++++++++++++++++++++++++ config.go | 2 -- example-config.yml | 2 ++ 4 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 activityPub_test.go diff --git a/activityPub.go b/activityPub.go index d50ab37..419b252 100644 --- a/activityPub.go +++ b/activityPub.go @@ -1,6 +1,8 @@ package main import ( + "crypto/rand" + "crypto/rsa" "crypto/x509" "database/sql" "encoding/json" @@ -11,7 +13,6 @@ import ( "log" "net/http" "net/url" - "os" "strings" "time" @@ -54,15 +55,7 @@ func (a *goBlog) initActivityPub() error { a.webfingerAccts[a.apIri(blog)] = acct } // Read key and prepare signing - pkfile, err := os.ReadFile(a.cfg.ActivityPub.KeyPath) - if err != nil { - return err - } - privateKeyDecoded, _ := pem.Decode(pkfile) - if privateKeyDecoded == nil { - return errors.New("failed to decode private key") - } - a.apPrivateKey, err = x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes) + err := a.loadActivityPubPrivateKey() if err != nil { return err } @@ -422,3 +415,39 @@ func (a *goBlog) apIri(b *configBlog) string { func apRequestIsSuccess(code int) bool { return code == http.StatusOK || code == http.StatusCreated || code == http.StatusAccepted || code == http.StatusNoContent } + +// Load or generate key for ActivityPub communication +func (a *goBlog) loadActivityPubPrivateKey() error { + // Check if already loaded + if a.apPrivateKey != nil { + return nil + } + // Check if already generated + if keyData, err := a.db.retrievePersistentCache("activitypub_key"); err == nil && keyData != nil { + privateKeyDecoded, _ := pem.Decode(keyData) + if privateKeyDecoded == nil { + log.Println("failed to decode cached private key") + // continue + } else { + key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes) + if err == nil && key != nil { + a.apPrivateKey = key + return nil + } + } + } + // Generate and cache key + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + encodedKey := x509.MarshalPKCS1PrivateKey(key) + pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encodedKey}) + err = a.db.cachePersistently("activitypub_key", pemEncoded) + if err != nil { + return err + } + a.apPrivateKey = key + // Return key + return nil +} diff --git a/activityPub_test.go b/activityPub_test.go new file mode 100644 index 0000000..c4121a0 --- /dev/null +++ b/activityPub_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_loadActivityPubPrivateKey(t *testing.T) { + + app := &goBlog{ + cfg: &config{ + Db: &configDb{ + File: filepath.Join(t.TempDir(), "test.db"), + }, + }, + } + _ = app.initDatabase(false) + + // Generate + err := app.loadActivityPubPrivateKey() + require.NoError(t, err) + + assert.NotNil(t, app.apPrivateKey) + + oldEncodedKey := x509.MarshalPKCS1PrivateKey(app.apPrivateKey) + oldPemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: oldEncodedKey}) + + // Reset and reload + err = app.loadActivityPubPrivateKey() + require.NoError(t, err) + + assert.NotNil(t, app.apPrivateKey) + + newEncodedKey := x509.MarshalPKCS1PrivateKey(app.apPrivateKey) + newPemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: newEncodedKey}) + + assert.Equal(t, string(oldPemEncoded), string(newPemEncoded)) + +} diff --git a/config.go b/config.go index 4b1994d..0f73cfa 100644 --- a/config.go +++ b/config.go @@ -232,7 +232,6 @@ type configRegexRedirect struct { type configActivityPub struct { Enabled bool `mapstructure:"enabled"` - KeyPath string `mapstructure:"keyPath"` TagsTaxonomies []string `mapstructure:"tagsTaxonomies"` } @@ -288,7 +287,6 @@ func (a *goBlog) initConfig() error { viper.SetDefault("micropub.photoParam", "images") viper.SetDefault("micropub.photoDescriptionParam", "imagealts") viper.SetDefault("micropub.locationParam", "location") - viper.SetDefault("activityPub.keyPath", "data/private.pem") viper.SetDefault("activityPub.tagsTaxonomies", []string{"tags"}) // Unmarshal config a.cfg = &config{} diff --git a/example-config.yml b/example-config.yml index ff9e8d5..02d6451 100644 --- a/example-config.yml +++ b/example-config.yml @@ -69,6 +69,8 @@ hooks: # ActivityPub activityPub: enabled: true # Enable ActivityPub + tagsTaxonomies: # Post taxonomies to use as "Hashtags" + - tags # Webmention webmention: