Skip to content

Commit

Permalink
Add an SSL_CREDENTIAL API for ECDSA/RSA and delegated credentials
Browse files Browse the repository at this point in the history
This adds a notion of "credentials" to BoringSSL's API, to support
certificate selection by key type (typically ECDSA vs RSA), though the
aim is for it to be generalizable to other certificate types and other
kinds of selection criteria, such as Trust Expressions, or Merkle Tree
Certificates. Since we already had some nascent delegated credentials
I've reworked that feature with SSL_CREDENTIALs as well.

The model is that you create an SSL_CREDENTIAL object containing all the
configuration for what you are authenticating as. An X.509
SSL_CREDENTIAL has a certificate chain, private key, optionally an OCSP
response and SCT list. Delegated credentials are similar. In the future,
we might use this for raw public keys, other certificate types, etc.
Once you set those up, you configure those on the SSL or SSL_CTX in
preference order, and BoringSSL will internally pick the first one that
is usable.

The current implementation ends up redundantly selecting the signature
algorithm a couple of times. This works but is a little goofy. A
follow-up change will remove this redundancy. The protocol between the
runner and shim for tests is also a little weird, but it was the easiest
way I could think of for injecting that. Long-term, I think we should
just replace that protocol with a JSON structure. (See
https://crbug.com/boringssl/704.)

As split handshakes are in the process of being replaced with handshake
hints, this won't work with split handshakes. It works with handshake
hints without any extra work.

Update-Note: The delegated credentials API has been revamped.
Previously, it worked by configuring an optional delegated credential
and key with your normal certificate chain. This has the side effect of
forcing your DC issuer and your fallback certificate to be the same. The
SSL_CREDENTIAL API lifts this restriction.

A delegated credential is now just a different kind of credential. It
may use the same certificate chain as an X.509 credential or be
completely separate. All the SSL_CREDENTIAL APIs take CRYPTO_BUFFERs,
so, if common, the buffers may be shared to reduce memory.

The SSL_delegated_credential_used API is also removed, in favor of the
more general SSL_get0_selected_credential API. Callers can use ex_data
or pointer equality to identify the credential.

Bug: 249
Change-Id: Ibc290df3b7b95f148df12625e41cf55c50566602
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66690
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
  • Loading branch information
davidben authored and Boringssl LUCI CQ committed Mar 7, 2024
1 parent 1a118bb commit 91a3f26
Show file tree
Hide file tree
Showing 29 changed files with 2,666 additions and 1,463 deletions.
3 changes: 2 additions & 1 deletion include/openssl/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ extern "C" {
// A consumer may use this symbol in the preprocessor to temporarily build
// against multiple revisions of BoringSSL at the same time. It is not
// recommended to do so for longer than is necessary.
#define BORINGSSL_API_VERSION 31
#define BORINGSSL_API_VERSION 32

#if defined(BORINGSSL_SHARED_LIBRARY)

Expand Down Expand Up @@ -358,6 +358,7 @@ typedef struct sha_state_st SHA_CTX;
typedef struct spake2_ctx_st SPAKE2_CTX;
typedef struct srtp_protection_profile_st SRTP_PROTECTION_PROFILE;
typedef struct ssl_cipher_st SSL_CIPHER;
typedef struct ssl_credential_st SSL_CREDENTIAL;
typedef struct ssl_ctx_st SSL_CTX;
typedef struct ssl_early_callback_ctx SSL_CLIENT_HELLO;
typedef struct ssl_ech_keys_st SSL_ECH_KEYS;
Expand Down
240 changes: 199 additions & 41 deletions include/openssl/ssl.h

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions ssl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_library(
ssl_buffer.cc
ssl_cert.cc
ssl_cipher.cc
ssl_credential.cc
ssl_file.cc
ssl_key_share.cc
ssl_lib.cc
Expand Down
75 changes: 37 additions & 38 deletions ssl/extensions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,8 @@ static bool ext_ocsp_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
static bool ext_ocsp_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
SSL *const ssl = hs->ssl;
if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
!hs->ocsp_stapling_requested || hs->config->cert->ocsp_response == NULL ||
!hs->ocsp_stapling_requested ||
hs->credential->ocsp_response == nullptr || //
ssl->s3->session_reused ||
!ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
return true;
Expand Down Expand Up @@ -1347,19 +1348,18 @@ static bool ext_sct_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
SSL *const ssl = hs->ssl;
// The extension shouldn't be sent when resuming sessions.
if (ssl_protocol_version(ssl) >= TLS1_3_VERSION || ssl->s3->session_reused ||
hs->config->cert->signed_cert_timestamp_list == NULL) {
hs->credential->signed_cert_timestamp_list == nullptr) {
return true;
}

CBB contents;
return CBB_add_u16(out, TLSEXT_TYPE_certificate_timestamp) &&
CBB_add_u16_length_prefixed(out, &contents) &&
CBB_add_bytes(
&contents,
CRYPTO_BUFFER_data(
hs->config->cert->signed_cert_timestamp_list.get()),
CRYPTO_BUFFER_len(
hs->config->cert->signed_cert_timestamp_list.get())) &&
CBB_add_bytes(&contents,
CRYPTO_BUFFER_data(
hs->credential->signed_cert_timestamp_list.get()),
CRYPTO_BUFFER_len(
hs->credential->signed_cert_timestamp_list.get())) &&
CBB_flush(out);
}

Expand Down Expand Up @@ -4104,61 +4104,60 @@ bool tls1_get_legacy_signature_algorithm(uint16_t *out, const EVP_PKEY *pkey) {
}
}

bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs, uint16_t *out) {
bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs,
const SSL_CREDENTIAL *cred,
uint16_t *out) {
SSL *const ssl = hs->ssl;
CERT *cert = hs->config->cert.get();
DC *dc = cert->dc.get();
if (!cred->UsesPrivateKey()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return false;
}

// Before TLS 1.2, the signature algorithm isn't negotiated as part of the
// handshake.
if (ssl_protocol_version(ssl) < TLS1_2_VERSION) {
if (!tls1_get_legacy_signature_algorithm(out, hs->local_pubkey.get())) {
uint16_t version = ssl_protocol_version(ssl);
if (version < TLS1_2_VERSION) {
if (!tls1_get_legacy_signature_algorithm(out, cred->pubkey.get())) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS);
return false;
}
return true;
}

Span<const uint16_t> sigalgs, peer_sigalgs;
if (ssl_signing_with_dc(hs)) {
sigalgs = MakeConstSpan(&dc->dc_cert_verify_algorithm, 1);
Span<const uint16_t> peer_sigalgs;
if (cred->type == SSLCredentialType::kDelegated) {
peer_sigalgs = hs->peer_delegated_credential_sigalgs;
} else {
sigalgs = cert->sigalgs.empty() ? MakeConstSpan(kSignSignatureAlgorithms)
: cert->sigalgs;
peer_sigalgs = tls1_get_peer_verify_algorithms(hs);
peer_sigalgs = hs->peer_sigalgs;
if (peer_sigalgs.empty() && version == TLS1_2_VERSION) {
// If the client didn't specify any signature_algorithms extension, it is
// interpreted as SHA-1. See
// http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
static const uint16_t kTLS12Default[] = {SSL_SIGN_RSA_PKCS1_SHA1,
SSL_SIGN_ECDSA_SHA1};
peer_sigalgs = kTLS12Default;
}
}

Span<const uint16_t> sigalgs = cred->sigalgs.empty()
? MakeConstSpan(kSignSignatureAlgorithms)
: cred->sigalgs;
for (uint16_t sigalg : sigalgs) {
if (!ssl_private_key_supports_signature_algorithm(hs, sigalg)) {
if (!ssl_pkey_supports_algorithm(ssl, cred->pubkey.get(), sigalg)) {
continue;
}

for (uint16_t peer_sigalg : peer_sigalgs) {
if (sigalg == peer_sigalg) {
*out = sigalg;
return true;
}
if (std::find(peer_sigalgs.begin(), peer_sigalgs.end(), sigalg) !=
peer_sigalgs.end()) {
*out = sigalg;
return true;
}
}

OPENSSL_PUT_ERROR(SSL, SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS);
return false;
}

Span<const uint16_t> tls1_get_peer_verify_algorithms(const SSL_HANDSHAKE *hs) {
Span<const uint16_t> peer_sigalgs = hs->peer_sigalgs;
if (peer_sigalgs.empty() && ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) {
// If the client didn't specify any signature_algorithms extension then
// we can assume that it supports SHA1. See
// http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
static const uint16_t kDefaultPeerAlgorithms[] = {SSL_SIGN_RSA_PKCS1_SHA1,
SSL_SIGN_ECDSA_SHA1};
peer_sigalgs = kDefaultPeerAlgorithms;
}
return peer_sigalgs;
}

bool tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
SSL *const ssl = hs->ssl;
// A Channel ID handshake message is structured to contain multiple
Expand Down
2 changes: 1 addition & 1 deletion ssl/handoff.cc
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ static bool apply_remote_features(SSL *ssl, CBS *in) {
// uses_disallowed_feature returns true iff |ssl| enables a feature that
// disqualifies it for split handshakes.
static bool uses_disallowed_feature(const SSL *ssl) {
return ssl->method->is_dtls || (ssl->config->cert && ssl->config->cert->dc) ||
return ssl->method->is_dtls || !ssl->config->cert->credentials.empty() ||
ssl->config->quic_transport_params.size() > 0 || ssl->ctx->ech_keys;
}

Expand Down
5 changes: 3 additions & 2 deletions ssl/handshake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,9 @@ bool ssl_send_tls12_certificate(SSL_HANDSHAKE *hs) {
return false;
}

if (ssl_has_certificate(hs)) {
STACK_OF(CRYPTO_BUFFER) *chain = hs->config->cert->chain.get();
if (hs->credential != nullptr) {
assert(hs->credential->type == SSLCredentialType::kX509);
STACK_OF(CRYPTO_BUFFER) *chain = hs->credential->chain.get();
for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain); i++) {
CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(chain, i);
if (!CBB_add_u24_length_prefixed(&certs, &cert) ||
Expand Down
91 changes: 62 additions & 29 deletions ssl/handshake_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
#include <limits.h>
#include <string.h>

#include <algorithm>
#include <utility>

#include <openssl/aead.h>
Expand Down Expand Up @@ -1331,6 +1332,42 @@ static enum ssl_hs_wait_t do_read_server_hello_done(SSL_HANDSHAKE *hs) {
return ssl_hs_ok;
}

static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred) {
if (cred->type != SSLCredentialType::kX509) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return false;
}

if (hs->config->check_client_certificate_type) {
// Check the certificate types advertised by the peer.
uint8_t cert_type;
switch (EVP_PKEY_id(cred->pubkey.get())) {
case EVP_PKEY_RSA:
cert_type = SSL3_CT_RSA_SIGN;
break;
case EVP_PKEY_EC:
case EVP_PKEY_ED25519:
cert_type = TLS_CT_ECDSA_SIGN;
break;
default:
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return false;
}
if (std::find(hs->certificate_types.begin(), hs->certificate_types.end(),
cert_type) == hs->certificate_types.end()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return false;
}
}

// Check that we will be able to generate a signature. Note this does not
// check the ECDSA curve. Prior to TLS 1.3, there is no way to determine which
// ECDSA curves are supported by the peer, so we must assume all curves are
// supported.
uint16_t unused;
return tls1_choose_signature_algorithm(hs, cred, &unused);
}

static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;

Expand Down Expand Up @@ -1358,42 +1395,38 @@ static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
}
}

if (!ssl_on_certificate_selected(hs)) {
Array<SSL_CREDENTIAL *> creds;
if (!ssl_get_credential_list(hs, &creds)) {
return ssl_hs_error;
}

if (ssl_has_certificate(hs)) {
if (hs->config->check_client_certificate_type) {
// Check the certificate types advertised by the peer.
uint8_t cert_type;
switch (EVP_PKEY_id(hs->local_pubkey.get())) {
case EVP_PKEY_RSA:
cert_type = SSL3_CT_RSA_SIGN;
break;
case EVP_PKEY_EC:
case EVP_PKEY_ED25519:
cert_type = TLS_CT_ECDSA_SIGN;
break;
default:
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return ssl_hs_error;
}
if (std::find(hs->certificate_types.begin(), hs->certificate_types.end(),
cert_type) == hs->certificate_types.end()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return ssl_hs_error;
if (creds.empty()) {
// If there were no credentials, proceed without a client certificate. In
// this case, the handshake buffer may be released early.
hs->transcript.FreeBuffer();
} else {
// Select the credential to use.
//
// TODO(davidben): In doing so, we pick the signature algorithm. Save that
// decision to avoid redoing it later.
for (SSL_CREDENTIAL *cred : creds) {
ERR_clear_error();
if (check_credential(hs, cred)) {
hs->credential = UpRef(cred);
break;
}
}
} else {
// Without a client certificate, the handshake buffer may be released.
hs->transcript.FreeBuffer();
if (hs->credential == nullptr) {
// The error from the last attempt is in the error queue.
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
return ssl_hs_error;
}
}

if (!ssl_send_tls12_certificate(hs)) {
return ssl_hs_error;
}


hs->state = state_send_client_key_exchange;
return ssl_hs_ok;
}
Expand Down Expand Up @@ -1570,12 +1603,11 @@ static enum ssl_hs_wait_t do_send_client_key_exchange(SSL_HANDSHAKE *hs) {
static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;

if (!hs->cert_request || !ssl_has_certificate(hs)) {
if (!hs->cert_request || hs->credential == nullptr) {
hs->state = state_send_client_finished;
return ssl_hs_ok;
}

assert(ssl_has_private_key(hs));
ScopedCBB cbb;
CBB body, child;
if (!ssl->method->init_message(ssl, cbb.get(), &body,
Expand All @@ -1584,7 +1616,8 @@ static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
}

uint16_t signature_algorithm;
if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
if (!tls1_choose_signature_algorithm(hs, hs->credential.get(),
&signature_algorithm)) {
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
return ssl_hs_error;
}
Expand All @@ -1597,7 +1630,7 @@ static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
}

// Set aside space for the signature.
const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
const size_t max_sig_len = EVP_PKEY_size(hs->credential->pubkey.get());
uint8_t *ptr;
if (!CBB_add_u16_length_prefixed(&body, &child) ||
!CBB_reserve(&child, &ptr, max_sig_len)) {
Expand Down
Loading

0 comments on commit 91a3f26

Please sign in to comment.