diff --git a/.dockerignore b/.dockerignore index 21934c1..e4722dc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,7 @@ bin *.pyc *.egg-info .vagrant +.git .tmp bower_components node_modules diff --git a/README.md b/README.md index 4f0e525..5a31830 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,18 @@ $ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "Max-Downloads $ curl --upload-file ./hello.txt https://transfer.sh/hello.txt -H "Max-Days: 1" # Set the number of days before deletion ``` +### X-Encrypt-Password +#### Beware, use this feature only on your self-hosted server: trusting a third-party service for server side encryption is at your own risk +```bash +$ curl --upload-file ./hello.txt https://your-transfersh-instance.tld/hello.txt -H "X-Encrypt-Password: test" # Encrypt the content sever side with AES265 using "test" as password +``` + +### X-Decrypt-Password +#### Beware, use this feature only on your self-hosted server: trusting a third-party service for server side encryption is at your own risk +```bash +$ curl https://your-transfersh-instance.tld/BAYh0/hello.txt -H "X-Decrypt-Password: test" # Decrypt the content sever side with AES265 using "test" as password +``` + ## Response Headers ### X-Url-Delete diff --git a/cmd/cmd.go b/cmd/cmd.go index 59a7c7d..ba37b75 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -2,11 +2,12 @@ package cmd import ( "fmt" - "github.com/dutchcoders/transfer.sh/server/storage" "log" "os" "strings" + "github.com/dutchcoders/transfer.sh/server/storage" + "github.com/dutchcoders/transfer.sh/server" "github.com/fatih/color" "github.com/urfave/cli" @@ -290,7 +291,7 @@ var globalFlags = []cli.Flag{ cli.IntFlag{ Name: "random-token-length", Usage: "", - Value: 6, + Value: 10, EnvVar: "RANDOM_TOKEN_LENGTH", }, } diff --git a/go.mod b/go.mod index 0efcf42..091efec 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/dutchcoders/transfer.sh go 1.18 require ( + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 + github.com/ProtonMail/gopenpgp/v2 v2.5.2 github.com/PuerkitoBio/ghost v0.0.0-20160324114900-206e6e460e14 github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2 github.com/aws/aws-sdk-go v1.44.211 @@ -14,13 +16,13 @@ require ( github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/microcosm-cc/bluemonday v1.0.22 + github.com/microcosm-cc/bluemonday v1.0.23 github.com/russross/blackfriday/v2 v2.1.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce github.com/urfave/cli v1.22.12 golang.org/x/crypto v0.6.0 - golang.org/x/net v0.7.0 + golang.org/x/net v0.8.0 golang.org/x/oauth2 v0.5.0 google.golang.org/api v0.111.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c @@ -33,6 +35,7 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect + github.com/cloudflare/circl v1.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -61,8 +64,8 @@ require ( github.com/zeebo/errs v1.3.0 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514 // indirect google.golang.org/grpc v1.53.0 // indirect diff --git a/go.sum b/go.sum index 9f374ce..75842c9 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,13 @@ cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2Aawl cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ProtonMail/go-crypto v0.0.0-20230124153114-0acdc8ae009b/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM= +github.com/ProtonMail/gopenpgp/v2 v2.5.2 h1:97SjlWNAxXl9P22lgwgrZRshQdiEfAht0g3ZoiA1GCw= +github.com/ProtonMail/gopenpgp/v2 v2.5.2/go.mod h1:52qDaCnto6r+CoWbuU50T77XQt99lIs46HtHtvgFO3o= github.com/PuerkitoBio/ghost v0.0.0-20160324114900-206e6e460e14 h1:3zOOc7WdrATDXof+h/rBgMsg0sAmZIEVHft1UbWHh94= github.com/PuerkitoBio/ghost v0.0.0-20160324114900-206e6e460e14/go.mod h1:+VFiaivV54Sa94ijzA/ZHQLoHuoUIS9hIqCK6f/76Zw= github.com/VojtechVitek/ratelimit v0.0.0-20160722140851-dc172bc0f6d2 h1:sIvihcW4qpN5qGSjmrsDDAbLpEq5tuHjJJfWY0Hud5Y= @@ -17,10 +24,13 @@ github.com/aws/aws-sdk-go v1.44.211/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/calebcase/tmpfile v1.0.3 h1:BZrOWZ79gJqQ3XbAQlihYZf/YCV0H4KPIdM5K5oMpJo= github.com/calebcase/tmpfile v1.0.3/go.mod h1:UAUc01aHeC+pudPagY/lWvt2qS9ZO5Zzof6/tIUzqeI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -135,8 +145,8 @@ github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/microcosm-cc/bluemonday v1.0.22 h1:p2tT7RNzRdCi0qmwxG+HbqD6ILkmwter1ZwVZn1oTxA= -github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= +github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= @@ -164,6 +174,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -188,6 +199,7 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -195,9 +207,15 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -215,8 +233,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY 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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= @@ -239,13 +257,14 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -254,13 +273,14 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/server/handlers.go b/server/handlers.go index 60dca88..e32a22e 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -52,6 +52,10 @@ import ( textTemplate "text/template" "time" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/dutchcoders/transfer.sh/server/storage" web "github.com/dutchcoders/transfer.sh-web" @@ -90,6 +94,128 @@ func initHTMLTemplates() *htmlTemplate.Template { return templates } +func attachEncryptionReader(reader io.ReadCloser, password string) (io.ReadCloser, error) { + if len(password) == 0 { + return reader, nil + } + + return encrypt(reader, []byte(password)) +} + +func attachDecryptionReader(reader io.ReadCloser, password string) (io.ReadCloser, error) { + if len(password) == 0 { + return reader, nil + } + + return decrypt(reader, []byte(password)) +} + +func decrypt(ciphertext io.ReadCloser, password []byte) (plaintext io.ReadCloser, err error) { + unarmored, err := armor.Decode(ciphertext) + if err != nil { + return + } + + firstTimeCalled := true + var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) { + if firstTimeCalled { + firstTimeCalled = false + return password, nil + } + // Re-prompt still occurs if SKESK pasrsing fails (i.e. when decrypted cipher algo is invalid). + // For most (but not all) cases, inputting a wrong passwords is expected to trigger this error. + return nil, errors.New("gopenpgp: wrong password in symmetric decryption") + } + + config := &packet.Config{ + DefaultCipher: packet.CipherAES256, + } + + var emptyKeyRing openpgp.EntityList + md, err := openpgp.ReadMessage(unarmored.Body, emptyKeyRing, prompt, config) + if err != nil { + // Parsing errors when reading the message are most likely caused by incorrect password, but we cannot know for sure + return + } + + plaintext = io.NopCloser(md.UnverifiedBody) + + return +} + +type encryptWrapperReader struct { + plaintext io.Reader + encrypt io.WriteCloser + armored io.WriteCloser + buffer io.ReadWriter + plaintextReadZero bool +} + +func (e *encryptWrapperReader) Read(p []byte) (n int, err error) { + p2 := make([]byte, len(p)) + + n, _ = e.plaintext.Read(p2) + if n == 0 { + if !e.plaintextReadZero { + err = e.encrypt.Close() + if err != nil { + return + } + + err = e.armored.Close() + if err != nil { + return + } + + e.plaintextReadZero = true + } + + return e.buffer.Read(p) + } + + return e.buffer.Read(p) +} + +func (e *encryptWrapperReader) Close() error { + return nil +} + +func NewEncryptWrapperReader(plaintext io.Reader, armored, encrypt io.WriteCloser, buffer io.ReadWriter) io.ReadCloser { + return &encryptWrapperReader{ + plaintext: io.TeeReader(plaintext, encrypt), + encrypt: encrypt, + armored: armored, + buffer: buffer, + } +} + +func encrypt(plaintext io.ReadCloser, password []byte) (ciphertext io.ReadCloser, err error) { + bufferReadWriter := new(bytes.Buffer) + armored, err := armor.Encode(bufferReadWriter, constants.PGPMessageHeader, nil) + if err != nil { + return + } + config := &packet.Config{ + DefaultCipher: packet.CipherAES256, + Time: time.Now, + } + + hints := &openpgp.FileHints{ + IsBinary: true, + FileName: "", + ModTime: time.Unix(time.Now().Unix(), 0), + } + + encryptWriter, err := openpgp.SymmetricallyEncrypt(armored, password, hints, config) + if err != nil { + return + } + + ciphertext = NewEncryptWrapperReader(plaintext, armored, encryptWriter, bufferReadWriter) + + return +} + func healthHandler(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("Approaching Neutral Zone, all systems normal and functioning.")) } @@ -362,7 +488,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { } } - metadata := metadataForRequest(contentType, s.randomTokenLength, r) + metadata := metadataForRequest(contentType, contentLength, s.randomTokenLength, r) buffer := &bytes.Buffer{} if err := json.NewEncoder(buffer).Encode(metadata); err != nil { @@ -379,7 +505,13 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) { s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) - if err = s.storage.Put(r.Context(), token, filename, file, contentType, uint64(contentLength)); err != nil { + reader, err := attachEncryptionReader(file, r.Header.Get("X-Encrypt-Password")) + if err != nil { + http.Error(w, "Could not crypt file", http.StatusInternalServerError) + return + } + + if err = s.storage.Put(r.Context(), token, filename, reader, contentType, uint64(contentLength)); err != nil { s.logger.Printf("Backend storage error: %s", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -417,8 +549,8 @@ func (s *Server) cleanTmpFile(f *os.File) { type metadata struct { // ContentType is the original uploading content type ContentType string - // Secret as knowledge to delete file - // Secret string + // ContentLength is is the original uploading content length + ContentLength int64 // Downloads is the actual number of downloads Downloads int // MaxDownloads contains the maximum numbers of downloads @@ -427,11 +559,16 @@ type metadata struct { MaxDate time.Time // DeletionToken contains the token to match against for deletion DeletionToken string + // Encrypted contains if the file was encrypted + Encrypted bool + // DecryptedContentType is the original uploading content type + DecryptedContentType string } -func metadataForRequest(contentType string, randomTokenLength int, r *http.Request) metadata { +func metadataForRequest(contentType string, contentLength int64, randomTokenLength int, r *http.Request) metadata { metadata := metadata{ ContentType: strings.ToLower(contentType), + ContentLength: contentLength, MaxDate: time.Time{}, Downloads: 0, MaxDownloads: -1, @@ -450,6 +587,14 @@ func metadataForRequest(contentType string, randomTokenLength int, r *http.Reque metadata.MaxDate = time.Now().Add(time.Hour * 24 * time.Duration(v)) } + if password := r.Header.Get("X-Encrypt-Password"); password != "" { + metadata.Encrypted = true + metadata.ContentType = "text/plain; charset=utf-8" + metadata.DecryptedContentType = contentType + } else { + metadata.Encrypted = false + } + return metadata } @@ -527,7 +672,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { token := token(s.randomTokenLength) - metadata := metadataForRequest(contentType, s.randomTokenLength, r) + metadata := metadataForRequest(contentType, contentLength, s.randomTokenLength, r) buffer := &bytes.Buffer{} if err := json.NewEncoder(buffer).Encode(metadata); err != nil { @@ -546,7 +691,13 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) { s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType) - if err := s.storage.Put(r.Context(), token, filename, reader, contentType, uint64(contentLength)); err != nil { + reader, err := attachEncryptionReader(reader, r.Header.Get("X-Encrypt-Password")) + if err != nil { + http.Error(w, "Could not crypt file", http.StatusInternalServerError) + return + } + + if err = s.storage.Put(r.Context(), token, filename, reader, contentType, uint64(contentLength)); err != nil { s.logger.Printf("Error putting new file: %s", err.Error()) http.Error(w, "Could not save file", http.StatusInternalServerError) return @@ -1054,7 +1205,6 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { } var disposition string - if action == "inline" { disposition = "inline" /* @@ -1070,9 +1220,7 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { remainingDownloads, remainingDays := metadata.remainingLimitHeaderValues() - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) - w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, filename)) + w.Header().Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"`, disposition, filename)) w.Header().Set("Connection", "keep-alive") w.Header().Set("Cache-Control", "no-store") w.Header().Set("X-Remaining-Downloads", remainingDownloads) @@ -1086,7 +1234,22 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) { reader = io.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader)) } - if _, err = io.Copy(w, reader); err != nil { + password := r.Header.Get("X-Decrypt-Password") + decryptionReader, err := attachDecryptionReader(reader, password) + if err != nil { + http.Error(w, "Could not decrypt file", http.StatusInternalServerError) + return + } + + if metadata.Encrypted && len(password) > 0 { + contentType = metadata.DecryptedContentType + contentLength = uint64(metadata.ContentLength) + } + + w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10)) + + if _, err = io.Copy(w, decryptionReader); err != nil { s.logger.Printf("%s", err.Error()) http.Error(w, "Error occurred copying to output stream", http.StatusInternalServerError) return diff --git a/server/storage/common.go b/server/storage/common.go index f152791..795bac5 100644 --- a/server/storage/common.go +++ b/server/storage/common.go @@ -4,10 +4,9 @@ import ( "context" "fmt" "io" + "regexp" "strconv" "time" - - "regexp" ) type Range struct {