Add new function on extended keys to zero them.

This commit adds a new function named Zero on the hdkeychain.ExtendedKey
which can be used to manually clear the memory used for an extended key.
This is useful for enhanced security by allowing the caller to explicitly
clear the memory when they're done with a key.  Otherwise it might hang
around in memory for a while.

Once a key has been zeroed it is no longer usable.

This commit also contains tests to ensure everything works as expected
after a key has been zeroed.
This commit is contained in:
Dave Collins 2014-08-18 17:53:37 -05:00
parent a36fbe9ade
commit f8ad0939a2
2 changed files with 108 additions and 0 deletions

View file

@ -360,6 +360,10 @@ func (k *ExtendedKey) Address(net *btcnet.Params) (*btcutil.AddressPubKeyHash, e
// String returns the extended key as a human-readable base58-encoded string.
func (k *ExtendedKey) String() string {
if len(k.key) == 0 {
return "zeroed extended key"
}
var childNumBytes [4]byte
depthByte := byte(k.depth % 256)
binary.BigEndian.PutUint32(childNumBytes[:], k.childNum)
@ -402,6 +406,31 @@ func (k *ExtendedKey) SetNet(net *btcnet.Params) {
}
}
// zero sets all bytes in the passed slice to zero. This is used to
// explicitly clear private key material from memory.
func zero(b []byte) {
lenb := len(b)
for i := 0; i < lenb; i++ {
b[i] = 0
}
}
// Zero manually clears all fields and bytes in the extended key. This can be
// used to explicitly clear key material from memory for enhanced security
// against memory scraping. This function only clears this particular key and
// not any children that have already been derived.
func (k *ExtendedKey) Zero() {
zero(k.key)
zero(k.pubKey)
zero(k.chainCode)
zero(k.parentFP)
zero(k.version)
k.key = nil
k.depth = 0
k.childNum = 0
k.isPrivate = false
}
// NewMaster creates a new master node for use in creating a hierarchical
// deterministic key chain. The seed must be between 128 and 512 bits and
// should be generated by a cryptographically secure random generation source.

View file

@ -674,3 +674,82 @@ func TestErrors(t *testing.T) {
}
}
}
// TestZero ensures that zeroing an extended key works as intended.
func TestZero(t *testing.T) {
tests := []struct {
name string
extKey string
}{
// Test vector 1
{
name: "test vector 1 chain m",
extKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
},
}
for i, test := range tests {
key, err := hdkeychain.NewKeyFromString(test.extKey)
if err != nil {
t.Errorf("NewKeyFromString #%d (%s): unexpected "+
"error: %v", i, test.name, err)
continue
}
key.Zero()
// Zeroing a key should result in it no longer being private
if key.IsPrivate() != false {
t.Errorf("IsPrivate #%d (%s): mismatched key type -- "+
"want private %v, got private %v", i, test.name,
false, key.IsPrivate())
continue
}
parentFP := key.ParentFingerprint()
if parentFP != 0 {
t.Errorf("ParentFingerprint #%d (%s): mismatched "+
"parent fingerprint -- want %d, got %d", i,
test.name, 0, parentFP)
continue
}
wantKey := "zeroed extended key"
serializedKey := key.String()
if serializedKey != wantKey {
t.Errorf("String #%d (%s): mismatched serialized key "+
"-- want %s, got %s", i, test.name, wantKey,
serializedKey)
continue
}
wantErr := hdkeychain.ErrNotPrivExtKey
_, err = key.ECPrivKey()
if !reflect.DeepEqual(err, wantErr) {
t.Errorf("ECPrivKey #%d (%s): mismatched error: want "+
"%v, got %v", i, test.name, wantErr, err)
continue
}
wantErr = errors.New("pubkey string is empty")
_, err = key.ECPubKey()
if !reflect.DeepEqual(err, wantErr) {
t.Errorf("ECPubKey #%d (%s): mismatched error: want "+
"%v, got %v", i, test.name, wantErr, err)
continue
}
wantAddr := "1HT7xU2Ngenf7D4yocz2SAcnNLW7rK8d4E"
addr, err := key.Address(&btcnet.MainNetParams)
if err != nil {
t.Errorf("Addres s #%d (%s): unexpected error: %v", i,
test.name, err)
continue
}
if addr.EncodeAddress() != wantAddr {
t.Errorf("Address #%d (%s): mismatched address -- want "+
"%s, got %s", i, test.name, wantAddr,
addr.EncodeAddress())
continue
}
}
}