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.
200 lines
5.9 KiB
200 lines
5.9 KiB
2 years ago
|
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"))
|
||
|
}
|
||
|
}
|