From b9606f06f270ad69c2ca76883eb62674ea5316cf Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 19 May 2018 22:16:37 +0200 Subject: [PATCH] Fix tests for HTTP Signatures. Also, bugfix the time.Now calls to use the federator's clock instead. This is how it should have been done in the beginning, but is necessary for the tests since the HTTP Signatures by default sign the dates in the headers. And I noticed said dates were being populated by time.Now instead of the mock-able Clock. --- pub/fed_test.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++- pub/internal.go | 44 +++++++-------- 2 files changed, 166 insertions(+), 23 deletions(-) diff --git a/pub/fed_test.go b/pub/fed_test.go index 5512dcf..bd72e1e 100644 --- a/pub/fed_test.go +++ b/pub/fed_test.go @@ -3,10 +3,14 @@ package pub import ( "bytes" "context" + "crypto" + "crypto/rand" + "crypto/rsa" "encoding/json" "fmt" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/vocab" + "github.com/go-fed/httpsig" "io/ioutil" "net/http" "net/http/httptest" @@ -34,6 +38,8 @@ const ( noteName = "A Note" otherOriginIRIString = "https://foo.net/activity/112358" otherOriginActorIRIString = "https://foo.net/peyton" + + testPublicKeyID = "publicKeyId" ) var ( @@ -85,6 +91,8 @@ var ( testClientExpectedLike *vocab.Like testClientExpectedUndo *vocab.Undo testClientExpectedBlock *vocab.Block + + testPrivateKey *rsa.PrivateKey ) func init() { @@ -391,6 +399,11 @@ func init() { testClientExpectedBlock.SetId(*testNewIRI) testClientExpectedBlock.AddActorObject(sallyActor) testClientExpectedBlock.AddObject(samActor) + + testPrivateKey, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(err) + } } func Must(l *time.Location, e error) *time.Location { @@ -734,6 +747,8 @@ type MockFederateApp struct { unblocked func(c context.Context, actorIRIs []url.URL) error getFollowing func(c context.Context, actor url.URL) (vocab.CollectionType, error) filterForwarding func(c context.Context, activity vocab.ActivityType, iris []url.URL) ([]url.URL, error) + newSigner func() (httpsig.Signer, error) + privateKey func(boxIRI url.URL) (crypto.PrivateKey, string, error) } func (m *MockFederateApp) CanAdd(c context.Context, obj vocab.ObjectType, target vocab.ObjectType) bool { @@ -778,6 +793,20 @@ func (m *MockFederateApp) FilterForwarding(c context.Context, activity vocab.Act return m.filterForwarding(c, activity, iris) } +func (m *MockFederateApp) NewSigner() (httpsig.Signer, error) { + if m.newSigner == nil { + m.t.Fatal("unexpected call to MockFederateApp NewSigner") + } + return m.newSigner() +} + +func (m *MockFederateApp) PrivateKey(boxIRI url.URL) (privKey crypto.PrivateKey, pubKeyId string, err error) { + if m.privateKey == nil { + m.t.Fatal("unexpected call to MockFederateApp PrivateKey") + } + return m.privateKey(boxIRI) +} + var _ Deliverer = &MockDeliverer{} type MockDeliverer struct { @@ -861,6 +890,16 @@ func PreparePostOutboxTest(t *testing.T, app *MockApplication, socialApp *MockSo socialApp.postOutboxAuthorized = func(c context.Context, r *http.Request) (bool, error) { return true, nil } + fedApp.newSigner = func() (httpsig.Signer, error) { + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + return testPrivateKey, testPublicKeyID, nil + } app.newId = func(c context.Context, t Typer) url.URL { return *testNewIRI } @@ -1324,7 +1363,7 @@ func TestPubber_GetInbox(t *testing.T) { } func TestPubber_PostOutbox(t *testing.T) { - app, socialApp, _, socialCb, _, d, httpClient, p := NewPubberTest(t) + app, socialApp, fedApp, socialCb, _, d, httpClient, p := NewPubberTest(t) resp := httptest.NewRecorder() req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))) gotPostOutboxAuthorized := 0 @@ -1332,6 +1371,22 @@ func TestPubber_PostOutbox(t *testing.T) { gotPostOutboxAuthorized++ return true, nil } + gotNewSigner := 0 + fedApp.newSigner = func() (httpsig.Signer, error) { + gotNewSigner++ + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + gotPrivateKey := 0 + var gotPrivateKeyIRI url.URL + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + gotPrivateKey++ + gotPrivateKeyIRI = boxIRI + return testPrivateKey, testPublicKeyID, nil + } gotNewId := 0 app.newId = func(c context.Context, t Typer) url.URL { gotNewId++ @@ -1411,6 +1466,12 @@ func TestPubber_PostOutbox(t *testing.T) { t.Fatalf("expected %d, got %d", 1, gotPostOutboxAuthorized) } else if gotNewId != 1 { t.Fatalf("expected %d, got %d", 1, gotNewId) + } else if gotNewSigner != 1 { + t.Fatalf("expected %d, got %d", 1, gotNewSigner) + } else if gotPrivateKey != 1 { + t.Fatalf("expected %d, got %d", 1, gotPrivateKey) + } else if s := (&gotPrivateKeyIRI).String(); s != testOutboxURI { + t.Fatalf("expected %s, got %s", testOutboxURI, s) } else if gotOutbox != 1 { t.Fatalf("expected %d, got %d", 1, gotOutbox) } else if gotSet != 2 { @@ -1454,6 +1515,14 @@ func TestPubber_PostOutbox(t *testing.T) { } else if resp.Code != http.StatusCreated { t.Fatalf("expected %d, got %d", http.StatusCreated, resp.Code) } + verif, err := httpsig.NewVerifier(httpDeliveryRequest) + if err != nil { + t.Fatal(err) + } else if verif.KeyId() != testPublicKeyID { + t.Fatalf("expected %s, got %s", testPublicKeyID, verif.KeyId()) + } else if err := verif.Verify(testPrivateKey.Public(), httpsig.RSA_SHA256); err != nil { + t.Fatal(err) + } } func TestPubber_GetOutbox(t *testing.T) { @@ -2094,6 +2163,22 @@ func TestPostInbox_Follow_AutoReject(t *testing.T) { gotOnFollow++ return AutomaticReject } + gotNewSigner := 0 + fedApp.newSigner = func() (httpsig.Signer, error) { + gotNewSigner++ + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + gotPrivateKey := 0 + var gotPrivateKeyIRI url.URL + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + gotPrivateKey++ + gotPrivateKeyIRI = boxIRI + return testPrivateKey, testPublicKeyID, nil + } fedCb.follow = func(c context.Context, s *streams.Follow) error { return nil } @@ -2145,6 +2230,12 @@ func TestPostInbox_Follow_AutoReject(t *testing.T) { t.Fatal(err) } else if !handled { t.Fatalf("expected handled, got !handled") + } else if gotNewSigner != 1 { + t.Fatalf("expected %d, got %d", 1, gotNewSigner) + } else if gotPrivateKey != 1 { + t.Fatalf("expected %d, got %d", 1, gotPrivateKey) + } else if s := (&gotPrivateKeyIRI).String(); s != testInboxURI { + t.Fatalf("expected %s, got %s", testInboxURI, s) } else if gotOnFollow != 1 { t.Fatalf("expected %d, got %d", 1, gotOnFollow) } else if gotHttpDo != 2 { @@ -2176,6 +2267,22 @@ func TestPostInbox_Follow_AutoAccept(t *testing.T) { gotOnFollow++ return AutomaticAccept } + gotNewSigner := 0 + fedApp.newSigner = func() (httpsig.Signer, error) { + gotNewSigner++ + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + gotPrivateKey := 0 + var gotPrivateKeyIRI url.URL + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + gotPrivateKey++ + gotPrivateKeyIRI = boxIRI + return testPrivateKey, testPublicKeyID, nil + } fedCb.follow = func(c context.Context, s *streams.Follow) error { return nil } @@ -2255,6 +2362,12 @@ func TestPostInbox_Follow_AutoAccept(t *testing.T) { t.Fatalf("expected handled, got !handled") } else if gotOnFollow != 1 { t.Fatalf("expected %d, got %d", 1, gotOnFollow) + } else if gotNewSigner != 1 { + t.Fatalf("expected %d, got %d", 1, gotNewSigner) + } else if gotPrivateKey != 1 { + t.Fatalf("expected %d, got %d", 1, gotPrivateKey) + } else if s := (&gotPrivateKeyIRI).String(); s != testInboxURI { + t.Fatalf("expected %s, got %s", testInboxURI, s) } else if gotHttpDo != 2 { t.Fatalf("expected %d, got %d", 2, gotHttpDo) } else if s := httpActorRequest.URL.String(); s != sallyIRIString { @@ -2290,6 +2403,16 @@ func TestPostInbox_Follow_DoesNotAddForAutoAcceptIfAlreadyPresent(t *testing.T) fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { return AutomaticAccept } + fedApp.newSigner = func() (httpsig.Signer, error) { + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + return testPrivateKey, testPublicKeyID, nil + } fedCb.follow = func(c context.Context, s *streams.Follow) error { return nil } @@ -2363,6 +2486,16 @@ func TestPostInbox_Follow_AutoAcceptFollowersIsOrderedCollection(t *testing.T) { fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { return AutomaticAccept } + fedApp.newSigner = func() (httpsig.Signer, error) { + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + return testPrivateKey, testPublicKeyID, nil + } fedCb.follow = func(c context.Context, s *streams.Follow) error { return nil } @@ -2432,6 +2565,16 @@ func TestPostInbox_Follow_AutoAcceptFollowersIsIRI(t *testing.T) { fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { return AutomaticAccept } + fedApp.newSigner = func() (httpsig.Signer, error) { + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + fedApp.privateKey = func(boxIRI url.URL) (crypto.PrivateKey, string, error) { + return testPrivateKey, testPublicKeyID, nil + } fedCb.follow = func(c context.Context, s *streams.Follow) error { return nil } diff --git a/pub/internal.go b/pub/internal.go index 050c924..af1d90d 100644 --- a/pub/internal.go +++ b/pub/internal.go @@ -73,7 +73,7 @@ func addJSONLDContext(m map[string]interface{}) { // ActivityStream representation. // // creds is allowed to be nil. -func dereference(c HttpClient, u url.URL, agent string, creds *creds) ([]byte, error) { +func dereference(c HttpClient, u url.URL, agent string, creds *creds, clock Clock) ([]byte, error) { // TODO: (section 7.1) The server MUST dereference the collection (with the user's credentials) req, err := http.NewRequest("GET", u.String(), nil) if err != nil { @@ -81,7 +81,7 @@ func dereference(c HttpClient, u url.URL, agent string, creds *creds) ([]byte, e } req.Header.Add(acceptHeader, getAcceptHeader) req.Header.Add("Accept-Charset", "utf-8") - req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT") + req.Header.Add("Date", clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT") req.Header.Add("User-Agent", fmt.Sprintf("%s (go-fed ActivityPub)", agent)) if creds != nil { err := creds.signer.SignRequest(creds.privKey, creds.pubKeyId, req) @@ -111,7 +111,7 @@ type creds struct { // body set to the provided bytes. // // creds is able to be nil. -func postToOutbox(c HttpClient, b []byte, to url.URL, agent string, creds *creds) error { +func postToOutbox(c HttpClient, b []byte, to url.URL, agent string, creds *creds, clock Clock) error { byteCopy := make([]byte, 0, len(b)) copy(b, byteCopy) buf := bytes.NewBuffer(byteCopy) @@ -121,7 +121,7 @@ func postToOutbox(c HttpClient, b []byte, to url.URL, agent string, creds *creds } req.Header.Add(contentTypeHeader, postContentTypeHeader) req.Header.Add("Accept-Charset", "utf-8") - req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT") + req.Header.Add("Date", clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT") req.Header.Add("User-Agent", fmt.Sprintf("%s (go-fed ActivityPub)", agent)) if creds != nil { err := creds.signer.SignRequest(creds.privKey, creds.pubKeyId, req) @@ -509,7 +509,7 @@ func (f *federator) deliver(obj vocab.ActivityType, boxIRI url.URL) error { if err != nil { return err } - var creds *creds + creds := &creds{} creds.signer, err = f.FederateApp.NewSigner() if err != nil { return err @@ -535,7 +535,7 @@ func (f *federator) deliverToRecipients(obj vocab.ActivityType, recipients []url } for _, to := range recipients { f.deliverer.Do(b, to, func(b []byte, u url.URL) error { - return postToOutbox(f.Client, b, u, f.Agent, creds) + return postToOutbox(f.Client, b, u, f.Agent, creds, f.Clock) }) } return nil @@ -620,7 +620,7 @@ func (c *federator) resolveInboxes(boxIRI url.URL, r []url.URL, depth int, max i uris = getURIsInItemer(c) return nil } - err := doForCollectionPage(c.Client, c.Agent, cb, cp.Raw(), cr) + err := doForCollectionPage(c.Client, c.Agent, cb, cp.Raw(), cr, c.Clock) if err != nil { return nil, err } @@ -634,7 +634,7 @@ func (c *federator) resolveInboxes(boxIRI url.URL, r []url.URL, depth int, max i uris = getURIsInOrderedItemer(c) return nil } - err := doForOrderedCollectionPage(c.Client, c.Agent, cb, ocp.Raw(), cr) + err := doForOrderedCollectionPage(c.Client, c.Agent, cb, ocp.Raw(), cr, c.Clock) if err != nil { return nil, err } @@ -654,7 +654,7 @@ func (c *federator) dereferenceForResolvingInboxes(u, boxIRI url.URL, cr *creds) // To pass back to calling function, since may be set recursively: cred = cr var resp []byte - resp, err = dereference(c.Client, u, c.Agent, cr) + resp, err = dereference(c.Client, u, c.Agent, cr, c.Clock) if err != nil { return } @@ -806,7 +806,7 @@ func (f *federator) dedupeOrderedItems(oc vocab.OrderedCollectionType) (vocab.Or id = pIri.String() } else if oc.IsOrderedItemsIRI(i) { removeFn = oc.RemoveOrderedItemsIRI - b, err := dereference(f.Client, oc.GetOrderedItemsIRI(i), f.Agent, nil) + b, err := dereference(f.Client, oc.GetOrderedItemsIRI(i), f.Agent, nil, f.Clock) var m map[string]interface{} if err := json.Unmarshal(b, &m); err != nil { return oc, err @@ -944,7 +944,7 @@ func getAudienceIRIs(o deliverableObject) []url.URL { // doForCollectionPage applies a function over a collection and its subsequent // pages recursively. It returns the first non-nil error it encounters. -func doForCollectionPage(h HttpClient, agent string, cb func(c vocab.CollectionPageType) error, c vocab.CollectionPageType, creds *creds) error { +func doForCollectionPage(h HttpClient, agent string, cb func(c vocab.CollectionPageType) error, c vocab.CollectionPageType, creds *creds, clock Clock) error { err := cb(c) if err != nil { return err @@ -952,12 +952,12 @@ func doForCollectionPage(h HttpClient, agent string, cb func(c vocab.CollectionP if c.IsNextCollectionPage() { // Handle this one weird trick that other peers HATE federating // with. - return doForCollectionPage(h, agent, cb, c.GetNextCollectionPage(), creds) + return doForCollectionPage(h, agent, cb, c.GetNextCollectionPage(), creds, clock) } else if c.IsNextLink() { l := c.GetNextLink() if l.HasHref() { u := l.GetHref() - resp, err := dereference(h, u, agent, creds) + resp, err := dereference(h, u, agent, creds, clock) if err != nil { return err } @@ -971,12 +971,12 @@ func doForCollectionPage(h HttpClient, agent string, cb func(c vocab.CollectionP return err } if next != nil { - return doForCollectionPage(h, agent, cb, next.Raw(), creds) + return doForCollectionPage(h, agent, cb, next.Raw(), creds, clock) } } } else if c.IsNextIRI() { u := c.GetNextIRI() - resp, err := dereference(h, u, agent, creds) + resp, err := dereference(h, u, agent, creds, clock) if err != nil { return err } @@ -990,7 +990,7 @@ func doForCollectionPage(h HttpClient, agent string, cb func(c vocab.CollectionP return err } if next != nil { - return doForCollectionPage(h, agent, cb, next.Raw(), creds) + return doForCollectionPage(h, agent, cb, next.Raw(), creds, clock) } } return nil @@ -999,7 +999,7 @@ func doForCollectionPage(h HttpClient, agent string, cb func(c vocab.CollectionP // doForOrderedCollectionPage applies a function over a collection and its // subsequent pages recursively. It returns the first non-nil error it // encounters. -func doForOrderedCollectionPage(h HttpClient, agent string, cb func(c vocab.OrderedCollectionPageType) error, c vocab.OrderedCollectionPageType, creds *creds) error { +func doForOrderedCollectionPage(h HttpClient, agent string, cb func(c vocab.OrderedCollectionPageType) error, c vocab.OrderedCollectionPageType, creds *creds, clock Clock) error { err := cb(c) if err != nil { return err @@ -1007,12 +1007,12 @@ func doForOrderedCollectionPage(h HttpClient, agent string, cb func(c vocab.Orde if c.IsNextOrderedCollectionPage() { // Handle this one weird trick that other peers HATE federating // with. - return doForOrderedCollectionPage(h, agent, cb, c.GetNextOrderedCollectionPage(), creds) + return doForOrderedCollectionPage(h, agent, cb, c.GetNextOrderedCollectionPage(), creds, clock) } else if c.IsNextLink() { l := c.GetNextLink() if l.HasHref() { u := l.GetHref() - resp, err := dereference(h, u, agent, creds) + resp, err := dereference(h, u, agent, creds, clock) if err != nil { return err } @@ -1026,12 +1026,12 @@ func doForOrderedCollectionPage(h HttpClient, agent string, cb func(c vocab.Orde return err } if next != nil { - return doForOrderedCollectionPage(h, agent, cb, next.Raw(), creds) + return doForOrderedCollectionPage(h, agent, cb, next.Raw(), creds, clock) } } } else if c.IsNextIRI() { u := c.GetNextIRI() - resp, err := dereference(h, u, agent, creds) + resp, err := dereference(h, u, agent, creds, clock) if err != nil { return err } @@ -1045,7 +1045,7 @@ func doForOrderedCollectionPage(h HttpClient, agent string, cb func(c vocab.Orde return err } if next != nil { - return doForOrderedCollectionPage(h, agent, cb, next.Raw(), creds) + return doForOrderedCollectionPage(h, agent, cb, next.Raw(), creds, clock) } } return nil