Skip to content

Commit

Permalink
teach lockaccount '*' how to lock all accounts
Browse files Browse the repository at this point in the history
extend the account properties struct with details about whether the
account is individually encrypted, and if so, whether currently
unlocked.
  • Loading branch information
jrick committed Sep 9, 2020
1 parent 5ec7b6a commit 9763fe4
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 7 deletions.
16 changes: 16 additions & 0 deletions internal/rpc/jsonrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -4221,6 +4221,22 @@ func (s *Server) lockAccount(ctx context.Context, icmd interface{}) (interface{}
return nil, errUnloadedWallet
}

if cmd.Account == "*" {
a, err := w.Accounts(ctx)
if err != nil {
return nil, err
}
for _, acct := range a.Accounts {
if acct.AccountEncrypted && acct.AccountUnlocked {
err = w.LockAccount(ctx, acct.AccountNumber)
if err != nil {
return nil, err
}
}
}
return nil, nil
}

account, err := w.AccountNumber(ctx, cmd.Account)
if err != nil {
if errors.Is(err, errors.NotExist) {
Expand Down
28 changes: 28 additions & 0 deletions wallet/udb/addressmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ type AccountProperties = struct {
LastReturnedExternalIndex uint32
LastReturnedInternalIndex uint32
ImportedKeyCount uint32
AccountEncrypted bool
AccountUnlocked bool
}

// defaultNewSecretKey returns a new secret key. See newSecretKey.
Expand Down Expand Up @@ -591,6 +593,8 @@ func (m *Manager) AccountProperties(ns walletdb.ReadBucket, account uint32) (*Ac
props.ImportedKeyCount = importedKeyCount
}

props.AccountEncrypted, props.AccountUnlocked = m.accountHasPassphrase(ns, account)

return props, nil
}

Expand Down Expand Up @@ -1736,6 +1740,30 @@ func (m *Manager) removeAccountPassphrase(ns walletdb.ReadWriteBucket, account u
return nil
}

// AccountHasPassphrase returns whether an account's keys are currently
// protected by a per-account passphrase, and if so, whether the account is
// currently locked or unlocked.
func (m *Manager) AccountHasPassphrase(dbtx walletdb.ReadTx, account uint32) (hasPassphrase, unlocked bool) {
defer m.mtx.RUnlock()
m.mtx.RLock()

ns := dbtx.ReadBucket(waddrmgrBucketKey)

return m.accountHasPassphrase(ns, account)
}

func (m *Manager) accountHasPassphrase(ns walletdb.ReadBucket, account uint32) (hasPassphrase, unlocked bool) {
acctInfo, err := m.loadAccountInfo(ns, account)
if err != nil {
return
}
hasPassphrase = acctInfo.uniqueKey != nil
if hasPassphrase {
unlocked = acctInfo.acctKeyPriv != nil
}
return
}

func maxUint32(a, b uint32) uint32 {
if a > b {
return a
Expand Down
47 changes: 40 additions & 7 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1450,23 +1450,49 @@ func (w *Wallet) Unlock(ctx context.Context, passphrase []byte, timeout <-chan t
return nil
}

// SetAccountPassphrase individually-encrypts or changes the passphrase for
// account private keys.
//
// If the passphrase has zero length, the private keys are re-encrypted with the
// manager's global passphrase.
func (w *Wallet) SetAccountPassphrase(ctx context.Context, account uint32, passphrase []byte) error {
return walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error {
return w.manager.SetAccountPassphrase(tx, account, passphrase)
return walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
return w.manager.SetAccountPassphrase(dbtx, account, passphrase)
})
}

// TODO: timeout?
// UnlockAccount decrypts a uniquely-encrypted account's private keys.
func (w *Wallet) UnlockAccount(ctx context.Context, account uint32, passphrase []byte) error {
return walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error {
return w.manager.UnlockAccount(tx, account, passphrase)
return walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
return w.manager.UnlockAccount(dbtx, account, passphrase)
})
}

// LockAccount locks an individually-encrypted account by removing private key
// access until unlocked again.
func (w *Wallet) LockAccount(ctx context.Context, account uint32) error {
return walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error {
return w.manager.LockAccount(tx, account)
return walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
return w.manager.LockAccount(dbtx, account)
})
}

// AccountUnlocked returns whether an individually-encrypted account is unlocked.
func (w *Wallet) AccountUnlocked(ctx context.Context, account uint32) (bool, error) {
const op errors.Op = "wallet.AccountUnlocked"
var unlocked bool
err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
var encrypted bool
encrypted, unlocked = w.manager.AccountHasPassphrase(dbtx, account)
if !encrypted {
const s = "account is not individually encrypted"
return errors.E(errors.Invalid, s)
}
return nil
})
if err != nil {
return false, errors.E(op, err)
}
return unlocked, nil
}

func (w *Wallet) replacePassphraseTimeout(wasLocked bool, newTimeout <-chan time.Time) {
Expand Down Expand Up @@ -1521,6 +1547,11 @@ func (w *Wallet) Locked() bool {
return w.manager.IsLocked()
}

// Unlocked returns whether the account manager for a wallet is unlocked.
func (w *Wallet) Unlocked() bool {
return !w.Locked()
}

// holdUnlock prevents the wallet from being locked. The heldUnlock object
// *must* be released, or the wallet will forever remain unlocked.
//
Expand Down Expand Up @@ -2999,6 +3030,8 @@ type AccountProperties struct {
LastReturnedExternalIndex uint32
LastReturnedInternalIndex uint32
ImportedKeyCount uint32
AccountEncrypted bool
AccountUnlocked bool
}

// AccountResult is a single account result for the AccountsResult type.
Expand Down

0 comments on commit 9763fe4

Please sign in to comment.