Skip to content

Fix TLS version selection in SSL transport security.#24955

Merged
matthewstevenson88 merged 6 commits intogrpc:masterfrom
matthewstevenson88:fix-tls-version-negotiation
Dec 12, 2020
Merged

Fix TLS version selection in SSL transport security.#24955
matthewstevenson88 merged 6 commits intogrpc:masterfrom
matthewstevenson88:fix-tls-version-negotiation

Conversation

@matthewstevenson88
Copy link
Copy Markdown
Contributor

This fixes a bug that appears when a machine uses an SSL library that does not conform to OpenSSL's TLS 1.3 semantics (e.g. LibreSSL). For such a library, it defaults the min/max TLS versions to TLS 1.2. This bug was introduced in #23165.

@veblush @markdroth @coryan FYI.

@matthewstevenson88 matthewstevenson88 self-assigned this Dec 10, 2020
@matthewstevenson88 matthewstevenson88 changed the title Fix TLS version negotiation in SSL transport security. Fix TLS version selection in SSL transport security. Dec 10, 2020
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000
// Set the min TLS version of the SSL context.
// Set the min TLS version of the SSL context if using OpenSSL version
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per discuss in the internal CL, we could move
#if defined(TLS1_3_VERSION) right after line 913.
Then we keep the original switch flow (except removing lines 918, 922, 932, 936). The code will be much readable and extensible.

Basically, we only set min & max proto version if OPENSSL_VERSION_NUMBER >= 0x10100000 and TLS1_3_VERSION is defined. Otherwise, it will be no-op.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To confirm, are you proposing the following code?

#if OPENSSL_VERSION_NUMBER >= 0x10100000
#if defined(TLS1_3_VERSION)
  switch (min_tls_version) {
    case tsi_tls_version::TSI_TLS1_2:
      SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION);
      break;
    case tsi_tls_version::TSI_TLS1_3:
      SSL_CTX_set_min_proto_version(ssl_context, TLS1_3_VERSION);
      break;
    default:
      // Some logging.
      break;
  }

  switch (max_tls_version) {
    case tsi_tls_version::TSI_TLS1_2:
      SSL_CTX_set_max_proto_version(ssl_context, TLS1_2_VERSION);
      break;
    case tsi_tls_version::TSI_TLS1_3:
      SSL_CTX_set_max_proto_version(ssl_context, TLS1_3_VERSION);
      break
    default:
      // Some logging.
      break;
  }
#endif
#endif

If so, that's more than just a readability change - that's a functional change. Consider the case when OPENSSL_VERSION_NUMBER >= 0x10100000 and TLS1_3_VERSION is not defined. Then, this method is a no-op, so there are no restrictions on the minimum TLS version. And since we've instantiated the SSL_CTX using TLS_method rather than TLSv1_2_method (see here), we have opened ourselves up to a downgrade attack where the peer could try to negotiate TLS 1.1 or worse.

Please correct me if I misunderstood your proposal. :)

@davidben to keep me honest on the fact that we must set a min TLS version if we use TLS_method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do not set a minimum version with TLS_method, it'll use the library's default. That default, for BoringSSL, is currently TLS 1.0. So, yeah, if your aim is to reject TLS 1.0 and 1.1, you need to set a minimum version.

This PR is a little odd though. So, if I understand right, the issue is that OpenSSLs which lack TLS 1.3 will break when you set the maximum to TSI_TLS1_3, rather than silently going down to TLS 1.2. But this also loses the TSI_FAILED_PRECONDITION logic, which seems valuable because if someone uses a totally random constant, you'd like for the library to fail.

It seems to me the desired logic should be something like:

Leave this block unchanged. If the caller requested TLS 1.3 and the underlying
library is incapable of providing TLS 1.3, the connection should fail, rather than
silently allowing TLS 1.2.

  // Set the min TLS version of the SSL context.
  switch (min_tls_version) {
    case tsi_tls_version::TSI_TLS1_2:
      SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION);
      break;
// If the library does not support TLS 1.3, and the caller requested a minimum
// of TLS 1.3, return an error. The caller's request cannot be satisfied.
#if defined(TLS1_3_VERSION)
    case tsi_tls_version::TSI_TLS1_3:
      SSL_CTX_set_min_proto_version(ssl_context, TLS1_3_VERSION);
      break;
#endif
    default:
      gpr_log(GPR_INFO, "TLS version is not supported.");
      return TSI_FAILED_PRECONDITION;
  }

Change this block so if the library doesn't support TLS 1.3, set a maximum of
TLS 1.2, which is still compatible with what they requested.

  // Set the max TLS version of the SSL context.
  switch (max_tls_version) {
    case tsi_tls_version::TSI_TLS1_2:
      SSL_CTX_set_max_proto_version(ssl_context, TLS1_2_VERSION);
      break;
    case tsi_tls_version::TSI_TLS1_3:
#if defined(TLS1_3_VERSION)
      SSL_CTX_set_max_proto_version(ssl_context, TLS1_3_VERSION);
#else
      // The library doesn't support TLS 1.3, so set a maximum of
      // TLS 1.2 instead.
      SSL_CTX_set_max_proto_version(ssl_context, TLS1_2_VERSION);
#endif
      break;
    default:
      gpr_log(GPR_INFO, "TLS version is not supported.");
      return TSI_FAILED_PRECONDITION;
  }

(There may be a cleaner way to express this.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion David! I've added it in the newest commit.

Some context: A user with LibreSSL encountered a failure due to this method: the gRPC stack requested min=1.2, max=1.3, and LibreSSL satisfied OPENSSL_VERSION_NUMBER >= 0x10100000 but did not have TLS1_3_VERSION defined. As I understand it, LibreSSL is actively working on adopting OpenSSL's TLS 1.3 semantics, but this fix is needed in the meantime.

To avoid this kind of problem in the future, @veblush will add e2e tests against different SSL libraries (see #24960).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @davidben for suggestion! The new logic makes sense to me.

The reason we introduced min/max TLS version was mainly for testing. Since TLS 1.2 and TLS 1.3 behave differently. We do not have customer request for only wanting a specific version of TLS.

Copy link
Copy Markdown

@jiangtaoli2016 jiangtaoli2016 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Matt for the PR!

SSL_CTX_set_max_proto_version(ssl_context, TLS1_3_VERSION);
break;
#else
// If the libraary does not support TLS 1.3, then set the max TLS version
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. s/libraary/library

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks!

}
#if OPENSSL_VERSION_NUMBER >= 0x10100000
// Set the min TLS version of the SSL context.
// Set the min TLS version of the SSL context if using OpenSSL version
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @davidben for suggestion! The new logic makes sense to me.

The reason we introduced min/max TLS version was mainly for testing. Since TLS 1.2 and TLS 1.3 behave differently. We do not have customer request for only wanting a specific version of TLS.

@matthewstevenson88 matthewstevenson88 merged commit 8f5dcdf into grpc:master Dec 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/security lang/core release notes: no Indicates if PR should not be in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants