diff --git a/cmd/signer/README.md b/cmd/signer/README.md index 9a09870dba..dad2f2bc04 100644 --- a/cmd/signer/README.md +++ b/cmd/signer/README.md @@ -35,6 +35,21 @@ Example: signer -keystore /my/keystore -chainid 4 ``` +## Security model + +The security model of the signer is as follows: + +* One critical component (the signer binary / daemon) is responsible for handling cryptographic operations; signing, private keys, encryption/decryption of keystore files. +* The signer binary has a well-defined 'external' API. +* The 'external' API is considered UNTRUSTED. +* The signer binary also communicates with whatever process that invoked the binary, via stdin/stdout. + * This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated. + +The general flow for signing a transaction using e.g. geth is as follows: +![image](sign_flow.png) + +In this case, `geth` would be started with `--externalsigner=http://localhost:8550` and would relay requests to `eth.sendTransaction`. + ## Communication ### External API @@ -81,9 +96,9 @@ All hex encoded values must be prefixed with `0x`. ### account_new #### Create new password protected account + The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory. -The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving -lost accounts. +The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. #### Arguments @@ -99,9 +114,7 @@ None "id": 0, "jsonrpc": "2.0", "method": "account_new", - "params": [ - "my password" - ] + "params": [] } { @@ -612,4 +625,9 @@ put together is a bit of a hack into the http server. This could probably be gre * There needs to be a very good structure around rules. Either a full language (lua/js) or a limited but flexible syntax based on e.g. json/yaml. Kind of like a firewall ruleset. * This implies that the signer would remember passwords, which is very problematic. However, a good UI implementation will want these things, and it would be better to implement it once in the signer, than having UI:s develop their own remember-password logic. + * See [rules.md](rules.md) for more info about this. +* Another potential thing to introduce is pairing. + * To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API). + * Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests. + * This feature would make the addition of rules less dangerous. \ No newline at end of file diff --git a/cmd/signer/main.go b/cmd/signer/main.go index d0b5c955b0..3953a5584d 100644 --- a/cmd/signer/main.go +++ b/cmd/signer/main.go @@ -217,20 +217,23 @@ func testExternalUI(api *SignerAPI) { } -// Create Account -// curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 +/** +//Create Account + +curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 // List accounts -// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/ + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/ // Make Transaction // safeSend(0x12) // 4401a6e40000000000000000000000000000000000000000000000000000000000000012 -/* +// supplied abi +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/ -curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/ +// Not supplied +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/ - -curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/ -*/ +**/ diff --git a/cmd/signer/pythonsigner.py b/cmd/signer/pythonsigner.py index 0a216310e9..a449e84c66 100644 --- a/cmd/signer/pythonsigner.py +++ b/cmd/signer/pythonsigner.py @@ -86,8 +86,8 @@ class StdIOHandler(): return { "approved" : False, - "transaction" : transaction, - "from" : _from, + #"transaction" : transaction, + # "from" : _from, # "password" : None, } diff --git a/cmd/signer/rules.md b/cmd/signer/rules.md new file mode 100644 index 0000000000..1b761f1120 --- /dev/null +++ b/cmd/signer/rules.md @@ -0,0 +1,107 @@ +# Rules + +The `signer` binary needs to have customizeable rules. Typical scenarios: + +* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period +* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei` + + +In order to reach this, there are two problems to solve + +1. Rule Implementation: how to create, manage and interpret rules in a flexible but secure manner +2. Credential managements and credentials; how to provide auto-unlock without exposing keys unnecessarily. + +The section below deals with both of them + +## Rule Implementation + +### Alt 1: javascript + +In this implementation, the rules would be implemented as a `js` file; which implements the same methods as the `json-rpc` methods +defined in the UI protocol. Example: + +```javascript + +// Approve transactions to a certain contract if value is below a certain limit +function ApproveTx(tx, from, call_info, metadata){ + + var limit = big.Newint("0xb1a2bc2ec50000") + + if (tx.to.String() == "0xae967917c465db8578ca9024c205720b1a3651A9" && + tx.value.Cmp(limit) < 0){ + return "Approve" + } +} + +//Approve listings if request made from IPC +function ApproveListing(accounts, metadata){ + if (metadata.scheme == "ipc"){ return "Approve"} +} + +``` + +In this case, whenever the external API was called, the `signer` would load `ruleset.js` and invoke the corresponding method. These would be +the possible cases: + +1. JS returns "Approve" -> auto-approve +2. JS returns "Reject" -> auto-reject +3. Method not implemented, or something else is returned -> pass on to regular UI channel. + +In the example above, the rule about "1 ether per 24h period" is not implemented; in order to allow for such rules, some sort of history-lookup needs to be implemented. + +#### Security considerations + +##### Security of ruleset + +Some security precautions can be made, such as: + +* Never load `ruleset.js` unless the file is `readonly` (`r-??-??-?`). If the user wishes to modify the ruleset, he must make it writeable and then set back to readonly. + * This is to prevent attacks where files are dropped on the users disk. +* Since we're going to have to have some form of secure storage (not defined in this section), we could also store the `sha3` of the `ruleset.js` file in there. + * If the user wishes to modify the ruleset, he'd then have to perform e.g. `signer --attest /path/to/ruleset --credential ` + +##### Security of implementation + +The drawbacks of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement, since it's already +implemented for `geth`. There are no known security vulnerabilities in, nor have we had any security-problems with it so far. + +The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered +an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit +to be gained from attacking the actual `signer` process from the `js` side would be if it could somehow extract cryptographic keys from memory. + +##### Security in usability + +Javascript is flexible, but also easy to get wrong, especially when users assume that `js` can handle large integers natively. Typical errors +include trying to multiply `gasCost` with `gas` without using `bigint`:s. + +It's unclear whether any other DSL could be more secure; since there's always the possibility of erroneously implementing a rule. + + +## Credential management + +The ability to auto-approve transaction means that the signer needs to have necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass). + +### Example implementation + +Upon startup of the signer, the signer is given a switch: `--seed ` +The `seed` contains a blob of bytes, which is the master seed for the `signer`. + +The `signer` uses the `seed` to: + +* Generate the `path` where the settings are stored. + * `./settings/1df094eb-c2b1-4689-90dd-790046d38025/vault.dat` + * `./settings/1df094eb-c2b1-4689-90dd-790046d38025/rules.js` +* Generate the encryption password for `vault.dat`. + +The `vault.dat` would be an encrypted container storing the following information: + +* `ksp` entries +* `sha256` hash of `rules.js` +* Information about pair:ed callers (not yet specified) + +### Security considerations + +This would leave it up to the user to ensure that the `path/to/masterseed` is handled in a secure way. It's difficult to get around this, although one could +imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`. + + diff --git a/cmd/signer/sign_flow.png b/cmd/signer/sign_flow.png new file mode 100644 index 0000000000..9c0f3cc5d5 Binary files /dev/null and b/cmd/signer/sign_flow.png differ