Compare commits

...

Author SHA1 Message Date
  Andrea Spacca fbf9a4facc gpg encryption support 5 years ago
  Andrea Spacca 0c844c1f11 gpg encryption support 5 years ago
1 changed files with 192 additions and 32 deletions
Unified View
  1. +192
    -32
      server/handlers.go

+ 192
- 32
server/handlers.go 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
// Secret string
// ContentLength is is the original uploading content length
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 contains 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,
MaxDate: time.Now().Add(time.Hour * 24 * 365 * 10),
Downloads: 0,
MaxDownloads: 99999999,
DeletionToken: Encode(10000000+int64(rand.Intn(1000000000))) + Encode(10000000+int64(rand.Intn(1000000000))),
ContentType: contentType,
ContentLength: contentLength,
MaxDate: time.Now().Add(time.Hour * 24 * 365 * 10),
Downloads: 0,
MaxDownloads: 99999999,
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 {
return err
if err := json.NewDecoder(file).Decode(&metadata); err != nil {
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 = "inline"
} else {
disposition = action
if action != "inline" {
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…
Cancel
Save