Skip to content

Commit d36c482

Browse files
fix: cleanse BIP32 secret derivation state
1 parent 6169ee9 commit d36c482

6 files changed

Lines changed: 182 additions & 50 deletions

File tree

include/dashbls/chaincode.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class ChainCode {
3838
static ChainCode FromBytes(const Bytes& bytes);
3939

4040
ChainCode(const ChainCode &cc);
41+
ChainCode& operator=(const ChainCode& cc);
42+
~ChainCode();
4143

4244
// Comparator implementation.
4345
friend bool operator==(ChainCode const &a, ChainCode const &b);
@@ -48,12 +50,11 @@ class ChainCode {
4850
std::vector<uint8_t> Serialize() const;
4951

5052
// Prevent direct construction, use static constructor
51-
ChainCode() {}
53+
ChainCode();
5254
private:
5355

5456
bn_t chainCode;
5557
};
5658
} // end namespace bls
5759

5860
#endif // SRC_CHAINCODE_HPP_
59-

include/dashbls/util.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ class Util {
9595
secureFreeCallback(ptr);
9696
}
9797

98+
/*
99+
* Overwrite sensitive memory through a volatile pointer so the writes are
100+
* not removed as dead stores by the compiler.
101+
*/
102+
static void SecureWipe(void* ptr, size_t size) noexcept {
103+
volatile uint8_t* bytes = static_cast<volatile uint8_t*>(ptr);
104+
while (size-- > 0) {
105+
*bytes++ = 0;
106+
}
107+
}
108+
98109
/*
99110
* Converts one hex character to an int.
100111
*/

src/chaincode.cpp

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,44 @@ ChainCode ChainCode::FromBytes(const Bytes& bytes) {
2020
if (bytes.size() != ChainCode::SIZE) {
2121
throw std::invalid_argument("ChainCode::FromBytes: Invalid size");
2222
}
23-
ChainCode c = ChainCode();
24-
bn_new(c.chainCode);
23+
ChainCode c;
2524
bn_read_bin(c.chainCode, bytes.begin(), ChainCode::SIZE);
2625
return c;
2726
}
2827

