| // Copyright 2021 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "mbedtls/ssl.h" |
| #include "pw_assert/check.h" |
| #include "pw_log/log.h" |
| #include "pw_tls_client/entropy.h" |
| #include "pw_tls_client/session.h" |
| #include "pw_tls_client_mbedtls/backend_types.h" |
| |
| namespace pw::tls_client { |
| namespace backend { |
| |
| int SessionImplementation::MbedTlsWrite(void* ctx, |
| const uint8_t* buf, |
| size_t len) { |
| PW_CHECK_NOTNULL(ctx); |
| PW_CHECK_NOTNULL(buf); |
| auto writer = |
| static_cast<SessionImplementation*>(ctx)->session_options_.transport(); |
| PW_CHECK_NOTNULL(writer); |
| return writer->Write(buf, len).ok() ? len : -1; |
| } |
| |
| int SessionImplementation::MbedTlsRead(void* ctx, |
| unsigned char* buf, |
| size_t len) { |
| PW_CHECK_NOTNULL(ctx); |
| PW_CHECK_NOTNULL(buf); |
| auto reader = |
| static_cast<SessionImplementation*>(ctx)->session_options_.transport(); |
| PW_CHECK_NOTNULL(reader); |
| auto res = reader->Read(buf, len); |
| if (!res.ok()) { |
| return -1; |
| } |
| return res.value().size() == 0 ? MBEDTLS_ERR_SSL_WANT_READ |
| : res.value().size(); |
| } |
| |
| Status SessionImplementation::entropy_source_status_ = OkStatus(); |
| |
| void SessionImplementation::SetEntropySourceStatus(Status status) { |
| entropy_source_status_ = status; |
| } |
| |
| // Entropy source callback |
| int SessionImplementation::MbedTlsEntropySource(void* ctx, |
| unsigned char* out, |
| size_t len, |
| size_t* output_length) { |
| Status status; |
| if (entropy_source_status_ != OkStatus()) { |
| status = entropy_source_status_; |
| } else { |
| status = GetRandomBytes({out, len}); |
| } |
| |
| if (!status.ok()) { |
| PW_LOG_DEBUG("Failed to generate random bytes"); |
| auto session_impl = static_cast<SessionImplementation*>(ctx); |
| session_impl->SetTlsStatus(pw::tls_client::TLSStatus::kEntropySourceFailed); |
| return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; |
| } |
| *output_length = len; |
| return 0; |
| } |
| |
| SessionImplementation::SessionImplementation(SessionOptions options) |
| : session_options_(options) { |
| mbedtls_ssl_init(&ssl_ctx_); |
| mbedtls_ssl_config_init(&ssl_config_); |
| mbedtls_ctr_drbg_init(&drbg_ctx_); |
| mbedtls_entropy_init(&entropy_ctx_); |
| } |
| |
| SessionImplementation::~SessionImplementation() { |
| mbedtls_ssl_free(&ssl_ctx_); |
| mbedtls_ssl_config_free(&ssl_config_); |
| mbedtls_ctr_drbg_free(&drbg_ctx_); |
| mbedtls_entropy_free(&entropy_ctx_); |
| } |
| |
| Status SessionImplementation::Setup() { |
| int ret = 0; |
| |
| // Set up default configuration. |
| ret = mbedtls_ssl_config_defaults( |
| &ssl_config_, |
| // Configured as client. |
| MBEDTLS_SSL_IS_CLIENT, |
| // Statndard TLS. The other option is MBEDTLS_SSL_TRANSPORT_DATAGRAM |
| // for DTLS, which we'll consider later. |
| MBEDTLS_SSL_TRANSPORT_STREAM, |
| // This option is used in all MbedTLS native examples. |
| // The other option is MBEDTLS_SSL_PRESET_SUITEB. |
| // However, there is no document/comment availalbe on what they do. |
| // Base on the source code, these options will restrict the version |
| // of TLS protocol. MBEDTLS_SSL_PRESET_SUITEB forces TLS 1.2. |
| // MBEDTLS_SSL_PRESET_DEFAULT is more relaxed. But since we |
| // define MBEDTLS_SSL_PROTO_TLS1_2 for all configs. There shouldn't be |
| // any difference. |
| MBEDTLS_SSL_PRESET_DEFAULT); |
| if (ret) { |
| return Status::Internal(); |
| } |
| |
| // Set up an entropy source. |
| ret = mbedtls_entropy_add_source(&entropy_ctx_, |
| MbedTlsEntropySource, |
| this, |
| 1, |
| MBEDTLS_ENTROPY_SOURCE_STRONG); |
| if (ret) { |
| return Status::Internal(); |
| } |
| |
| // Set up drbg. |
| unsigned char personalized_bytes[] = "pw_tls_client"; |
| ret = mbedtls_ctr_drbg_seed(&drbg_ctx_, |
| mbedtls_entropy_func, |
| &entropy_ctx_, |
| personalized_bytes, |
| sizeof(personalized_bytes)); |
| if (ret) { |
| if (ret == MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED) { |
| tls_status_ = TLSStatus::kEntropySourceFailed; |
| } |
| return Status::Internal(); |
| } |
| |
| // The API does not fail. |
| mbedtls_ssl_conf_rng(&ssl_config_, mbedtls_ctr_drbg_random, &drbg_ctx_); |
| |
| // The API does not fail. |
| mbedtls_ssl_conf_authmode(&ssl_config_, MBEDTLS_SSL_VERIFY_REQUIRED); |
| |
| // TODO(pwbug/398): Add logic for loading trust anchors. |
| |
| // Load configuration to SSL. |
| ret = mbedtls_ssl_setup(&ssl_ctx_, &ssl_config_); |
| if (ret) { |
| return Status::Internal(); |
| } |
| |
| // Set up transport. |
| // The API does not fail. |
| mbedtls_ssl_set_bio(&ssl_ctx_, this, MbedTlsWrite, MbedTlsRead, nullptr); |
| |
| ret = mbedtls_ssl_set_hostname(&ssl_ctx_, |
| session_options_.server_name().data()); |
| if (ret) { |
| return Status::Internal(); |
| } |
| |
| return OkStatus(); |
| } |
| |
| } // namespace backend |
| |
| Session::Session(const SessionOptions& options) : session_impl_(options) {} |
| |
| Session::~Session() = default; |
| |
| Result<Session*> Session::Create(const SessionOptions& options) { |
| if (!options.transport()) { |
| PW_LOG_DEBUG("Must provide a transport"); |
| return Status::Internal(); |
| } |
| |
| auto sess = new Session(options); |
| if (!sess) { |
| return Status::ResourceExhausted(); |
| } |
| |
| // Set up the client. |
| auto setup_status = sess->session_impl_.Setup(); |
| if (!setup_status.ok()) { |
| PW_LOG_DEBUG("Failed to setup"); |
| // TODO(pwbug/398): `tls_status_` may be set, but the session object will |
| // be released. Map `tls_stauts_` to string and print out here so that |
| // the information can be catched. |
| delete sess; |
| return setup_status; |
| } |
| |
| return sess; |
| } |
| |
| Status Session::Open() { |
| // TODO(pwbug/398): To implement |
| return Status::Unimplemented(); |
| } |
| |
| Status Session::Close() { |
| // TODO(pwbug/398): To implement |
| return Status::Unimplemented(); |
| } |
| |
| StatusWithSize Session::DoRead(ByteSpan) { |
| // TODO(pwbug/398): To implement |
| return StatusWithSize(Status::Unimplemented(), 0); |
| } |
| |
| Status Session::DoWrite(ConstByteSpan) { |
| // TODO(pwbug/398): To implement |
| return Status::Unimplemented(); |
| } |
| |
| TLSStatus Session::GetLastTLSStatus() { return session_impl_.GetTlsStatus(); } |
| |
| } // namespace pw::tls_client |