Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Commit

Permalink
Merge pull request #1820 from NebulousLabs/wallet-changepw
Browse files Browse the repository at this point in the history
add `/wallet/changekey` API endpoint for change the wallet's encryption key
  • Loading branch information
David Vorick committed May 22, 2017
2 parents 413dae0 + f5dd396 commit 4b73873
Show file tree
Hide file tree
Showing 8 changed files with 484 additions and 0 deletions.
1 change: 1 addition & 0 deletions api/api.go
Expand Up @@ -257,6 +257,7 @@ func New(requiredUserAgent string, requiredPassword string, cs modules.Consensus
router.GET("/wallet/transactions/:addr", api.walletTransactionsAddrHandler)
router.GET("/wallet/verify/address/:addr", api.walletVerifyAddressHandler)
router.POST("/wallet/unlock", RequirePassword(api.walletUnlockHandler, requiredPassword))
router.POST("/wallet/changepassword", RequirePassword(api.walletChangePasswordHandler, requiredPassword))
}

// Apply UserAgent middleware and return the API
Expand Down
29 changes: 29 additions & 0 deletions api/wallet.go
Expand Up @@ -532,6 +532,35 @@ func (api *API) walletUnlockHandler(w http.ResponseWriter, req *http.Request, _
WriteError(w, Error{"error when calling /wallet/unlock: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest)
}

// walletChangePasswordHandler handles API calls to /wallet/changepassword
func (api *API) walletChangePasswordHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
var newKey crypto.TwofishKey
newPassword := req.FormValue("newpassword")
if newPassword == "" {
WriteError(w, Error{"a password must be provided to newpassword"}, http.StatusBadRequest)
return
}
newKey = crypto.TwofishKey(crypto.HashObject(newPassword))

originalKeys := encryptionKeys(req.FormValue("encryptionpassword"))
if len(originalKeys) != 1 {
WriteError(w, Error{"expected one encryption key passed to encryptionpassword"}, http.StatusBadRequest)
return
}
for _, key := range originalKeys {
err := api.wallet.ChangeKey(key, newKey)
if err == nil {
WriteSuccess(w)
return
}
if err != nil && err != modules.ErrBadEncryptionKey {
WriteError(w, Error{"error when calling /wallet/changepassword: " + err.Error()}, http.StatusBadRequest)
return
}
}
WriteError(w, Error{"error when calling /wallet/changepassword: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest)
}

// walletVerifyAddressHandler handles API calls to /wallet/verify/address/:addr.
func (api *API) walletVerifyAddressHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
addrString := ps.ByName("addr")
Expand Down
232 changes: 232 additions & 0 deletions api/wallet_test.go
Expand Up @@ -238,6 +238,143 @@ func TestWalletRescanning(t *testing.T) {
<-doneChan
}

// TestWalletChangePasswordDeep is a more through validation test of the
// /wallet/changepassword endpoint.
func TestWalletChangePasswordDeep(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()

st, err := createServerTester(t.Name())
if err != nil {
t.Fatal(err)
}
defer st.server.panicClose()

st2, err := blankServerTester(t.Name() + "-wallet1")
if err != nil {
t.Fatal(err)
}
defer st2.server.Close()

st3, err := blankServerTester(t.Name() + "-wallet2")
if err != nil {
t.Fatal(err)
}
defer st3.server.Close()

st4, err := blankServerTester(t.Name() + "-wallet3")
if err != nil {
t.Fatal(err)
}
defer st4.server.Close()

st5, err := blankServerTester(t.Name() + "-wallet4")
if err != nil {
t.Fatal(err)
}
defer st5.server.Close()

wallets := []*serverTester{st, st2, st3, st4, st5}
err = fullyConnectNodes(wallets)
if err != nil {
t.Fatal(err)
}

// send 10KS to each of the blank wallets
sendSiacoins := func(srcST *serverTester, destST *serverTester, amount uint64) {
var wag WalletAddressGET
err = destST.getAPI("/wallet/address", &wag)
if err != nil {
t.Fatal(err)
}
sendValue := types.SiacoinPrecision.Mul64(amount)
sendSiacoinsValues := url.Values{}
sendSiacoinsValues.Set("amount", sendValue.String())
sendSiacoinsValues.Set("destination", wag.Address.String())
if err = srcST.stdPostAPI("/wallet/siacoins", sendSiacoinsValues); err != nil {
t.Fatal(err)
}
_, err = st.miner.AddBlock()
if err != nil {
t.Fatal(err)
}
}
for _, wallet := range wallets {
sendSiacoins(st, wallet, 10000)
}

// mine a few blocks
for i := 0; i < 15; i++ {
_, err = st.miner.AddBlock()
if err != nil {
t.Fatal(err)
}
}

st2seed, _, err := st2.wallet.PrimarySeed()
if err != nil {
t.Fatal(err)
}
st3seed, _, err := st3.wallet.PrimarySeed()
if err != nil {
t.Fatal(err)
}

// close 2 of the 3 blank wallets
err = st2.server.Close()
if err != nil {
t.Fatal(err)
}
err = st3.server.Close()
if err != nil {
t.Fatal(err)
}

// load their seeds into the third wallet
loadSeed := func(seed modules.Seed, st *serverTester) {
err = st.wallet.LoadSeed(st.walletKey, seed)
if err != nil {
t.Fatal(err)
}
}
loadSeed(st2seed, st4)
loadSeed(st3seed, st4)

// restart the third wallet
err = st4.server.Close()
if err != nil {
t.Fatal(err)
}
st4, err = assembleServerTester(st4.walletKey, st4.dir)
if err != nil {
t.Fatal(err)
}

// changekey the third wallet
newKey := crypto.TwofishKey(crypto.HashObject("newpassword"))
err = st4.wallet.ChangeKey(st4.walletKey, newKey)
if err != nil {
t.Fatal(err)
}

// send all of the money from the third wallet to the fourth wallet
sendSiacoins(st4, st5, 5000)
sendSiacoins(st4, st5, 5000)
sendSiacoins(st4, st5, 5000)
sendSiacoins(st4, st5, 5000)
sendSiacoins(st4, st5, 5000)
sendSiacoins(st4, st5, 4000)

// verify the money went through
minExpectedBalance := types.SiacoinPrecision.Mul64(26900)
balance, _, _ := st5.wallet.ConfirmedBalance()
if balance.Cmp(minExpectedBalance) < 0 {
t.Fatalf("balance should end up in the final wallet, wanted %v got %v\n", minExpectedBalance.Div(types.SiacoinPrecision), balance.Div(types.SiacoinPrecision))
}
}

// TestWalletEncrypt tries to encrypt and unlock the wallet through the api
// using a provided encryption key.
func TestWalletEncrypt(t *testing.T) {
Expand Down Expand Up @@ -1340,3 +1477,98 @@ func TestWalletVerifyAddress(t *testing.T) {
t.Fatal("expected /wallet/verify to pass a valid address")
}
}

// TestWalletChangePassword verifies that the /wallet/changepassword endpoint
// works correctly and changes a wallet password.
func TestWalletChangePassword(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()

testdir := build.TempDir("api", t.Name())

originalPassword := "testpass"
newPassword := "newpass"
originalKey := crypto.TwofishKey(crypto.HashObject(originalPassword))
newKey := crypto.TwofishKey(crypto.HashObject(newPassword))

st, err := assembleServerTester(originalKey, testdir)
if err != nil {
t.Fatal(err)
}

// lock the wallet
err = st.stdPostAPI("/wallet/lock", nil)
if err != nil {
t.Fatal(err)
}

// Use the password to call /wallet/unlock.
unlockValues := url.Values{}
unlockValues.Set("encryptionpassword", originalPassword)
err = st.stdPostAPI("/wallet/unlock", unlockValues)
if err != nil {
t.Fatal(err)
}
// Check that the wallet actually unlocked.
if !st.wallet.Unlocked() {
t.Error("wallet is not unlocked")
}

// change the wallet key
changeKeyValues := url.Values{}
changeKeyValues.Set("encryptionpassword", originalPassword)
changeKeyValues.Set("newpassword", newPassword)
err = st.stdPostAPI("/wallet/changepassword", changeKeyValues)
if err != nil {
t.Fatal(err)
}
// wallet should still be unlocked
if !st.wallet.Unlocked() {
t.Fatal("changepassword locked the wallet")
}

// lock the wallet and verify unlocking works with the new password
err = st.stdPostAPI("/wallet/lock", nil)
if err != nil {
t.Fatal(err)
}
unlockValues.Set("encryptionpassword", newPassword)
err = st.stdPostAPI("/wallet/unlock", unlockValues)
if err != nil {
t.Fatal(err)
}
// Check that the wallet actually unlocked.
if !st.wallet.Unlocked() {
t.Error("wallet is not unlocked")
}

// reload the server and verify unlocking still works
err = st.server.Close()
if err != nil {
t.Fatal(err)
}

st2, err := assembleServerTester(newKey, st.dir)
if err != nil {
t.Fatal(err)
}
defer st2.server.panicClose()

// lock the wallet
err = st2.stdPostAPI("/wallet/lock", nil)
if err != nil {
t.Fatal(err)
}

// Use the password to call /wallet/unlock.
err = st2.stdPostAPI("/wallet/unlock", unlockValues)
if err != nil {
t.Fatal(err)
}
// Check that the wallet actually unlocked.
if !st2.wallet.Unlocked() {
t.Error("wallet is not unlocked")
}
}
15 changes: 15 additions & 0 deletions doc/API.md
Expand Up @@ -964,6 +964,7 @@ Wallet
| [/wallet/transactions/___:addr___](#wallettransactionsaddr-get) | GET |
| [/wallet/unlock](#walletunlock-post) | POST |
| [/wallet/verify/address/:___addr___](#walletverifyaddress-get) | GET |
| [/wallet/changepassword](#walletchangepassword-post) | POST |

For examples and detailed descriptions of request and response parameters,
refer to [Wallet.md](/doc/api/Wallet.md).
Expand Down Expand Up @@ -1339,3 +1340,17 @@ takes the address specified by :addr and returns a JSON response indicating if t
"valid": true
}
```

#### /wallet/changepassword [POST]

changes the wallet's encryption key.

###### Query String Parameters [(with comments)](/doc/api/Wallet.md#query-string-parameters-12)
```
encryptionpassword
newpassword
```

###### Response
standard success or error response. See
[#standard-responses](#standard-responses).
17 changes: 17 additions & 0 deletions doc/api/Wallet.md
Expand Up @@ -48,6 +48,7 @@ Index
| [/wallet/transactions/___:addr___](#wallettransactionsaddr-get) | GET |
| [/wallet/unlock](#walletunlock-post) | POST |
| [/wallet/verify/address/:___addr___](#walletverifyaddress-get) | GET |
| [/wallet/changepassword](#walletchangepassword-post) | POST |

#### /wallet [GET]

Expand Down Expand Up @@ -615,3 +616,19 @@ takes the address specified by :addr and returns a JSON response indicating if t
"valid": true
}
```

#### /wallet/changepassword [POST]

changes the wallet's encryption password.

###### Query String Parameter
```
// encryptionpassword is the wallet's current encryption password.
encryptionpassword
// newpassword is the new password for the wallet.
newpassword
```

###### Response
standard success or error response. See
[#standard-responses](#standard-responses).
4 changes: 4 additions & 0 deletions modules/wallet.go
Expand Up @@ -256,6 +256,10 @@ type (
// derived from the master key.
Unlock(masterKey crypto.TwofishKey) error

// ChangeKey changes the wallet's materKey from masterKey to newKey,
// re-encrypting the wallet with the provided key.
ChangeKey(masterKey crypto.TwofishKey, newKey crypto.TwofishKey) error

// Unlocked returns true if the wallet is currently unlocked, false
// otherwise.
Unlocked() bool
Expand Down

0 comments on commit 4b73873

Please sign in to comment.