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