diff options
-rw-r--r-- | http/router.go | 2 | ||||
-rw-r--r-- | registry/registry.go | 79 | ||||
-rw-r--r-- | tools/verifier.go | 81 |
3 files changed, 107 insertions, 55 deletions
diff --git a/http/router.go b/http/router.go index 456ed27..fcbd7d2 100644 --- a/http/router.go +++ b/http/router.go @@ -88,7 +88,7 @@ func (s *Router) Start(zapWriter io.Writer) { payload, ).Debug("received item") actorParam := c.Param("actor") - err := s.registry.Inbox(actorParam, c.Request) + err := s.registry.Inbox(actorParam, c.Request, buf.Bytes()) resource := map[string]interface{}{} render(c, resource, err) }) diff --git a/registry/registry.go b/registry/registry.go index 6f47f8a..aca1db7 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -7,19 +7,17 @@ import ( "encoding/gob" "encoding/json" "encoding/pem" - "fmt" - "io" "net/http" "net/url" "strings" "git.capotej.com/capotej/communique/config" "git.capotej.com/capotej/communique/models" + "git.capotej.com/capotej/communique/tools" "git.capotej.com/capotej/communique/urls" "git.capotej.com/capotej/communique/views" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" - "github.com/go-fed/httpsig" "go.uber.org/zap" ) @@ -157,81 +155,54 @@ func (r *Registry) Followers(name string) (map[string]interface{}, error) { return result, nil } -func (r *Registry) Inbox(name string, req *http.Request) error { +func (r *Registry) Inbox(name string, req *http.Request, payload []byte) error { handler := r.findByName(name) if handler == nil { return nil } - logger := r.log.With("type", "inbox") domainUrl, err := url.Parse(r.cfg.Domain) if err != nil { return err } + logger := r.log.With("type", "inbox") + if req.Host != domainUrl.Host { logger.Warnf("%s != %s, configured domain should match incoming Host: header otherwise things may not work right. You'll need to enable 'ProxyPreserveHost' (for apache2) or proxy_set_header Host $host; (for nginx)", req.Host, r.cfg.Domain) } - verifier, err := httpsig.NewVerifier(req) - if err != nil { - return err - } - - keyId := verifier.KeyId() - logger.With("keyId", keyId).Debugf("fetching") - req, err = http.NewRequest("GET", keyId, nil) - req.Header.Set("Accept", "application/json; charset=UTF-8") + ctx := context.Background() - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err + person, verifyError := tools.VerifyRequest(ctx, req, r.log) + if verifyError != nil { + return verifyError } - defer resp.Body.Close() - keyPage, err := io.ReadAll(resp.Body) - logger.With("keyId", keyId).With("response", string(keyPage)).Debugf("received response") - var keyPageData map[string]interface{} - err = json.Unmarshal(keyPage, &keyPageData) + + var followData map[string]interface{} + err = json.Unmarshal(payload, &followData) if err != nil { return err } - var person vocab.ActivityStreamsPerson - - resolver, err := streams.NewJSONResolver(func(c context.Context, p vocab.ActivityStreamsPerson) error { - // Store the person in the enclosing scope, for later. - person = p + resolver, err := streams.NewJSONResolver(func(c context.Context, p vocab.ActivityStreamsFollow) error { + idProp := person.GetJSONLDId() + idPropUrl := idProp.Get() + inboxProp := person.GetActivityStreamsInbox() + url := inboxProp.GetIRI() + logger.With("actor", idPropUrl).With("inbox", url).Debugf("follow") return nil - }, func(c context.Context, note vocab.ActivityStreamsNote) error { - //TODO not needed, need to figure out how to only pass one func + }, func(c context.Context, note vocab.ActivityStreamsUndo) error { + idProp := person.GetJSONLDId() + idPropUrl := idProp.Get() + inboxProp := person.GetActivityStreamsInbox() + url := inboxProp.GetIRI() + logger.With("actor", idPropUrl).With("inbox", url).Debugf("undo") return nil }) + err = resolver.Resolve(ctx, followData) - ctx := context.Background() - err = resolver.Resolve(ctx, keyPageData) - - pubKeyProp := person.GetW3IDSecurityV1PublicKey() - iter := pubKeyProp.At(0) // TODO not safe - pubKey := iter.Get() - pemProp := pubKey.GetW3IDSecurityV1PublicKeyPem() - pemStr := pemProp.Get() - logger.With("keyId", keyId).With("pem", pemStr).Debugf("extracted pem") - pemObj, _ := pem.Decode([]byte(pemStr)) - if pemObj == nil { - return fmt.Errorf("no PEM block found") - } - if pemObj.Type != "PUBLIC KEY" { - return fmt.Errorf("no public key found in PEM block") - } - - decodedKey, err := x509.ParsePKIXPublicKey(pemObj.Bytes) - if err != nil { - return err - } - logger.With("keyId", keyId).With("pem", pemStr).Debugf("got %T", decodedKey) - algo := httpsig.RSA_SHA256 - return verifier.Verify(decodedKey, algo) + return nil } func (r *Registry) ActivityOrNote(activityOrNote, name, id string) (map[string]interface{}, error) { diff --git a/tools/verifier.go b/tools/verifier.go new file mode 100644 index 0000000..4063ccf --- /dev/null +++ b/tools/verifier.go @@ -0,0 +1,81 @@ +package tools + +import ( + "context" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "net/http" + + "github.com/go-fed/activity/streams" + "github.com/go-fed/activity/streams/vocab" + "github.com/go-fed/httpsig" + "go.uber.org/zap" +) + +func VerifyRequest(ctx context.Context, req *http.Request, log *zap.SugaredLogger) (vocab.ActivityStreamsPerson, error) { + logger := log.With("type", "verifier") + verifier, err := httpsig.NewVerifier(req) + if err != nil { + return nil, err + } + + keyId := verifier.KeyId() + logger.With("keyId", keyId).Debugf("fetching key page") + req, err = http.NewRequest("GET", keyId, nil) + req.Header.Set("Accept", "application/json; charset=UTF-8") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + keyPage, err := io.ReadAll(resp.Body) + logger.With("keyId", keyId).With("response", string(keyPage)).Debugf("received response") + var keyPageData map[string]interface{} + err = json.Unmarshal(keyPage, &keyPageData) + if err != nil { + return nil, err + } + + var person vocab.ActivityStreamsPerson + + resolver, err := streams.NewJSONResolver(func(c context.Context, p vocab.ActivityStreamsPerson) error { + // Store the person in the enclosing scope, for later. + person = p + return nil + }, func(c context.Context, note vocab.ActivityStreamsNote) error { + //TODO not needed, need to figure out how to only pass one func + return nil + }) + + err = resolver.Resolve(ctx, keyPageData) + if err != nil { + return nil, err + } + + pubKeyProp := person.GetW3IDSecurityV1PublicKey() + iter := pubKeyProp.At(0) // TODO not safe, use a for instead + pubKey := iter.Get() + pemProp := pubKey.GetW3IDSecurityV1PublicKeyPem() + pemStr := pemProp.Get() + logger.With("keyId", keyId).With("pem", pemStr).Debugf("extracted pem") + pemObj, _ := pem.Decode([]byte(pemStr)) + if pemObj == nil { + return nil, fmt.Errorf("no PEM block found") + } + if pemObj.Type != "PUBLIC KEY" { + return nil, fmt.Errorf("no public key found in PEM block") + } + + decodedKey, err := x509.ParsePKIXPublicKey(pemObj.Bytes) + if err != nil { + return nil, err + } + logger.With("keyId", keyId).With("pem", pemStr).Debugf("got %T", decodedKey) + algo := httpsig.RSA_SHA256 + return person, verifier.Verify(decodedKey, algo) +} |