package SecREST import ( "bytes" "encoding/json" "log" "net/http" "time" "github.com/ProtonMail/gopenpgp/v2/helper" "github.com/google/uuid" "github.com/gorilla/mux" ) var ( // Handlers that will be called for internall routing after de/encryption Handlers []SecRESTHandler // Auth handler Auth SecRESTAuth // SERVER public key, armored PubKey string // SERVER private key, armored PrivKey string // Password for SERVER private key KeyPass string // AuthClients is intentionally made non-persistent, expires all clients on reboot. // This however is open for debate. AuthClients = make(map[string]string) ) // StartRouter accepts a Port string, and a slice of SecRESTHandler // then starts the router. func StartRouter(port string, pubKey string, privKey string, keyPass string, handlers []SecRESTHandler, auth SecRESTAuth) { log.Printf("Initializing SecREST Server") // Get Config Handlers = handlers Auth = auth PrivKey = privKey KeyPass = keyPass PubKey = pubKey log.Printf("Preparing SecREST Server on port %+v", port) r := mux.NewRouter() r.HandleFunc("/", handleRoot) r.HandleFunc("/secure", handleSecure) r.HandleFunc("/insecure", handleInsecure) r.HandleFunc("/handshake", handleHandshake) log.Printf("Starting SecREST Server") log.Printf("Fatal error serving SecREST: %+v", http.ListenAndServe(":"+port, r)) } // Serve the base site's basic HTML and JavaScript, maybe via static file? func handleRoot(w http.ResponseWriter, r *http.Request) { } // Accept RAW POST of PGP armored text, and decrypt with server key // should be in SecRESTRequest format // will respond with SecRESTResponse, encrypted to client // ClientIdentifier header needs to match UUID in storage func handleSecure(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/crypt64") clientIdentifier := w.Header().Get("ClientIdentifier") if val, ok := AuthClients[clientIdentifier]; !ok { log.Printf("%+v not found in authorized clients. Returning 401 with empty body.", val) w.WriteHeader(401) w.Write(nil) return } buf := new(bytes.Buffer) buf.ReadFrom(r.Body) decrypted, err := helper.DecryptVerifyMessageArmored(AuthClients[clientIdentifier], PrivKey, []byte(KeyPass), buf.String()) if err != nil { log.Printf("Unable to decrypt request from %+v, returning 500 with empty body.\n%+v", clientIdentifier, err) w.WriteHeader(500) w.Write(nil) return } var request SecRESTRequest err = json.Unmarshal([]byte(decrypted), &request) if err != nil { log.Printf("Unable to unmarshal decrypted request from %+v\n%+v", clientIdentifier, err) w.WriteHeader(500) w.Write(nil) return } request.Insecure = false request.TimeStamp = time.Now() resp := routeRequest(request) payload, err := json.Marshal(resp) if err != nil { log.Printf("Unable to marshal internal response for %+v\n%+v", clientIdentifier, err) w.WriteHeader(500) w.Write(nil) return } armor, err := helper.EncryptSignMessageArmored(AuthClients[clientIdentifier], PrivKey, []byte(KeyPass), string(payload)) if err != nil { log.Printf("Unable to encrypt and sign message for %+v\n%+v", clientIdentifier, err) w.WriteHeader(500) w.Write(nil) return } w.Write([]byte(armor)) } // Accept JSON Request, will route if destination allows insecure, // otherwise will respond with HTTP 401 func handleInsecure(w http.ResponseWriter, r *http.Request) { ipaddr := r.Header.Get("X-Real-IP") request := SecRESTRequest{ TimeStamp: time.Now(), Insecure: true, } w.Header().Set("Content-Type", "application/json") decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&request); err != nil { log.Printf("Unable to decode request from %+v\n%+v", ipaddr, err) w.WriteHeader(400) w.Write(nil) return } defer r.Body.Close() resp := routeRequest(request) payload, err := json.Marshal(resp) if err != nil { log.Printf("Unable to marshal internal response for %+v\n%+v", ipaddr, err) w.WriteHeader(500) w.Write(nil) return } w.Write(payload) } func routeRequest(r SecRESTRequest) SecRESTResponse { var resp SecRESTResponse for _, h := range Handlers { if h.Path == r.Path { if h.Insecure == r.Insecure { resp = h.Run(r) } else { log.Printf("Client attempted to access secure path \"%+v\" via /insecure but is denied.", r.Path) resp.Status = 401 } } } // If resp.Status isn't set above, no matching path found. if resp.Status == 0 { log.Printf("Client attempted to access \"%+v\" but it was not found.", r.Path) resp.Status = 404 } resp.Ellapsed = time.Since(r.TimeStamp).String() return resp } // Request body should contain PGP Public Key for new client, // and respond with PGP Public Key for Server if authorized. func handleHandshake(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") ipaddr := r.Header.Get("X-Real-IP") start := time.Now() success, req := Auth.Run(w, r) if success { w.WriteHeader(200) clientIdentifier := uuid.New().String() AuthClients[clientIdentifier] = req.ClientKey authResp := SecRESTAuthResponse{ ServerKey: PubKey, ClientIdentifier: clientIdentifier, } resp := SecRESTResponse{ AuthResponse: authResp, Ellapsed: time.Since(start).String(), Status: 200, Body: "Success", } payload, err := json.Marshal(resp) if err != nil { log.Printf("Unable to marshal response payload for %+v's successful handshake:\n%+v", clientIdentifier, err) w.WriteHeader(500) w.Write(nil) } armor, err := helper.EncryptSignMessageArmored(AuthClients[clientIdentifier], PrivKey, []byte(KeyPass), string(payload)) if err != nil { log.Printf("Unable to encrypt response payload for %+v's successful handshake:\n%+v", clientIdentifier, err) w.WriteHeader(500) w.Write(nil) } w.Write([]byte(armor)) } else { log.Printf("%+v tried to authenticate, unsuccessfully.", ipaddr) w.WriteHeader(418) w.Write([]byte("no")) } }