Add server-side support for asynchronous RSA decryption.
Change-Id: I6df623f3e9bc88acc52043f16b34649b7af67663
Reviewed-on: https://boringssl-review.googlesource.com/5531
Reviewed-by: Adam Langley <alangley@gmail.com>
diff --git a/ssl/d1_srvr.c b/ssl/d1_srvr.c
index 267bdf1..f1e8826 100644
--- a/ssl/d1_srvr.c
+++ b/ssl/d1_srvr.c
@@ -330,6 +330,7 @@
case SSL3_ST_SR_KEY_EXCH_A:
case SSL3_ST_SR_KEY_EXCH_B:
+ case SSL3_ST_SR_KEY_EXCH_C:
ret = ssl3_get_client_key_exchange(s);
if (ret <= 0) {
goto end;
diff --git a/ssl/internal.h b/ssl/internal.h
index 4d0f968..0a6c31c 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -464,6 +464,13 @@
enum ssl_private_key_result_t ssl_private_key_sign_complete(
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out);
+enum ssl_private_key_result_t ssl_private_key_decrypt(
+ SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+ const uint8_t *in, size_t in_len);
+
+enum ssl_private_key_result_t ssl_private_key_decrypt_complete(
+ SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out);
+
/* Custom extensions */
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index 2c915e6..c9b8172 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -399,6 +399,7 @@
case SSL3_ST_SR_KEY_EXCH_A:
case SSL3_ST_SR_KEY_EXCH_B:
+ case SSL3_ST_SR_KEY_EXCH_C:
ret = ssl3_get_client_key_exchange(s);
if (ret <= 0) {
goto end;
@@ -1586,16 +1587,13 @@
}
int ssl3_get_client_key_exchange(SSL *s) {
- int al, ok;
- long n;
+ int al;
CBS client_key_exchange;
uint32_t alg_k;
uint32_t alg_a;
uint8_t *premaster_secret = NULL;
size_t premaster_secret_len = 0;
- RSA *rsa = NULL;
uint8_t *decrypt_buf = NULL;
- EVP_PKEY *pkey = NULL;
BIGNUM *pub = NULL;
DH *dh_srvr;
@@ -1606,17 +1604,18 @@
unsigned int psk_len = 0;
uint8_t psk[PSK_MAX_PSK_LEN];
- n = s->method->ssl_get_message(s, SSL3_ST_SR_KEY_EXCH_A,
- SSL3_ST_SR_KEY_EXCH_B,
- SSL3_MT_CLIENT_KEY_EXCHANGE, 2048, /* ??? */
- ssl_hash_message, &ok);
-
- if (!ok) {
- return n;
+ if (s->state == SSL3_ST_SR_KEY_EXCH_A ||
+ s->state == SSL3_ST_SR_KEY_EXCH_B) {
+ int ok;
+ const long n = s->method->ssl_get_message(
+ s, SSL3_ST_SR_KEY_EXCH_A, SSL3_ST_SR_KEY_EXCH_B,
+ SSL3_MT_CLIENT_KEY_EXCHANGE, 2048 /* ??? */, ssl_hash_message, &ok);
+ if (!ok) {
+ return n;
+ }
}
- CBS_init(&client_key_exchange, s->init_msg, n);
-
+ CBS_init(&client_key_exchange, s->init_msg, s->init_num);
alg_k = s->s3->tmp.new_cipher->algorithm_mkey;
alg_a = s->s3->tmp.new_cipher->algorithm_auth;
@@ -1673,57 +1672,8 @@
CBS encrypted_premaster_secret;
uint8_t rand_premaster_secret[SSL_MAX_MASTER_KEY_LENGTH];
uint8_t good;
- size_t rsa_size, decrypt_len, premaster_index, j;
-
- pkey = s->cert->privatekey;
- if (pkey == NULL || pkey->type != EVP_PKEY_RSA || pkey->pkey.rsa == NULL) {
- al = SSL_AD_HANDSHAKE_FAILURE;
- OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_RSA_CERTIFICATE);
- goto f_err;
- }
- rsa = pkey->pkey.rsa;
-
- /* TLS and [incidentally] DTLS{0xFEFF} */
- if (s->version > SSL3_VERSION) {
- CBS copy = client_key_exchange;
- if (!CBS_get_u16_length_prefixed(&client_key_exchange,
- &encrypted_premaster_secret) ||
- CBS_len(&client_key_exchange) != 0) {
- if (!(s->options & SSL_OP_TLS_D5_BUG)) {
- al = SSL_AD_DECODE_ERROR;
- OPENSSL_PUT_ERROR(SSL, SSL_R_TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG);
- goto f_err;
- } else {
- CRYPTO_STATIC_MUTEX_lock_write(&g_d5_bug_lock);
- g_d5_bug_use_count++;
- CRYPTO_STATIC_MUTEX_unlock(&g_d5_bug_lock);
-
- encrypted_premaster_secret = copy;
- }
- }
- } else {
- encrypted_premaster_secret = client_key_exchange;
- }
-
- /* Reject overly short RSA keys because we want to be sure that the buffer
- * size makes it safe to iterate over the entire size of a premaster secret
- * (SSL_MAX_MASTER_KEY_LENGTH). The actual expected size is larger due to
- * RSA padding, but the bound is sufficient to be safe. */
- rsa_size = RSA_size(rsa);
- if (rsa_size < SSL_MAX_MASTER_KEY_LENGTH) {
- al = SSL_AD_DECRYPT_ERROR;
- OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
- goto f_err;
- }
-
- /* We must not leak whether a decryption failure occurs because of
- * Bleichenbacher's attack on PKCS #1 v1.5 RSA padding (see RFC 2246,
- * section 7.4.7.1). The code follows that advice of the TLS RFC and
- * generates a random premaster secret for the case that the decrypt fails.
- * See https://tools.ietf.org/html/rfc5246#section-7.4.7.1 */
- if (!RAND_bytes(rand_premaster_secret, sizeof(rand_premaster_secret))) {
- goto err;
- }
+ size_t decrypt_len, premaster_index, j;
+ const size_t rsa_size = ssl_private_key_max_signature_len(s);
/* Allocate a buffer large enough for an RSA decryption. */
decrypt_buf = OPENSSL_malloc(rsa_size);
@@ -1732,13 +1682,72 @@
goto err;
}
- /* Decrypt with no padding. PKCS#1 padding will be removed as part of the
- * timing-sensitive code below. */
- if (!RSA_decrypt(rsa, &decrypt_len, decrypt_buf, rsa_size,
- CBS_data(&encrypted_premaster_secret),
- CBS_len(&encrypted_premaster_secret), RSA_NO_PADDING)) {
- goto err;
+ enum ssl_private_key_result_t decrypt_result;
+ if (s->state == SSL3_ST_SR_KEY_EXCH_B) {
+ if (!ssl_has_private_key(s) || ssl_private_key_type(s) != EVP_PKEY_RSA) {
+ al = SSL_AD_HANDSHAKE_FAILURE;
+ OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_RSA_CERTIFICATE);
+ goto f_err;
+ }
+ /* TLS and [incidentally] DTLS{0xFEFF} */
+ if (s->version > SSL3_VERSION) {
+ CBS copy = client_key_exchange;
+ if (!CBS_get_u16_length_prefixed(&client_key_exchange,
+ &encrypted_premaster_secret) ||
+ CBS_len(&client_key_exchange) != 0) {
+ if (!(s->options & SSL_OP_TLS_D5_BUG)) {
+ al = SSL_AD_DECODE_ERROR;
+ OPENSSL_PUT_ERROR(SSL,
+ SSL_R_TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG);
+ goto f_err;
+ } else {
+ CRYPTO_STATIC_MUTEX_lock_write(&g_d5_bug_lock);
+ g_d5_bug_use_count++;
+ CRYPTO_STATIC_MUTEX_unlock(&g_d5_bug_lock);
+
+ encrypted_premaster_secret = copy;
+ }
+ }
+ } else {
+ encrypted_premaster_secret = client_key_exchange;
+ }
+
+ /* Reject overly short RSA keys because we want to be sure that the buffer
+ * size makes it safe to iterate over the entire size of a premaster
+ * secret (SSL_MAX_MASTER_KEY_LENGTH). The actual expected size is larger
+ * due to RSA padding, but the bound is sufficient to be safe. */
+ if (rsa_size < SSL_MAX_MASTER_KEY_LENGTH) {
+ al = SSL_AD_DECRYPT_ERROR;
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
+ goto f_err;
+ }
+
+ /* Decrypt with no padding. PKCS#1 padding will be removed as part of the
+ * timing-sensitive code below. */
+ decrypt_result = ssl_private_key_decrypt(
+ s, decrypt_buf, &decrypt_len, rsa_size,
+ CBS_data(&encrypted_premaster_secret),
+ CBS_len(&encrypted_premaster_secret));
+ } else {
+ assert(s->state == SSL3_ST_SR_KEY_EXCH_C);
+ /* Complete async decrypt. */
+ decrypt_result = ssl_private_key_decrypt_complete(
+ s, decrypt_buf, &decrypt_len, rsa_size);
}
+
+ switch (decrypt_result) {
+ case ssl_private_key_success:
+ s->rwstate = SSL_NOTHING;
+ break;
+ case ssl_private_key_failure:
+ s->rwstate = SSL_NOTHING;
+ goto err;
+ case ssl_private_key_retry:
+ s->rwstate = SSL_PRIVATE_KEY_OPERATION;
+ s->state = SSL3_ST_SR_KEY_EXCH_C;
+ goto err;
+ }
+
if (decrypt_len != rsa_size) {
/* This should never happen, but do a check so we do not read
* uninitialized memory. */
@@ -1782,6 +1791,15 @@
good &= constant_time_eq_8(premaster_secret[1],
(unsigned)(s->client_version & 0xff));
+ /* We must not leak whether a decryption failure occurs because of
+ * Bleichenbacher's attack on PKCS #1 v1.5 RSA padding (see RFC 2246,
+ * section 7.4.7.1). The code follows that advice of the TLS RFC and
+ * generates a random premaster secret for the case that the decrypt
+ * fails. See https://tools.ietf.org/html/rfc5246#section-7.4.7.1 */
+ if (!RAND_bytes(rand_premaster_secret, sizeof(rand_premaster_secret))) {
+ goto err;
+ }
+
/* Now copy rand_premaster_secret over premaster_secret using
* decrypt_good_mask. */
for (j = 0; j < sizeof(rand_premaster_secret); j++) {
diff --git a/ssl/ssl_rsa.c b/ssl/ssl_rsa.c
index ccd3858..512a41f 100644
--- a/ssl/ssl_rsa.c
+++ b/ssl/ssl_rsa.c
@@ -376,3 +376,33 @@
/* Only custom keys may be asynchronous. */
return ssl->cert->key_method->sign_complete(ssl, out, out_len, max_out);
}
+
+enum ssl_private_key_result_t ssl_private_key_decrypt(
+ SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+ const uint8_t *in, size_t in_len) {
+ if (ssl->cert->key_method != NULL) {
+ return ssl->cert->key_method->decrypt(ssl, out, out_len, max_out, in,
+ in_len);
+ }
+
+ if (ssl_private_key_type(ssl) != EVP_PKEY_RSA) {
+ /* Decrypt operations are only supported for RSA keys. */
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ return ssl_private_key_failure;
+ }
+
+ enum ssl_private_key_result_t ret = ssl_private_key_failure;
+ RSA *rsa = ssl->cert->privatekey->pkey.rsa;
+ /* Decrypt with no padding. PKCS#1 padding will be removed as part
+ * of the timing-sensitive code by the caller. */
+ if (RSA_decrypt(rsa, out_len, out, max_out, in, in_len, RSA_NO_PADDING)) {
+ ret = ssl_private_key_success;
+ }
+ return ret;
+}
+
+enum ssl_private_key_result_t ssl_private_key_decrypt_complete(
+ SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out) {
+ /* Only custom keys may be asynchronous. */
+ return ssl->cert->key_method->decrypt_complete(ssl, out, out_len, max_out);
+}
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 17a91ad..3f4e9cf 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -97,10 +97,10 @@
bool handshake_done = false;
// private_key is the underlying private key used when testing custom keys.
ScopedEVP_PKEY private_key;
- std::vector<uint8_t> signature;
- // signature_retries is the number of times an asynchronous sign operation has
- // been retried.
- unsigned signature_retries = 0;
+ std::vector<uint8_t> private_key_result;
+ // private_key_retries is the number of times an asynchronous private key
+ // operation has been retried.
+ unsigned private_key_retries = 0;
bool got_new_session = false;
};
@@ -153,7 +153,7 @@
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
const EVP_MD *md, const uint8_t *in, size_t in_len) {
TestState *test_state = GetTestState(ssl);
- if (!test_state->signature.empty()) {
+ if (!test_state->private_key_result.empty()) {
fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
abort();
}
@@ -171,42 +171,103 @@
!EVP_PKEY_sign(ctx.get(), nullptr, &len, in, in_len)) {
return ssl_private_key_failure;
}
- test_state->signature.resize(len);
- if (!EVP_PKEY_sign(ctx.get(), bssl::vector_data(&test_state->signature), &len,
- in, in_len)) {
+ test_state->private_key_result.resize(len);
+ if (!EVP_PKEY_sign(ctx.get(), bssl::vector_data(
+ &test_state->private_key_result), &len, in, in_len)) {
return ssl_private_key_failure;
}
- test_state->signature.resize(len);
+ test_state->private_key_result.resize(len);
- // The signature will be released asynchronously in |AsyncPrivateKeySignComplete|.
+ // The signature will be released asynchronously in
+ // |AsyncPrivateKeySignComplete|.
return ssl_private_key_retry;
}
static ssl_private_key_result_t AsyncPrivateKeySignComplete(
SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out) {
TestState *test_state = GetTestState(ssl);
- if (test_state->signature.empty()) {
+ if (test_state->private_key_result.empty()) {
fprintf(stderr,
"AsyncPrivateKeySignComplete called without operation pending.\n");
abort();
}
- if (test_state->signature_retries < 2) {
+ if (test_state->private_key_retries < 2) {
// Only return the signature on the second attempt, to test both incomplete
// |sign| and |sign_complete|.
return ssl_private_key_retry;
}
- if (max_out < test_state->signature.size()) {
+ if (max_out < test_state->private_key_result.size()) {
fprintf(stderr, "Output buffer too small.\n");
return ssl_private_key_failure;
}
- memcpy(out, bssl::vector_data(&test_state->signature),
- test_state->signature.size());
- *out_len = test_state->signature.size();
+ memcpy(out, bssl::vector_data(&test_state->private_key_result),
+ test_state->private_key_result.size());
+ *out_len = test_state->private_key_result.size();
- test_state->signature.clear();
- test_state->signature_retries = 0;
+ test_state->private_key_result.clear();
+ test_state->private_key_retries = 0;
+ return ssl_private_key_success;
+}
+
+static ssl_private_key_result_t AsyncPrivateKeyDecrypt(
+ SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+ const uint8_t *in, size_t in_len) {
+ TestState *test_state = GetTestState(ssl);
+ if (!test_state->private_key_result.empty()) {
+ fprintf(stderr,
+ "AsyncPrivateKeyDecrypt called with operation pending.\n");
+ abort();
+ }
+
+ EVP_PKEY *pkey = test_state->private_key.get();
+ if (pkey->type != EVP_PKEY_RSA || pkey->pkey.rsa == NULL) {
+ fprintf(stderr,
+ "AsyncPrivateKeyDecrypt called with incorrect key type.\n");
+ abort();
+ }
+ RSA *rsa = pkey->pkey.rsa;
+ test_state->private_key_result.resize(RSA_size(rsa));
+ if (!RSA_decrypt(rsa, out_len,
+ bssl::vector_data(&test_state->private_key_result),
+ RSA_size(rsa), in, in_len, RSA_NO_PADDING)) {
+ return ssl_private_key_failure;
+ }
+
+ test_state->private_key_result.resize(*out_len);
+
+ // The decryption will be released asynchronously in
+ // |AsyncPrivateKeyDecryptComplete|.
+ return ssl_private_key_retry;
+}
+
+static ssl_private_key_result_t AsyncPrivateKeyDecryptComplete(
+ SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out) {
+ TestState *test_state = GetTestState(ssl);
+ if (test_state->private_key_result.empty()) {
+ fprintf(stderr,
+ "AsyncPrivateKeyDecryptComplete called without operation "
+ "pending.\n");
+ abort();
+ }
+
+ if (test_state->private_key_retries < 2) {
+ // Only return the decryption on the second attempt, to test both incomplete
+ // |decrypt| and |decrypt_complete|.
+ return ssl_private_key_retry;
+ }
+
+ if (max_out < test_state->private_key_result.size()) {
+ fprintf(stderr, "Output buffer too small.\n");
+ return ssl_private_key_failure;
+ }
+ memcpy(out, bssl::vector_data(&test_state->private_key_result),
+ test_state->private_key_result.size());
+ *out_len = test_state->private_key_result.size();
+
+ test_state->private_key_result.clear();
+ test_state->private_key_retries = 0;
return ssl_private_key_success;
}
@@ -215,6 +276,8 @@
AsyncPrivateKeyMaxSignatureLen,
AsyncPrivateKeySign,
AsyncPrivateKeySignComplete,
+ AsyncPrivateKeyDecrypt,
+ AsyncPrivateKeyDecryptComplete
};
template<typename T>
@@ -250,7 +313,7 @@
}
if (!config->key_file.empty()) {
- if (config->use_async_private_key) {
+ if (config->async) {
test_state->private_key = LoadPrivateKey(config->key_file.c_str());
if (!test_state->private_key) {
return false;
@@ -789,7 +852,7 @@
// The handshake will resume without a second call to the early callback.
return InstallCertificate(ssl);
case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
- test_state->signature_retries++;
+ test_state->private_key_retries++;
return true;
default:
return false;
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index fb1e46f..17fdff9 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2585,7 +2585,7 @@
// TLS client auth.
tests = append(tests, testCase{
testType: clientTest,
- name: "ClientAuth-Client",
+ name: "ClientAuth-RSA-Client",
config: Config{
ClientAuth: RequireAnyClientCert,
},
@@ -2594,35 +2594,50 @@
"-key-file", path.Join(*resourceDir, rsaKeyFile),
},
})
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "ClientAuth-ECDSA-Client",
+ config: Config{
+ ClientAuth: RequireAnyClientCert,
+ },
+ flags: []string{
+ "-cert-file", path.Join(*resourceDir, ecdsaCertificateFile),
+ "-key-file", path.Join(*resourceDir, ecdsaKeyFile),
+ },
+ })
if async {
+ // Test async keys against each key exchange.
tests = append(tests, testCase{
- testType: clientTest,
- name: "ClientAuth-Client-AsyncKey",
+ testType: serverTest,
+ name: "Basic-Server-RSA",
config: Config{
- ClientAuth: RequireAnyClientCert,
+ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
},
flags: []string{
"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
"-key-file", path.Join(*resourceDir, rsaKeyFile),
- "-use-async-private-key",
},
})
tests = append(tests, testCase{
testType: serverTest,
- name: "Basic-Server-RSAAsyncKey",
+ name: "Basic-Server-ECDHE-RSA",
+ config: Config{
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
flags: []string{
"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
"-key-file", path.Join(*resourceDir, rsaKeyFile),
- "-use-async-private-key",
},
})
tests = append(tests, testCase{
testType: serverTest,
- name: "Basic-Server-ECDSAAsyncKey",
+ name: "Basic-Server-ECDHE-ECDSA",
+ config: Config{
+ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ },
flags: []string{
"-cert-file", path.Join(*resourceDir, ecdsaCertificateFile),
"-key-file", path.Join(*resourceDir, ecdsaKeyFile),
- "-use-async-private-key",
},
})
}
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 54492df..abec0e1 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -78,7 +78,6 @@
{ "-use-export-context", &TestConfig::use_export_context },
{ "-no-legacy-server-connect", &TestConfig::no_legacy_server_connect },
{ "-tls-unique", &TestConfig::tls_unique },
- { "-use-async-private-key", &TestConfig::use_async_private_key },
{ "-expect-ticket-renewal", &TestConfig::expect_ticket_renewal },
{ "-expect-no-session", &TestConfig::expect_no_session },
{ "-use-ticket-callback", &TestConfig::use_ticket_callback },
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index af1dd80..23fa1f11 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -79,7 +79,6 @@
bool use_export_context = false;
bool no_legacy_server_connect = false;
bool tls_unique = false;
- bool use_async_private_key = false;
bool expect_ticket_renewal = false;
bool expect_no_session = false;
bool use_ticket_callback = false;