Add |SSL_set_retain_only_sha256_of_client_certs|.

Previously the option to retain only the SHA-256 hash of client
certificates could only be set at the |SSL_CTX| level. This change makes
|SSL| objects inherit the setting from the |SSL_CTX|, but allows it to
be overridden on a per-|SSL| basis.

Change-Id: Id435934af3d425d5f008d2f3b9751d1d0884ee55
Reviewed-on: https://boringssl-review.googlesource.com/12182
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
This commit is contained in:
David Benjamin 2016-11-17 10:53:09 +09:00 committed by CQ bot account: commit-bot@chromium.org
parent a933c38f1a
commit bbaf367969
9 changed files with 149 additions and 5 deletions

View File

@ -3116,6 +3116,14 @@ OPENSSL_EXPORT size_t SSL_get_server_random(const SSL *ssl, uint8_t *out,
* NULL if one has not been negotiated yet or there is no pending handshake. */
OPENSSL_EXPORT const SSL_CIPHER *SSL_get_pending_cipher(const SSL *ssl);
/* SSL_set_retain_only_sha256_of_client_certs, on a server, sets whether only
* the SHA-256 hash of peer's certificate should be saved in memory and in the
* session. This can save memory, ticket size and session cache space. If
* enabled, |SSL_get_peer_certificate| will return NULL after the handshake
* completes. See the |peer_sha256| field of |SSL_SESSION| for the hash. */
OPENSSL_EXPORT void SSL_set_retain_only_sha256_of_client_certs(SSL *ssl,
int enable);
/* SSL_CTX_set_retain_only_sha256_of_client_certs, on a server, sets whether
* only the SHA-256 hash of peer's certificate should be saved in memory and in
* the session. This can save memory, ticket size and session cache space. If
@ -4200,6 +4208,11 @@ struct ssl_st {
* we'll advertise support. */
unsigned tlsext_channel_id_enabled:1;
/* retain_only_sha256_of_client_certs is true if we should compute the SHA256
* hash of the peer's certificate and then discard it to save memory and
* session space. Only effective on the server side. */
unsigned retain_only_sha256_of_client_certs:1;
/* TODO(agl): remove once node.js not longer references this. */
int tlsext_status_type;
};

View File

@ -482,7 +482,7 @@ int ssl3_accept(SSL *ssl) {
/* If we aren't retaining peer certificates then we can discard it
* now. */
if (ssl->s3->new_session != NULL &&
ssl->ctx->retain_only_sha256_of_client_certs) {
ssl->retain_only_sha256_of_client_certs) {
X509_free(ssl->s3->new_session->x509_peer);
ssl->s3->new_session->x509_peer = NULL;
sk_X509_pop_free(ssl->s3->new_session->x509_chain, X509_free);
@ -1313,7 +1313,7 @@ static int ssl3_get_client_certificate(SSL *ssl) {
CBS_init(&certificate_msg, ssl->init_msg, ssl->init_num);
uint8_t alert;
STACK_OF(X509) *chain = ssl_parse_cert_chain(
ssl, &alert, ssl->ctx->retain_only_sha256_of_client_certs
ssl, &alert, ssl->retain_only_sha256_of_client_certs
? ssl->s3->new_session->peer_sha256
: NULL,
&certificate_msg);
@ -1352,7 +1352,7 @@ static int ssl3_get_client_certificate(SSL *ssl) {
ssl->s3->new_session->verify_result = X509_V_OK;
} else {
/* The hash would have been filled in. */
if (ssl->ctx->retain_only_sha256_of_client_certs) {
if (ssl->retain_only_sha256_of_client_certs) {
ssl->s3->new_session->peer_sha256_valid = 1;
}

View File

@ -412,6 +412,8 @@ SSL *SSL_new(SSL_CTX *ctx) {
assert(ssl->sid_ctx_length <= sizeof ssl->sid_ctx);
memcpy(&ssl->sid_ctx, &ctx->sid_ctx, sizeof(ssl->sid_ctx));
ssl->verify_callback = ctx->default_verify_callback;
ssl->retain_only_sha256_of_client_certs =
ctx->retain_only_sha256_of_client_certs;
ssl->param = X509_VERIFY_PARAM_new();
if (!ssl->param) {
@ -2908,6 +2910,10 @@ const SSL_CIPHER *SSL_get_pending_cipher(const SSL *ssl) {
return ssl->s3->tmp.new_cipher;
}
void SSL_set_retain_only_sha256_of_client_certs(SSL *ssl, int enabled) {
ssl->retain_only_sha256_of_client_certs = !!enabled;
}
void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx, int enabled) {
ctx->retain_only_sha256_of_client_certs = !!enabled;
}

View File

@ -641,7 +641,13 @@ int ssl_session_is_resumable(const SSL *ssl, const SSL_SESSION *session) {
* version. */
ssl->version == session->ssl_version &&
/* Only resume if the session's cipher matches the negotiated one. */
ssl->s3->tmp.new_cipher == session->cipher;
ssl->s3->tmp.new_cipher == session->cipher &&
/* If the session contains a client certificate (either the full
* certificate or just the hash) then require that the form of the
* certificate matches the current configuration. */
((session->x509_peer == NULL && !session->peer_sha256_valid) ||
session->peer_sha256_valid ==
ssl->retain_only_sha256_of_client_certs);
}
/* ssl_lookup_session looks up |session_id| in the session cache and sets

View File

@ -1436,6 +1436,25 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume) {
}
}
bool expected_sha256_client_cert = config->expect_sha256_client_cert_initial;
if (is_resume) {
expected_sha256_client_cert = config->expect_sha256_client_cert_resume;
}
if (SSL_get_session(ssl)->peer_sha256_valid != expected_sha256_client_cert) {
fprintf(stderr,
"Unexpected SHA-256 client cert state: expected:%d is_resume:%d.\n",
expected_sha256_client_cert, is_resume);
return false;
}
if (expected_sha256_client_cert &&
SSL_get_session(ssl)->x509_peer != nullptr) {
fprintf(stderr, "Have both client cert and SHA-256 hash: is_resume:%d.\n",
is_resume);
return false;
}
return true;
}
@ -1595,6 +1614,12 @@ static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
if (config->max_cert_list > 0) {
SSL_set_max_cert_list(ssl.get(), config->max_cert_list);
}
if (!is_resume && config->retain_only_sha256_client_cert_initial) {
SSL_set_retain_only_sha256_of_client_certs(ssl.get(), 1);
}
if (is_resume && config->retain_only_sha256_client_cert_resume) {
SSL_set_retain_only_sha256_of_client_certs(ssl.get(), 1);
}
int sock = Connect(config->port);
if (sock == -1) {

View File

@ -9532,6 +9532,87 @@ func addCertificateTests() {
}
}
func addRetainOnlySHA256ClientCertTests() {
for _, ver := range tlsVersions {
// Test that enabling
// SSL_CTX_set_retain_only_sha256_of_client_certs without
// actually requesting a client certificate is a no-op.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-NoCert-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
flags: []string{
"-retain-only-sha256-client-cert-initial",
"-retain-only-sha256-client-cert-resume",
},
resumeSession: true,
})
// Test that when retaining only a SHA-256 certificate is
// enabled, the hash appears as expected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-Cert-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Certificates: []Certificate{rsaCertificate},
},
flags: []string{
"-verify-peer",
"-retain-only-sha256-client-cert-initial",
"-retain-only-sha256-client-cert-resume",
"-expect-sha256-client-cert-initial",
"-expect-sha256-client-cert-resume",
},
resumeSession: true,
})
// Test that when the config changes from on to off, a
// resumption is rejected because the server now wants the full
// certificate chain.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-OnOff-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Certificates: []Certificate{rsaCertificate},
},
flags: []string{
"-verify-peer",
"-retain-only-sha256-client-cert-initial",
"-expect-sha256-client-cert-initial",
},
resumeSession: true,
expectResumeRejected: true,
})
// Test that when the config changes from off to on, a
// resumption is rejected because the server now wants just the
// hash.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-OffOn-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Certificates: []Certificate{rsaCertificate},
},
flags: []string{
"-verify-peer",
"-retain-only-sha256-client-cert-resume",
"-expect-sha256-client-cert-resume",
},
resumeSession: true,
expectResumeRejected: true,
})
}
}
func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
defer wg.Done()
@ -9658,6 +9739,7 @@ func main() {
addPeekTests()
addRecordVersionTests()
addCertificateTests()
addRetainOnlySHA256ClientCertTests()
var wg sync.WaitGroup

View File

@ -108,6 +108,14 @@ const Flag<bool> kBoolFlags[] = {
{ "-peek-then-read", &TestConfig::peek_then_read },
{ "-enable-grease", &TestConfig::enable_grease },
{ "-use-exporter-between-reads", &TestConfig::use_exporter_between_reads },
{ "-retain-only-sha256-client-cert-initial",
&TestConfig::retain_only_sha256_client_cert_initial },
{ "-retain-only-sha256-client-cert-resume",
&TestConfig::retain_only_sha256_client_cert_resume },
{ "-expect-sha256-client-cert-initial",
&TestConfig::expect_sha256_client_cert_initial },
{ "-expect-sha256-client-cert-resume",
&TestConfig::expect_sha256_client_cert_resume },
};
const Flag<std::string> kStringFlags[] = {

View File

@ -122,6 +122,10 @@ struct TestConfig {
int expect_cipher_no_aes = 0;
std::string expect_peer_cert_file;
int resumption_delay = 0;
bool retain_only_sha256_client_cert_initial = false;
bool retain_only_sha256_client_cert_resume = false;
bool expect_sha256_client_cert_initial = false;
bool expect_sha256_client_cert_resume = false;
};
bool ParseConfig(int argc, char **argv, TestConfig *out_config);

View File

@ -175,7 +175,7 @@ int tls13_process_certificate(SSL *ssl, int allow_anonymous) {
}
const int retain_sha256 =
ssl->server && ssl->ctx->retain_only_sha256_of_client_certs;
ssl->server && ssl->retain_only_sha256_of_client_certs;
int ret = 0;
STACK_OF(X509) *chain = sk_X509_new_null();