Skip to content

Commit 0f9307c

Browse files
committed
Merge bitcoin/bitcoin#28500: Prevent default/invalid CKey objects from allocating secure memory
6ef405d key: don't allocate secure mem for null (invalid) key (Pieter Wuille) d9841a7 Add make_secure_unique helper (Anthony Towns) Pull request description: Bitcoin Core has `secure_allocator`, which allocates inside special "secure" (non-swappable) memory pages, which may be limited in availability. Currently, every `CKey` object uses 32 such secure bytes, even when the `CKey` object contains the (invalid) value zero. Change this to not use memory when the `CKey` is invalid. This is particularly relevant for `BIP324Cipher` which briefly holds a `CKey`, but after receiving the remote's public key and initializing the encryption ciphers, the key is wiped. In case secure memory usage is in high demand, it'd be silly to waste it on P2P encryption keys instead of wallet keys. ACKs for top commit: ajtowns: ACK 6ef405d john-moffett: ACK 6ef405d Tree-SHA512: 987f4376ed825daf034ea4d7c4b4952fe664b25b48f1c09fbcfa6257a40b06c4da7c2caaafa35c346c86bdf298ae21f16c68ea4b1039836990d1a205de2034fd
2 parents e3b0528 + 6ef405d commit 0f9307c

File tree

3 files changed

+83
-38
lines changed

3 files changed

+83
-38
lines changed

src/key.cpp

+19-18
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,21 @@ bool CKey::Check(const unsigned char *vch) {
159159
}
160160

161161
void CKey::MakeNewKey(bool fCompressedIn) {
162+
MakeKeyData();
162163
do {
163-
GetStrongRandBytes(keydata);
164-
} while (!Check(keydata.data()));
165-
fValid = true;
164+
GetStrongRandBytes(*keydata);
165+
} while (!Check(keydata->data()));
166166
fCompressed = fCompressedIn;
167167
}
168168

169169
bool CKey::Negate()
170170
{
171-
assert(fValid);
172-
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata.data());
171+
assert(keydata);
172+
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata->data());
173173
}
174174

175175
CPrivKey CKey::GetPrivKey() const {
176-
assert(fValid);
176+
assert(keydata);
177177
CPrivKey seckey;
178178
int ret;
179179
size_t seckeylen;
@@ -186,7 +186,7 @@ CPrivKey CKey::GetPrivKey() const {
186186
}
187187

188188
CPubKey CKey::GetPubKey() const {
189-
assert(fValid);
189+
assert(keydata);
190190
secp256k1_pubkey pubkey;
191191
size_t clen = CPubKey::SIZE;
192192
CPubKey result;
@@ -212,7 +212,7 @@ bool SigHasLowR(const secp256k1_ecdsa_signature* sig)
212212
}
213213

214214
bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, bool grind, uint32_t test_case) const {
215-
if (!fValid)
215+
if (!keydata)
216216
return false;
217217
vchSig.resize(CPubKey::SIGNATURE_SIZE);
218218
size_t nSigLen = CPubKey::SIGNATURE_SIZE;
@@ -253,7 +253,7 @@ bool CKey::VerifyPubKey(const CPubKey& pubkey) const {
253253
}
254254

255255
bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) const {
256-
if (!fValid)
256+
if (!keydata)
257257
return false;
258258
vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE);
259259
int rec = -1;
@@ -301,10 +301,12 @@ bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint2
301301
}
302302

303303
bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) {
304-
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size()))
304+
MakeKeyData();
305+
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) {
306+
ClearKeyData();
305307
return false;
308+
}
306309
fCompressed = vchPubKey.IsCompressed();
307-
fValid = true;
308310

309311
if (fSkipCheck)
310312
return true;
@@ -325,22 +327,21 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
325327
BIP32Hash(cc, nChild, 0, begin(), vout.data());
326328
}
327329
memcpy(ccChild.begin(), vout.data()+32, 32);
328-
memcpy((unsigned char*)keyChild.begin(), begin(), 32);
330+
keyChild.Set(begin(), begin() + 32, true);
329331
bool ret = secp256k1_ec_seckey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), vout.data());
330-
keyChild.fCompressed = true;
331-
keyChild.fValid = ret;
332+
if (!ret) keyChild.ClearKeyData();
332333
return ret;
333334
}
334335

335336
EllSwiftPubKey CKey::EllSwiftCreate(Span<const std::byte> ent32) const
336337
{
337-
assert(fValid);
338+
assert(keydata);
338339
assert(ent32.size() == 32);
339340
std::array<std::byte, EllSwiftPubKey::size()> encoded_pubkey;
340341

341342
auto success = secp256k1_ellswift_create(secp256k1_context_sign,
342343
UCharCast(encoded_pubkey.data()),
343-
keydata.data(),
344+
keydata->data(),
344345
UCharCast(ent32.data()));
345346

346347
// Should always succeed for valid keys (asserted above).
@@ -350,7 +351,7 @@ EllSwiftPubKey CKey::EllSwiftCreate(Span<const std::byte> ent32) const
350351

351352
ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, const EllSwiftPubKey& our_ellswift, bool initiating) const
352353
{
353-
assert(fValid);
354+
assert(keydata);
354355

355356
ECDHSecret output;
356357
// BIP324 uses the initiator as party A, and the responder as party B. Remap the inputs
@@ -359,7 +360,7 @@ ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, c
359360
UCharCast(output.data()),
360361
UCharCast(initiating ? our_ellswift.data() : their_ellswift.data()),
361362
UCharCast(initiating ? their_ellswift.data() : our_ellswift.data()),
362-
keydata.data(),
363+
keydata->data(),
363364
initiating ? 0 : 1,
364365
secp256k1_ellswift_xdh_hash_function_bip324,
365366
nullptr);

