diff --git a/Exit.go b/Exit.go index c3486be..727091e 100644 --- a/Exit.go +++ b/Exit.go @@ -19,5 +19,6 @@ const ( ExitPrivateKeyCreate = 7 // Cannot create a new private key. ExitBlockchainCorrupt = 8 // Blockchain is corrupt. ExitGraceful = 9 // Graceful shutdown. + ExitParamApiKeyInvalid = 10 // API key parameter is invalid. STATUS_CONTROL_C_EXIT = 0xC000013A // The application terminated as a result of a CTRL+C. This is a Windows NTSTATUS value. ) diff --git a/webapi/API.go b/webapi/API.go index 549ffc9..7b92b6c 100644 --- a/webapi/API.go +++ b/webapi/API.go @@ -14,6 +14,7 @@ import ( "time" "github.com/PeernetOfficial/core" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/gorilla/websocket" ) @@ -33,13 +34,18 @@ var WSUpgrader = websocket.Upgrader{ // Start starts the API. ListenAddresses is a list of IP:Ports. // The certificate file and key are only used if SSL is enabled. The read and write timeout may be 0 for no timeout. -func Start(ListenAddresses []string, UseSSL bool, CertificateFile, CertificateKey string, TimeoutRead, TimeoutWrite time.Duration) { +// The API key may be uuid.Nil to disable it although this is not recommended for security reasons. +func Start(ListenAddresses []string, UseSSL bool, CertificateFile, CertificateKey string, TimeoutRead, TimeoutWrite time.Duration, APIKey uuid.UUID) { if len(ListenAddresses) == 0 { return } Router = mux.NewRouter() + if APIKey != uuid.Nil { + Router.Use(authenticateMiddleware(APIKey)) + } + Router.HandleFunc("/test", apiTest).Methods("GET") Router.HandleFunc("/status", apiStatus).Methods("GET") Router.HandleFunc("/account/info", apiAccountInfo).Methods("GET") @@ -134,3 +140,23 @@ func DecodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err e return nil } + +// authenticateMiddleware returns a middleware function to be used with mux.Router.Use(). It handles all authentication functionality. +func authenticateMiddleware(APIKey uuid.UUID) func(http.Handler) http.Handler { + return (func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + keyID, err := uuid.Parse(r.Header.Get("x-api-key")) + if err != nil { // Invalid key format + w.WriteHeader(http.StatusUnauthorized) + return + } + + if keyID != APIKey { + w.WriteHeader(http.StatusUnauthorized) + return + } + + next.ServeHTTP(w, r) + }) + }) +} diff --git a/webapi/readme.md b/webapi/readme.md index 7f70aaf..6e53c73 100644 --- a/webapi/readme.md +++ b/webapi/readme.md @@ -6,21 +6,30 @@ It can be used by local client software to connect to the Peernet network and us ## Use Considerations (when not to use it) -Do not expose this API to the internet or local network. The API is unauthenticated and provides direct access to the users blockchain. It also provide sensitive actions such as deleting the account (including the private key). An attacker could abuse the API to add any local file to the user's blockchain and then read it. - -The API shall only run on a loopback IP such as `127.0.0.1` or `::1`. Special HTTP headers (including the Access-Control headers) are intentionally not set. +* Do not expose this API to the internet or local network. The API provides direct access to the users blockchain. It provides sensitive actions such as deleting the account (including the private key). If the use of an API key is disabled, an unauthenticated attacker could abuse the API to add any local file to the user's blockchain and then read it. +* The API shall only run on a loopback IP such as `127.0.0.1` or `::1`. +* The API is not supposed to be used by regular web browsers. CORS HTTP headers are intentionally not set. +* You should use the API key functionality, which enforces the API key in every call using the HTTP header `x-api-key`. ## Deployment -The API must be initialized and started before use. +The API must be initialized and started before use. The last parameter is the API key (in this example no API key is used). For security reasons it is recommended to use a random local port and provide a randomly generated API key. ```go -webapi.Start([]string{"127.0.0.1:112"}, false, "", "", 10*time.Second, 10*time.Second) +webapi.Start([]string{"127.0.0.1:112"}, false, "", "", 10*time.Second, 10*time.Second, uuid.Nil) // To register an additional API endpoint: webapi.Router.HandleFunc("/newfunction", newFunction).Methods("GET") ``` +## API Key + +Each API instance should use a random UUID as API key. Subsequently, that UUID must be provided by the client in every API call in the `x-api-key` HTTP header. Failure to provide the API key in calls results in HTTP status 401 Unauthorized. + +This effectively secures the API against unauthenticated attackers, including other software running on the same machine, malicious websites using a DNS rebinding attack, and accidental link opening by the user. + +To disable the use of API keys a null UUID (= `00000000-0000-0000-0000-000000000000`) can be provided when starting the API. This may be useful for development purposes, but should never be used in production. + # Available Functions These are the functions provided by the API: @@ -880,7 +889,7 @@ These helper functions are usually not needed, but can be useful in special case ### Detect file type and file format -This function detects the file type and file format of the specified file. It will primarily use the file extension for detection. If unavailable, it uses the first 512 bytes of the file data to detect the type. +This function detects the file type and file format of the specified file. It will primarily use the file extension for detection. If unavailable, it uses the first 512 bytes of the file data to detect the type. The path is the full file path (including directory) on disk. ``` Request: GET /file/format?path=[file path on disk]