Skip to content
Permalink
Browse files

Added server-side code

  • Loading branch information
k3d3 committed Jun 6, 2015
1 parent b36c6d0 commit 72cc4507d2f0d33dd38d80f963cd4cf951b421b8
Showing with 244 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +20 −0 LICENSE
  3. +54 −0 README.md
  4. 0 i/.placeholder
  5. +6 −0 server.conf
  6. +161 −0 server.go
@@ -0,0 +1,3 @@
i/

server
20 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.
@@ -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!
No changes.
@@ -0,0 +1,6 @@
{
"listen": ":9000",
"static_key": "c61540b5ceecd05092799f936e27755f",
"static_delete_key": "bb77e727aefe632fb21ab857b98577c6",
"maximum_file_size": 50000000
}
161 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))
}

0 comments on commit 72cc450

Please sign in to comment.
You can’t perform that action at this time.