29-
ChainCode::ChainCode(const ChainCode &cc) {
30-
uint8_t bytes[ChainCode::SIZE];
31-
cc.Serialize(bytes);
28+
ChainCode::ChainCode() {
29+
bn_null(chainCode);
3230
bn_new(chainCode);
33-
bn_read_bin(chainCode, bytes, ChainCode::SIZE);
31+
bn_zero(chainCode);
32+
}
33+
34+
ChainCode::ChainCode(const ChainCode &cc) : ChainCode() {
35+
bn_copy(chainCode, cc.chainCode);
36+
}
37+
38+
ChainCode& ChainCode::operator=(const ChainCode& cc) {
39+
if (this != &cc) {
40+
#if ALLOC == DYNAMIC
41+
if (chainCode->dp != nullptr && chainCode->alloc > 0) {
42+
Util::SecureWipe(chainCode->dp, chainCode->alloc * sizeof(dig_t));
43+
}
44+
#elif ALLOC == AUTO
45+
Util::SecureWipe(chainCode->dp, sizeof(chainCode->dp));
46+
#endif
47+
bn_copy(chainCode, cc.chainCode);
48+
}
49+
return *this;
50+
}
51+
52+
ChainCode::~ChainCode() {
53+
#if ALLOC == DYNAMIC
54+
if (chainCode != nullptr && chainCode->dp != nullptr && chainCode->alloc > 0) {
55+
Util::SecureWipe(chainCode->dp, chainCode->alloc * sizeof(dig_t));
56+
}
57+
bn_free(chainCode);
58+
#elif ALLOC == AUTO
59+
Util::SecureWipe(chainCode, sizeof(chainCode));
60+
#endif
3461
}
3562

3663
// Comparator implementation.

src/extendedprivatekey.cpp

Lines changed: 87 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,95 @@
1313
// limitations under the License.
1414

1515
#include <cstring>
16+
#include <new>
1617
#include "bls.hpp"
1718

1819
namespace bls {
20+
namespace {
21+
22+
class SecureBytes {
23+
public:
24+
explicit SecureBytes(size_t size) : m_size(size), m_data(Util::SecAlloc<uint8_t>(size)) {
25+
if (m_data == nullptr) {
26+
throw std::bad_alloc();
27+
}
28+
}
29+
30+
~SecureBytes() {
31+
Util::SecureWipe(m_data, m_size);
32+
Util::SecFree(m_data);
33+
}
34+
35+
SecureBytes(const SecureBytes&) = delete;
36+
SecureBytes& operator=(const SecureBytes&) = delete;
37+
38+
uint8_t* data() { return m_data; }
39+
40+
private:
41+
const size_t m_size;
42+
uint8_t* const m_data;
43+
};
44+
45+
class SecureRelicBn {
46+
public:
47+
SecureRelicBn() {
48+
bn_null(m_value);
49+
bn_new(m_value);
50+
}
51+
52+
~SecureRelicBn() {
53+
#if ALLOC == DYNAMIC
54+
if (m_value != nullptr && m_value->dp != nullptr && m_value->alloc > 0) {
55+
Util::SecureWipe(m_value->dp, m_value->alloc * sizeof(dig_t));
56+
}
57+
bn_free(m_value);
58+
#elif ALLOC == AUTO
59+
Util::SecureWipe(m_value, sizeof(m_value));
60+
#endif
61+
}
62+
63+
SecureRelicBn(const SecureRelicBn&) = delete;
64+
SecureRelicBn& operator=(const SecureRelicBn&) = delete;
65+
66+
bn_st* get() { return m_value; }
67+
68+
private:
69+
bn_t m_value;
70+
};
71+
72+
} // namespace
1973

2074
ExtendedPrivateKey ExtendedPrivateKey::FromSeed(const Bytes& bytes) {
2175
// "BLS HD seed" in ascii
2276
const uint8_t prefix[] = {66, 76, 83, 32, 72, 68, 32, 115, 101, 101, 100};
2377

24-
uint8_t* hashInput = Util::SecAlloc<uint8_t>(bytes.size() + 1);
25-
std::memcpy(hashInput, bytes.begin(), bytes.size());
78+
SecureBytes hashInput(bytes.size() + 1);
79+
std::memcpy(hashInput.data(), bytes.begin(), bytes.size());
2680

2781
// 32 bytes for secret key, and 32 bytes for chaincode
28-
uint8_t* ILeft = Util::SecAlloc<uint8_t>(
29-
PrivateKey::PRIVATE_KEY_SIZE);
30-
uint8_t IRight[ChainCode::SIZE];
82+
SecureBytes ILeft(PrivateKey::PRIVATE_KEY_SIZE);
83+
SecureBytes IRight(ChainCode::SIZE);
3184

3285
// Hash the seed into 64 bytes, half will be sk, half will be cc
33-
hashInput[bytes.size()] = 0;
34-
md_hmac(ILeft, hashInput, bytes.size() + 1, prefix, sizeof(prefix));
86+
hashInput.data()[bytes.size()] = 0;
87+
md_hmac(ILeft.data(), hashInput.data(), bytes.size() + 1, prefix, sizeof(prefix));
3588

36-
hashInput[bytes.size()] = 1;
37-
md_hmac(IRight, hashInput, bytes.size() + 1, prefix, sizeof(prefix));
89+
hashInput.data()[bytes.size()] = 1;
90+
md_hmac(IRight.data(), hashInput.data(), bytes.size() + 1, prefix, sizeof(prefix));
3891

3992
// Make sure private key is less than the curve order
40-
bn_t* skBn = Util::SecAlloc<bn_t>(1);
41-
bn_t order;
42-
bn_new(order);
43-
g1_get_ord(order);
93+
SecureRelicBn skBn;
94+
SecureRelicBn order;
95+
g1_get_ord(order.get());
4496

45-
bn_new(*skBn);
46-
bn_read_bin(*skBn, ILeft, PrivateKey::PRIVATE_KEY_SIZE);
47-
bn_mod_basic(*skBn, *skBn, order);
48-
bn_write_bin(ILeft, PrivateKey::PRIVATE_KEY_SIZE, *skBn);
97+
bn_read_bin(skBn.get(), ILeft.data(), PrivateKey::PRIVATE_KEY_SIZE);
98+
bn_mod_basic(skBn.get(), skBn.get(), order.get());
99+
bn_write_bin(ILeft.data(), PrivateKey::PRIVATE_KEY_SIZE, skBn.get());
49100

50101
ExtendedPrivateKey esk(ExtendedPublicKey::REVISION, 0, 0, 0,
51-
ChainCode::FromBytes(Bytes(IRight, ChainCode::SIZE)),
52-
PrivateKey::FromBytes(Bytes(ILeft, PrivateKey::PRIVATE_KEY_SIZE)));
102+
ChainCode::FromBytes(Bytes(IRight.data(), ChainCode::SIZE)),
103+
PrivateKey::FromBytes(Bytes(ILeft.data(), PrivateKey::PRIVATE_KEY_SIZE)));
53104

54-
Util::SecFree(skBn);
55-
Util::SecFree(ILeft);
56-
Util::SecFree(hashInput);
57105
return esk;
58106
}
59107

@@ -79,48 +127,45 @@ ExtendedPrivateKey ExtendedPrivateKey::PrivateChild(uint32_t i, const bool fLega
79127
uint32_t cmp = (1 << 31);
80128
bool hardened = i >= cmp;
81129

82-
uint8_t* ILeft = Util::SecAlloc<uint8_t>(PrivateKey::PRIVATE_KEY_SIZE);
83-
uint8_t IRight[ChainCode::SIZE];
130+
SecureBytes ILeft(PrivateKey::PRIVATE_KEY_SIZE);
131+
SecureBytes IRight(ChainCode::SIZE);
84132

85133
// Chain code is used as hmac key
86-
uint8_t hmacKey[ChainCode::SIZE];
87-
chainCode.Serialize(hmacKey);
134+
SecureBytes hmacKey(ChainCode::SIZE);
135+
chainCode.Serialize(hmacKey.data());
88136

89137
size_t inputLen = hardened ? PrivateKey::PRIVATE_KEY_SIZE + 4 + 1
90138
: G1Element::SIZE + 4 + 1;
91139
// Hmac input includes sk or pk, int i, and byte with 0 or 1
92-
uint8_t* hmacInput = Util::SecAlloc<uint8_t>(inputLen);
140+
SecureBytes hmacInput(inputLen);
93141

94142
// Fill the input with the required data
95143
if (hardened) {
96-
sk.Serialize(hmacInput);
97-
Util::IntToFourBytes(hmacInput + PrivateKey::PRIVATE_KEY_SIZE, i);
144+
sk.Serialize(hmacInput.data());
145+
Util::IntToFourBytes(hmacInput.data() + PrivateKey::PRIVATE_KEY_SIZE, i);
98146
} else {
99-
memcpy(hmacInput, sk.GetG1Element().Serialize(fLegacy).data(), G1Element::SIZE);
100-
Util::IntToFourBytes(hmacInput + G1Element::SIZE, i);
147+
memcpy(hmacInput.data(), sk.GetG1Element().Serialize(fLegacy).data(), G1Element::SIZE);
148+
Util::IntToFourBytes(hmacInput.data() + G1Element::SIZE, i);
101149
}
102-
hmacInput[inputLen - 1] = 0;
150+
hmacInput.data()[inputLen - 1] = 0;
103151

104-
md_hmac(ILeft, hmacInput, inputLen,
105-
hmacKey, ChainCode::SIZE);
152+
md_hmac(ILeft.data(), hmacInput.data(), inputLen,
153+
hmacKey.data(), ChainCode::SIZE);
106154

107155
// Change 1 byte to generate a different sequence for chaincode
108-
hmacInput[inputLen - 1] = 1;
156+
hmacInput.data()[inputLen - 1] = 1;
109157

110-
md_hmac(IRight, hmacInput, inputLen,
111-
hmacKey, ChainCode::SIZE);
158+
md_hmac(IRight.data(), hmacInput.data(), inputLen,
159+
hmacKey.data(), ChainCode::SIZE);
112160

113-
PrivateKey newSk = PrivateKey::FromBytes(Bytes(ILeft, PrivateKey::PRIVATE_KEY_SIZE), true);
161+
PrivateKey newSk = PrivateKey::FromBytes(Bytes(ILeft.data(), PrivateKey::PRIVATE_KEY_SIZE), true);
114162
newSk = PrivateKey::Aggregate({sk, newSk});
115163

116164
ExtendedPrivateKey esk(version, depth + 1,
117165
sk.GetG1Element().GetFingerprint(), i,
118-
ChainCode::FromBytes(Bytes(IRight, ChainCode::SIZE)),
166+
ChainCode::FromBytes(Bytes(IRight.data(), ChainCode::SIZE)),
119167
newSk);
120168

121-
Util::SecFree(ILeft);
122-
Util::SecFree(hmacInput);
123-
124169
return esk;
125170
}
126171

src/privatekey.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616
#include "legacy.hpp"
1717

1818
namespace bls {
19+
namespace {
20+
21+
void SecureWipePrivateKey(bn_st* keydata) noexcept
22+
{
23+
#if ALLOC == DYNAMIC
24+
if (keydata->dp != nullptr && keydata->alloc > 0) {
25+
Util::SecureWipe(keydata->dp, keydata->alloc * sizeof(dig_t));
26+
}
27+
#elif ALLOC == AUTO
28+
Util::SecureWipe(keydata->dp, sizeof(keydata->dp));
29+
#endif
30+
}
31+
32+
} // namespace
1933

2034
const size_t PrivateKey::PRIVATE_KEY_SIZE;
2135

@@ -119,6 +133,7 @@ PrivateKey::~PrivateKey()
119133
void PrivateKey::DeallocateKeyData()
120134
{
121135
if(keydata != nullptr) {
136+
SecureWipePrivateKey(keydata);
122137
Util::SecFree(keydata);
123138
keydata = nullptr;
124139
}
@@ -133,9 +148,13 @@ void PrivateKey::InvalidateCaches()
133148

134149
PrivateKey& PrivateKey::operator=(const PrivateKey& other)
135150
{
151+
if (this == &other) {
152+
return *this;
153+
}
136154
CheckKeyData();
137155
other.CheckKeyData();
138156
InvalidateCaches();
157+
SecureWipePrivateKey(keydata);
139158
bn_copy(keydata, other.keydata);
140159
return *this;
141160
}

src/test.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// limitations under the License.
1515

1616
#define CATCH_CONFIG_RUNNER
17+
#include <new>
1718
#include <thread>
1819

1920
#include "bls.hpp"
@@ -73,6 +74,8 @@ TEST_CASE("class PrivateKey") {
7374
REQUIRE(pk1.GetG1Element() == pk2.GetG1Element());
7475
REQUIRE(pk1.GetG2Element() == pk2.GetG2Element());
7576
REQUIRE(pk3 != pk2);
77+
pk2 = pk2;
78+
REQUIRE(pk1 == pk2);
7679
}
7780
SECTION("Move {constructor|assignment operator}") {
7881
PrivateKey pk1 = PrivateKey::RandomPrivateKey();
@@ -1267,6 +1270,32 @@ TEST_CASE("Schemes") {
12671270
}
12681271

12691272
TEST_CASE("Legacy HD keys") {
1273+
SECTION("Chain code copy construction and assignment preserve value") {
1274+
std::array<uint8_t, ChainCode::SIZE> first{};
1275+
std::array<uint8_t, ChainCode::SIZE> second{};
1276+
first.front() = 1;
1277+
second.back() = 2;
1278+
1279+
const ChainCode source = ChainCode::FromBytes(Bytes(first));
1280+
ChainCode copy{source};
1281+
ChainCode assigned = ChainCode::FromBytes(Bytes(second));
1282+
1283+
assigned = source;
1284+
REQUIRE(copy == source);
1285+
REQUIRE(assigned == source);
1286+
1287+
assigned = assigned;
1288+
REQUIRE(assigned == source);
1289+
1290+
#if ALLOC == AUTO
1291+
alignas(ChainCode) std::array<uint8_t, sizeof(ChainCode)> storage;
1292+
storage.fill(0xa5);
1293+
auto* stored = new (storage.data()) ChainCode{source};
1294+
stored->~ChainCode();
1295+
REQUIRE(std::all_of(storage.begin(), storage.end(), [](uint8_t byte) { return byte == 0; }));
1296+
#endif
1297+
}
1298+
12701299
SECTION("Should create an extended private key from seed") {
12711300
std::vector<uint8_t> seed{1, 50, 6, 244, 24, 199, 1, 25};
12721301
ExtendedPrivateKey esk = ExtendedPrivateKey::FromSeed(Bytes(seed));

0 commit comments

Comments
 (0)