diff --git a/server/crypto/main.go b/server/crypto/main.go index 1919831..ed36cbf 100644 --- a/server/crypto/main.go +++ b/server/crypto/main.go @@ -5,29 +5,47 @@ import ( "crypto/rsa" "crypto/sha512" "encoding/base64" + "encoding/json" - "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/jwk" ) -func VerifySignature(jwkPublicKey []byte, payload []byte, encodedSignature string) error { - signature, err := base64.StdEncoding.DecodeString(encodedSignature) +func ParsePublicKey(jwkPublicKey interface{}) (*rsa.PublicKey, error) { + + json, err := json.Marshal(jwkPublicKey) + if err != nil { - return err + return nil, err } - jwkKey, err := jwk.ParseKey(jwkPublicKey) + jwkKey, err := jwk.ParseKey(json) if err != nil { - return err + return nil, err } var publicKey rsa.PublicKey err = jwkKey.Raw(&publicKey) + if err != nil { + return nil, err + } + + return &publicKey, nil +} + +func VerifySignature(jwkPublicKey interface{}, payload []byte, encodedSignature string) error { + signature, err := base64.StdEncoding.DecodeString(encodedSignature) + if err != nil { + return err + } + + publicKey, err := ParsePublicKey(jwkPublicKey) + if err != nil { return err } hashed := sha512.Sum512(payload) - return rsa.VerifyPKCS1v15(&publicKey, crypto.SHA512, hashed[:], signature) + return rsa.VerifyPKCS1v15(publicKey, crypto.SHA512, hashed[:], signature) } diff --git a/server/crypto/main_test.go b/server/crypto/main_test.go index 0b84d04..9bf9115 100644 --- a/server/crypto/main_test.go +++ b/server/crypto/main_test.go @@ -2,6 +2,7 @@ package crypto import ( "encoding/base64" + "encoding/json" "testing" ) @@ -16,7 +17,15 @@ func TestVerifySignature(t *testing.T) { t.Fatal(err) } - err = VerifySignature([]byte(publicKeyString), []byte(payload), signature) + var publicKey interface{} + + err = json.Unmarshal([]byte(publicKeyString), &publicKey) + + if err != nil { + t.Fatal(err) + } + + err = VerifySignature((publicKey), []byte(payload), signature) if err != nil { t.Fatal(err) diff --git a/server/go.mod b/server/go.mod index 9149c58..a051fa9 100644 --- a/server/go.mod +++ b/server/go.mod @@ -13,22 +13,26 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.5 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/sys v0.18.0 // indirect ) require ( + github.com/georgysavva/scany/v2 v2.1.1 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/lestrrat-go/jwx v1.2.29 github.com/ugorji/go/codec v1.2.12 github.com/vmihailenco/msgpack/v5 v5.4.1 golang.org/x/crypto v0.21.0 // indirect diff --git a/server/go.sum b/server/go.sum index 38bc351..a693b8a 100644 --- a/server/go.sum +++ b/server/go.sum @@ -3,8 +3,11 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/georgysavva/scany/v2 v2.1.1 h1:XK/EUvs4q0mS9Vti/P4U8/4BMBB0/94IV+zOBaam7Ow= +github.com/georgysavva/scany/v2 v2.1.1/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -23,6 +26,8 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -31,19 +36,29 @@ github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/O github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= +github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -52,14 +67,52 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/server/httpError/clientError.go b/server/httpError/clientError.go index 6b85973..1173afb 100644 --- a/server/httpError/clientError.go +++ b/server/httpError/clientError.go @@ -1,6 +1,7 @@ package httpError import ( + "fmt" "net/http" "github.com/go-chi/render" @@ -10,9 +11,10 @@ type ErrResponse struct { Err error `json:"-"` // low-level runtime error HTTPStatusCode int `json:"-"` // http response status code - StatusText string `json:"status"` // user-level status message - AppCode int64 `json:"code,omitempty"` // application-specific error code - ErrorText string `json:"error,omitempty"` // application-level error message, for debugging + StatusText string `json:"status"` // user-level status message + AppCode int64 `json:"code,omitempty"` // application-specific error code + Message string `json:"message,omitempty"` // application-level error message, for debugging + Data interface{} `json:"data,omitempty"` } func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { @@ -21,19 +23,32 @@ func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { } func InvalidRequest(err error) render.Renderer { + fmt.Println(err) return &ErrResponse{ Err: err, HTTPStatusCode: 400, StatusText: "Invalid request.", - ErrorText: err.Error(), + Message: err.Error(), + } +} + +func InvalidRequestWithData(err error, data interface{}) render.Renderer { + fmt.Println(err) + return &ErrResponse{ + Err: err, + HTTPStatusCode: 400, + StatusText: "Invalid request.", + Message: err.Error(), + Data: data, } } func Internal(err error) render.Renderer { + fmt.Println(err) return &ErrResponse{ Err: err, HTTPStatusCode: 500, StatusText: "Internal server error.", - ErrorText: err.Error(), + Message: err.Error(), } } diff --git a/server/main.go b/server/main.go index e6e89ca..970d349 100644 --- a/server/main.go +++ b/server/main.go @@ -16,7 +16,7 @@ import ( ) func main() { - err := godotenv.Load() + err := godotenv.Load("../.env", ".env") if err != nil { panic(err) diff --git a/server/migrations/20240304073843_init.down.sql b/server/migrations/20240304073843_init.down.sql index a5b6e28..3bad5e4 100644 --- a/server/migrations/20240304073843_init.down.sql +++ b/server/migrations/20240304073843_init.down.sql @@ -1,5 +1,5 @@ -drop table if exists account_outbox; -drop table if exists account_page_key_hash; -drop table if exists account; -drop table if exists page; -drop table if exists file; +drop table if exists account_outboxes; +drop table if exists account_page_key_hashes; +drop table if exists accounts; +drop table if exists pages; +drop table if exists files; diff --git a/server/migrations/20240304073843_init.up.sql b/server/migrations/20240304073843_init.up.sql index cd56fa6..8858b88 100644 --- a/server/migrations/20240304073843_init.up.sql +++ b/server/migrations/20240304073843_init.up.sql @@ -5,23 +5,26 @@ -- login is accomplished by signing a login token -- with the private key that can be verified with -- the public key -create table account ( +create table accounts ( id uuid PRIMARY KEY default gen_random_uuid(), name text, username text unique, -- private key hashed by user password encryption_private_key_hash bytea, - encryption_public_key bytea, + encryption_public_key jsonb, signing_private_key_hash bytea, - signing_public_key bytea, + signing_public_key jsonb, + + + password_salt bytea, avatar_uri text, primary_color int, accent_color int ); -create table file ( +create table files ( id uuid primary key default gen_random_uuid(), -- header_hash is a yjs document -- encrypted by a symmetric key @@ -36,16 +39,16 @@ create table file ( ); -- each page has it's own symetric key -create table page ( +create table pages ( id uuid PRIMARY KEY default gen_random_uuid(), - parent_id uuid references page(id), - file_id uuid references file(id) + parent_id uuid references pages(id), + file_id uuid references files(id) ); -create table account_page_key_hash ( - account_id uuid references account(id) on delete cascade, - page_id uuid references page(id) on delete cascade, +create table account_page_key_hashes ( + account_id uuid references accounts(id) on delete cascade, + page_id uuid references pages(id) on delete cascade, -- symmetric key hashed with public key -- the symmetric key is unique for each page @@ -54,9 +57,9 @@ create table account_page_key_hash ( primary key(account_id, page_id) ); -create table account_outbox ( +create table account_outboxes ( id uuid primary key default gen_random_uuid(), - account_id uuid references account(id) on delete cascade, + account_id uuid references accounts(id) on delete cascade, payload_hash bytea ); diff --git a/server/models/account.go b/server/models/account.go index e7ca5b7..b6df0f3 100644 --- a/server/models/account.go +++ b/server/models/account.go @@ -11,16 +11,16 @@ type Account struct { AccentColor *int `msgpack:"accentColor" json:"accentColor" db:"accent_color"` SigningKeys struct { - PrivateKeyHash string `msgpack:"privateKeyHash" json:"privateKeyHash" db:"signing_private_key_hash"` + PrivateKeyHash []byte `msgpack:"privateKeyHash" json:"privateKeyHash" db:"signing_private_key_hash"` PublicKey interface{} `msgpack:"publicKey" json:"publicKey" db:"signing_public_key"` - } `json:"signingKeys" msgpack:"signingKeys"` + } `json:"signingKeys" msgpack:"signingKeys" db:""` EncryptionKeys struct { - PrivateKeyHash string `msgpack:"privateKeyHash" json:"privateKeyHash" db:"encryption_private_key_hash"` + PrivateKeyHash []byte `msgpack:"privateKeyHash" json:"privateKeyHash" db:"encryption_private_key_hash"` PublicKey interface{} `msgpack:"publicKey" json:"publicKey" db:"encryption_public_key"` - } `msgpack:"encryptionKeys" json:"encryptionKeys"` + } `msgpack:"encryptionKeys" json:"encryptionKeys" db:""` - PasswordSalt interface{} `json:"passwordSalt" db:"password_salt"` + PasswordSalt []byte `msgpack:"passwordSalt" json:"passwordSalt" db:"password_salt"` } func (u *Account) Bind(r *http.Request) error { diff --git a/server/models/document.go b/server/models/document.go index f1592e9..7a7c564 100644 --- a/server/models/document.go +++ b/server/models/document.go @@ -2,14 +2,15 @@ package models import "net/http" -type Page struct { - Id string `json:"id" db:"id"` - Title string `json:"title" db:"title"` +type Document struct { + Id string `json:"id" db:"id"` + // Title string `json:"title" db:"title"` doesn't exist, since encrypted CreatedBy string `json:"createdBy" db:"created_by"` IsDeleted string `json:"isDeleted" db:"is_deleted"` FileId string `json:"fileId" db:"file_id"` + // should each document have a signing public key? } -func (p *Page) Bind(r *http.Request) error { +func (d *Document) Bind(r *http.Request) error { return nil } diff --git a/server/models/event.go b/server/models/event.go new file mode 100644 index 0000000..59f89eb --- /dev/null +++ b/server/models/event.go @@ -0,0 +1,7 @@ +package models + +type Event struct { + To []string `msgpack:"to" json:"to"` + + Payload []byte `msgpack:"payload" json:"payload"` +} diff --git a/server/routes/account.go b/server/routes/account.go index eb57313..6fb4036 100644 --- a/server/routes/account.go +++ b/server/routes/account.go @@ -1,36 +1,224 @@ package routes import ( + "context" + "errors" "fmt" + "io" "net/http" + "github.com/georgysavva/scany/v2/pgxscan" "github.com/go-chi/chi/v5" "github.com/go-chi/render" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/odama626/tasks/server/crypto" "github.com/odama626/tasks/server/httpError" "github.com/odama626/tasks/server/models" "github.com/vmihailenco/msgpack/v5" ) -func BindMsgPack(request *http.Request, v render.Binder) error { - decoder := msgpack.NewDecoder(request.Body) - return decoder.Decode(v) +func Bind(body io.ReadCloser, v interface{}) error { + payload, err := io.ReadAll(body) + + if err != nil { + return err + } + err = msgpack.Unmarshal(payload, v) + + if err != nil { + return err + } + + return nil +} + +func GetDb(ctx context.Context) *pgxpool.Pool { + return ctx.Value("db").(*pgxpool.Pool) } func register(w http.ResponseWriter, r *http.Request) { account := &models.Account{} + signature := r.Header.Get("signature") + + payload, err := io.ReadAll(r.Body) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + err = msgpack.Unmarshal(payload, account) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + + err = crypto.VerifySignature(account.SigningKeys.PublicKey, payload, signature) + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + + ctx := r.Context() + db := GetDb(ctx) + + existing := models.Account{} + + fmt.Println(account.Username) + + err = pgxscan.Get(ctx, db, &existing, `Select id from accounts where username = $1 limit 1`, account.Username) + + if pgxscan.NotFound(err) { + // fall through + } else if err != nil { + render.Render(w, r, httpError.Internal(err)) + return + } else { + render.Status(r, 400) + render.Render(w, r, httpError.InvalidRequestWithData( + errors.New("Failed to create record"), + map[string]interface{}{"username": "The username is invalid or already in use."}, + )) + return + } + + result := models.Account{} + + query := `INSERT into accounts + (name, username, password_salt, encryption_private_key_hash, encryption_public_key, signing_private_key_hash, signing_public_key) VALUES ( + @name, @username, @password_salt, @encryption_private_key_hash, @encryption_public_key, @signing_private_key_hash, @signing_public_key + ) returning *` + + args := pgx.NamedArgs{ + "name": account.Name, + "username": account.Username, + "password_salt": account.PasswordSalt, + "encryption_private_key_hash": account.EncryptionKeys.PrivateKeyHash, + "encryption_public_key": account.EncryptionKeys.PublicKey, + "signing_private_key_hash": account.SigningKeys.PrivateKeyHash, + "signing_public_key": account.SigningKeys.PublicKey, + } + + err = pgxscan.Get(ctx, db, &result, query, args) + + if err != nil { + render.Render(w, r, httpError.Internal(err)) + return + } + payload, err = msgpack.Marshal(result) + + if err != nil { + render.Render(w, r, httpError.Internal(err)) + return + } + + w.WriteHeader(200) + w.Write(payload) +} + +type LoginPayload struct { + Username string `json:"username"` +} + +func Login(w http.ResponseWriter, r *http.Request) { + account := &models.Account{} + payload := &LoginPayload{} - err := BindMsgPack(r, account) + signature := r.Header.Get("signature") - if err != err { + rawPayload, err := io.ReadAll(r.Body) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + + err = msgpack.Unmarshal(rawPayload, payload) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + + ctx := r.Context() + db := GetDb(ctx) + + err = pgxscan.Get(ctx, db, &account, `select * from accounts where username = $1`, payload.Username) + + if err != nil { render.Render(w, r, httpError.InvalidRequest(err)) return } - // fmt.Print(account) - fmt.Println(len(account.PasswordSalt.([]byte))) + err = crypto.VerifySignature(account.SigningKeys.PublicKey, rawPayload, signature) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + } +} + +func getByUsername(w http.ResponseWriter, r *http.Request) { + username := chi.URLParam(r, "username") + + account := models.Account{} + + ctx := r.Context() + db := GetDb(ctx) + + err := pgxscan.Get(ctx, db, &account, `select * from accounts where username = $1`, username) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(errors.New("Failed to authenticate"))) + return + } + + payload, err := msgpack.Marshal(account) + + if err != nil { + render.Render(w, r, httpError.Internal(err)) + } + + w.Write(payload) + +} + +type DeleteAccountPayload struct { + Username string `msgpack:"username"` +} + +func deleteAccount(w http.ResponseWriter, r *http.Request) { + payload := &DeleteAccountPayload{} + ctx := r.Context() + db := GetDb(ctx) + + rawPayload, err := io.ReadAll(r.Body) + + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + + err = msgpack.Unmarshal(rawPayload, payload) + + if err = VerifySignature(rawPayload, r); err != nil { + fmt.Println(err) + render.Render(w, r, httpError.InvalidRequest(errors.New("Invalid Signature"))) + return + } + + _, err = db.Exec(ctx, `delete from accounts where username = $1`, payload.Username) + + if err != nil { + render.Render(w, r, httpError.Internal(err)) + return + } + w.WriteHeader(200) } func ConnectAccountRoutes(router chi.Router) { router.Post("/register", register) + router.Post("/delete", deleteAccount) + router.Get("/username/{username}", getByUsername) } diff --git a/server/routes/event.go b/server/routes/event.go new file mode 100644 index 0000000..11fd53d --- /dev/null +++ b/server/routes/event.go @@ -0,0 +1,68 @@ +package routes + +import ( + "errors" + "fmt" + "io" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/odama626/tasks/server/crypto" + "github.com/odama626/tasks/server/httpError" + "github.com/odama626/tasks/server/models" + "github.com/vmihailenco/msgpack/v5" +) + +func VerifySignature(body []byte, r *http.Request) error { + var publicKey interface{} + + signature := r.Header.Get("signature") + signer := r.Header.Get("signer") + + ctx := r.Context() + db := GetDb(ctx) + + err := db.QueryRow(ctx, `select signing_public_key from accounts where username = $1`, signer).Scan(&publicKey) + + if err != nil { + return err + } + + err = crypto.VerifySignature(publicKey, body, signature) + + if err != nil { + return err + } + + return nil + +} + +func post(w http.ResponseWriter, r *http.Request) { + event := &models.Event{} + + rawPayload, err := io.ReadAll(r.Body) + err = msgpack.Unmarshal(rawPayload, event) + if err != nil { + render.Render(w, r, httpError.InvalidRequest(err)) + return + } + + // fmt.Println(event) + + if err := VerifySignature(rawPayload, r); err != nil { + fmt.Println(err) + render.Render(w, r, httpError.InvalidRequest(errors.New("Signature Invalid"))) + return + } + + // fmt.Println(event) + + render.JSON(w, r, map[string]interface{}{"success": true}) +} + +func ConnectEventRoutes(router chi.Router) { + + router.Post("/", post) +} diff --git a/server/routes/main.go b/server/routes/main.go index 69a0cee..b99a9e2 100644 --- a/server/routes/main.go +++ b/server/routes/main.go @@ -10,6 +10,7 @@ import ( func ConnectRoute(parent *chi.Mux) { parent.Get("/ping", pong) parent.Route("/account", ConnectAccountRoutes) + parent.Route("/event", ConnectEventRoutes) } func pong(w http.ResponseWriter, r *http.Request) { diff --git a/server/todo.md b/server/todo.md new file mode 100644 index 0000000..ce8b87e --- /dev/null +++ b/server/todo.md @@ -0,0 +1,23 @@ +# msgpack and encrypt payloads +- header should be json, body should be blob +- body is always encrypted -- treat like file +- for federation, just forward to all recipients' servers + + +example +``` +{ + from: 'adam@lilbyte.dev' // lookup user to verify signature + to: [ + 'amanda@lilbyte.dev' // goes to amandas inbox + 'brandon@brandonville.com' // entire payload gets forwarded to brandonville.com server + ], + event: 'create' // what type of event is this? create / delete / update + model: 'document', // the datastructure that is affected + body: `asdfasdfasf` // binary -- encrypted based on reference [2] +} +``` + +# References +1. double ratchet messaging - https://www.youtube.com/watch?v=9sO2qdTci-s +2. group messages (more than 2 people) - https://www.youtube.com/watch?v=Q0_lcKrUdWg diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..dd80ae7 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,105 @@ +import { + createPayloadSignature, + encryptPayload, + encryptWithKey, + exportUserKeypair, + generateEncryptionKeypair, + generateSalt, + generateSigningKeypair +} from '$lib/crypto'; +import { decode, encode } from '@msgpack/msgpack'; +import wretch, { type Wretch, type WretchAddon } from 'wretch'; +import { login as cryptoLogin } from '$lib/crypto'; +import { userStore } from './storage'; + +type Account = any; + +async function preparePayload(payload, account: Account, encrypt = true) { + let body = encode(payload); + if (encrypt) { + body = await encryptPayload(account.keys.encryptionKeys, body); + } + const signature = await createPayloadSignature(account.keys.signingKeys, body); + return { + body, + headers: { signature, 'Content-Type': 'application/msgpack', signer: account.username } + }; +} + +const apiUrl = wretch(`http://localhost:4000`); +export async function register(data) { + const passwordSalt = generateSalt(); + const { password, passwordConfirm, ...rest } = data; + let unencodedSigningKeys; + const [signingKeys, encryptionKeys] = await Promise.all([ + generateSigningKeypair().then((keys) => { + unencodedSigningKeys = keys; + return exportUserKeypair(keys, password, passwordSalt); + }), + generateEncryptionKeypair().then((keys) => exportUserKeypair(keys, password, passwordSalt)) + ]); + + const rawPayload = { + ...rest, + passwordSalt: passwordSalt, + signingKeys, + encryptionKeys + }; + const { body, headers } = await preparePayload( + rawPayload, + { keys: { signingKeys: unencodedSigningKeys }, username: rest.username }, + false + ); + const account = await apiUrl + .url('/account/register') + .body(body) + .headers(headers) + .post() + .arrayBuffer(decode); + + return account; +} + +export async function login(username: string, password: string): Promise { + const payload = await apiUrl.url(`/account/username/${username}`).get().arrayBuffer(); + const account = decode(payload); + + const keys = await cryptoLogin( + account.encryptionKeys, + account.signingKeys, + password, + account.passwordSalt + ).catch((e) => { + throw { + code: 400, + message: 'Failed to authenticate' + }; + }); + + Object.defineProperty(account, 'keys', { + get() { + return keys; + } + }); + return account; +} + +export async function deleteAccount(account: Account) { + const { body, headers } = await preparePayload({ username: account.username }, account, false); + + const result = await apiUrl.url('/account/delete').headers(headers).body(body).post().res(); + return result.ok; +} + +export async function createEvent(account: Account, event) { + const { body: payload } = await preparePayload(event.payload, account); + const { payload: _, ...rest } = event; + const { body, headers } = await preparePayload( + { ...rest, payload: new Uint8Array(payload) }, + account, + false + ); + + console.log({ body, headers }); + const result = await apiUrl.url('/event').headers(headers).body(body).post().res(); +} diff --git a/src/lib/crypto.test.ts b/src/lib/crypto.test.ts index da4e79e..cb56649 100644 --- a/src/lib/crypto.test.ts +++ b/src/lib/crypto.test.ts @@ -1,6 +1,6 @@ import { test, expect } from 'vitest'; import * as aesjs from 'aes-js'; -import { bytesToBase64 } from 'byte-base64'; +import { base64ToBytes, base64decode, base64encode, bytesToBase64 } from 'byte-base64'; import { createPayloadSignature, createSymmKey, @@ -14,13 +14,16 @@ import { login, generateSigningKeypair, importSigningKeyPair, - verifySignature + verifySignature, + encryptPayload, + decryptPayload } from './crypto'; import { encode, decode } from '@msgpack/msgpack'; const encryptionKey = { - privateKeyHash: - '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', + privateKeyHash: base64ToBytes( + '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' + ), publicKey: { alg: 'RSA-OAEP-512', e: 'AQAB', @@ -31,8 +34,9 @@ const encryptionKey = { } }; const signingKey = { - privateKeyHash: - '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', + privateKeyHash: base64ToBytes( + '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' + ), publicKey: { alg: 'RS512', e: 'AQAB', @@ -55,7 +59,7 @@ test('generate keys', async () => { expect(signingKeys).toBeTruthy(); expect(encryptionKeys).toBeTruthy(); -}); +}, 5000); test('import export encryption keypair with password', async () => { const password = 'test password'; @@ -128,12 +132,12 @@ test('create signature', async () => { const payload = encode(data); expect(bytesToBase64(payload)).toMatchInlineSnapshot( - `"g6RuYW1lqEpvaG4gRG9lqXB1YmxpY0tleYajYWxnrFJTQS1PQUVQLTUxMqFlpEFRQUKjZXh0w6drZXlfb3BzkadlbmNyeXB0o2t0eaNSU0GhbtoCq2xwbGhveXQ2cUNFdllJZkN3blJ4MlViV3JDeDNucEE4WkNIWHlyR3pzNjFnUmhiSVRsR3dDV2xXSzh0ZUhHaUU3akl0X2JqMmRpRnR3RXJOc3RnNXpVSFd1T1NFa0k1YjBSM1E2SmRjVDJpZ3ZsLXFXblJjQ3Y1dHlfNm5Ha1BKLUR6UHBENGl4THRBams2YVZHZzhzVEtSM3hKbVpTVkxSVGF6dmxpQWJLSVlpOWlJU0VTWDRYbERYN3hCSXFMZjF4cEZlUVlEa3BiMThZME9SRW1SMFB5QUVhU0F1NXB1STNRX2QtV0Q1LWNjenZjaFFKcUFhdWsyZjJHV1NxS1YyQXM4UUxzYkF1R0pfZDNJZ252ak1hUXlHRmV1YV9MZmdvZ3NQeDlOQXlvM1JIV3ZPRHlVT1lPRDFCRWk3VUV4eWltY2dDRExEVGZ2SHAtblZTU3B3ZEpPS3AzTXEyVXNLM3ZPcmdkQnNwejJwNUpsZmEzMXFaWldrTFBSSjZmbHJsUF9iUEdnUEJUMWZ2OWhyWGhIMks5SDVyaWNaa2VnczBaVVdEVkM5N2FUNkF2cWlVN004aENybC1Hb2FfQXc0RWNhdFFDamM0X0tCaW5OSElTVnVsM2pYaUt1S1c0ZzFiZW8wUWZBZ0RWa3l5NHZBdmhLQW56U2hzQ1FiUzRPTWVyX3hCekdSQXkwSnpyNzNrN0xCb2JtMGVtN2pobGZ6R3pVOXhseThfczhSd2VaOGE0ZzlXbGt4djh6eXFQVGRqQmpLSXhuc2dLV2k5Zmx5TDI5QmI3aEY5T1dlYktON25SeFVXMDc3Y3VaOWFPYm1CTUU2ZmRuTkNsTmFIT1NnSHBidWRlajR5TEVtcmRrUUItaHFraG1YblFGMXgyMTM0OElWMzB6MnJNsHByaXZhdGVLZXlIYXNoZWTaDGBVTHZ5T00wdUR6Um8yZndBRWhvV3hHK0pZOE1rYUhxUWNTYlJ1NjVqeFZTOUdGVkVheG9UQ0ZmNFdvVzcrNlVBQzFCQ2w3ejU1Q3ZNQlI3SjFHRllkMlhJTzQza05WMkY5dFNNWVphUG1yakxjenY5YlRJVWUxK215MHBWNWJhYlE1NGt1UFBlN3FlK3lzb0J5dnJmZllBdU5GeDArTUh5WXFVbFJSWnRnT0M2U0M3alZ1L3pzWElHWkx2V25nNXV5M2R3ZzJzYkRHUEtPS29EVllWbmNiSkhEc256cDFYUGhYQUVqV041SEJuVzB6ODRoOHJFRzNkc0VIekpaRSs5YXJEK1lKQUtLanFnY2NWdExPMEFZWnlGYVVMZ0t6eXRMYWFWMWxjT2lRZVA5YlE2eFc5SWNTOVlEL1dHUWNSM3F0cDVTZGJRZ0Jtb2JwNzhwOUo3N1ZBbXdjbFFYWDAwQ09ZSnN0TjNDRFgzYnFkdWxMdk53SlJZYWN6YkpxL2U5U0kwa3FMTkNqR1Ntd2F3UGwxV0FuMXNZMk9oNXJqYzNUcURYT2xPcTg4RGJtam1lamU3Ti9qZ3RFSnduY2twZFVBLzV5MEg0OEMvUWFKaDlSeHhpYUozcnBiS1hnSm9kUFVQNDVVcFJ6TlJ4elBVZWNiSlgyODY0MzBYZEdOKzFoT2RmZUFHUkNwSWwzR21HSDJTdm9MNzRDS3RDUWxXUHFaZStEUGhZOE5LUGR4WWF4QTcxSEZ5bWRQaGZPWXFUL3V3Z0dXRU15bW42Q2ZRd2FUOVI4bDMxM0t5Kzd5U012MmRMWDBWNEdNRDJnbkxLNlZLS3BQR0RqSm51V3YrWmp4dWRNQVpnSG5ScU83YUhnZ1Vrb1dyNnlIMGhZWWt0L2psamU1amNoVlF0b3VUMklYek5pdVFQbDVnODNydzJkOTE4dUVrQmI5MzFxUGRIYTZDU0cvSHZza0dWektlVnRqL2VNMzRWRjBIbHhpVWorYmh2QUF5N2xzL0dNMjczZzhkYzhoeVVlcnZaZTVwSGYrK1VzRjZZa2NYNzFyNHNoNEpUbnkrRVRJMTlYVnZXdEkwLzdVR0lPeUpXVmxkSXBVR0lqZ05mbEJSTmdXangwNC9FQ1RsNmdYNE1TZzJPRm9XcFJYRkxNd0VaZlN2SERMc2owbnBwb2xLTmhSci9DUWZKK3VPMGFISTUxZ0NjT2laL0pMYmt3dFB4SXJrTHlORzF5YlFRVXQ5NS9POHh6ZjBBbm5pUHY1cnZmZTIweWlSSmRRY2FwREFQVzljcGpjcmU2allXdGc4V2x0TmhUR1NrV0F2d0NXdktKQ0QrWkgyL3Urd1BxL0hDa24vTUs0RnJId3B6M3FvQnBwa0Izdkw4bVR3V2h0Y0tBbWpGYUYwNTVqUlpLcE9vZ0s1YWdPOWozc3JMbjh0Sm5EYlk4TVB2clJoTXZ4RjJsU3d2UFd0Z05iWE4wdzlVMlVXMm9mdlRMbmpYTlUzd3BNVU1hdnUwTFdUcHA3T0I3dWNzTHVhUGpsbUovOGxYTVJEQ2tIL2F5aFVDUW1ER3V0UXRjbmVpMW9PaDRralJrQnhFQnNhNWs5VFEzZGNJYmlQM3BmTlFXU3RMcW5PL0l5UENaZXNuMHVDbFJCOGV5WnFlWHhlWTdQNzl4S2tUbk16cDU2OFhkNk80OFIxdnVISjhmZElVU1VEbkl3citkQWtkYzNzZGs4TFJ0K0hFS05GSHdRUE51Rmd4Mm1HUTNWQlozdkJ1RC81YWRaRTZZZGFDcUMrM0dId3pqQnBiaEtZZlA0UGZSRnNiSG1OTE11b3h1TGF3djhEdE5IZnNLY2ovVWc2aVBRMnNSUkkzVlU3eTR6NzNvV3ZzRGt3T0kvdDhpanZleVRzRXdpT0xWMFgvc0dDenlsU0xOdnZsZERLQUFDeFlxWERwR0tyTTg0clJvNGZ2T09kazBNK0NXOTZTbUYwNnNXTUEzRTRXTTc2ajlMLzdHSmFBblZlbUN0WVNialRKcUd6UkxKczlsSnU5L2xsRzkySXUvSytOdGV1Q01GS3pFSXlOQitVY3MxdFBFa056RzdqTFpVSWJqUG1oVVNmWlVhNWFGeVlqZFY5eXEwTlhMZHlPRml6Z1I0N0x3M0hiMDNVUHZ5d3JyajdMSWhXM1o0TEtFbUVldmc1Y1lrY0VlNWMxaWJSZXlnQTRCOUp1UU5QUUR6d3NMVzdiM1VPY0JRM2F6cHhNWlNjSkIyMXlnOElnVWZMZ09JZ0xqNjRsa1RUdFN5TGcxMUYxbHBKdG0wanVXSFJ0SFI3Sng4RUFpbE96eHBwNkpvaHZ5cVVXTUtVenFmZ0dGSTlXUzNOTEs0SmVGYk13NExZR0lXQWFvL2U4NEJ2K3lWQWErVkJBYkxlRlZTTmRJdjlmVkcyUVh1alZNVlFXSis2NWdlQnNlQzRTYkFaakFld3VwZmFSUHpKMW9WbTFBYlpCYVl3U2xCZ3o5Sy91QUk0RHZuNE1LQUVDN2VEdHVPOUhBTVJSdndoL1F1dEtjVSt2cFdPM3hOcEpIVHI1Y0xRZGtJNDlPVEZod0FNSkdjbkZiZVJEYTVPb1FmUnFrYmxTMWxqT0dhNENaVVQxMlNrS09nenhFc0ZwdjM0TjcxbjZhUUV1TW9UMXdkNXVGUXpoTm8vYm1UaE9lY3RqYUlwSy9kUUpydW4wK3A3VUREMW45OTkzUUFkd1J0bmlJOFYwTGd6V2FOckV4WlBNQTRIb1Z6d0daQnRNdXpDWmpUOTR4MUtINzVvbWhXN3ZpRU1KbFovZGFIakppZ3BkdVU5eStJb0c4M1lITTY4dzBVdXc1d0l5N2Y5Mm44T05NdlB0eTB6di9MWWcwUXBVL1JZM0hTcmlkMnpaRWVNZ2gxNUpIVDNYSnZFMGdzWFkrMGREUU50R0pZVkVnYU5LU2tmb2dUSGdLNFY2dkNSbENYTlJ6VUdoYzNab01mdTZPc21HQXQ4cDVzSGRrQkVDMXpSQTVsV29RSlRjdWdVS01mbFBhT0lRN3RNQ1NoRTk0aWtyS2VMeXpVbHdsRStNSUppK0wwV0g3R1FFUTMvdmo5d0VxR2prWGg0MUJVQ2JRWGJoZlYreWkyNlBudHEvY3Jqb0VQNGdkYzR5YndPMTNRK3ZEQS90eTE5djdYR3djc002cnc1dWNLRXNYQzUrYitJaEFRU1cwV09QbmR3aXd0clBCb3Qyb3JlbG9mRHFabFpxbnM2ZDRWQ0lRaTVURisveXkwMGhMbUkvL0VqYlFaZFpsNzFCMFBjbmdzTThYOThrR0RkaXZUelM4T3hYWHhxVllCZWMwSXZnTk9XZXJBQ2tZUElENmh0OHBUak5oRXJjdzN1UUdZVkdGUWtIZXRUYmRCRVNMRDVmWnRseE4yL1BRQms5L3g5blFnczZ3T3k4cytTUzExTjFxNzdjdXZQZk9NYXZ3ZjJ1b0VpamZXOGJzK05xNkpnWW9kdzdJMDBVdklqM3RlL3RSbkNHZmNDYnRHeU11Ynk1dlh2YUtTVlNuL2tTSEdHOVdUUlBBbWhsYSs1RTBRZVluR0VySXpzNDh5ZUV6QmtsR0pkbjd3dFAzaks4UWVhekc2OENGSlQyVDZIQlVwQ3FTWXoweVJseWt3VlVaVE5Oa2REMVl2T0s5M3hkTnhVTG13eU9kWEpPZ1gxdFhxR1E1LzdRM2lLUFh2OVdVOWZoWkprWGZoQng0Z1hmNmxMbnoxcFdCZ3FUbnlBYW5DNGh6YmU0SkFsS2EwbjNVZjl6T3NhVk9oTU5qMFYvZ0xiYVVWVG5wdTV6NXlUaWZWMUViWEF1VmdPUlNzcTY2WXYwWWRmdDNKcUNaeVgrR3dyOHdOTTJ1bWNUNzQ1YlM3QnlWQ3Z6cXg2ZHdiNkdBa3pUc0pMNVJoaFg4eHFBRGtBQ2RyaFBveXdHT1pmTzJkenFTUFBwOXVrYkEzY3U2TDhHdzdWRHlwWEo5MnVqeCtDZVV3Q3F2dFhFQ1A0WlUrY2xNU1Fpd2pGcHQyK3BoY2YyS1A2SWx0d0dONEZHTzdmT3dnR0kzTVVuN29VZUxDOUppekNDVlJJdzVqMjUxMm04L2RNUDJSM2RBditZR0t0TmxNRU0vcWpKWGdhaEpPaGQ0aXg5Qy8zNDcrNUpGRFlQaVFxdEF5SDJDZTgrcFJRK2VaKzY4ND0="` + `"g6RuYW1lqEpvaG4gRG9lqXB1YmxpY0tleYajYWxnrFJTQS1PQUVQLTUxMqFlpEFRQUKjZXh0w6drZXlfb3BzkadlbmNyeXB0o2t0eaNSU0GhbtoCq2xwbGhveXQ2cUNFdllJZkN3blJ4MlViV3JDeDNucEE4WkNIWHlyR3pzNjFnUmhiSVRsR3dDV2xXSzh0ZUhHaUU3akl0X2JqMmRpRnR3RXJOc3RnNXpVSFd1T1NFa0k1YjBSM1E2SmRjVDJpZ3ZsLXFXblJjQ3Y1dHlfNm5Ha1BKLUR6UHBENGl4THRBams2YVZHZzhzVEtSM3hKbVpTVkxSVGF6dmxpQWJLSVlpOWlJU0VTWDRYbERYN3hCSXFMZjF4cEZlUVlEa3BiMThZME9SRW1SMFB5QUVhU0F1NXB1STNRX2QtV0Q1LWNjenZjaFFKcUFhdWsyZjJHV1NxS1YyQXM4UUxzYkF1R0pfZDNJZ252ak1hUXlHRmV1YV9MZmdvZ3NQeDlOQXlvM1JIV3ZPRHlVT1lPRDFCRWk3VUV4eWltY2dDRExEVGZ2SHAtblZTU3B3ZEpPS3AzTXEyVXNLM3ZPcmdkQnNwejJwNUpsZmEzMXFaWldrTFBSSjZmbHJsUF9iUEdnUEJUMWZ2OWhyWGhIMks5SDVyaWNaa2VnczBaVVdEVkM5N2FUNkF2cWlVN004aENybC1Hb2FfQXc0RWNhdFFDamM0X0tCaW5OSElTVnVsM2pYaUt1S1c0ZzFiZW8wUWZBZ0RWa3l5NHZBdmhLQW56U2hzQ1FiUzRPTWVyX3hCekdSQXkwSnpyNzNrN0xCb2JtMGVtN2pobGZ6R3pVOXhseThfczhSd2VaOGE0ZzlXbGt4djh6eXFQVGRqQmpLSXhuc2dLV2k5Zmx5TDI5QmI3aEY5T1dlYktON25SeFVXMDc3Y3VaOWFPYm1CTUU2ZmRuTkNsTmFIT1NnSHBidWRlajR5TEVtcmRrUUItaHFraG1YblFGMXgyMTM0OElWMzB6MnJNsHByaXZhdGVLZXlIYXNoZWTFCUdQu/I4zS4PNGjZ/AASGhbEb4ljwyRoepBxJtG7rmPFVL0YVURrGhMIV/hahbv7pQALUEKXvPnkK8wFHsnUYVh3Zcg7jeQ1XYX21Ixhlo+auMtzO/1tMhR7X6bLSlXltptDniS4897up77KygHK+t99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"` ); const signature = await createPayloadSignature(keys.signingKeys, payload); expect(signature).toMatchInlineSnapshot( - `"IbrXD/ENxXNAmwCEXJEcNTGZOqt64af4u72uhkBgB5evAAOx1H4cCJ3EGol0AYdKhUI2GUsrCjcLFwKgq10zdjlSc527BhlVVQraH2gTL/4Ivp5X9hfQt75+EfTFLTBh7VW7P3ODXox2c1h3CENjQrjAWJ08URowe/Iamf4XnVPuuAQAAGhKeAXNlmjJvs5jpQW0m1PfWP9WbhBj3/IvVSam1dFTpDsiqtiXu0ByIKSPzG+dqpMqeetsf1GtEZxmU0wGhlrP2hJ9XuvboQok5XfGu9o1R78VxV9hoyPG/Caz+gnl7amvSr56uYE83ang8f7coRKlwOO0E++FWOTHTFqyL09cdMeZldsR5tpPq5DBzERe77A7VvX2hnTBg3Q61+oflm0LN8WOIaDond3o3ZSdsWVpsfmLfP8dSNm/xhhhj/ZsROjbagdgg+RAqh5M82yjmaqGP/3yXTe0Z0tDBGmgfo5dpv3uAriVZL/8C20nnzmIRPUwRY2GZJ/8ZeIhhdHQD7+/VvjrDXazG6SPnb2koJRc5vSE5HShAaqOy17qJvpS04ng7zqKp1ITK4bDoW4NjoCw3i2sOqudYmACqCEs81XG2lTxd2yoH5uzP33nsXN1TqQxXgT49nLN+zgwnICDe6RP61Z9Ip7jEtSy7MzyHz8eUoaTXfCOAG1Jet4="` + `"iSFhBwDuy7DfkJFO9tzF2J5yTWL7ze7rAWCLuAgGaAhSiyrJF3emw7N5dJTr01JvCqPYmQZWCzvKs59aZb55Do/3xgyGZjjoSkWyaIbONWXQB7spMtssmUv5HUIcd/iUn4zREbE8LLJ00353xrW7330ioeE2vXtOtWPcBjKkeio0LQN3A8BdulYkTPtqZgPZRuiWB+KOUVndkzcV8IueJpj+tr+GDCRpWCo0FcFDbcyxzEn5OGOwHbp8s/3MYQje/w5Kj5x7vmU9+ENPoiv1fA2Mrp9vV7Y7LlBrKV8fKHuKCtctPn3HVxXNT1aDqlyZNGBJUIoNVfdP4R1zipab7g4XY+nayHB8tw5b3uBv37Me94PnYy+8aov/bWgWGz9Bhkm1aqRUpOocgnRcbOONHhCcEz7ikaK7WsCW9+NJQXy38I0ML+ZHDAS16PqBCDODSq+VdFQQqqEWV54JnGAyUSfZmdFRcs0Uq3RUH2HIKyxyYuwdHgK1gyi54oKlU5COZ7W7cWFteZg2eKZeQgnF8J5jyyyFFeTHN7Gp/czzCx8UmNHdObKxrweO+ApHdGpIxuxnCUjQ3OsWBpdDDyObgmsuv6oPGRRp6EqZ/pax0r3RT4r+b+ww3NZ0ao+vxj0OkarHmDp3RC4KPQcPNGTIhpR6JrwK/D3fSAsxPpqoNlo="` ); const valid = await verifySignature(keys.signingKeys, payload, signature); @@ -142,3 +146,20 @@ test('create signature', async () => { const result = decode(payload); expect(result).toStrictEqual(data); }); + +test('encrypt and decrypt payload', async () => { + const password = 'test password'; + const keys = await login(encryptionKey, signingKey, password, salt); + + const data = { + msg: 'secure message' + }; + + const rawPayload = encode(data); + const encryptedPayload = await encryptPayload(keys.encryptionKeys, rawPayload); + const decryptedPayload = await decryptPayload(keys.encryptionKeys, encryptedPayload); + + const payload = decode(decryptedPayload); + + expect(payload).toStrictEqual(data); +}); diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index faf9c2c..23873c2 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -58,7 +58,7 @@ export async function exportUserKeypair( return { publicKey, - privateKeyHash: bytesToBase64(encPrivateKeyBytes) + privateKeyHash: encPrivateKeyBytes }; } @@ -76,7 +76,7 @@ export async function importKeyPair( publicUsages: KeyUsage[] ): Promise { const passKey = await createSymmKeyFromPassword(password, salt); - const encPrivBytes = base64ToBytes(key.privateKeyHash); + const encPrivBytes = key.privateKeyHash; const privBytes = new Uint8Array(passKey.decrypt(encPrivBytes)); const subtle = globalThis.crypto.subtle; @@ -128,6 +128,24 @@ export async function createPayloadSignature(keyPair: CryptoKeyPair, payload: Ar return bytesToBase64(new Uint8Array(signature)); } +export async function encryptPayload(keyPair: CryptoKeyPair, payload: ArrayBuffer) { + const encrypted = await globalThis.crypto.subtle.encrypt( + ENCRYPTION_ALGORITHM.name, + keyPair.publicKey, + payload + ); + return encrypted; +} + +export async function decryptPayload(keyPair: CryptoKeyPair, payload: ArrayBuffer) { + const decrypted = await globalThis.crypto.subtle.decrypt( + ENCRYPTION_ALGORITHM.name, + keyPair.privateKey, + payload + ); + return decrypted; +} + export async function verifySignature( keyPair: CryptoKeyPair, payload: ArrayBuffer, @@ -155,7 +173,7 @@ export async function login( } export function generateSalt() { - const salt = new Uint32Array(32); + const salt = new Uint8Array(128); globalThis.crypto.getRandomValues(salt); return salt; } @@ -170,6 +188,8 @@ export async function createSymmKey() { return await hashPassword(new TextDecoder().decode(password), salt); } +export async function encryptWithPrivateKey(encryptionKeys: CryptoKeyPair) {} + export function encryptWithKey(key: Uint8Array, payload: ArrayBuffer): ArrayBuffer { const aesCtr = new aesjs.ModeOfOperation.ctr(key); return aesCtr.encrypt(payload); diff --git a/src/lib/e2e.test.ts b/src/lib/e2e.test.ts new file mode 100644 index 0000000..17adf70 --- /dev/null +++ b/src/lib/e2e.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from 'vitest'; +import { createEvent, deleteAccount, login, register } from './api'; + +let account: Account; +let username = 'test-user'; +let password = 'test-password'; + +test.sequential('register', async () => { + const result = await register({ + name: 'Test User', + username, + email: 'test-user@example.com', + password, + passwordConfirm: password + }); + expect(result).toBeTruthy(); +}); + +test.sequential('login', async () => { + const result = await login(username, password); + + account = result; +}); + +test.sequential('create document', async () => { + console.log(account); + await createEvent(account, { + to: ['self'], + action: 'create', + type: '', + payload: { + update: 'document', + blah: '123' + } + }); +}); + +test.sequential('delete account', async () => { + expect(deleteAccount(account)).resolves.toBeTruthy(); +}); diff --git a/src/routes/user/+page.svelte b/src/routes/user/+page.svelte index 78fdf42..4f4ebc7 100644 --- a/src/routes/user/+page.svelte +++ b/src/routes/user/+page.svelte @@ -6,15 +6,9 @@ import { ZodError, z } from 'zod'; import Profile from './profile.svelte'; import { handleRedirect } from './utils'; - import wretch from 'wretch'; - import { - createPayloadSignature, - exportUserKeypair, - generateEncryptionKeypair, - generateSalt, - generateSigningKeypair - } from '$lib/crypto'; - import { encode } from '@msgpack/msgpack'; + import { encode, ExtensionCodec } from '@msgpack/msgpack'; + import { bytesToBase64 } from 'byte-base64'; + import { register as handleRegister, login as handleLogin } from '$lib/api'; let registerErrors: ZodError; let loginErrors: ZodError; @@ -39,41 +33,13 @@ password: z.string() }); - async function handleRegister(data) { - const salt = generateSalt(); - const { password, passwordConfirm, ...rest } = data; - let unencodedSigningKeys; - const [signingKeys, encryptionKeys] = await Promise.all([ - generateSigningKeypair().then((keys) => { - unencodedSigningKeys = keys; - return exportUserKeypair(keys, password, salt); - }), - generateEncryptionKeypair().then((keys) => exportUserKeypair(keys, password, salt)) - ]); - const rawPayload = { - ...rest, - salt, - // signingPublicKey: signingKeys.publicKey, - // signingPrivateKeyHash: signingKeys.privateKeyHash, - // encryptionPublicKey: encryptionKeys.publicKey, - // encryptionPrivateKeyHash: encryptionKeys.privateKeyHash, - signingKeys, - encryptionKeys - }; - const payload = encode(rawPayload); - console.log({ payload }); - const signature = await createPayloadSignature(unencodedSigningKeys, payload); - wretch('http://localhost:4000/account/register') - .headers({ signature, 'Content-Type': 'application/msgpack' }) - .post(payload); - } - const register = collectFormData(async (data, event) => { const result = registrationSchema.safeParse(data); if (!result.success) return (registerErrors = result.error); - // await handleRegister(data); - // return; + const account = await handleRegister(data); + + userStore.update((user) => ({ ...user, account })); const payload = await pb.collection('users').create(data).catch(convertPbErrorToZod); if (payload.error) return (registerErrors = payload.error); @@ -89,6 +55,9 @@ const login = collectFormData(async (data, event) => { const result = loginSchema.safeParse(data); if (!result.success) return (loginErrors = result.error); + + await handleLogin(result.data.username, result.data.password); + const auth = await pb .collection('users') .authWithPassword(result.data.username, result.data.password)