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:
parent
a36fbe9ade
commit
f8ad0939a2
2 changed files with 108 additions and 0 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue