This post comes out of a recent discussion with @ryan where we both felt uncomfortable from a developer UX perspective about having to provide the Core API password when starting the Portal for Development.
The conversation led me to spend some time thinking about our Portal-Core threat model. What problems are we trying to solve with Core API password, and are we succeeding?
TL;DR: We're trying to keep malicious apps from gaining administrative access over Core and the credentials it controls, and, as you probably guessed by the length of this post, we're not succeeding!
Blockstack Core API is an Python application that runs with user-level permissions. It provides a HTTP REST API that accepts requests on localhost. Through this API, clients can access a user's storage and bitcoin wallet.
Only the Portal should be allowed to have "administrative access" to a user's Core API.
We want 3rd party apps besides the Portal to be required to request a permission-restricted session token, approved by the user via the Portal and issued by the Core API. 3rd party applications must use that token to make Core API requests that require authentication.
Core API holds the user's storage provider credentials and consequently has administrative read-write access the user's storage. This means that any API client with administrative to Core can arbitrarily share, modify or delete items in the user's personal storage.
In our current (v0.6) deployment method, Core API also has full access to the private keys of the user's Bitcoin wallet with the ability to generate and send Bitcoin transactions at well. API clients with administrative access to Core can spend the user's bitcoin without their knowledge or approval.
Portal provides the API password in the
Authorization header of requests to the Core API. Core API makes a decision whether or not to grant access to certain API calls based on this password.
Additionally, Core encrypts the user's bitcoin wallet with a password that must be provided at start up.
Problems with current approach
Any application running on the same machine can obtain the API password by connecting to the Portal web proxy port, extracting the API password from the source code and gain administrative access to the Core API.
Any application running as the same user can obtain the Core API password and Core wallet password by inspecting the environment variables of the Core API python process.
On macOS, one can list environment variables of a process by running:
ps eww <process-id>
On Linux, the same can be accomplished by reading:
Problem 1 - any app on localhost can obtain Core API password from Portal source code
Source code to the Portal web app is by necessity readable by any client that connects to the Portal web proxy port. This is how we're able to make it available to any web browser running on the user's computer. There's no mechanism by which we can authenticate the Portal code to the Core API node. Instead, we should be authenticating the user of the Portal code.
The way for us to authenticate the user to the Core API to do that is for the user to provide some information - a shared secret, a signed message, etc - in the context of a browser session.
I have a few ideas for how to solve this that I'll share once I finish organizing them. In the meantime, what are yours?
Problem 2 - any app running as same user can obtain Core API password
Our threat model includes other applications running as the user. Potential threats include both apps in the user's browser and native apps running with the same permissions as the user.
Preventing unauthorized applications from gaining administrative-level access to a user's Core Node means not only preventing circumvention of the API's access controls, but also preventing unauthorized applications from gaining access to the secrets that Core protects such as Bitcoin private keys and storage provider access tokens.
Core protects a user's wallet on disk by encrypting it with a password. This password is required by Core when starting an API endpoint. It can be provided to Core by one of 3 methods:
- Setting an environment variable
- Interactive input by the user
- As a command line parameter
No matter how the password is provided, Core sets environment variables with both the API password and the Wallet password. (see screenshot below)
All 3 password input methods fail to protect the user's private keys if the malicious app is running as the same user as Core. This is because the bad actor process is able to directly read the wallet and API passwords from the Core process's environment variables.
- Run Core as different user. (There are risk and complexity in this)
- Change Core to not store these passwords in environment variable. Not sure if this is possible.