Browse Source

Added server-side code

Keith Morrow 9 years ago
parent
commit
72cc4507d2
6 changed files with 244 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 20 0
      LICENSE
  3. 54 0
      README.md
  4. 0 0
      i/.placeholder
  5. 6 0
      server.conf
  6. 161 0
      server.go

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+i/
+
+server

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+Copyright (c) 2015 Andre Drapeau
+Copyright (c) 2015 Keith Morrow
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 54 - 0
README.md

@@ -0,0 +1,54 @@
+Upload - Client-side Encrypted Image Host
+===
+
+Upload is a simple host that client-side encrypts images, text, and other data, and stores them, with the server knowing nothing about the contents. It uses AES-CCM for the encryption.
+
+
+How it works
+---
+
+When an image is uploaded, a "seed" is generated. This seed can be of any length (because really, the server will never be able to tell). When an image is then generated or accessed, the seed is run through SHA512. The first 256 bits are used for the AES key, the next 128 bits are used for the CCM IV, and the last 128 bits are used as an identifier on the server for the encrypted image data.
+
+Image deletion functionality is also available. When an image is uploaded, a delete token is returned. Sending this delete token back to the server will delete the image. On the server side, HMAC-SHA256(key, identifier) is used, where the key is a secret on the server.
+
+There is also a static key that is used between the client and server for uploading - that is, you can't upload if you don't know the static key. The included server allows the option of setting HTTP auth on the upload page, which will protect the static key from external users. This allows outsiders to view images but not upload them.
+
+
+Technologies
+---
+
+The browser-side is written in plain Javascript using SJCL for the AES-CCM encryption. Everything is HTML5 and its associated Javascript APIs, but should work in IE10 and later, plus any decently modern copy of Firefox, Chrome or Safari.
+
+The server-side is written in Go and uses no dependencies outside of the standard library. In order to build it, install the go compiler and run `go build server.go`, then run the `./server` executable.
+
+
+External tools
+---
+
+Currently, there are two external programs adapted to work with EncImg: [ShareX](todo:link-to-sharex) [(relevant code changes)](todo:link-to-commit), and [Scup](todo:link-to-scup) [(relevant code changes)](todo:link-to-commit). At this point in time, these both use hard-coded static keys, and so the source would need to be modified to change this. We hope to fix this in the near future.
+
+
+Configuration
+---
+
+The main place you will find configuration settings are in `server.conf`. It's here that you can set things like listening port, hostname, static key, etc.
+
+If you choose to change the default static key (recommended if you run a private instance), you will also need to change it on the upload page. The configuration settings can be found at the top of `web/upload.html`. Here, you can also change the length of the seed (by default it's 20 characters).
+
+**It is important to make sure the `i/` directory is writable by the server.** This is where image data is stored.
+
+Building
+---
+To build the server, download the Go language compiler, and run `go build server.go` in the root of this repository. You can then run the server via `./server`. There is no separate compilation step needed for the client side Javascript.
+
+
+Caveats
+---
+
+This application uses Javascript crypto in the browser. There are numerous articles on why this is a bad idea, and numerous reasons why it should not be done. Be aware that while this software has a high probability of being safe, it does not guarantee in any way that your images won't be seen by unintended users, and beyond that is experimental and unaudited software (despite being very little code to audit). Use at your own risk.
+
+
+License
+---
+
+This software is licensed under the MIT license. Do whatever you want with it!

+ 0 - 0
i/.placeholder


+ 6 - 0
server.conf

@@ -0,0 +1,6 @@
+{
+  "listen": ":9000",
+  "static_key": "c61540b5ceecd05092799f936e27755f",
+  "static_delete_key": "bb77e727aefe632fb21ab857b98577c6",
+  "maximum_file_size": 50000000
+}

+ 161 - 0
server.go

@@ -0,0 +1,161 @@
+package main
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"path"
+)
+
+type Config struct {
+	Listen      string `json:"listen"`
+	StaticKey   string `json:"static_key"`
+	DeleteKey   string `json:"static_delete_key"`
+	MaxFileSize int64  `json:"maximum_file_size"`
+}
+
+var config Config
+
+type ErrorMessage struct {
+	Error string `json:"error"`
+	Code  int    `json:"code"`
+}
+
+type SuccessMessage struct {
+	Delkey string `json:"delkey"`
+}
+
+func readConfig() Config {
+	file, _ := os.Open("server.conf")
+	decoder := json.NewDecoder(file)
+	config := Config{}
+	err := decoder.Decode(&config)
+	if err != nil {
+		fmt.Println("Error reading config: ", err)
+	}
+	return config
+}
+
+func makeDelkey(ident string) string {
+	key := []byte(config.DeleteKey)
+	h := hmac.New(sha256.New, key)
+	h.Write([]byte(ident))
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+func index(w http.ResponseWriter, r *http.Request) {
+	if r.URL.Path == "/" {
+		http.ServeFile(w, r, "index.html")
+	} else {
+		http.NotFound(w, r)
+	}
+}
+
+func upload(w http.ResponseWriter, r *http.Request) {
+	if r.ContentLength > config.MaxFileSize {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "File size too large", Code: 1})
+		w.Write(msg)
+		return
+	}
+
+	r.ParseMultipartForm(50000000)
+	file, _, err := r.FormFile("file")
+
+	if err != nil {
+		msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 5})
+		w.Write(msg)
+		return
+	}
+
+	defer file.Close()
+
+	privkey := r.FormValue("privkey")
+	if privkey != config.StaticKey {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "Static key doesn't match", Code: 2})
+		w.Write(msg)
+		return
+	}
+
+	ident := r.FormValue("ident")
+	if len(ident) != 22 {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "Ident filename length is incorrect", Code: 3})
+		w.Write(msg)
+		return
+	}
+
+	identPath := path.Join("i", ident)
+	if _, err := os.Stat(identPath); err == nil {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "Ident is already taken.", Code: 4})
+		w.Write(msg)
+		return
+	}
+
+	out, err := os.Create(identPath)
+	if err != nil {
+		msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 6})
+		w.Write(msg)
+		return
+	}
+
+	defer out.Close()
+
+	_, err = io.Copy(out, file)
+	if err != nil {
+		msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 7})
+		w.Write(msg)
+		return
+	}
+
+	delkey := makeDelkey(ident)
+
+	result, err := json.Marshal(&SuccessMessage{Delkey: delkey})
+	if err != nil {
+		msg, _ := json.Marshal(&ErrorMessage{Error: err.Error(), Code: 8})
+		w.Write(msg)
+	}
+	w.Write(result)
+}
+
+func delfile(w http.ResponseWriter, r *http.Request) {
+	ident := r.FormValue("ident")
+	delkey := r.FormValue("delkey")
+
+	if len(ident) != 22 {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "Ident filename length is incorrect", Code: 3})
+		w.Write(msg)
+		return
+	}
+
+	identPath := path.Join("i", ident)
+	if _, err := os.Stat(identPath); os.IsNotExist(err) {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "Ident does not exist.", Code: 9})
+		w.Write(msg)
+		return
+	}
+
+	if delkey != makeDelkey(ident) {
+		msg, _ := json.Marshal(&ErrorMessage{Error: "Incorrect delete key", Code: 10})
+		w.Write(msg)
+		return
+	}
+
+	os.Remove(identPath)
+	http.Redirect(w, r, "/", 301)
+}
+
+func main() {
+	http.HandleFunc("/", index)
+	http.HandleFunc("/up", upload)
+	http.HandleFunc("/del", delfile)
+	http.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("static"))))
+	http.Handle("/i/", http.StripPrefix("/i", http.FileServer(http.Dir("i"))))
+	config = readConfig()
+	fmt.Printf("Listening on %s\n", config.Listen)
+	log.Fatal(http.ListenAndServe(config.Listen, nil))
+}