1
0
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:
Andrea Spacca 2018-10-06 19:04:46 +02:00
parent 3ca36f8c9c
commit 0c844c1f11

View File

@ -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)