mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2020-11-18 19:53:40 -08:00
gpg encryption support
This commit is contained in:
parent
3ca36f8c9c
commit
0c844c1f11
@ -61,11 +61,18 @@ import (
|
|||||||
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
qrcode "github.com/skip2/go-qrcode"
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
htmlTemplates = initHTMLTemplates()
|
htmlTemplates = initHTMLTemplates()
|
||||||
textTemplates = initTextTemplates()
|
textTemplates = initTextTemplates()
|
||||||
|
|
||||||
|
packetConfig = &packet.Config{
|
||||||
|
DefaultCipher: packet.CipherAES256,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func stripPrefix(path string) string {
|
func stripPrefix(path string) string {
|
||||||
@ -89,6 +96,112 @@ func initHTMLTemplates() *html_template.Template {
|
|||||||
return templates
|
return templates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyReadCloser struct {
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *dummyReadCloser) Read(p []byte) (n int, err error) {
|
||||||
|
return rc.reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *dummyReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toDummyReadCloser(reader io.Reader) io.ReadCloser {
|
||||||
|
return &dummyReadCloser{reader: reader}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformEncryptionReader(reader io.Reader, r *http.Request) (io.Reader, error) {
|
||||||
|
password := r.Header.Get("X-Encrypt-Password")
|
||||||
|
if len(password) == 0 {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return encrypt(reader, password, packetConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformDecryptionReader(reader io.ReadCloser, r *http.Request) (io.ReadCloser, error) {
|
||||||
|
password := r.Header.Get("X-Decrypt-Password")
|
||||||
|
if len(password) == 0 {
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypt(reader, password, packetConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(ciphertext io.Reader, password string, packetConfig *packet.Config) (plaintext io.ReadCloser, err error) {
|
||||||
|
content, err := ioutil.ReadAll(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decbuf := bytes.NewBuffer(content)
|
||||||
|
|
||||||
|
armorBlock, err := armor.Decode(decbuf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
failed := false
|
||||||
|
prompt := func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||||
|
// If the given passphrase isn't correct, the function will be called again, forever.
|
||||||
|
// This method will fail fast.
|
||||||
|
// Ref: https://godoc.org/golang.org/x/crypto/openpgp#PromptFunction
|
||||||
|
if failed {
|
||||||
|
return nil, errors.New("decryption failed")
|
||||||
|
}
|
||||||
|
failed = true
|
||||||
|
return []byte(password), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := openpgp.ReadMessage(armorBlock.Body, nil, prompt, packetConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext = toDummyReadCloser(md.UnverifiedBody)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(plaintext io.Reader, password string, packetConfig *packet.Config) (ciphertext io.Reader, err error) {
|
||||||
|
encbuf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
w, err := armor.Encode(encbuf, "PGP MESSAGE", nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
pt, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, packetConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer pt.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(plaintext)
|
||||||
|
if err != nil {
|
||||||
|
pt.Close()
|
||||||
|
w.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pt.Write(content)
|
||||||
|
if err != nil {
|
||||||
|
pt.Close()
|
||||||
|
w.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close writers to force-flush their buffer
|
||||||
|
pt.Close()
|
||||||
|
w.Close()
|
||||||
|
ciphertext = bytes.NewReader(encbuf.Bytes())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.")
|
fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.")
|
||||||
}
|
}
|
||||||
@ -277,7 +390,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
contentLength := n
|
contentLength := n
|
||||||
|
|
||||||
metadata := MetadataForRequest(contentType, r)
|
metadata := MetadataForRequest(contentType, contentLength, r)
|
||||||
|
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||||
@ -292,6 +405,12 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
|
log.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)
|
||||||
|
|
||||||
|
reader, err = transformEncryptionReader(reader, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, errors.New("Could not crypt file").Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
||||||
log.Printf("Backend storage error: %s", err.Error())
|
log.Printf("Backend storage error: %s", err.Error())
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
@ -306,10 +425,10 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
// ContentType is the original uploading content type
|
// ContentType is the original uploading content type or text/plain if encrypted
|
||||||
ContentType string
|
ContentType string
|
||||||
// Secret as knowledge to delete file
|
// ContentLength is is the original uploading content length
|
||||||
// Secret string
|
ContentLength int64
|
||||||
// Downloads is the actual number of downloads
|
// Downloads is the actual number of downloads
|
||||||
Downloads int
|
Downloads int
|
||||||
// MaxDownloads contains the maximum numbers of downloads
|
// MaxDownloads contains the maximum numbers of downloads
|
||||||
@ -318,15 +437,23 @@ type Metadata struct {
|
|||||||
MaxDate time.Time
|
MaxDate time.Time
|
||||||
// DeletionToken contains the token to match against for deletion
|
// DeletionToken contains the token to match against for deletion
|
||||||
DeletionToken string
|
DeletionToken string
|
||||||
|
// Encrypted contaings if the file was encrypted
|
||||||
|
Encrypted bool
|
||||||
|
// DecryptedContentType is the original uploading content type
|
||||||
|
DecryptedContentType string
|
||||||
|
// WillBeDecrypted is a flag to know if content will be decrypted (password provided)
|
||||||
|
WillBeDecrypted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func MetadataForRequest(contentType string, r *http.Request) Metadata {
|
func MetadataForRequest(contentType string, contentLength int64, r *http.Request) Metadata {
|
||||||
metadata := Metadata{
|
metadata := Metadata{
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
|
ContentLength: contentLength,
|
||||||
MaxDate: time.Now().Add(time.Hour * 24 * 365 * 10),
|
MaxDate: time.Now().Add(time.Hour * 24 * 365 * 10),
|
||||||
Downloads: 0,
|
Downloads: 0,
|
||||||
MaxDownloads: 99999999,
|
MaxDownloads: 99999999,
|
||||||
DeletionToken: Encode(10000000+int64(rand.Intn(1000000000))) + Encode(10000000+int64(rand.Intn(1000000000))),
|
DeletionToken: Encode(10000000+int64(rand.Intn(1000000000))) + Encode(10000000+int64(rand.Intn(1000000000))),
|
||||||
|
WillBeDecrypted: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := r.Header.Get("Max-Downloads"); v == "" {
|
if v := r.Header.Get("Max-Downloads"); v == "" {
|
||||||
@ -341,6 +468,14 @@ func MetadataForRequest(contentType string, r *http.Request) Metadata {
|
|||||||
metadata.MaxDate = time.Now().Add(time.Hour * 24 * time.Duration(v))
|
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
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +546,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
token := Encode(10000000 + int64(rand.Intn(1000000000)))
|
token := Encode(10000000 + int64(rand.Intn(1000000000)))
|
||||||
|
|
||||||
metadata := MetadataForRequest(contentType, r)
|
metadata := MetadataForRequest(contentType, contentLength, r)
|
||||||
|
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||||
@ -428,6 +563,12 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
reader, err = transformEncryptionReader(reader, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, errors.New("Could not crypt file").Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
if err = s.storage.Put(token, filename, reader, contentType, uint64(contentLength)); err != nil {
|
||||||
log.Printf("Error putting new file: %s", err.Error())
|
log.Printf("Error putting new file: %s", err.Error())
|
||||||
http.Error(w, errors.New("Could not save file").Error(), 500)
|
http.Error(w, errors.New("Could not save file").Error(), 500)
|
||||||
@ -510,27 +651,27 @@ func (s *Server) Unlock(token, filename string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CheckMetadata(token, filename string) error {
|
func (s *Server) CheckMetadata(token, filename string, r *http.Request, isSingleRequest bool) (Metadata, error) {
|
||||||
s.Lock(token, filename)
|
s.Lock(token, filename)
|
||||||
defer s.Unlock(token, filename)
|
defer s.Unlock(token, filename)
|
||||||
|
|
||||||
var metadata Metadata
|
var metadata Metadata
|
||||||
|
|
||||||
r, _, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
file, _, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
||||||
if s.storage.IsNotExist(err) {
|
if s.storage.IsNotExist(err) {
|
||||||
return nil
|
return metadata, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return metadata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer r.Close()
|
defer file.Close()
|
||||||
|
|
||||||
if err := json.NewDecoder(r).Decode(&metadata); err != nil {
|
if err := json.NewDecoder(file).Decode(&metadata); err != nil {
|
||||||
return err
|
return metadata, err
|
||||||
} else if metadata.Downloads >= metadata.MaxDownloads {
|
} else if metadata.Downloads >= metadata.MaxDownloads {
|
||||||
return errors.New("MaxDownloads expired.")
|
return metadata, errors.New("MaxDownloads expired.")
|
||||||
} else if time.Now().After(metadata.MaxDate) {
|
} else if time.Now().After(metadata.MaxDate) {
|
||||||
return errors.New("MaxDate expired.")
|
return metadata, errors.New("MaxDate expired.")
|
||||||
} else {
|
} else {
|
||||||
// todo(nl5887): mutex?
|
// todo(nl5887): mutex?
|
||||||
|
|
||||||
@ -539,13 +680,21 @@ func (s *Server) CheckMetadata(token, filename string) error {
|
|||||||
|
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
|
||||||
return errors.New("Could not encode metadata")
|
return metadata, errors.New("Could not encode metadata")
|
||||||
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
|
} else if err := s.storage.Put(token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
|
||||||
return errors.New("Could not save metadata")
|
return metadata, errors.New("Could not save metadata")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if !isSingleRequest {
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if password := r.Header.Get("X-Decrypt-Password"); metadata.Encrypted && len(password) > 0 {
|
||||||
|
metadata.WillBeDecrypted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error {
|
func (s *Server) CheckDeletionToken(deletionToken, token, filename string) error {
|
||||||
@ -619,7 +768,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := strings.Split(key, "/")[0]
|
token := strings.Split(key, "/")[0]
|
||||||
filename := sanitize(strings.Split(key, "/")[1])
|
filename := sanitize(strings.Split(key, "/")[1])
|
||||||
|
|
||||||
if err := s.CheckMetadata(token, filename); err != nil {
|
if _, err := s.CheckMetadata(token, filename, r, false); err != nil {
|
||||||
log.Printf("Error metadata: %s", err.Error())
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -695,7 +844,7 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := strings.Split(key, "/")[0]
|
token := strings.Split(key, "/")[0]
|
||||||
filename := sanitize(strings.Split(key, "/")[1])
|
filename := sanitize(strings.Split(key, "/")[1])
|
||||||
|
|
||||||
if err := s.CheckMetadata(token, filename); err != nil {
|
if _, err := s.CheckMetadata(token, filename, r, false); err != nil {
|
||||||
log.Printf("Error metadata: %s", err.Error())
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -752,7 +901,7 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := strings.Split(key, "/")[0]
|
token := strings.Split(key, "/")[0]
|
||||||
filename := strings.Split(key, "/")[1]
|
filename := strings.Split(key, "/")[1]
|
||||||
|
|
||||||
if err := s.CheckMetadata(token, filename); err != nil {
|
if _, err := s.CheckMetadata(token, filename, r, false); err != nil {
|
||||||
log.Printf("Error metadata: %s", err.Error())
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -797,7 +946,7 @@ func (s *Server) headHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := vars["token"]
|
token := vars["token"]
|
||||||
filename := vars["filename"]
|
filename := vars["filename"]
|
||||||
|
|
||||||
if err := s.CheckMetadata(token, filename); err != nil {
|
if _, err := s.CheckMetadata(token, filename, r, false); err != nil {
|
||||||
log.Printf("Error metadata: %s", err.Error())
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@ -825,7 +974,8 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := vars["token"]
|
token := vars["token"]
|
||||||
filename := vars["filename"]
|
filename := vars["filename"]
|
||||||
|
|
||||||
if err := s.CheckMetadata(token, filename); err != nil {
|
metadata, err := s.CheckMetadata(token, filename, r, true)
|
||||||
|
if err != nil {
|
||||||
log.Printf("Error metadata: %s", err.Error())
|
log.Printf("Error metadata: %s", err.Error())
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@ -845,17 +995,27 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var disposition string
|
var disposition string
|
||||||
|
|
||||||
if action == "inline" {
|
disposition = action
|
||||||
disposition = "inline"
|
if action != "inline" {
|
||||||
} else {
|
|
||||||
disposition = "attachment"
|
disposition = "attachment"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if metadata.WillBeDecrypted {
|
||||||
|
contentType = metadata.DecryptedContentType
|
||||||
|
contentLength = uint64(metadata.ContentLength)
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10))
|
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("Connection", "keep-alive")
|
||||||
|
|
||||||
|
reader, err = transformDecryptionReader(reader, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, errors.New("Could not decrypt file").Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = io.Copy(w, reader); err != nil {
|
if _, err = io.Copy(w, reader); err != nil {
|
||||||
log.Printf("%s", err.Error())
|
log.Printf("%s", err.Error())
|
||||||
http.Error(w, "Error occurred copying to output stream", 500)
|
http.Error(w, "Error occurred copying to output stream", 500)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user