package server_test import ( "bytes" "fmt" "net/http" "testing" "github.com/keys-pub/keys" "github.com/keys-pub/keys/dstore" "github.com/stretchr/testify/require" ) func TestSigchains(t *testing.T) { // SetContextLogger(NewContextLogger(DebugLevel)) // firestore.SetContextLogger(NewContextLogger(DebugLevel)) env := newEnv(t) srv := newTestServer(t, env) clock := env.clock alice := keys.NewEdX25519KeyFromSeed(keys.Bytes32(bytes.Repeat([]byte{0x01}, 32))) bob := keys.NewEdX25519KeyFromSeed(keys.Bytes32(bytes.Repeat([]byte{0x02}, 32))) // GET /invalidloc (not found) req, err := http.NewRequest("GET", "/invalidloc", nil) require.NoError(t, err) code, _, body := srv.Serve(req) require.Equal(t, http.StatusNotFound, code) expected := `{"error":{"code":404,"message":"resource not found"}}` require.Equal(t, expected, body) // PUT /sigchains (method not allowed) req, err = http.NewRequest("PUT", "/sigchains", bytes.NewReader([]byte("test"))) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusMethodNotAllowed, code) expected = `{"error":{"code":405,"message":"method not allowed"}}` require.Equal(t, expected, body) // Alice sign "testing" sca := keys.NewSigchain(alice.ID()) sta, err := keys.NewSigchainStatement(sca, []byte("testing"), alice, "", clock.Now()) require.NoError(t, err) err = sca.Add(sta) require.NoError(t, err) staBytes, err := sta.Bytes() require.NoError(t, err) // PUT /sigchain/:kid/:seq req, err = http.NewRequest("PUT", fmt.Sprintf("/sigchain/%s/1", alice.ID()), bytes.NewReader(staBytes)) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusOK, code) require.Equal(t, "{}", body) // PUT /sigchain/:kid/:seq again (conflict error) req, err = http.NewRequest("PUT", fmt.Sprintf("/sigchain/%s/1", alice.ID()), bytes.NewReader(staBytes)) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusConflict, code) expected = `{"error":{"code":409,"message":"statement already exists"}}` require.Equal(t, expected, body) // Bob sign "testing" scb := keys.NewSigchain(bob.ID()) stb, err := keys.NewSigchainStatement(scb, []byte("testing"), bob, "", clock.Now()) require.NoError(t, err) // PUT /sigchain/:kid/:seq (invalid, bob's statement) b, err := stb.Bytes() require.NoError(t, err) req, err = http.NewRequest("PUT", fmt.Sprintf("/sigchain/%s/1", alice.ID()), bytes.NewReader([]byte(b))) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusBadRequest, code) expected = `{"error":{"code":400,"message":"invalid kid"}}` require.Equal(t, expected, body) // PUT /sigchain/:kid/:seq (empty json) req, err = http.NewRequest("PUT", fmt.Sprintf("/sigchain/%s/1", alice.ID()), bytes.NewReader([]byte("{}"))) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusBadRequest, code) expected = `{"error":{"code":400,"message":"not enough bytes for statement"}}` require.Equal(t, expected, body) // PUT /sigchain/:kid/:seq (no body) req, err = http.NewRequest("PUT", fmt.Sprintf("/sigchain/%s/1", alice.ID()), nil) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusBadRequest, code) expected = `{"error":{"code":400,"message":"missing body"}}` require.Equal(t, expected, body) // GET /sigchain/:kid/:seq req, err = http.NewRequest("GET", fmt.Sprintf("/sigchain/%s/1", alice.ID()), nil) require.NoError(t, err) code, header, body := srv.Serve(req) require.Equal(t, http.StatusOK, code) require.Equal(t, "Fri, 13 Feb 2009 23:31:30 GMT", header.Get("CreatedAt")) require.Equal(t, "2009-02-13T23:31:30.002Z", header.Get("CreatedAt-RFC3339M")) require.Equal(t, "Fri, 13 Feb 2009 23:31:30 GMT", header.Get("Last-Modified")) require.Equal(t, "2009-02-13T23:31:30.002Z", header.Get("Last-Modified-RFC3339M")) expectedSigned := `{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}` require.Equal(t, expectedSigned, body) // GET /sigchain/:kid req, err = http.NewRequest("GET", fmt.Sprintf("/sigchain/%s", alice.ID()), nil) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusOK, code) expectedSigchain := `{"kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","statements":[{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}]}` require.Equal(t, expectedSigchain, body) // GET /sigchain/:kid (not found) req, err = http.NewRequest("GET", dstore.Path("sigchain", keys.RandID("kex")), nil) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusNotFound, code) require.Equal(t, `{"error":{"code":404,"message":"sigchain not found"}}`, body) // GET /sigchain/:kid?include=md req, err = http.NewRequest("GET", fmt.Sprintf("/sigchain/%s?include=md", alice.ID()), nil) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusOK, code) expectedSigchain2 := `{"kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","md":{"/kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077/1":{"createdAt":"2009-02-13T23:31:30.002Z","updatedAt":"2009-02-13T23:31:30.002Z"}},"statements":[{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}]}` require.Equal(t, expectedSigchain2, body) // GET /sigchains // req, err = http.NewRequest("GET", "/sigchains", nil) // require.NoError(t, err) // code, _, body = srv.Serve(req) // require.Equal(t, http.StatusOK, code) // expectedSigs := `{"statements":[{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}],"version":"1234567890003"}` // require.Equal(t, expectedSigs, body) // GET /sigchains?include=md&limit=1 // req, err = http.NewRequest("GET", "/sigchains?include=md&limit=1", nil) // require.NoError(t, err) // code, _, body = srv.Serve(req) // require.Equal(t, http.StatusOK, code) // expectedSigsWithMetadata := `{"md":{"/kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077/1":{"createdAt":"2009-02-13T23:31:30.002Z","updatedAt":"2009-02-13T23:31:30.002Z"}},"statements":[{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}],"version":"1234567890003"}` // require.Equal(t, expectedSigsWithMetadata, body) // GET /:kid req, err = http.NewRequest("GET", dstore.Path(alice.ID()), nil) require.NoError(t, err) req.Host = "sigcha.in" code, _, body = srv.Serve(req) require.Equal(t, http.StatusOK, code) expectedSigchain = `{"kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","statements":[{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}]}` require.Equal(t, expectedSigchain, body) // Alice sign "testing2" sta2, err := keys.NewSigchainStatement(sca, []byte("testing2"), alice, "", clock.Now()) require.NoError(t, err) err = sca.Add(sta2) require.NoError(t, err) // GET /:kid/:seq req, err = http.NewRequest("GET", fmt.Sprintf("/%s/1", alice.ID()), nil) require.NoError(t, err) req.Host = "sigcha.in" code, header, body = srv.Serve(req) require.Equal(t, http.StatusOK, code) require.Equal(t, "Fri, 13 Feb 2009 23:31:30 GMT", header.Get("CreatedAt")) require.Equal(t, "2009-02-13T23:31:30.002Z", header.Get("CreatedAt-RFC3339M")) require.Equal(t, "Fri, 13 Feb 2009 23:31:30 GMT", header.Get("Last-Modified")) require.Equal(t, "2009-02-13T23:31:30.002Z", header.Get("Last-Modified-RFC3339M")) expectedSigned = `{".sig":"j5FZVQKWrnclXHHHIVX7JZ0letgR22cGl7ItlAUHqEsW+kCCMZvDBGEunVJScjVphrqGrPb7oCuMZouGv7GwCQ==","data":"dGVzdGluZw==","kid":"kex132yw8ht5p8cetl2jmvknewjawt9xwzdlrk2pyxlnwjyqrdq0dawqqph077","seq":1,"ts":1234567890001}` require.Equal(t, expectedSigned, body) // PUT /:kid/:seq b, err = sta2.Bytes() require.NoError(t, err) req, err = http.NewRequest("PUT", fmt.Sprintf("/%s/2", alice.ID()), bytes.NewReader(b)) require.NoError(t, err) req.Host = "sigcha.in" code, _, body = srv.Serve(req) require.Equal(t, http.StatusOK, code) require.Equal(t, "{}", body) // PUT /invalidloc/1 req, err = http.NewRequest("PUT", dstore.Path("invalidloc", 1), bytes.NewReader(b)) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusBadRequest, code) require.Equal(t, `{"error":{"code":400,"message":"invalid kid"}}`, body) // Alice sign large message large := bytes.Repeat([]byte{0x01}, 17*1024) sta, err = keys.NewSigchainStatement(sca, large, alice, "", clock.Now()) require.NoError(t, err) err = sca.Add(sta) require.NoError(t, err) // PUT /sigchain/:kid/:seq (too large) b, err = sta.Bytes() require.NoError(t, err) req, err = http.NewRequest("PUT", fmt.Sprintf("/sigchain/%s/%d", alice.ID(), sta.Seq), bytes.NewReader(b)) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusBadRequest, code) require.Equal(t, `{"error":{"code":400,"message":"too much data for sigchain statement (greater than 16KiB)"}}`, body) // GET /foo/bar req, err = http.NewRequest("GET", "/foo/bar", nil) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusNotFound, code) require.Equal(t, `{"error":{"code":404,"message":"resource not found"}}`, body) // GET /:kid/bar req, err = http.NewRequest("GET", dstore.Path(alice.ID(), "bar"), nil) require.NoError(t, err) code, _, body = srv.Serve(req) require.Equal(t, http.StatusNotFound, code) require.Equal(t, `{"error":{"code":404,"message":"resource not found"}}`, body) }