Secret REST service using OpenPGP.js
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
5.9 KiB

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"))
}
}