src/key.h

+40-20
Original file line numberDiff line numberDiff line change
@@ -46,57 +46,77 @@ class CKey
4646
"COMPRESSED_SIZE is larger than SIZE");
4747

4848
private:
49-
//! Whether this private key is valid. We check for correctness when modifying the key
50-
//! data, so fValid should always correspond to the actual state.
51-
bool fValid{false};
49+
/** Internal data container for private key material. */
50+
using KeyType = std::array<unsigned char, 32>;
5251

5352
//! Whether the public key corresponding to this private key is (to be) compressed.
5453
bool fCompressed{false};
5554

56-
//! The actual byte data
57-
std::vector<unsigned char, secure_allocator<unsigned char> > keydata;
55+
//! The actual byte data. nullptr for invalid keys.
56+
secure_unique_ptr<KeyType> keydata;
5857

5958
//! Check whether the 32-byte array pointed to by vch is valid keydata.
6059
bool static Check(const unsigned char* vch);
6160

61+
void MakeKeyData()
62+
{
63+
if (!keydata) keydata = make_secure_unique<KeyType>();
64+
}
65+
66+
void ClearKeyData()
67+
{
68+
keydata.reset();
69+
}
70+
6271
public:
63-
//! Construct an invalid private key.
64-
CKey()
72+
CKey() noexcept = default;
73+
CKey(CKey&&) noexcept = default;
74+
CKey& operator=(CKey&&) noexcept = default;
75+
76+
CKey& operator=(const CKey& other)
6577
{
66-
// Important: vch must be 32 bytes in length to not break serialization
67-
keydata.resize(32);
78+
if (other.keydata) {
79+
MakeKeyData();
80+
*keydata = *other.keydata;
81+
} else {
82+
ClearKeyData();
83+
}
84+
fCompressed = other.fCompressed;
85+
return *this;
6886
}
6987

88+
CKey(const CKey& other) { *this = other; }
89+
7090
friend bool operator==(const CKey& a, const CKey& b)
7191
{
7292
return a.fCompressed == b.fCompressed &&
7393
a.size() == b.size() &&
74-
memcmp(a.keydata.data(), b.keydata.data(), a.size()) == 0;
94+
memcmp(a.data(), b.data(), a.size()) == 0;
7595
}
7696

7797
//! Initialize using begin and end iterators to byte data.
7898
template <typename T>
7999
void Set(const T pbegin, const T pend, bool fCompressedIn)
80100
{
81-
if (size_t(pend - pbegin) != keydata.size()) {
82-
fValid = false;
101+
if (size_t(pend - pbegin) != std::tuple_size_v<KeyType>) {
102+
ClearKeyData();
83103
} else if (Check(&pbegin[0])) {
84-
memcpy(keydata.data(), (unsigned char*)&pbegin[0], keydata.size());
85-
fValid = true;
104+
MakeKeyData();
105+
memcpy(keydata->data(), (unsigned char*)&pbegin[0], keydata->size());
86106
fCompressed = fCompressedIn;
87107
} else {
88-
fValid = false;
108+
ClearKeyData();
89109
}
90110
}
91111

92112
//! Simple read-only vector-like interface.
93-
unsigned int size() const { return (fValid ? keydata.size() : 0); }
94-
const std::byte* data() const { return reinterpret_cast<const std::byte*>(keydata.data()); }
95-
const unsigned char* begin() const { return keydata.data(); }
96-
const unsigned char* end() const { return keydata.data() + size(); }
113+
unsigned int size() const { return keydata ? keydata->size() : 0; }
114+
const std::byte* data() const { return keydata ? reinterpret_cast<const std::byte*>(keydata->data()) : nullptr; }
115+
const unsigned char* begin() const { return keydata ? keydata->data() : nullptr; }
116+
const unsigned char* end() const { return begin() + size(); }
97117

98118
//! Check whether this private key is valid.
99-
bool IsValid() const { return fValid; }
119+
bool IsValid() const { return !!keydata; }
100120

101121
//! Check whether the public key corresponding to this private key is (to be) compressed.
102122
bool IsCompressed() const { return fCompressed; }

src/support/allocators/secure.h

+24
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,28 @@ struct secure_allocator {
5757
// TODO: Consider finding a way to make incoming RPC request.params[i] mlock()ed as well
5858
typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString;
5959

60+
template<typename T>
61+
struct SecureUniqueDeleter {
62+
void operator()(T* t) noexcept {
63+
secure_allocator<T>().deallocate(t, 1);
64+
}
65+
};
66+
67+
template<typename T>
68+
using secure_unique_ptr = std::unique_ptr<T, SecureUniqueDeleter<T>>;
69+
70+
template<typename T, typename... Args>
71+
secure_unique_ptr<T> make_secure_unique(Args&&... as)
72+
{
73+
T* p = secure_allocator<T>().allocate(1);
74+
75+
// initialize in place, and return as secure_unique_ptr
76+
try {
77+
return secure_unique_ptr<T>(new (p) T(std::forward(as)...));
78+
} catch (...) {
79+
secure_allocator<T>().deallocate(p, 1);
80+
throw;
81+
}
82+
}
83+
6084
#endif // BITCOIN_SUPPORT_ALLOCATORS_SECURE_H

0 commit comments

Comments
 (0)