Skip to content

Commit 724a640

Browse files
authored
Resolve SSL_PRIVATE_METHOD and certificate slots functionality (#2429)
### Description of changes: There was an issue with trying to use `SSL_PRIVATE_KEY_METHOD` functionality that was working for the most part, but had some edge scenarios that had stopped working. For example setting the `privkey_method` argument when calling `SSL_CTX_set_chain_and_key` or `SSL_set_chain_and_key`. Originally I thought this feature had been in a complete broken state, but upon further investigation realized that the bssl runner framework was using this functionality for testing async and hints functionality. The caveat was the framework was using alternative functions to directly set the certificate and private key method without using this entrypoint. In theory this entrypoint should work as it is for the most part coded correctly, but had a bug in that is tried to use the private key to determine the slot index when it could possibly be null. This corrects that behavior to correctly use the public key, to allow the chain to be set correctly, but still delegate the private key operations to the registered `SSL_PRIVATE_KEY_METHOD` provided. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.
1 parent f0b4afe commit 724a640

File tree

5 files changed

+189
-12
lines changed

5 files changed

+189
-12
lines changed

ssl/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ class GrowableArray {
461461
// CBBFinishArray behaves like |CBB_finish| but stores the result in an Array.
462462
OPENSSL_EXPORT bool CBBFinishArray(CBB *cbb, Array<uint8_t> *out);
463463

464+
OPENSSL_EXPORT UniquePtr<CRYPTO_BUFFER> x509_to_buffer(X509 *x509);
465+
464466
// GetAllNames helps to implement |*_get_all_*_names| style functions. It
465467
// writes at most |max_out| string pointers to |out| and returns the number that
466468
// it would have liked to have written. The strings written consist of

ssl/ssl_cert.cc

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -262,15 +262,24 @@ static enum leaf_cert_and_privkey_result_t do_leaf_cert_and_privkey_checks(
262262
// returns |leaf_cert_and_privkey_mismatch|. Otherwise it returns
263263
// |leaf_cert_and_privkey_ok|.
264264
static enum leaf_cert_and_privkey_result_t check_leaf_cert_and_privkey(
265-
CRYPTO_BUFFER *leaf_buffer, EVP_PKEY *privkey) {
265+
CRYPTO_BUFFER *leaf_buffer, EVP_PKEY *privkey, int *out_pubslot_idx) {
266266
CBS cert_cbs;
267267
CRYPTO_BUFFER_init_CBS(leaf_buffer, &cert_cbs);
268268
UniquePtr<EVP_PKEY> pubkey = ssl_cert_parse_pubkey(&cert_cbs);
269269
if (pubkey == nullptr) {
270270
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
271271
return leaf_cert_and_privkey_error;
272272
}
273-
return do_leaf_cert_and_privkey_checks(&cert_cbs, pubkey.get(), privkey);
273+
enum leaf_cert_and_privkey_result_t result = do_leaf_cert_and_privkey_checks(
274+
&cert_cbs, pubkey.get(), privkey);
275+
if(out_pubslot_idx != nullptr) {
276+
if(result == leaf_cert_and_privkey_ok) {
277+
*out_pubslot_idx = ssl_get_certificate_slot_index(pubkey.get());
278+
} else {
279+
*out_pubslot_idx = -1;
280+
}
281+
}
282+
return result;
274283
}
275284

276285
static int cert_set_chain_and_key(
@@ -291,7 +300,9 @@ static int cert_set_chain_and_key(
291300
return 0;
292301
}
293302

294-
switch (check_leaf_cert_and_privkey(leaf_buf, privkey)) {
303+
int slot_idx = -1;
304+
305+
switch (check_leaf_cert_and_privkey(leaf_buf, privkey, &slot_idx)) {
295306
case leaf_cert_and_privkey_error:
296307
return 0;
297308
case leaf_cert_and_privkey_mismatch:
@@ -300,27 +311,35 @@ static int cert_set_chain_and_key(
300311
case leaf_cert_and_privkey_ok:
301312
break;
302313
}
314+
assert(slot_idx >= 0);
303315

304316
if (!ssl_cert_check_cert_private_keys_usage(cert)) {
305317
return 0;
306318
}
307319

308-
// Certificate slot validity already checked in |check_leaf_cert_and_privkey|.
309-
int idx = ssl_get_certificate_slot_index(privkey);
310-
assert(idx >= 0);
311-
CERT_PKEY *cert_pkey = &cert->cert_private_keys[idx];
320+
// Certificate slot validity already checked and set by
321+
// |check_leaf_cert_and_privkey|.
322+
CERT_PKEY *cert_pkey = &cert->cert_private_keys[slot_idx];
312323

313-
// Update certificate slot index once all checks have passed.
324+
// If privatekey is currently set then reset it.
325+
// We either set a new |privatekey| or |privkey_method|
326+
// later below.
314327
if (cert_pkey->privatekey) {
315328
cert_pkey->privatekey.reset();
316329
}
317-
cert_pkey->privatekey = UpRef(privkey);
318-
cert->key_method = privkey_method;
330+
331+
if(privkey != nullptr) {
332+
cert_pkey->privatekey = UpRef(privkey);
333+
} else {
334+
cert->key_method = privkey_method;
335+
}
336+
319337
if (cert_pkey->chain) {
320338
cert_pkey->chain.reset();
321339
}
322340
cert_pkey->chain = std::move(certs);
323-
cert->cert_private_key_idx = idx;
341+
cert->cert_private_key_idx = slot_idx;
342+
324343
return 1;
325344
}
326345

ssl/ssl_privkey.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ static bool ssl_set_pkey(CERT *cert, EVP_PKEY *pkey) {
104104

105105
// Update certificate slot index once all checks have passed.
106106
cert->cert_private_keys[idx].privatekey = UpRef(pkey);
107+
cert->key_method = nullptr; // key_method should be cleared since we've set a private key
107108
cert->cert_private_key_idx = idx;
108109
return true;
109110
}

ssl/ssl_test.cc

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13637,5 +13637,160 @@ TEST(SSLTest, MixContextAndConnection) {
1363713637
EXPECT_FALSE(SSL_get_privatekey(ssl2.get()));
1363813638
}
1363913639

13640+
static size_t test_ecc_privkey_calls = 0;
13641+
13642+
static enum ssl_private_key_result_t test_ecc_privkey_complete(SSL *ssl,
13643+
uint8_t *out,
13644+
size_t *out_len,
13645+
size_t max_out) {
13646+
test_ecc_privkey_calls += 1;
13647+
return ssl_private_key_success;
13648+
}
13649+
13650+
static enum ssl_private_key_result_t test_ecc_privkey_sign(
13651+
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
13652+
uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
13653+
bssl::UniquePtr<EVP_PKEY> pkey(GetECDSATestKey());
13654+
13655+
if (EVP_PKEY_id(pkey.get()) !=
13656+
SSL_get_signature_algorithm_key_type(signature_algorithm)) {
13657+
return ssl_private_key_failure;
13658+
}
13659+
13660+
const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm);
13661+
bssl::ScopedEVP_MD_CTX ctx;
13662+
EVP_PKEY_CTX *pctx = nullptr;
13663+
if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr,
13664+
pkey.get())) {
13665+
return ssl_private_key_failure;
13666+
}
13667+
13668+
size_t len = 0;
13669+
if (!EVP_DigestSign(ctx.get(), nullptr, &len, in, in_len) || len > max_out) {
13670+
return ssl_private_key_failure;
13671+
}
13672+
13673+
*out_len = max_out;
13674+
13675+
if (!EVP_DigestSign(ctx.get(), out, out_len, in, in_len)) {
13676+
return ssl_private_key_failure;
13677+
}
13678+
13679+
return test_ecc_privkey_complete(ssl, out, out_len, max_out);
13680+
}
13681+
13682+
static enum ssl_private_key_result_t test_ecc_privkey_decrypt(
13683+
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out, const uint8_t *in,
13684+
size_t in_len) {
13685+
return ssl_private_key_failure;
13686+
}
13687+
13688+
static const SSL_PRIVATE_KEY_METHOD test_ecc_private_key_method = {
13689+
test_ecc_privkey_sign,
13690+
test_ecc_privkey_decrypt,
13691+
test_ecc_privkey_complete,
13692+
};
13693+
13694+
TEST(SSLTest, SSLPrivateKeyMethod) {
13695+
{
13696+
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
13697+
bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
13698+
13699+
bssl::UniquePtr<X509> ecdsa_cert(GetECDSATestCertificate());
13700+
bssl::UniquePtr<CRYPTO_BUFFER> ecdsa_leaf =
13701+
x509_to_buffer(ecdsa_cert.get());
13702+
std::vector<CRYPTO_BUFFER *> chain = {
13703+
ecdsa_leaf.get(),
13704+
};
13705+
13706+
// Index should be have been set to default, 0, but no key loaded
13707+
EXPECT_EQ(server_ctx->cert->cert_private_key_idx, SSL_PKEY_RSA);
13708+
EXPECT_EQ(
13709+
server_ctx->cert->cert_private_keys[SSL_PKEY_RSA].privatekey.get(),
13710+
nullptr);
13711+
EXPECT_EQ(server_ctx->cert->key_method, nullptr);
13712+
13713+
13714+
// Load a certificate chain containg the leaf but set private key method
13715+
ASSERT_TRUE(SSL_CTX_set_chain_and_key(server_ctx.get(), &chain[0],
13716+
chain.size(), nullptr,
13717+
&test_ecc_private_key_method));
13718+
13719+
// Should be initiall zero
13720+
ASSERT_EQ(test_ecc_privkey_calls, (size_t)0);
13721+
13722+
// Index must be ECC key now, but key_method must be set.
13723+
ASSERT_EQ(server_ctx->cert->cert_private_key_idx, SSL_PKEY_ECC);
13724+
ASSERT_EQ(server_ctx->cert->key_method, &test_ecc_private_key_method);
13725+
13726+
bssl::UniquePtr<SSL> client, server;
13727+
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
13728+
server_ctx.get(), ClientConfig(),
13729+
false));
13730+
13731+
ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
13732+
13733+
ASSERT_EQ(test_ecc_privkey_calls, (size_t)1);
13734+
13735+
// Check the internal slot index to verify that the correct slot was used
13736+
// during the handshake.
13737+
ASSERT_EQ(server->config->cert->cert_private_key_idx, SSL_PKEY_ECC);
13738+
ASSERT_EQ(server->config->cert->key_method, &test_ecc_private_key_method);
13739+
}
13740+
13741+
{
13742+
size_t current_invoke_count = test_ecc_privkey_calls;
13743+
13744+
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
13745+
bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
13746+
13747+
// Index should be have been set to default, 0, but no key loaded
13748+
EXPECT_EQ(server_ctx->cert->cert_private_key_idx, SSL_PKEY_RSA);
13749+
EXPECT_EQ(
13750+
server_ctx->cert->cert_private_keys[SSL_PKEY_RSA].privatekey.get(),
13751+
nullptr);
13752+
EXPECT_EQ(server_ctx->cert->key_method, nullptr);
13753+
13754+
bssl::UniquePtr<X509> ed_cert(GetED25519TestCertificate());
13755+
bssl::UniquePtr<EVP_PKEY> ed_key(GetED25519TestKey());
13756+
bssl::UniquePtr<CRYPTO_BUFFER> ed_leaf = x509_to_buffer(ed_cert.get());
13757+
std::vector<CRYPTO_BUFFER *> ed_chain = {
13758+
ed_leaf.get(),
13759+
};
13760+
13761+
// Load a certificate chain containg the leaf but set private key method
13762+
ASSERT_TRUE(SSL_CTX_set_chain_and_key(server_ctx.get(), &ed_chain[0],
13763+
ed_chain.size(), ed_key.get(),
13764+
nullptr));
13765+
13766+
// Index must be ECC key now, but key_method must not be set.
13767+
ASSERT_EQ(server_ctx->cert->cert_private_key_idx, SSL_PKEY_ED25519);
13768+
ASSERT_EQ(server_ctx->cert->key_method, nullptr);
13769+
13770+
std::vector<uint16_t> sigalgs = {SSL_SIGN_ED25519};
13771+
13772+
ASSERT_TRUE(SSL_CTX_set_signing_algorithm_prefs(
13773+
client_ctx.get(), sigalgs.data(), sigalgs.size()));
13774+
ASSERT_TRUE(SSL_CTX_set_verify_algorithm_prefs(
13775+
client_ctx.get(), sigalgs.data(), sigalgs.size()));
13776+
13777+
bssl::UniquePtr<SSL> client, server;
13778+
ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
13779+
server_ctx.get(), ClientConfig(),
13780+
false));
13781+
13782+
ASSERT_TRUE(CompleteHandshakes(client.get(), server.get()));
13783+
13784+
// This should still be the same, as we didn't use the private key method
13785+
// functionality, so it shouldn't have incremented.
13786+
ASSERT_EQ(test_ecc_privkey_calls, current_invoke_count);
13787+
13788+
// Check the internal slot index to verify that the correct slot was used
13789+
// during the handshake and that key_method was not set.
13790+
ASSERT_EQ(server->config->cert->cert_private_key_idx, SSL_PKEY_ED25519);
13791+
ASSERT_EQ(server->config->cert->key_method, nullptr);
13792+
}
13793+
}
13794+
1364013795
} // namespace
1364113796
BSSL_NAMESPACE_END

ssl/ssl_x509.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ static void check_ssl_ctx_x509_method(const SSL_CTX *ctx) {
170170

171171
// x509_to_buffer returns a |CRYPTO_BUFFER| that contains the serialised
172172
// contents of |x509|.
173-
static UniquePtr<CRYPTO_BUFFER> x509_to_buffer(X509 *x509) {
173+
UniquePtr<CRYPTO_BUFFER> x509_to_buffer(X509 *x509) {
174174
uint8_t *buf = NULL;
175175
int cert_len = i2d_X509(x509, &buf);
176176
if (cert_len <= 0) {

0 commit comments

Comments
 (0)